diff --git a/doc/api/globals.md b/doc/api/globals.md
index 73d34250ac06..68ece7042b39 100644
--- a/doc/api/globals.md
+++ b/doc/api/globals.md
@@ -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`
@@ -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`
+
+
+
+* Type: {string}
+
+The `navigator.appCodeName` read-only property returns `'Mozilla'`,
+the constant value mandated by the [Navigator API][] specification.
+
+### `navigator.appName`
+
+
+
+* Type: {string}
+
+The `navigator.appName` read-only property returns `'Netscape'`,
+the constant value mandated by the [Navigator API][] specification.
+
+### `navigator.appVersion`
+
+
+
+* 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`
@@ -840,6 +883,18 @@ navigator.locks.request('shared_resource', { mode: 'shared' }, async (lock) => {
See [`worker_threads.locks`][] for detailed API documentation.
+### `navigator.onLine`
+
+
+
+* 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`
+
+* Type: {string}
+
+The `navigator.product` read-only property returns `'Gecko'`,
+the constant value mandated by the [Navigator API][] specification.
+
### `navigator.userAgent`
+
+
+
registerProtocolHandler() and a handler with %s in the fragment
+
+
+
+
+
+
+
First, register the handler: .
+
Then, run the test: .
+
Or, run the test with U+0000 NULL: .
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html
new file mode 100644
index 000000000000..be3a6be66652
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html
@@ -0,0 +1,20 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the fragment (does not use a service worker)
+
+
+
+
+
+
+
First, register the handler: .
+
Then, run the test: .
+
Or, run the test with U+0000 NULL: .
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https.html
new file mode 100644
index 000000000000..61b9722ec29a
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https.html
@@ -0,0 +1,25 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the fragment (does not use a service worker)
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https.html
new file mode 100644
index 000000000000..1eaefaa45f73
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https.html
@@ -0,0 +1,25 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the fragment
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html
new file mode 100644
index 000000000000..085c5723ec41
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html
@@ -0,0 +1,20 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the path
+
+
+
+
+
+
+
First, register the handler: .
+
Then, run the test: .
+
Or, run the test with U+0000 NULL: .
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https.html
new file mode 100644
index 000000000000..a86e79e42eb6
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https.html
@@ -0,0 +1,25 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the path
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html
new file mode 100644
index 000000000000..8ce65a5bad88
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html
@@ -0,0 +1,20 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the query
+
+
+
+
+
+
+
First, register the handler: .
+
Then, run the test: .
+
Or, run the test with U+0000 NULL: .
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html
new file mode 100644
index 000000000000..9b4473fb8952
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html
@@ -0,0 +1,20 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the query (does not use a service worker)
+
+
+
+
+
+
+
First, register the handler: .
+
Then, run the test: .
+
Or, run the test with U+0000 NULL: .
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https.html
new file mode 100644
index 000000000000..97f67e94eceb
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https.html
@@ -0,0 +1,25 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the query (does not use a service worker)
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https.html
new file mode 100644
index 000000000000..8c7b2e1cfdde
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https.html
@@ -0,0 +1,25 @@
+
+
+
+
+registerProtocolHandler() and a handler with %s in the query
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
new file mode 100644
index 000000000000..add78d2fd47a
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
@@ -0,0 +1,218 @@
+
+
+protocol handlers
+
+
+
+
+
+
+
Note: If your browser limits the number of handler
+registration requests on a page, you might need to disable or significantly
+increase that limit for the tests below to run.
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html
new file mode 100644
index 000000000000..0120aaa12ff8
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html
@@ -0,0 +1,30 @@
+
+
+protocol handlers
+
+
+
+
+
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js
new file mode 100644
index 000000000000..5fd915d17f98
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js
@@ -0,0 +1,3 @@
+onfetch = e => {
+ e.respondWith(fetch("handler.html"));
+}
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js
new file mode 100644
index 000000000000..88c62ec373d1
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js
@@ -0,0 +1,53 @@
+// These can be used in an environment that has these global variables defined:
+// * type (one of "path", "query", or "fragment")
+// * noSW (a boolean)
+
+if (type === "path" && noSW) {
+ throw new Error("There is no support for a path handler without a service worker.");
+}
+
+const swString = noSW ? "" : "sw";
+const handler = {
+ "path": "PSS%sPSE/?QES\u2020QEE#FES\u2020FEE",
+ "query": "?QES\u2020QEEPSS%sPSE#FES\u2020FEE",
+ "fragment": "?QES\u2020QEE#FES\u2020FEEPSS%sPSE"
+}[type];
+const scheme = `web+wpt${type}${swString}`;
+
+function register() {
+ const handlerURL = noSW ? `resources/handler.html${handler}${type}` : `resources/handler/${type}/${handler}`;
+ navigator.registerProtocolHandler(scheme, handlerURL, `WPT ${type} handler${noSW ? ", without service worker" : ""}`);
+}
+
+function runTest({ includeNull = false } = {}) {
+ promise_test(async t => {
+ const bc = new BroadcastChannel(`protocol-handler-${type}${swString}`);
+ if (!noSW) {
+ const reg = await service_worker_unregister_and_register(t, "resources/handler-sw.js", "resources/handler/");
+ t.add_cleanup(async () => await reg.unregister());
+ await wait_for_state(t, reg.installing, 'activated');
+ }
+ const a = document.body.appendChild(document.createElement("a"));
+ const codePoints = [];
+ let i = includeNull ? 0 : 1;
+ for (; i < 0x82; i++) {
+ codePoints.push(String.fromCharCode(i));
+ }
+ a.href = `${scheme}:${codePoints.join("")}`;
+ a.target = "_blank";
+ a.click();
+ await new Promise(resolve => {
+ bc.onmessage = t.step_func(e => {
+ resultingURL = e.data;
+ assert_equals(stringBetweenMarkers(resultingURL, "QES", "QEE"), "%86", "query baseline");
+ assert_equals(stringBetweenMarkers(resultingURL, "FES", "FEE"), "%E2%80%A0", "fragment baseline");
+ assert_equals(stringBetweenMarkers(resultingURL, "PSS", "PSE"), `${encodeURIComponent(scheme)}%3A${includeNull ? "%2500" : ""}%2501%2502%2503%2504%2505%2506%2507%2508%250B%250C%250E%250F%2510%2511%2512%2513%2514%2515%2516%2517%2518%2519%251A%251B%251C%251D%251E%251F%20!%22%23%24%25%26${type === "query" ? "%27" : "'"}()*%2B%2C-.%2F0123456789%3A%3B%253C%3D%253E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%2560abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%257F%25C2%2580%25C2%2581`, "actual test");
+ resolve();
+ });
+ });
+ });
+}
+
+function stringBetweenMarkers(string, start, end) {
+ return string.substring(string.indexOf(start) + start.length, string.indexOf(end));
+}
diff --git a/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html
new file mode 100644
index 000000000000..552e5417842e
--- /dev/null
+++ b/test/fixtures/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html
@@ -0,0 +1,23 @@
+
+
This popup can be closed if it does not close itself.
+