From b6f3d1e0efeff683860ea49a89730ef90643e955 Mon Sep 17 00:00:00 2001 From: Estelle Weyl Date: Fri, 25 Sep 2026 18:17:13 +0200 Subject: [PATCH 1/7] Clarify behavior of 'normal' value in row-rule-break (#45848) --- .../en-us/web/css/reference/properties/row-rule-break/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 3dad2299b9d045afbcefc2fd5500ed7257ceedda Mon Sep 17 00:00:00 2001 From: rebloor Date: Sat, 26 Sep 2026 04:35:50 +1200 Subject: [PATCH 2/7] Bug 1992418 runtime.getVersion support (#45841) --- .../add-ons/webextensions/api/runtime/getversion/index.md | 2 +- .../mozilla/add-ons/webextensions/content_scripts/index.md | 1 + files/en-us/mozilla/firefox/releases/158/index.md | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) 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)) + From a49976f8a055175d39d1806a1646de3824cf9edf Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Fri, 25 Sep 2026 11:39:54 -0700 Subject: [PATCH 3/7] Document USB: requestDevice() exclusionFilters option (#45839) * Document USB: requestDevice() exclusionFilters option * Apply batched suggestions from code review Co-authored-by: Chris Mills * Update index.md --------- Co-authored-by: Chris Mills --- .../en-us/web/api/usb/requestdevice/index.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) 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}} From 6daf06123a4c7d8b8e2a038e339a05666b22695f Mon Sep 17 00:00:00 2001 From: BaconMan1168 <198354403+BaconMan1168@users.noreply.github.com> Date: Sat, 26 Sep 2026 03:24:10 +0800 Subject: [PATCH 4/7] docs(window): describe focus and blur events as fired on the window (#45801) * docs(window): describe focus and blur events as fired on the window * Add more explanation --------- Co-authored-by: Joshua Chen --- files/en-us/web/api/window/blur_event/index.md | 4 +--- files/en-us/web/api/window/focus_event/index.md | 2 +- files/en-us/web/api/window/index.md | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) 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 From cce594ebff79d155da35415eeebb144251355cce Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Fri, 25 Sep 2026 13:11:28 -0700 Subject: [PATCH 5/7] Split SW architecture steps into installation & replacement, fix diagrams (#45821) * Fix SW architecture diagram * Update again * Split * More fixes --- .../using_service_workers/index.md | 57 +- .../using_service_workers/sw-activated.svg | 1 + .../using_service_workers/sw-activation.svg | 1 + .../using_service_workers/sw-installation.svg | 1 + .../using_service_workers/sw-installed.svg | 1 + .../using_service_workers/sw-lifecycle.svg | 1796 ----------------- .../using_service_workers/sw-registration.svg | 1 + .../sw-replacement-activated.svg | 1 + .../sw-replacement-activation.svg | 1 + .../sw-replacement-fetched.svg | 1 + .../sw-replacement-installation.svg | 1 + .../sw-replacement-waiting.svg | 1 + 12 files changed, 60 insertions(+), 1803 deletions(-) create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-activated.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-activation.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-installation.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-installed.svg delete mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-lifecycle.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-registration.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activated.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-activation.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-fetched.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-installation.svg create mode 100644 files/en-us/web/api/service_worker_api/using_service_workers/sw-replacement-waiting.svg 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. + + ![Registration of the first service worker, showing its parsed state, scope, and two open, uncontrolled clients.](sw-registration.svg) -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. -![lifecycle diagram](sw-lifecycle.svg) + ![The install event populates a cache while the same two clients remain open.](sw-installation.svg) + +3. When installation completes successfully, the service worker is considered installed. + + ![The service worker is installed, with a populated cache, but does not yet control clients.](sw-installed.svg) + +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. + + ![The activate event finishes setup while the same two existing clients remain open.](sw-activation.svg) + +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). + + ![A new client opens and is controlled by the activated service worker, while the two existing clients remain open and uncontrolled.](sw-activated.svg) + +### 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. + + ![The open client calls register(), and version 2 is parsed while version 1 remains activated and controls the client.](sw-replacement-fetched.svg) + +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. + + ![Version 2 receives the install event and populates a new cache while version 1 continues to control the same client.](sw-replacement-installation.svg) + +3. When installation completes successfully, the new version waits while the old version is still controlling clients. The new version is not yet active. + + ![Version 2 is installed and waiting, with its new cache ready, while version 1 still controls the open client.](sw-replacement-waiting.svg) + +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. + + ![The client controlled by version 1 closes. Version 1 is retired, and version 2 receives activate and deletes the old cache.](sw-replacement-activation.svg) + +5. After activation, newly opened pages within the registration's scope are controlled by the new version. + + ![A new client opens and is controlled by version 2, which uses its new cache.](sw-replacement-activated.svg) + +### 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 @@ +Initial service worker installation: activatedNew clientControlled by v1Existing clientUncontrolledExisting clientUncontrolledv1v1: Activatedv1CacheLeft page opensfetch eventsUsing cache 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 @@ +Initial service worker installation: activatingExisting clientUncontrolledExisting clientUncontrolledv1v1: Activatingv1Cacheactivate eventFinishing setup 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 @@ +Initial service worker installation: installingExisting clientUncontrolledExisting clientUncontrolledv1v1: Installingv1Populating cacheinstall eventevent.waitUntil() 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 @@ +Initial service worker installation: installedExisting clientUncontrolledExisting clientUncontrolledv1v1: Installedv1Cache 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 @@ - - - - - - - - Service worker fetched - and registered - - - - - - - - - - v1 - - - - v1 - - - - - - - - - - - - Pages loaded in the browser (client) - - - - Pages loaded in the browser (client) - - - - - - - - - - - - Scope of pages for the service worker - - - - Scope of pages for the service worker - - - - - - - - - register(workerURL,{options = scope}) - - - - - register(workerURL,{options = scope}) - - - - - - - - 0. Initial situation, no service worker - - - - - - 0. Initial situation:no service worker - - - - - - - - 1. Registration of the - - - first version of a service worker - - - - - - - 1. Registration of thefirst version of aservice worker - - - Service worker fetched - and registered - - - - - - - - - - v1 - - - - v1 - - - - - - - - - - - 2. Installation - - - - - - 2. Installation - - - - - - - - - install - - -event - - - - install event - - - - - - - - - - - - - - - event.waitUntil() - - - - event.waitUntil() - - - - - - - - - - - - Setting up caches, offline assets, etc. - - - - Setting up caches, offline assets - - - - - - v1 - - - - v1 - - - Service worker installed - but not controlling - - - - - - - - - - v1 - - - - v1 - - - - - - - - 3. Waiting for clients to be closed - - - - - - 3. Waiting for clientsto be closed - - - Service worker installed - but not controlling - - - - - - - - - - v1 - - - - v1 - - - - - - - - 4. Activation - - - - - - 4. Activation - - - - - - - - - activate - - -event - - - - activate event - - - - - - Finishing setup, cleaning old resources for previous versions - - - - Finishing setup, cleaningold resources for previous versions - - - Service worker - controlling documents - in its scope - - - - - - - - - - v1 - - - - v1 - - - - - - - - - - 5. Activated - - - - - - 5. Activated - - - - - - - - - - - functional events - - - (e.g.fetch - - -) - - - - - - - functional events(e.g. fetch) - - - - - - - - - - - Cache - - - - - Cache - - - - - - v1 - - - - v1 - - - - - - - - - - v1 - - - - v1 - - - - - - - - - - v1 - - - - v1 - - - - - - - - - - - - - Using cache - - - - - Using cache - - Previous version - - - - - - - - - v1 - - - - v1 - - - - - - - - - - 6. Replacement - - - - - - 6. Replacement - - - - - - - Old cache: - purged during v2 activation - - - - - - v1 - - - - v1 - - - New version: - will control pagesonce installed and activated - - - - - - - - - - v2 - - - - v2 - - - - - - - New cache: - populated during installation - - - - - - v2 - - - - v2 - - 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 @@ +Initial service worker installation: parsedExisting clientUncontrolledExisting clientUncontrolledv1v1: Parsedregister(workerURL, { scope })Scope of pages for the service worker 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 @@ +Replacing an existing service worker: activatedNew clientControlled by v2v1v1: Redundantv1Deleted duringactivationv2v2: Activatedv2New cacheRight page opensfetch eventsUsing cache 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 @@ +Replacing an existing service worker: activatingv1v1: Redundantv1Deleted duringactivationv2v2: Activatingv2New cacheactivate eventControlled client closes 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 @@ +Replacing an existing service worker: parsedExisting clientControlled by v1v1v1: Activatedv1Existing cachefetch eventsUsing cachev2v2: Parsedregister() 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 @@ +Replacing an existing service worker: installingExisting clientControlled by v1v1v1: Activatedv1Existing cachefetch eventsUsing cachev2v2: Installingv2Populating new cacheinstall eventevent.waitUntil() 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 @@ +Replacing an existing service worker: installed (waiting)Existing clientControlled by v1v1v1: Activatedv1Existing cachefetch eventsUsing cachev2v2: Installed (waiting)v2New cache From 64685d67035fa6c3ad63a36e89ce8f59d670c3ac Mon Sep 17 00:00:00 2001 From: rahulkotargasthi Date: Sat, 26 Sep 2026 01:45:07 +0530 Subject: [PATCH 6/7] Add WebTransport server implementation examples (#45824) * docs: document WebTransport server implementation * Still add server examples * Last fixes * Add marker --------- Co-authored-by: Joshua Chen --- .vscode/dictionaries/code-entities.txt | 2 + .../createunidirectionalstream/index.md | 2 +- files/en-us/web/api/webtransport_api/index.md | 191 +++++++++++++++++- 3 files changed, 189 insertions(+), 6 deletions(-) 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/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")}} From d227f4ac374be9efd0d9e502c9ffdb050253eb43 Mon Sep 17 00:00:00 2001 From: "Michael@WCD" Date: Fri, 25 Sep 2026 16:46:42 -0400 Subject: [PATCH 7/7] Clarify image load timing and make front matter tests portable (#45831) * docs: clarify image load listener timing * test: use native paths in front matter expectations * Add more solutions * Restore markup --------- Co-authored-by: Joshua Chen --- .../web/api/htmlelement/load_event/index.md | 33 ++++++++++++++++++- tests/front-matter_linter.test.js | 9 ++--- 2 files changed, 37 insertions(+), 5 deletions(-) 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/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]);