Skip to content

Commit

Permalink
lib: add navigator.deviceMemory
Browse files Browse the repository at this point in the history
  • Loading branch information
Uzlopak committed Oct 30, 2023
1 parent 65087c0 commit 0aa3941
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 0 deletions.
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(`The process has approximately ${navigator.deviceMemory} GiB of RAM`);
```

## `PerformanceEntry`

<!-- YAML
Expand Down
35 changes: 35 additions & 0 deletions lib/internal/navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,41 @@ const {

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

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

function getApproximatedDeviceMemory(totalMemoryInBytes) {
let 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;

let result = 0;
// Find the closest bound, and convert it to GB.
if (totalMemoryInMB - lowerBound <= upperBound - totalMemoryInMB) {
result = parseFloat((lowerBound / 1024.0).toFixed(2));
} else {
result = parseFloat((upperBound / 1024.0).toFixed(2));
}
return result;
}

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

constructor() {
if (arguments[0] === kInitialize) {
Expand All @@ -46,14 +72,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,
};
35 changes: 35 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,36 @@ const is = {
},
};

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

assert.strictEqual(getApproximatedDeviceMemory(0), 0);
assert.strictEqual(getApproximatedDeviceMemory(2 ** 27), 0.13);
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

0 comments on commit 0aa3941

Please sign in to comment.