Skip to content

Commit 0e2a9ed

Browse files
committed
lib: add navigator.deviceMemory
1 parent 6dadb99 commit 0e2a9ed

File tree

6 files changed

+105
-0
lines changed

6 files changed

+105
-0
lines changed

LICENSE

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,3 +2170,34 @@ The externally maintained libraries used by Node.js are:
21702170
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21712171
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21722172
"""
2173+
2174+
- GetApproximatedDeviceMemory, located at src/node_os.cc, is licensed as follows:
2175+
"""
2176+
// Copyright 2017 The Chromium Authors
2177+
//
2178+
// Redistribution and use in source and binary forms, with or without
2179+
// modification, are permitted provided that the following conditions are
2180+
// met:
2181+
//
2182+
// * Redistributions of source code must retain the above copyright
2183+
// notice, this list of conditions and the following disclaimer.
2184+
// * Redistributions in binary form must reproduce the above
2185+
// copyright notice, this list of conditions and the following disclaimer
2186+
// in the documentation and/or other materials provided with the
2187+
// distribution.
2188+
// * Neither the name of Google Inc. nor the names of its
2189+
// contributors may be used to endorse or promote products derived from
2190+
// this software without specific prior written permission.
2191+
//
2192+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2193+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2194+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2195+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2196+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2197+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2198+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2199+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2200+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2201+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2202+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2203+
"""

doc/api/globals.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,27 @@ logical processors available to the current Node.js instance.
629629
console.log(`This process is running on ${navigator.hardwareConcurrency}`);
630630
```
631631

632+
### `navigator.deviceMemory`
633+
634+
<!-- YAML
635+
added: REPLACEME
636+
-->
637+
638+
> Stability: 1 - Experimental
639+
640+
* {number}
641+
642+
The `navigator.deviceMemory` read-only property indicates the approximate
643+
amount of available RAM on the device. It is part of the
644+
[Device Memory API](https://www.w3.org/TR/device-memory/).
645+
646+
The amount of device RAM can be used as a fingerprinting variable, so values
647+
are intentionally coarse to reduce the potential for its misuse.
648+
649+
```js
650+
console.log(`The process has approximately ${navigator.deviceMemory} GiB of RAM`);
651+
```
652+
632653
## `PerformanceEntry`
633654

634655
<!-- YAML

lib/internal/navigator.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ const {
1515

1616
const {
1717
getAvailableParallelism,
18+
getApproximatedDeviceMemory,
1819
} = internalBinding('os');
1920

2021
const kInitialize = Symbol('kInitialize');
2122

2223
class Navigator {
2324
// Private properties are used to avoid brand validations.
2425
#availableParallelism;
26+
#deviceMemory;
2527

2628
constructor() {
2729
if (arguments[0] === kInitialize) {
@@ -37,10 +39,19 @@ class Navigator {
3739
this.#availableParallelism ??= getAvailableParallelism();
3840
return this.#availableParallelism;
3941
}
42+
43+
/**
44+
* @returns {number}
45+
*/
46+
get deviceMemory() {
47+
this.#deviceMemory ??= getApproximatedDeviceMemory();
48+
return this.#deviceMemory;
49+
}
4050
}
4151

4252
ObjectDefineProperties(Navigator.prototype, {
4353
hardwareConcurrency: kEnumerableProperty,
54+
deviceMemory: kEnumerableProperty,
4455
});
4556

4657
module.exports = {

src/node_os.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,40 @@ static void GetTotalMemory(const FunctionCallbackInfo<Value>& args) {
146146
args.GetReturnValue().Set(amount);
147147
}
148148

149+
// The calculations in this method are described in the specification:
150+
// https://w3c.github.io/device-memory/.
151+
static void GetApproximatedDeviceMemory(
152+
const FunctionCallbackInfo<Value>& args) {
153+
float approximated_device_memory_gb_ = 0.0;
154+
double physical_memory_mb_ =
155+
static_cast<double>(uv_get_total_memory() / (1024 * 1024));
156+
DCHECK_GT(physical_memory_mb_, 0);
157+
int power = 0;
158+
int lower_bound = physical_memory_mb_;
159+
160+
// Extract the most-significant-bit and its location.
161+
while (lower_bound > 1) {
162+
lower_bound >>= 1;
163+
power++;
164+
}
165+
// The remaining should always be equal to exactly 1.
166+
DCHECK_EQ(lower_bound, 1);
167+
168+
int64_t upper_bound = lower_bound + 1;
169+
lower_bound = lower_bound << power;
170+
upper_bound = upper_bound << power;
171+
172+
// Find the closest bound, and convert it to GB.
173+
if (physical_memory_mb_ - lower_bound <= upper_bound - physical_memory_mb_)
174+
approximated_device_memory_gb_ = static_cast<float>(lower_bound) / 1024.0;
175+
else
176+
approximated_device_memory_gb_ = static_cast<float>(upper_bound) / 1024.0;
177+
178+
// Max-limit the reported value to 8GB to reduce fingerprintability of
179+
// high-spec machines.
180+
if (approximated_device_memory_gb_ > 8) approximated_device_memory_gb_ = 8.0;
181+
args.GetReturnValue().Set(approximated_device_memory_gb_);
182+
}
149183

150184
static void GetUptime(const FunctionCallbackInfo<Value>& args) {
151185
Environment* env = Environment::GetCurrent(args);
@@ -394,6 +428,10 @@ void Initialize(Local<Object> target,
394428
SetMethod(context, target, "getLoadAvg", GetLoadAvg);
395429
SetMethod(context, target, "getUptime", GetUptime);
396430
SetMethod(context, target, "getTotalMem", GetTotalMemory);
431+
SetMethod(context,
432+
target,
433+
"getApproximatedDeviceMemory",
434+
GetApproximatedDeviceMemory);
397435
SetMethod(context, target, "getFreeMem", GetFreeMemory);
398436
SetMethod(context, target, "getCPUs", GetCPUInfo);
399437
SetMethod(context, target, "getInterfaceAddresses", GetInterfaceAddresses);
@@ -415,6 +453,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
415453
registry->Register(GetHostname);
416454
registry->Register(GetLoadAvg);
417455
registry->Register(GetUptime);
456+
registry->Register(GetApproximatedDeviceMemory);
418457
registry->Register(GetTotalMemory);
419458
registry->Register(GetFreeMemory);
420459
registry->Register(GetCPUInfo);

test/parallel/test-navigator.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ const is = {
1313
is.number(+navigator.hardwareConcurrency, 'hardwareConcurrency');
1414
is.number(navigator.hardwareConcurrency, 'hardwareConcurrency');
1515
assert.ok(navigator.hardwareConcurrency > 0);
16+
17+
assert.strictEqual(typeof navigator.deviceMemory, 'number');

typings/internalBinding/os.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface OSBinding {
77
getLoadAvg(array: Float64Array): void;
88
getUptime(): number;
99
getTotalMem(): number;
10+
getApproximatedDeviceMemory(): number;
1011
getFreeMem(): number;
1112
getCPUs(): Array<string | number>;
1213
getInterfaceAddresses(ctx: InternalOSBinding.OSContext): Array<string | number | boolean> | undefined;

0 commit comments

Comments
 (0)