diff --git a/.eslintrc.js b/.eslintrc.js index f5100fd3cc91b8..dcd81d76fe3755 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -361,5 +361,6 @@ module.exports = { btoa: 'readable', atob: 'readable', performance: 'readable', + navigator: 'readable', }, }; diff --git a/doc/api/globals.md b/doc/api/globals.md index 8ce4ff1942be27..a8254d9abd1fd2 100644 --- a/doc/api/globals.md +++ b/doc/api/globals.md @@ -295,6 +295,58 @@ The `MessagePort` class. See [`MessagePort`][] for more details. This variable may appear to be global but is not. See [`module`][]. +## `navigator` + + +> Stability: 1 - Experimental + +An implementation of the [Navigator API][]. Similar to [`window.navigator`][] +in browsers. + +### `navigator.deviceMemory` + + +* {number} + +The total amount of device memory in GiB, rounded to the nearest power of 2, +between 0.25 and 8 GiB. Part of the [Device Memory API][]. + +```js +console.log(`This device has ${navigator.deviceMemory} GiB of RAM`); +``` + +### `navigator.hardwareConcurrency` + + +* {integer} + +The number of logical processors. + +```js +console.log(`This device has ${navigator.hardwareConcurrency} logical CPUs`); +``` + +### `navigator.platform` + + +* {string} + +A string identifying the operating system platform on which the Node.js process +is running. For example, it returns 'Linux' on Linux, 'Darwin' on macOS, and +'Win32' on Windows. + +```js +console.log(`This process is running on ${navigator.platform}`); +``` + ## `performance` The [`perf_hooks.performance`][] object. @@ -429,6 +481,8 @@ The object that acts as the namespace for all W3C [WebAssembly][webassembly-org] related functionality. See the [Mozilla Developer Network][webassembly-mdn] for usage and compatibility. +[Device Memory API]: https://w3c.github.io/device-memory/ +[Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object [`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController [`EventTarget` and `Event` API]: events.md#event-target-and-event-api [`MessageChannel`]: worker_threads.md#worker_threads_class_messagechannel @@ -455,6 +509,7 @@ The object that acts as the namespace for all W3C [`setImmediate`]: timers.md#timers_setimmediate_callback_args [`setInterval`]: timers.md#timers_setinterval_callback_delay_args [`setTimeout`]: timers.md#timers_settimeout_callback_delay_args +[`window.navigator`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator [buffer section]: buffer.md [built-in objects]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects [module system documentation]: modules.md diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 58f7396990dddb..d42c99fd1a1272 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -241,6 +241,14 @@ if (!config.noBrowserGlobals) { defineOperation(globalThis, 'queueMicrotask', queueMicrotask); + // https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object + ObjectDefineProperty(globalThis, 'navigator', { + enumerable: true, + configurable: true, + writable: false, + value: require('internal/navigator'), + }); + // https://www.w3.org/TR/hr-time-2/#the-performance-attribute defineReplacableAttribute(globalThis, 'performance', require('perf_hooks').performance); diff --git a/lib/internal/navigator.js b/lib/internal/navigator.js new file mode 100644 index 00000000000000..f1cfa6a80ad0db --- /dev/null +++ b/lib/internal/navigator.js @@ -0,0 +1,67 @@ +'use strict'; + +const { + ObjectDefineProperties, + ObjectSetPrototypeOf, + MathClz32, +} = primordials; + +const { + codes: { + ERR_ILLEGAL_CONSTRUCTOR, + } +} = require('internal/errors'); + +const { + getCPUs, + getOSInformation, + getTotalMem, +} = internalBinding('os'); + +class Navigator { + constructor() { + throw new ERR_ILLEGAL_CONSTRUCTOR(); + } +} + +class InternalNavigator {} +InternalNavigator.prototype.constructor = Navigator.prototype.constructor; +ObjectSetPrototypeOf(InternalNavigator.prototype, Navigator.prototype); + +function getDeviceMemory() { + const mem = getTotalMem() / 1024 / 1024; + if (mem <= 0.25 * 1024) return 0.25; + if (mem >= 8 * 1024) return 8; + const lowerBound = 1 << 31 - MathClz32(mem - 1); + const upperBound = lowerBound * 2; + return mem - lowerBound <= upperBound - mem ? + lowerBound / 1024 : + upperBound / 1024; +} + +function getPlatform() { + if (process.platform === 'win32') return 'Win32'; + if (process.platform === 'android') return 'Android'; + return getOSInformation()[0]; +} + +const cpuData = getCPUs(); +ObjectDefineProperties(Navigator.prototype, { + deviceMemory: { + configurable: true, + enumerable: true, + value: getDeviceMemory(), + }, + hardwareConcurrency: { + configurable: true, + enumerable: true, + value: cpuData ? cpuData.length / 7 : 1, + }, + platform: { + configurable: true, + enumerable: true, + value: getPlatform(), + }, +}); + +module.exports = new InternalNavigator(); diff --git a/test/common/index.js b/test/common/index.js index 2ac4538cbea804..77e8dc0017ffa5 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -282,7 +282,9 @@ if (global.AbortController) if (global.gc) { knownGlobals.push(global.gc); } - +if (global.navigator) { + knownGlobals.push(global.navigator); +} if (global.performance) { knownGlobals.push(global.performance); } diff --git a/test/fixtures/wpt/LICENSE.md b/test/fixtures/wpt/LICENSE.md index ad4858c8745cfa..39c46d03ac2988 100644 --- a/test/fixtures/wpt/LICENSE.md +++ b/test/fixtures/wpt/LICENSE.md @@ -1,6 +1,6 @@ # The 3-Clause BSD License -Copyright 2019 web-platform-tests contributors +Copyright © web-platform-tests contributors Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 27261f56e46467..84317012075fd7 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -12,6 +12,7 @@ Last update: - common: https://github.com/web-platform-tests/wpt/tree/bb97a68974/common - console: https://github.com/web-platform-tests/wpt/tree/3b1f72e99a/console +- device-memory: https://github.com/web-platform-tests/wpt/tree/c0cdd63f19/device-memory - dom/abort: https://github.com/web-platform-tests/wpt/tree/1728d198c9/dom/abort - encoding: https://github.com/web-platform-tests/wpt/tree/35f70910d3/encoding - FileAPI: https://github.com/web-platform-tests/wpt/tree/3b279420d4/FileAPI @@ -19,7 +20,7 @@ Last update: - html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob - html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing - html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers -- interfaces: https://github.com/web-platform-tests/wpt/tree/80a4176623/interfaces +- interfaces: https://github.com/web-platform-tests/wpt/tree/fc086c82d5/interfaces - performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline - resources: https://github.com/web-platform-tests/wpt/tree/972ca5b669/resources - streams: https://github.com/web-platform-tests/wpt/tree/8f60d94439/streams diff --git a/test/fixtures/wpt/device-memory/META.yml b/test/fixtures/wpt/device-memory/META.yml new file mode 100644 index 00000000000000..b8dd4761adff3e --- /dev/null +++ b/test/fixtures/wpt/device-memory/META.yml @@ -0,0 +1,3 @@ +spec: https://w3c.github.io/device-memory/ +suggested_reviewers: + - npm1 diff --git a/test/fixtures/wpt/device-memory/device-memory.https.any.js b/test/fixtures/wpt/device-memory/device-memory.https.any.js new file mode 100644 index 00000000000000..4fe6f04ebc959e --- /dev/null +++ b/test/fixtures/wpt/device-memory/device-memory.https.any.js @@ -0,0 +1,8 @@ +test(function() { + assert_equals(typeof navigator.deviceMemory, "number", + "navigator.deviceMemory returns a number"); + assert_true(navigator.deviceMemory >= 0, + "navigator.deviceMemory returns a positive value"); + assert_true([0.25, 0.5, 1, 2, 4, 8].includes(navigator.deviceMemory), + "navigator.deviceMemory returns a power of 2 between 0.25 and 8"); +}, "navigator.deviceMemory is a positive number, a power of 2, between 0.25 and 8"); diff --git a/test/fixtures/wpt/device-memory/idlharness.https.any.js b/test/fixtures/wpt/device-memory/idlharness.https.any.js new file mode 100644 index 00000000000000..71973394655bdd --- /dev/null +++ b/test/fixtures/wpt/device-memory/idlharness.https.any.js @@ -0,0 +1,19 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +// https://w3c.github.io/device-memory/ + +"use strict"; + +idl_test( + ['device-memory'], + ['html'], + async idl_array => { + if (self.GLOBAL.isWorker()) { + idl_array.add_objects({ WorkerNavigator: ['navigator'] }); + } else { + idl_array.add_objects({ Navigator: ['navigator'] }); + } + } +); diff --git a/test/fixtures/wpt/interfaces/device-memory.idl b/test/fixtures/wpt/interfaces/device-memory.idl new file mode 100644 index 00000000000000..3be709e8a20624 --- /dev/null +++ b/test/fixtures/wpt/interfaces/device-memory.idl @@ -0,0 +1,14 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Device Memory 1 (https://w3c.github.io/device-memory/) + +[ + SecureContext, + Exposed=(Window,Worker) +] interface mixin NavigatorDeviceMemory { + readonly attribute double deviceMemory; +}; + +Navigator includes NavigatorDeviceMemory; +WorkerNavigator includes NavigatorDeviceMemory; diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index fb49e36cc07747..730aac008bc93b 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -7,6 +7,10 @@ "commit": "3b1f72e99a91d31551edd2147dc7b564eaf25d72", "path": "console" }, + "device-memory": { + "commit": "c0cdd63f19507a784531ff4ccc1c71456f79b867", + "path": "device-memory" + }, "dom/abort": { "commit": "1728d198c92834d92f7f399ef35e7823d5bfa0e4", "path": "dom/abort" @@ -36,7 +40,7 @@ "path": "html/webappapis/timers" }, "interfaces": { - "commit": "80a417662387b6eda904607d78ad246c5d8bf191", + "commit": "fc086c82d5a7e9b01a684a23336d6d1f9e79e303", "path": "interfaces" }, "performance-timeline": { diff --git a/test/parallel/test-global.js b/test/parallel/test-global.js index dbd3b06d290d31..b42f944def0c81 100644 --- a/test/parallel/test-global.js +++ b/test/parallel/test-global.js @@ -47,6 +47,7 @@ builtinModules.forEach((moduleName) => { 'clearImmediate', 'clearInterval', 'clearTimeout', + 'navigator', 'performance', 'setImmediate', 'setInterval', diff --git a/test/parallel/test-navigator-global.js b/test/parallel/test-navigator-global.js new file mode 100644 index 00000000000000..6f07adced16655 --- /dev/null +++ b/test/parallel/test-navigator-global.js @@ -0,0 +1,7 @@ +'use strict'; +/* eslint-disable no-global-assign */ +require('../common'); +const { notStrictEqual } = require('assert'); + +performance = undefined; +notStrictEqual(globalThis.performance, undefined); diff --git a/test/parallel/test-navigator.js b/test/parallel/test-navigator.js new file mode 100644 index 00000000000000..04b41abe55c81c --- /dev/null +++ b/test/parallel/test-navigator.js @@ -0,0 +1,21 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { cpus } = require('os'); +const { platform } = require('process'); + +assert.ok(navigator.deviceMemory >= 0.25 && navigator.deviceMemory <= 8); + +assert.strictEqual(navigator.platform, { + aix: 'AIX', + android: 'Android', + darwin: 'Darwin', + freebsd: 'FreeBSD', + linux: 'Linux', + openbsd: 'OpenBSD', + sunos: 'SunOS', + win32: 'Win32', +}[platform]); + +assert.ok(navigator.hardwareConcurrency >= 1); +assert.strictEqual(navigator.hardwareConcurrency, cpus().length); diff --git a/test/wpt/status/device-memory.json b/test/wpt/status/device-memory.json new file mode 100644 index 00000000000000..29983e01d4d289 --- /dev/null +++ b/test/wpt/status/device-memory.json @@ -0,0 +1,5 @@ +{ + "idlharness.https.any.js": { + "skip": "idlharness cannot recognize Node.js environment" + } +} diff --git a/test/wpt/test-device-memory.js b/test/wpt/test-device-memory.js new file mode 100644 index 00000000000000..efb7d4626e479e --- /dev/null +++ b/test/wpt/test-device-memory.js @@ -0,0 +1,7 @@ +'use strict'; +require('../common'); +const { WPTRunner } = require('../common/wpt'); + +const runner = new WPTRunner('device-memory'); + +runner.runJsTests();