Skip to content
Open
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
70 changes: 68 additions & 2 deletions doc/api/globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,11 @@ added: v21.0.0
> Stability: 1.1 - Active development. Disable this API with the
> [`--no-experimental-global-navigator`][] CLI flag.

A partial implementation of the [Navigator API][].
A partial implementation of the [Navigator API][]. All properties that the
HTML specification exposes to non-`Window` environments are implemented;
properties exposed only on `Window` (such as `navigator.plugins`) and most
properties defined by other specifications (such as `navigator.clipboard`)
are not. See below for the full list of supported options.

## `navigator`

Expand All @@ -733,7 +737,46 @@ added: v21.0.0
> Stability: 1.1 - Active development. Disable this API with the
> [`--no-experimental-global-navigator`][] CLI flag.

A partial implementation of [`window.navigator`][].
A partial implementation of [`window.navigator`][]. All properties of the
[Navigator API][] that the HTML specification exposes to non-`Window`
environments are implemented; properties exposed only on `Window` (such as
`navigator.plugins`) and most properties defined by other specifications
(such as `navigator.clipboard`) are not.

### `navigator.appCodeName`

<!-- YAML
added: REPLACEME
-->

* Type: {string}

The `navigator.appCodeName` read-only property returns `'Mozilla'`,
the constant value mandated by the [Navigator API][] specification.

### `navigator.appName`

<!-- YAML
added: REPLACEME
-->

* Type: {string}

The `navigator.appName` read-only property returns `'Netscape'`,
the constant value mandated by the [Navigator API][] specification.

### `navigator.appVersion`

<!-- YAML
added: REPLACEME
-->

* Type: {string}

The `navigator.appVersion` read-only property returns the empty string.
The [Navigator API][] specification requires this value to be derived from
the default `User-Agent` value and to be the empty string when that value
does not start with `Mozilla/5.0 (`.

### `navigator.hardwareConcurrency`

