Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: add navigator.deviceMemory #50229

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions doc/api/globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,27 @@ consisting of the runtime name and major version number.
console.log(`The user-agent is ${navigator.userAgent}`); // Prints "Node.js/21"
```

### `navigator.deviceMemory`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

* {number}

The `navigator.deviceMemory` read-only property indicates the approximate
amount of available RAM on the device. It is part of the
[Device Memory API](https://www.w3.org/TR/device-memory/).

The amount of device RAM can be used as a fingerprinting variable, so values
are intentionally coarse to reduce the potential for its misuse.

```js
console.log(`This device has at least ${memory}GiB of RAM.`);
```

## `PerformanceEntry`

<!-- YAML
Expand Down
33 changes: 33 additions & 0 deletions lib/internal/navigator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
NumberParseFloat,
ObjectDefineProperties,
Symbol,
} = primordials;
Expand All @@ -15,15 +16,38 @@ const {

const {
getAvailableParallelism,
getTotalMem,
} = internalBinding('os');

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

function getApproximatedDeviceMemory(totalMemoryInBytes) {
const totalMemoryInMB = totalMemoryInBytes / (1024 * 1024);
let mostSignificantByte = 0;
let lowerBound = totalMemoryInMB;

// Extract the most-significant-bit and its location.
while (lowerBound > 1) {
lowerBound >>= 1;
mostSignificantByte++;
}

const upperBound = (lowerBound + 1) << mostSignificantByte;
lowerBound = lowerBound << mostSignificantByte;

// Find the closest bound, and convert it to GB.
if (totalMemoryInMB - lowerBound <= upperBound - totalMemoryInMB) {
return NumberParseFloat((lowerBound / 1024.0).toFixed(3));
}
return NumberParseFloat((upperBound / 1024.0).toFixed(3));
}

class Navigator {
// Private properties are used to avoid brand validations.
#availableParallelism;
#userAgent = `Node.js/${nodeVersion.slice(1, nodeVersion.indexOf('.'))}`;
Uzlopak marked this conversation as resolved.
Show resolved Hide resolved
#deviceMemory = getApproximatedDeviceMemory(getTotalMem());

constructor() {
if (arguments[0] === kInitialize) {
Expand All @@ -46,14 +70,23 @@ class Navigator {
get userAgent() {
return this.#userAgent;
}

/**
* @returns {number}
*/
get deviceMemory() {
return this.#deviceMemory;
}
}

ObjectDefineProperties(Navigator.prototype, {
hardwareConcurrency: kEnumerableProperty,
userAgent: kEnumerableProperty,
deviceMemory: kEnumerableProperty,
});

module.exports = {
getApproximatedDeviceMemory,
navigator: new Navigator(kInitialize),
Navigator,
};
33 changes: 33 additions & 0 deletions test/parallel/test-navigator.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Flags: --expose-internals

'use strict';

require('../common');
const assert = require('assert');
const {
getApproximatedDeviceMemory,
} = require('internal/navigator');

const is = {
number: (value, key) => {
Expand All @@ -10,6 +15,34 @@ const is = {
},
};

assert.strictEqual(typeof navigator.deviceMemory, 'number');
assert.ok(navigator.deviceMemory >= 0);

assert.strictEqual(getApproximatedDeviceMemory(0), 0);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 27), 0.125);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 28), 0.25);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 29), 0.5);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 30), 1);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 31), 2);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 32), 4);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 33), 8);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 34), 16);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 35), 32);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 36), 64);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 37), 128);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 38), 256);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 39), 512);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 40), 1024);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 41), 2048);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 42), 4096);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 43), 8192);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 44), 16384);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 45), 32768);

// https://github.com/w3c/device-memory#the-header
assert.strictEqual(getApproximatedDeviceMemory(768 * 1024 * 1024), 0.5);
assert.strictEqual(getApproximatedDeviceMemory(1793 * 1024 * 1024), 2);

is.number(+navigator.hardwareConcurrency, 'hardwareConcurrency');
is.number(navigator.hardwareConcurrency, 'hardwareConcurrency');
assert.ok(navigator.hardwareConcurrency > 0);
Expand Down