Skip to content

Commit 169c7d0

Browse files
committed
lib: add navigator.deviceMemory
1 parent 65087c0 commit 169c7d0

File tree

3 files changed

+87
-0
lines changed

3 files changed

+87
-0
lines changed

doc/api/globals.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,27 @@ consisting of the runtime name and major version number.
652652
console.log(`The user-agent is ${navigator.userAgent}`); // Prints "Node.js/21"
653653
```
654654

655+
### `navigator.deviceMemory`
656+
657+
<!-- YAML
658+
added: REPLACEME
659+
-->
660+
661+
> Stability: 1 - Experimental
662+
663+
* {number}
664+
665+
The `navigator.deviceMemory` read-only property indicates the approximate
666+
amount of available RAM on the device. It is part of the
667+
[Device Memory API](https://www.w3.org/TR/device-memory/).
668+
669+
The amount of device RAM can be used as a fingerprinting variable, so values
670+
are intentionally coarse to reduce the potential for its misuse.
671+
672+
```js
673+
console.log(`The process has approximately ${navigator.deviceMemory} GiB of RAM`);
674+
```
675+
655676
## `PerformanceEntry`
656677

657678
<!-- YAML

lib/internal/navigator.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
NumberParseFloat,
45
ObjectDefineProperties,
56
Symbol,
67
} = primordials;
@@ -15,15 +16,38 @@ const {
1516

1617
const {
1718
getAvailableParallelism,
19+
getTotalMem,
1820
} = internalBinding('os');
1921

2022
const kInitialize = Symbol('kInitialize');
2123
const nodeVersion = process.version;
2224

25+
function getApproximatedDeviceMemory(totalMemoryInBytes) {
26+
const totalMemoryInMB = totalMemoryInBytes / (1024 * 1024);
27+
let mostSignificantByte = 0;
28+
let lowerBound = totalMemoryInMB;
29+
30+
// Extract the most-significant-bit and its location.
31+
while (lowerBound > 1) {
32+
lowerBound >>= 1;
33+
mostSignificantByte++;
34+
}
35+
36+
const upperBound = (lowerBound + 1) << mostSignificantByte;
37+
lowerBound = lowerBound << mostSignificantByte;
38+
39+
// Find the closest bound, and convert it to GB.
40+
if (totalMemoryInMB - lowerBound <= upperBound - totalMemoryInMB) {
41+
return NumberParseFloat((lowerBound / 1024.0).toFixed(3));
42+
}
43+
return NumberParseFloat((upperBound / 1024.0).toFixed(3));
44+
}
45+
2346
class Navigator {
2447
// Private properties are used to avoid brand validations.
2548
#availableParallelism;
2649
#userAgent = `Node.js/${nodeVersion.slice(1, nodeVersion.indexOf('.'))}`;
50+
#deviceMemory = getApproximatedDeviceMemory(getTotalMem());
2751

2852
constructor() {
2953
if (arguments[0] === kInitialize) {
@@ -46,14 +70,23 @@ class Navigator {
4670
get userAgent() {
4771
return this.#userAgent;
4872
}
73+
74+
/**
75+
* @returns {number}
76+
*/
77+
get deviceMemory() {
78+
return this.#deviceMemory;
79+
}
4980
}
5081

5182
ObjectDefineProperties(Navigator.prototype, {
5283
hardwareConcurrency: kEnumerableProperty,
5384
userAgent: kEnumerableProperty,
85+
deviceMemory: kEnumerableProperty,
5486
});
5587

5688
module.exports = {
89+
getApproximatedDeviceMemory,
5790
navigator: new Navigator(kInitialize),
5891
Navigator,
5992
};

test/parallel/test-navigator.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
// Flags: --expose-internals
2+
13
'use strict';
24

35
require('../common');
46
const assert = require('assert');
7+
const {
8+
getApproximatedDeviceMemory,
9+
} = require('internal/navigator');
510

611
const is = {
712
number: (value, key) => {
@@ -10,6 +15,34 @@ const is = {
1015
},
1116
};
1217

18+
assert.strictEqual(typeof navigator.deviceMemory, 'number');
19+
assert.ok(navigator.deviceMemory >= 0);
20+
21+
assert.strictEqual(getApproximatedDeviceMemory(0), 0);
22+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 27), 0.125);
23+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 28), 0.25);
24+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 29), 0.5);
25+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 30), 1);
26+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 31), 2);
27+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 32), 4);
28+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 33), 8);
29+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 34), 16);
30+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 35), 32);
31+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 36), 64);
32+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 37), 128);
33+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 38), 256);
34+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 39), 512);
35+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 40), 1024);
36+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 41), 2048);
37+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 42), 4096);
38+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 43), 8192);
39+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 44), 16384);
40+
assert.strictEqual(getApproximatedDeviceMemory(2 ** 45), 32768);
41+
42+
// https://github.com/w3c/device-memory#the-header
43+
assert.strictEqual(getApproximatedDeviceMemory(768 * 1024 * 1024), 0.5);
44+
assert.strictEqual(getApproximatedDeviceMemory(1793 * 1024 * 1024), 2);
45+
1346
is.number(+navigator.hardwareConcurrency, 'hardwareConcurrency');
1447
is.number(navigator.hardwareConcurrency, 'hardwareConcurrency');
1548
assert.ok(navigator.hardwareConcurrency > 0);

0 commit comments

Comments
 (0)