Expand Down Expand Up @@ -840,6 +883,18 @@ navigator.locks.request('shared_resource', { mode: 'shared' }, async (lock) => {

See [`worker_threads.locks`][] for detailed API documentation.

### `navigator.onLine`

<!-- YAML
added: REPLACEME
-->

* Type: {boolean}

The `navigator.onLine` read-only property always returns `true`. The
[Navigator API][] specification only allows returning `false` when the user
agent is definitely offline, which Node.js cannot determine.

### `navigator.platform`

<!-- YAML
Expand All @@ -855,6 +910,17 @@ platform on which the Node.js instance is running.
console.log(`This process is running on ${navigator.platform}`);
```

### `navigator.product`

<!-- YAML
added: REPLACEME
-->

* Type: {string}

The `navigator.product` read-only property returns `'Gecko'`,
the constant value mandated by the [Navigator API][] specification.

### `navigator.userAgent`

<!-- YAML
Expand Down
62 changes: 57 additions & 5 deletions lib/internal/navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ function getNavigatorPlatform(arch, platform) {

class Navigator {
// Private properties are used to avoid brand validations: reading a private
// field from a non-Navigator receiver throws a TypeError on its own.
// field or calling a private method from a non-Navigator receiver throws a
// TypeError on its own.
#availableParallelism;
#locks;
#userAgent;
Expand All @@ -94,6 +95,11 @@ class Navigator {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}

// Getters that do not read a private field call this method so they still
// throw a TypeError on a non-Navigator receiver
// (e.g. `Navigator.prototype.product`).
#brandCheck() {}

/**
* @returns {number}
*/
Expand All @@ -114,10 +120,7 @@ class Navigator {
* @returns {string}
*/
get language() {
// `language` does not read a private field, so brand-check explicitly
// to keep parity with the other getters when called on a non-Navigator
// receiver (e.g. `Navigator.prototype.language`).
this.#languages; // eslint-disable-line no-unused-expressions
this.#brandCheck();
// The default locale might be changed dynamically, so always invoke the
// binding.
return getDefaultLocale() || 'en-US';
Expand Down Expand Up @@ -146,6 +149,50 @@ class Navigator {
this.#platform ??= getNavigatorPlatform(arch, platform);
return this.#platform;
}

/**
* @returns {string}
*/
get appCodeName() {
this.#brandCheck();
return 'Mozilla';
}

/**
* @returns {string}
*/
get appName() {
this.#brandCheck();
return 'Netscape';
}

/**
* @returns {string}
*/
get appVersion() {
this.#brandCheck();
// If userAgent does not start with `Mozilla/5.0 (`, then return the empty string.
return '';
}

/**
* @returns {string}
*/
get product() {
this.#brandCheck();
return 'Gecko';
}

/**
* @returns {boolean}
*/
get onLine() {
this.#brandCheck();
// "Returns true if the user agent might be online."
// Since we have no way to truely check the online-ness in the navigator,
// the user agent might be online, so return true.
return true;
}
}

ObjectDefineProperties(Navigator.prototype, {
Expand All @@ -155,6 +202,11 @@ ObjectDefineProperties(Navigator.prototype, {
userAgent: kEnumerableProperty,
platform: kEnumerableProperty,
locks: kEnumerableProperty,
appCodeName: kEnumerableProperty,
appName: kEnumerableProperty,
appVersion: kEnumerableProperty,
product: kEnumerableProperty,
onLine: kEnumerableProperty,
});

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
rules:
- plugins-and-mimetypes.html: [pdf-viewer]
- navigator_user_agent.tentative.html: [ua-client-hints]
- navigator_user_agent.https.tentative.html: [ua-client-hints]
- protocol-*: [registerprotocolhandler]
- navigator-window-controls-overlay.tentative.html: [window-controls-overlay]
- navigator.any.js: [user-agent-sniffing]
- get-navigatorlanguage-manual.html: [language]
- navigatorlanguage.html: [language]
- navigatorcookies-cookieenabled-*: [cookie-enabled]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
test(() => {
assert_equals(window.clientInformation, window.navigator);
}, "window.clientInformation exists and equals window.navigator");

test(() => {
window.clientInformation = 1;
assert_equals(window.clientInformation, 1);
}, "window.clientInformation is Replaceable");
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>NavigatorLanguage: navigator.language returns the user's preferred language</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigatorlanguage">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<h2>Precondition</h2>
<p>The user agent's preferred language is set as English (en).</p>
<div id="log"></div>
<script>
test(function() {
assert_equals(navigator.language, "en");
});
</script>

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
"registerContentHandler",
"isProtocolHandlerRegistered",
"isContentHandlerRegistered",
"unregisterContentHandler"
].forEach(method => {
test(() => {
assert_false(method in self.navigator);
}, method + "() is removed");
});

test(() => {
let called = false;
self.navigator.registerProtocolHandler("web+test", "%s", { toString: () => called = true });
assert_false(called);
}, "registerProtocolHandler has no third argument");
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Test for lack of indexed getter on Navigator</title>
<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-navigator-object">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
test(function() {
assert_false("0" in window.navigator);
assert_equals(window.navigator[0], undefined);
}, "window.navigator[0] should not exist");
test(function() {
window.navigator[0] = "pass";
assert_true("0" in window.navigator);
assert_equals(window.navigator[0], "pass");
}, "window.navigator[0] should be settable");
test(function() {
assert_false("-1" in window.navigator);
assert_equals(window.navigator[-1], undefined);
}, "window.navigator[-1] should not exist");
test(function() {
window.navigator[-1] = "pass";
assert_true("-1" in window.navigator);
assert_equals(window.navigator[-1], "pass");
}, "window.navigator[-1] should be settable");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<meta charset='utf-8'>
<title>navigator.windowControlsOverlay</title>

<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>

<script>
test(function(){
assert_idl_attribute(navigator, 'windowControlsOverlay');
}, 'the windowControlsOverlay object should exist on the navigator object');

test(function(){
assert_idl_attribute(navigator.windowControlsOverlay, 'visible');
}, 'visible should be a member of the windowControlsOverlay object');

test(function(){
assert_false(navigator.windowControlsOverlay.visible);
}, 'visible should be false');

test(function(){
assert_idl_attribute(navigator.windowControlsOverlay, 'getTitlebarAreaRect');
}, 'getTitlebarAreaRect should be a method of the windowControlsOverlay object');

test(function(){
var rect = navigator.windowControlsOverlay.getTitlebarAreaRect();
assert_true(rect instanceof DOMRect);
}, 'getTitlebarAreaRect return type should be DOMRect');

test(function(){
var rect = navigator.windowControlsOverlay.getTitlebarAreaRect();
assert_equals(rect.x, 0);
assert_equals(rect.y, 0);
assert_equals(rect.width, 0);
assert_equals(rect.height, 0);
}, 'getTitlebarAreaRect should return a empty DOMRect');

test(function(){
assert_idl_attribute(navigator.windowControlsOverlay, 'ongeometrychange');
}, 'ongeometrychange should be a member of the windowControlsOverlay object');
</script>
Loading
Loading