diff --git a/.vscode/dictionaries/code-entities.txt b/.vscode/dictionaries/code-entities.txt
index da202b6326e74c6..33e258dfc43d740 100644
--- a/.vscode/dictionaries/code-entities.txt
+++ b/.vscode/dictionaries/code-entities.txt
@@ -30,6 +30,7 @@ adlm
advertisementreceived
afrc
afterscriptexecute
+aioquic
alaw
allowdirs
allowevents
@@ -950,6 +951,7 @@ workon
WORKON_HOME
writingsuggestions
wtai
+wtransport
x-abiword
X-cept-Encoding
x-ms-aria-flowfrom
diff --git a/files/en-us/mozilla/add-ons/webextensions/api/runtime/getversion/index.md b/files/en-us/mozilla/add-ons/webextensions/api/runtime/getversion/index.md
index ba7729c7970ab4e..a3401ba7f83afa3 100644
--- a/files/en-us/mozilla/add-ons/webextensions/api/runtime/getversion/index.md
+++ b/files/en-us/mozilla/add-ons/webextensions/api/runtime/getversion/index.md
@@ -11,7 +11,7 @@ Returns the extension's version from the [`version`](/en-US/docs/Mozilla/Add-ons
## Syntax
```js-nolint
-let extensionVersion = await browser.runtime.getVersion()
+let extensionVersion = browser.runtime.getVersion()
```
### Parameters
diff --git a/files/en-us/mozilla/add-ons/webextensions/content_scripts/index.md b/files/en-us/mozilla/add-ons/webextensions/content_scripts/index.md
index 0f8afe68a82e099..9e295a8c597ad7d 100644
--- a/files/en-us/mozilla/add-ons/webextensions/content_scripts/index.md
+++ b/files/en-us/mozilla/add-ons/webextensions/content_scripts/index.md
@@ -184,6 +184,7 @@ In addition to the standard DOM APIs, content scripts can use these WebExtension
- {{WebExtAPIRef("runtime.getDocumentId()","getDocumentId()")}}
- {{WebExtAPIRef("runtime.getFrameId()","getFrameId()")}}
- [`getManifest()`](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/getManifest)
+- {{WebExtAPIRef("runtime.getVersion()","getVersion()")}}
- [`getURL()`](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/getURL)
- [`onConnect`](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onConnect)
- [`onMessage`](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)
diff --git a/files/en-us/mozilla/firefox/releases/158/index.md b/files/en-us/mozilla/firefox/releases/158/index.md
index 624af07cb5f314f..3f3b37a5ade85a4 100644
--- a/files/en-us/mozilla/firefox/releases/158/index.md
+++ b/files/en-us/mozilla/firefox/releases/158/index.md
@@ -72,6 +72,8 @@ Firefox 158 is the current [Nightly version of Firefox](https://www.firefox.com/
## Changes for add-on developers
+- Adds [`runtime.getVersion()`](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/getVersion) to return the extension's version as declared in the manifest. ([Firefox bug 1992418](https://bugzil.la/1992418))
+
diff --git a/files/en-us/web/api/htmlelement/load_event/index.md b/files/en-us/web/api/htmlelement/load_event/index.md
index 23c25b7cc9172e3..c8d9057ded07480 100644
--- a/files/en-us/web/api/htmlelement/load_event/index.md
+++ b/files/en-us/web/api/htmlelement/load_event/index.md
@@ -8,7 +8,7 @@ browser-compat: api.HTMLElement.load_event
{{APIRef("HTML DOM")}}
-The **`load`** event fires for elements containing a resource when the resource has successfully loaded. Currently, the list of supported HTML elements are: {{HTMLElement("body")}}, {{HTMLElement("embed")}}, {{HTMLElement("iframe")}}, {{HTMLElement("img")}}, {{HTMLElement("link")}}, {{HTMLElement("object")}}, {{HTMLElement("script")}}, {{HTMLElement("style")}}, and {{HTMLElement("track")}}.
+The **`load`** event fires for elements containing a resource when the resource has successfully loaded. Currently, the list of supported HTML elements are: {{HTMLElement("embed")}}, {{HTMLElement("iframe")}}, {{HTMLElement("img")}}, {{HTMLElement("link")}}, {{HTMLElement("object")}}, {{HTMLElement("script")}}, {{HTMLElement("style")}}, and {{HTMLElement("track")}}.
> [!NOTE]
> The `load` event on {{domxref("HTMLBodyElement#event_handlers", "HTMLBodyElement")}} is actually an alias for the {{domxref("Window/load_event", "window.onload")}} event. Therefore, the `load` event will only fire on the `
` element once all of the document's resources have loaded or errored. However, for the sake of clarity, it is recommended that the event handler is attached to the `window` object directly rather than on `HTMLBodyElement`.
@@ -29,6 +29,35 @@ onload = (event) => { }
A generic {{domxref("Event")}}.
+## Usage notes
+
+### Handling resources that have already loaded
+
+A resource may finish loading before your script registers a `load` event listener. In that case, the listener will not receive the event that has already fired.
+
+For example, an image in the HTML may load while the browser is still receiving and parsing the rest of the document, before it reaches a subsequent script that registers the listener. The listener can also be registered too late if the script is loaded asynchronously or deferred, or if it waits for {{domxref("Document/DOMContentLoaded_event", "DOMContentLoaded")}} before registering the listener. Server-rendered HTML using frameworks such as React or Vue can have the same issue because event handlers written in JSX or templates are compiled into JavaScript calls, not HTML event handler attributes.
+
+There are some ways to ensure that the event handler is registered as soon as possible, before the resource loads. For example, you can use an [HTML event handler attribute](/en-US/docs/Web/HTML/Reference/Attributes#event_handler_attributes) if you don't mind its undesirable aspects, or you can dynamically create the whole element in JavaScript and make sure the event listener is attached before starting the load (such as by assigning to `src` for images).
+
+Alternatively, when registering the event handler, you can check if the resource has already loaded—and if so, immediately trigger the handler. For an image, you can check its {{domxref("HTMLImageElement.complete", "complete")}} and {{domxref("HTMLImageElement.naturalWidth", "naturalWidth")}} properties after registering the listener. `complete` checks that the request completed; `naturalWidth > 0` ensures that an actual image was loaded.
+
+```js
+const image = document.getElementById("image");
+let handled = false;
+
+function handleLoaded() {
+ if (handled) return;
+ handled = true;
+ // Use the loaded image here.
+}
+
+image.addEventListener("load", handleLoaded, { once: true });
+
+if (image.complete && image.naturalWidth > 0) {
+ handleLoaded();
+}
+```
+
## Examples
This example prints to the screen whenever the {{HtmlElement("img")}} element successfully loads its resource.
@@ -62,6 +91,8 @@ function reload() {
### Result
+The example's `` element contains an `src` attribute in the markup, so the image may load before the `load` event listener attaches. Clicking "reload" is guaranteed to trigger the event listener.
+
{{EmbedLiveSample("Example", "100%", "200")}}
## Specifications
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/index.md b/files/en-us/web/api/service_worker_api/using_service_workers/index.md
index a5f316d4a05a2b6..29f5a7a0ef671cc 100644
--- a/files/en-us/web/api/service_worker_api/using_service_workers/index.md
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/index.md
@@ -22,16 +22,59 @@ Service workers are enabled by default in all modern browsers. To run code using
## Basic architecture
-With service workers, the following steps are generally observed for basic setup:
+With service workers, the following steps are generally observed for initial installation and for replacing an existing service worker. The diagrams show an example that populates versioned caches during installation and removes old caches during activation.
+
+### Initial installation
+
+In this example, two pages are already open before the first service worker is registered. One of the pages calls [`serviceWorkerContainer.register()`](/en-US/docs/Web/API/ServiceWorkerContainer/register), which initiates the process.
+
+1. The service worker code is fetched and then registered. If successful, the service worker is executed in a [`ServiceWorkerGlobalScope`](/en-US/docs/Web/API/ServiceWorkerGlobalScope); this is basically a special kind of worker context, running off the main script execution thread, with no DOM access. The service worker is now ready to process events.
+
+ 
-1. The service worker code is fetched and then registered using [`serviceWorkerContainer.register()`](/en-US/docs/Web/API/ServiceWorkerContainer/register). If successful, the service worker is executed in a [`ServiceWorkerGlobalScope`](/en-US/docs/Web/API/ServiceWorkerGlobalScope); this is basically a special kind of worker context, running off the main script execution thread, with no DOM access. The service worker is now ready to process events.
2. Installation takes place. An `install` event is always the first one sent to a service worker (this can be used to start the process of populating an IndexedDB, and caching site assets). During this step, the application is preparing to make everything available for use offline.
-3. When the `install` handler completes, the service worker is considered installed. At this point a previous version of the service worker may be active and controlling open pages. Because we don't want two different versions of the same service worker running at the same time, the new version is not yet active.
-4. Once all pages controlled by the old version of the service worker have closed, it's safe to retire the old version, and the newly installed service worker receives an `activate` event. The primary use of `activate` is to clean up resources used in previous versions of the service worker. The new service worker can call [`skipWaiting()`](/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting) to ask to be activated immediately without waiting for open pages to be closed. The new service worker will then receive `activate` immediately, and will take over any open pages.
-5. After activation, the service worker will now control pages, but only those that were opened after the `register()` is successful. In other words, documents will have to be reloaded to actually be controlled, because a document starts life with or without a service worker and maintains that for its lifetime. To override this default behavior and adopt open pages, a service worker can call [`clients.claim()`](/en-US/docs/Web/API/Clients/claim).
-6. Whenever a new version of a service worker is fetched, this cycle happens again and the remains of the previous version are cleaned during the new version's activation.
-
+ 
+
+3. When installation completes successfully, the service worker is considered installed.
+
+ 
+
+4. Because this is the first service worker, it receives an `activate` event without waiting for open pages to close. The `activate` handler can finish setting up the service worker.
+
+ 
+
+5. After activation, the service worker will control pages opened within its scope. Existing documents will have to be reloaded to actually be controlled, because a document starts life with or without a service worker and maintains that for its lifetime. To override this default behavior and adopt open pages, a service worker can call [`clients.claim()`](/en-US/docs/Web/API/Clients/claim).
+
+ 
+
+### Replacing an existing service worker
+
+This independent example starts with one open client controlled by version 1. It illustrates the default waiting behavior when replacing an existing service worker.
+
+1. Whenever a new version of a service worker is fetched, this cycle happens again. The previous version remains active and continues to control its clients.
+
+ 
+
+2. Installation takes place for the new version. Its `install` handler can populate a new cache while the old version continues to use its existing cache.
+
+ 
+
+3. When installation completes successfully, the new version waits while the old version is still controlling clients. The new version is not yet active.
+
+ 
+
+4. Once all pages controlled by the old version of the service worker have closed and the old version has finished handling pending events, it's safe to retire the old version, and the newly installed service worker receives an `activate` event. The primary use of `activate` is to clean up resources used in previous versions of the service worker, such as the old cache in this example.
+
+ The new service worker can call [`skipWaiting()`](/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting) to ask to be activated without waiting for open pages to be closed. It then takes over the pages controlled by the old version.
+
+ 
+
+5. After activation, newly opened pages within the registration's scope are controlled by the new version.
+
+ 
+
+### Service worker events
Here is a summary of the available service worker events:
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-activated.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-activated.svg
new file mode 100644
index 000000000000000..1c58cd7ea31df2f
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-activated.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-activation.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-activation.svg
new file mode 100644
index 000000000000000..b6d41049b6a2dca
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-activation.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-installation.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-installation.svg
new file mode 100644
index 000000000000000..6cc61c95964fd6b
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-installation.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-installed.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-installed.svg
new file mode 100644
index 000000000000000..85f8bf906d3fa9f
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-installed.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-lifecycle.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-lifecycle.svg
deleted file mode 100644
index a0f36c8ed0c1ca3..000000000000000
--- a/files/en-us/web/api/service_worker_api/using_service_workers/sw-lifecycle.svg
+++ /dev/null
@@ -1,1796 +0,0 @@
-
-
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-registration.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-registration.svg
new file mode 100644
index 000000000000000..eeebf08276b126d
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-registration.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activated.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activated.svg
new file mode 100644
index 000000000000000..742db93fff01ade
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activated.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activation.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activation.svg
new file mode 100644
index 000000000000000..0b28e52383237d6
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activation.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-fetched.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-fetched.svg
new file mode 100644
index 000000000000000..aed249a797f4e4f
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-fetched.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-installation.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-installation.svg
new file mode 100644
index 000000000000000..83be6e3a3d8b509
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-installation.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-waiting.svg b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-waiting.svg
new file mode 100644
index 000000000000000..a08bc1090c166b7
--- /dev/null
+++ b/files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-waiting.svg
@@ -0,0 +1 @@
+
diff --git a/files/en-us/web/api/usb/requestdevice/index.md b/files/en-us/web/api/usb/requestdevice/index.md
index c220162bbe871d2..7cd5801eb3f2fb4 100644
--- a/files/en-us/web/api/usb/requestdevice/index.md
+++ b/files/en-us/web/api/usb/requestdevice/index.md
@@ -35,6 +35,8 @@ requestDevice(options)
- `subclassCode`
- `protocolCode`
- `serialNumber`
+ - `exclusionFilters` {{optional_inline}}
+ - : An array of filter objects representing devices to exclude from the pairing flow. These objects have the same properties as those in `filters`. Exclusion takes priority over inclusion.
### Return value
@@ -46,6 +48,8 @@ A {{JSxRef("Promise")}} that resolves with an instance of {{DOMxRef("USBDevice")
## Examples
+### Requesting specific USB devices
+
The following example looks for one of two USB devices. Notice that two product IDs are
specified. Both are passed to `requestDevice()`. This triggers a user-agent
flow that prompts the user to select a device for pairing. Only the selected device is
@@ -72,6 +76,24 @@ navigator.usb
});
```
+### Excluding devices
+
+The following example requests a device with vendor ID `0x1209`; it excludes devices with that vendor ID that have the product ID `0xa850`:
+
+```js
+navigator.usb
+ .requestDevice({
+ filters: [{ vendorId: 0x1209 }],
+ exclusionFilters: [{ vendorId: 0x1209, productId: 0xa850 }],
+ })
+ .then((usbDevice) => {
+ console.log(`Product name: ${usbDevice.productName}`);
+ })
+ .catch((e) => {
+ console.error(`There is no device. ${e}`);
+ });
+```
+
## Specifications
{{Specifications}}
diff --git a/files/en-us/web/api/webtransport/createunidirectionalstream/index.md b/files/en-us/web/api/webtransport/createunidirectionalstream/index.md
index 1ec037890a3d7fd..dfd093435a9ae0b 100644
--- a/files/en-us/web/api/webtransport/createunidirectionalstream/index.md
+++ b/files/en-us/web/api/webtransport/createunidirectionalstream/index.md
@@ -50,7 +50,7 @@ A {{jsxref("Promise")}} that resolves to a `WebTransportSendStream` object (this
Use the `createUnidirectionalStream()` method to get a reference to a {{domxref("WritableStream")}}. From this you can {{domxref("WritableStream.getWriter", "get a writer", "", "nocode")}} to allow data to be written to the stream and sent to the server.
-Use the {{domxref("WritableStreamDefaultWriter.close", "close()")}} method of the resulting {{domxref("WritableStreamDefaultWriter")}} to close the associated HTTP/3 connection. The browser tries to send all pending data before actually closing the associated connection.
+Use the {{domxref("WritableStreamDefaultWriter.close", "close()")}} method of the resulting {{domxref("WritableStreamDefaultWriter")}} to close the stream. The browser tries to send all pending data before actually closing the stream.
```js
async function writeData() {
diff --git a/files/en-us/web/api/webtransport_api/index.md b/files/en-us/web/api/webtransport_api/index.md
index 5da0212b275ae9a..7144161540d4c9f 100644
--- a/files/en-us/web/api/webtransport_api/index.md
+++ b/files/en-us/web/api/webtransport_api/index.md
@@ -45,6 +45,72 @@ async function initTransport(url) {
}
```
+### Server implementation
+
+A WebTransport connection requires a supporting server. To establish a session, the client sends an extended `CONNECT` request with a `:protocol` pseudo-header identifying WebTransport. For browser clients, the request also includes an `Origin` header, which the server must verify before accepting the session. (This is automatically handled by the `WebTransport()` constructor.) The server accepts the session by sending a successful (2xx) response. The client and server can then exchange data using multiple bidirectional streams, unidirectional streams, and datagrams associated with that session.
+
+The [WebTransport over HTTP/3 specification](https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/) describes the protocol and server requirements in more detail.
+
+You should use a WebTransport library to handle the server-side protocol details. For example:
+
+- **Go**: [`webtransport-go`](https://github.com/quic-go/webtransport-go)
+- **Python**: [`aioquic`](https://github.com/aiortc/aioquic); see also [Google Chrome's WebTransport server example](https://github.com/GoogleChrome/samples/blob/gh-pages/webtransport/webtransport_server.py)
+- **Rust**: [`wtransport`](https://github.com/BiagioFesta/wtransport)
+- **Node.js**: [`@fails-components/webtransport`](https://github.com/fails-components/webtransport)
+- **Deno**: [built-in WebTransport support](https://docs.deno.com/examples/web_transport/) (unstable)
+
+For all our client examples, we'll provide minimal server examples using the Node.js `@fails-components/webtransport` package. Code written with other libraries or languages may look substantially different.
+
+> [!NOTE]
+> Server-side JavaScript examples will be marked with `// -- server.js --`. JavaScript examples without this comment are client-side code.
+
+Here's an example server for the [initial connection](#initial_connection) example:
+
+```js
+// -- server.js --
+import { randomBytes } from "node:crypto";
+import { readFileSync } from "node:fs";
+import { Http3Server } from "@fails-components/webtransport";
+
+const allowedOrigin = "https://example.com";
+const server = new Http3Server({
+ host: "0.0.0.0",
+ port: 4999,
+ secret: randomBytes(32).toString("hex"),
+ cert: readFileSync("certificate.pem", "utf8"),
+ privKey: readFileSync("private-key.pem", "utf8"),
+});
+
+async function acceptRequest({ header }) {
+ const path = header[":path"];
+ if (path !== "/wt") {
+ return { status: 404, path };
+ }
+ if (header.origin !== allowedOrigin) {
+ return { status: 403, path };
+ }
+ return { status: 200, path };
+}
+
+server.setRequestCallback(acceptRequest);
+const sessions = server.sessionStream("/wt");
+server.startServer();
+await server.ready;
+
+async function handleSession(session) {
+ // Add one of the server-side examples below here.
+}
+
+for await (const session of sessions) {
+ session.closed.catch(console.error);
+ session.ready.then(() => handleSession(session)).catch(console.error);
+}
+```
+
+If you want to run the code, you need to replace all instances of `example.com` in both the client and server code with their actual respective endpoints. You also need to supply `certificate.pem` and `private-key.pem`, which contain a browser-trusted TLS certificate and its private key.
+
+Unless stated otherwise, each server-side example below replaces the body of `handleSession()`. The `session` object represents one accepted client session.
+
### Closing the connection
You can respond to the connection closing by waiting for the {{domxref("WebTransport.closed")}} promise to fulfill. Errors returned by WebTransport operations are of type {{domxref("WebTransportError")}}, and contain additional data on top of the standard {{domxref("DOMException")}} set.
@@ -61,7 +127,16 @@ async function closeTransport(transport) {
}
```
+In `@fails-components/webtransport`, call `session.close()` to close the session:
+
+```js
+// -- server.js --
+session.close({ closeCode: 0, reason: "Work complete" });
+await session.closed;
+```
+
The server may also indicate that it wants to drain the connection prior to closing, perhaps due to management of the underlying transport.
+For example, in `@fails-components/webtransport`, this is done with `session.notifySessionDraining()`.
When this happens, the client should start closing streams, and create a new session if it needs to continue its work.
You can detect this using the {{domxref("WebTransport.draining")}} promise, which fulfills once the server signals that the session is entering the draining state:
@@ -102,6 +177,29 @@ async function initTransport(url) {
}
```
+When the `@fails-components/webtransport` library parses the headers, it special cases `header["wt-available-protocols"]` and converts the value into an array. For example, you can replace `server.setRequestCallback(acceptRequest)` with the following to reject malformed values and select the first protocol supported by the server:
+
+```js
+// -- server.js --
+const supportedProtocols = new Set(["chat", "file-transfer"]);
+
+server.setRequestCallback(async (request) => {
+ const response = await acceptRequest(request);
+ if (response.status !== 200) {
+ return response;
+ }
+
+ const offeredProtocols = request.header["wt-available-protocols"] ?? [];
+ if (!Array.isArray(offeredProtocols)) {
+ return { ...response, status: 400 };
+ }
+ const selectedProtocol = offeredProtocols.find((protocol) =>
+ supportedProtocols.has(protocol),
+ );
+ return { ...response, selectedProtocol };
+});
+```
+
### Unreliable transmission via datagrams
"Unreliable" means that transmission of data is not guaranteed, nor is arrival in a specific order. This is fine in some situations and provides very fast delivery. For example, you might want to transmit regular game state updates where each message supersedes the last one that arrives, and order is not important.
@@ -118,6 +216,15 @@ writer.write(data1);
writer.write(data2);
```
+In `@fails-components/webtransport`, read these datagrams from `session.datagrams.readable`:
+
+```js
+// -- server.js --
+for await (const data of session.datagrams.readable) {
+ console.log(data); // A Uint8Array sent by the client.
+}
+```
+
The {{domxref("WebTransportDatagramDuplexStream.readable")}} property returns a {{domxref("ReadableStream")}} object that you can use to receive data from the server:
```js
@@ -134,6 +241,19 @@ async function readData() {
}
```
+In `@fails-components/webtransport`, to send datagrams for this client code to read, obtain a writer:
+
+```js
+// -- server.js --
+const writer = session.datagrams.createWritable().getWriter();
+try {
+ await writer.write(new Uint8Array([65, 66, 67]));
+ await writer.write(new Uint8Array([68, 69, 70]));
+} finally {
+ writer.releaseLock();
+}
+```
+
### Reliable transmission via streams
"Reliable" means that transmission and order of data are guaranteed. That provides slower delivery (albeit faster than with WebSockets), and is needed in situations where reliability and ordering are important (such as chat applications, for example).
@@ -147,7 +267,7 @@ To open a unidirectional stream from a user agent, you use the {{domxref("WebTra
```js
async function writeData() {
const stream = await transport.createUnidirectionalStream();
- const writer = stream.writable.getWriter();
+ const writer = stream.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
@@ -162,7 +282,22 @@ async function writeData() {
}
```
-Note also the use of the {{domxref("WritableStreamDefaultWriter.close()")}} method to close the associated HTTP/3 connection once all data has been sent.
+Note also the use of the {{domxref("WritableStreamDefaultWriter.close()")}} method to close the stream once all data has been sent.
+
+In `@fails-components/webtransport`, each item in `session.incomingUnidirectionalStreams` is a readable stream carrying data from the client. Start a separate reader for each stream so that a stream waiting for data does not prevent the server from accepting another:
+
+```js
+// -- server.js --
+async function receiveStream(stream) {
+ for await (const data of stream) {
+ console.log(data); // A Uint8Array sent by the client.
+ }
+}
+
+for await (const stream of session.incomingUnidirectionalStreams) {
+ receiveStream(stream).catch(console.error);
+}
+```
If the server opens a unidirectional stream to transmit data to the client, this can be accessed on the client via the {{domxref("WebTransport.incomingUnidirectionalStreams")}} property, which returns a {{domxref("ReadableStream")}} of {{domxref("WebTransportReceiveStream")}} objects. These can be used to read {{jsxref("Uint8Array")}} instances sent by the server.
@@ -182,7 +317,7 @@ async function readData(receiveStream) {
}
```
-Next, call {{domxref("WebTransport.incomingUnidirectionalStreams")}} and get a reference to the reader available on the `ReadableStream` it returns, and then use the reader to read the data from the server. Each chunk is a `WebTransportReceiveStream`, and we use the `readFrom()` set up earlier to read them:
+Next, call {{domxref("WebTransport.incomingUnidirectionalStreams")}} and get a reference to the reader available on the `ReadableStream` it returns, and then use the reader to read the data from the server. Each chunk is a `WebTransportReceiveStream`, and we use the `readData()` set up earlier to read them:
```js
async function receiveUnidirectional() {
@@ -199,6 +334,17 @@ async function receiveUnidirectional() {
}
```
+In `@fails-components/webtransport`, to supply a stream for the client's `receiveUnidirectional()` and `readData()` functions, create a unidirectional stream and write to it:
+
+```js
+// -- server.js --
+const stream = await session.createUnidirectionalStream();
+const writer = stream.getWriter();
+await writer.write(new Uint8Array([65, 66, 67]));
+await writer.write(new Uint8Array([68, 69, 70]));
+await writer.close();
+```
+
#### Bidirectional transmission
To open a bidirectional stream from a user agent, you use the {{domxref("WebTransport.createBidirectionalStream()")}} method to get a reference to a {{domxref("WebTransportBidirectionalStream")}}.
@@ -217,6 +363,7 @@ async function setUpBidirectional() {
const writable = stream.writable;
// …
+ return stream;
}
```
@@ -243,11 +390,31 @@ async function writeData(writable) {
const writer = writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
- writer.write(data1);
- writer.write(data2);
+ await writer.write(data1);
+ await writer.write(data2);
+ await writer.close();
+}
+```
+
+Run the client functions concurrently so that reading can proceed while writing:
+
+```js
+const stream = await setUpBidirectional();
+await Promise.all([readData(stream.readable), writeData(stream.writable)]);
+```
+
+In `@fails-components/webtransport`, accept the client-created streams from `session.incomingBidirectionalStreams`. Each has a `readable` side for data from the client and a `writable` side for data to the client:
+
+```js
+// -- server.js --
+for await (const stream of session.incomingBidirectionalStreams) {
+ // Echo received bytes back to the client. Each stream is handled separately.
+ stream.readable.pipeTo(stream.writable).catch(console.error);
}
```
+This handler pairs with both functions: `pipeTo()` reads the bytes sent by `writeData()` and writes them back for `readData()`.
+
If the server opens a bidirectional stream to transmit data to and receive it from the client, this can be accessed via the {{domxref("WebTransport.incomingBidirectionalStreams")}} property, which returns a {{domxref("ReadableStream")}} of `WebTransportBidirectionalStream` objects. Each one can be used to read and write {{jsxref("Uint8Array")}} instances as shown above. However, as with the unidirectional example, you need an initial function to read the bidirectional stream in the first place:
```js
@@ -266,6 +433,20 @@ async function receiveBidirectional() {
}
```
+To pair with `receiveBidirectional()`, the server creates a stream, sends data, and closes its sending side before reading the client's reply. Closing the sending side allows the client's `readData()` call to finish so that it can call `writeData()`:
+
+```js
+// -- server.js --
+const stream = await session.createBidirectionalStream();
+const writer = stream.writable.getWriter();
+await writer.write(new Uint8Array([65, 66, 67]));
+await writer.close();
+
+for await (const data of stream.readable) {
+ console.log(data); // The client's reply.
+}
+```
+
## Interfaces
- {{domxref("WebTransport")}}
diff --git a/files/en-us/web/api/window/blur_event/index.md b/files/en-us/web/api/window/blur_event/index.md
index da230b9b456fc10..0f7dc70e0fba5e9 100644
--- a/files/en-us/web/api/window/blur_event/index.md
+++ b/files/en-us/web/api/window/blur_event/index.md
@@ -8,7 +8,7 @@ browser-compat: api.Window.blur_event
{{APIRef("UI Events")}}
-The **`blur`** event fires when an element has lost focus.
+The **`blur`** event fires when the window has lost focus,, for example when the user moves focus from the page to the address bar. Focus may previously have been on the document's viewport or on an element within it.
The opposite of `blur` is {{domxref("Window/focus_event", "focus")}}.
@@ -83,8 +83,6 @@ window.addEventListener("focus", play);
{{Compat}}
-The value of {{DOMxRef("Document.activeElement")}} varies across browsers while this event is being handled ([Firefox bug 452307](https://bugzil.la/452307)): IE10 sets it to the element that the focus will move to, while Firefox and Chrome often set it to the `body` of the document.
-
## See also
- Related event: {{domxref("Window/focus_event", "focus")}}
diff --git a/files/en-us/web/api/window/focus_event/index.md b/files/en-us/web/api/window/focus_event/index.md
index 241c6c6b797d047..baafde21465c783 100644
--- a/files/en-us/web/api/window/focus_event/index.md
+++ b/files/en-us/web/api/window/focus_event/index.md
@@ -8,7 +8,7 @@ browser-compat: api.Window.focus_event
{{APIRef("UI Events")}}
-The **`focus`** event fires when an element has received focus.
+The **`focus`** event fires when the window has received focus, such as when focus transitions from the address bar into the page. Focus can be on the document's viewport or on an element within it.
The opposite of `focus` is {{domxref("Window/blur_event", "blur")}}.
diff --git a/files/en-us/web/api/window/index.md b/files/en-us/web/api/window/index.md
index 5c664f05d59c8aa..7e9f51551049294 100644
--- a/files/en-us/web/api/window/index.md
+++ b/files/en-us/web/api/window/index.md
@@ -322,9 +322,9 @@ Listen to these events using [`addEventListener()`](/en-US/docs/Web/API/EventTar
### Focus events
- {{domxref("Window/blur_event", "blur")}}
- - : Fired when an element has lost focus.
+ - : Fired when the window has lost focus.
- {{domxref("Window/focus_event", "focus")}}
- - : Fired when an element has gained focus.
+ - : Fired when the window has gained focus.
### Gamepad events
diff --git a/files/en-us/web/css/reference/properties/row-rule-break/index.md b/files/en-us/web/css/reference/properties/row-rule-break/index.md
index b81965e0493d495..606aed914e55fc8 100644
--- a/files/en-us/web/css/reference/properties/row-rule-break/index.md
+++ b/files/en-us/web/css/reference/properties/row-rule-break/index.md
@@ -96,7 +96,7 @@ This property is specified as a single keyword from the following list:
- `none`
- : There are no breaks in row rules when they intersect column gaps; rather, a continuous row rule is painted the whole width of the container, from edge to edge.
- `normal`
- - : In grid and flex containers, behaves as `none`. In multi-col, behaves as `none`. This is the default value.
+ - : In grid, flex containers, and multi-col layout, behaves as `none`. This is the default value.
- `intersection`
- : Row rules always break when they intersect column gaps, with row rule segments starting and ending at container and gap edges.
diff --git a/tests/front-matter_linter.test.js b/tests/front-matter_linter.test.js
index bac6b42b887984b..720f52b39fd858a 100644
--- a/tests/front-matter_linter.test.js
+++ b/tests/front-matter_linter.test.js
@@ -1,4 +1,5 @@
import fs from "node:fs";
+import path from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import assert from "node:assert/strict";
@@ -59,7 +60,7 @@ test("Front-matter linter", async (t) => {
options.fix = false;
let result = await checkFrontMatter(filePath, options);
const expected =
- "tests/front-matter_test_files/attribute_order.md\n\t " +
+ `${path.join("tests", "front-matter_test_files", "attribute_order.md")}\n\t ` +
"Front matter attributes are not in required order: " +
"title->short-title->slug->page-type->status->browser-compat->spec-urls";
assert.deepStrictEqual(result, [null, expected, null]);
@@ -75,7 +76,7 @@ test("Front-matter linter", async (t) => {
options.fix = false;
const result = await checkFrontMatter(filePath, options);
const expected =
- "Error: tests/front-matter_test_files/values.md\n" +
+ `Error: ${path.join("tests", "front-matter_test_files", "values.md")}\n` +
"'page-type' property must be equal to one of the allowed values:\n" +
"\tlanding-page, guide, web-api-method\n" +
"Front matter must match 'then' schema\n" +
@@ -93,7 +94,7 @@ test("Front-matter linter", async (t) => {
options.fix = true;
const result = await checkFrontMatter(filePath, options);
const expected =
- "Error: tests/front-matter_test_files/prettify.md\n" +
+ `Error: ${path.join("tests", "front-matter_test_files", "prettify.md")}\n` +
"property 'title' must not have more than 120 characters";
assert.deepStrictEqual(result, [expected, null, validContent]);
});
@@ -105,7 +106,7 @@ test("Front-matter linter", async (t) => {
options.fix = false;
let result = await checkFrontMatter(filePath, options);
const expected =
- "Error: tests/front-matter_test_files/unknown_attribute.md\n" +
+ `Error: ${path.join("tests", "front-matter_test_files", "unknown_attribute.md")}\n` +
"'tags' property is not expected to be here";
assert.deepStrictEqual(result, [expected, null, null]);