diff --git a/files/en-us/mozilla/add-ons/webextensions/api/runtime/index.md b/files/en-us/mozilla/add-ons/webextensions/api/runtime/index.md index 3b1a2683508ce87..fb74482e15c045c 100644 --- a/files/en-us/mozilla/add-ons/webextensions/api/runtime/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/api/runtime/index.md @@ -70,9 +70,9 @@ It also provides messaging APIs enabling you to: - {{WebExtAPIRef("runtime.connectNative()")}} - : Connects the extension to a native application on the user's computer. - {{WebExtAPIRef("runtime.sendMessage()")}} - - : Sends a single message to event listeners within your extension or a different extension. Similar to {{WebExtAPIRef('runtime.connect')}} but only sends a single message, with an optional response. + - : Sends a message to event listeners within your extension or a different extension. Similar to {{WebExtAPIRef('runtime.connect')}} but only sends a single message, with an optional response. - {{WebExtAPIRef("runtime.sendNativeMessage()")}} - - : Sends a single message from an extension to a native application. + - : Sends a message from an extension to a native application. - {{WebExtAPIRef("runtime.getPlatformInfo()")}} - : Returns information about the current platform. - {{WebExtAPIRef("runtime.getBrowserInfo()")}} @@ -98,10 +98,14 @@ It also provides messaging APIs enabling you to: - : Fired when a connection is made with either an extension process or a content script. - {{WebExtAPIRef("runtime.onConnectExternal")}} - : Fired when a connection is made with another extension. +- {{WebExtAPIRef("runtime.onserScriptConnect")}} + - : Fired when a connection is made with a user script registered by the extension. - {{WebExtAPIRef("runtime.onMessage")}} - : Fired when a message is sent from either an extension process or a content script. - {{WebExtAPIRef("runtime.onMessageExternal")}} - : Fired when a message is sent from another extension. Cannot be used in a content script. +- {{WebExtAPIRef("runtime.onUserScriptMessage")}} + - : Fired when a message is sent from a user script registered by the extension. - {{WebExtAPIRef("runtime.onPerformanceWarning")}} - : Fired when a runtime performance issue is detected for the extension. - {{WebExtAPIRef("runtime.onRestartRequired")}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/runtime/messagesender/index.md b/files/en-us/mozilla/add-ons/webextensions/api/runtime/messagesender/index.md index 2aea678bd5fd2ca..4e17ac8fac95e59 100644 --- a/files/en-us/mozilla/add-ons/webextensions/api/runtime/messagesender/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/api/runtime/messagesender/index.md @@ -37,6 +37,9 @@ Values of this type are objects. They contain the following properties: If the sender is a script running in a web page (including content and normal page scripts), then `url` is the web page URL. If the script is running in an iframe, `url` is the iframe's URL. +- `userScriptWorldId` {{optional_inline}} + - : `string`. The `worldId` of the `USER_SCRIPT` world that sent the message. Only present in {{WebExtAPIRef("runtime.onUserScriptMessage")}} and in {{WebExtAPIRef("port.sender")}} for {{WebExtAPIRef("runtime.onUserScriptConnect")}}. + {{WebExtExamples}} ## Browser compatibility diff --git a/files/en-us/mozilla/add-ons/webextensions/api/runtime/onuserscriptconnect/index.md b/files/en-us/mozilla/add-ons/webextensions/api/runtime/onuserscriptconnect/index.md new file mode 100644 index 000000000000000..e69f817e594eef3 --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/runtime/onuserscriptconnect/index.md @@ -0,0 +1,48 @@ +--- +title: runtime.onUserScriptConnect +slug: Mozilla/Add-ons/WebExtensions/API/runtime/onUserScriptConnect +page-type: webextension-api-event +browser-compat: webextensions.api.runtime.onUserScriptConnect +--- + +{{AddonSidebar}} + +Fired when a connection is made with a user script from one of the extension's [`USER_SCRIPT` worlds](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts/ExecutionWorld). + +In Firefox, this event requires the [`userScripts` permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#permissions). In Chrome, the event is always present, including in extensions that don't declare the `userScripts` permission. + +A user script can only connect and then sent messages from a `USER_SCRIPT` world that is configured by {{WebExtAPIRef('userScripts.configureWorld()')}} with `messaging` set to `true`. + +## Syntax + +```js-nolint +browser.runtime.onUserScriptConnect.addListener(listener) +browser.runtime.onUserScriptConnect.removeListener(listener) +browser.runtime.onUserScriptConnect.hasListener(listener) +``` + +Events have three functions: + +- `addListener(listener)` + - : Adds a listener to this event. +- `removeListener(listener)` + - : Stop listening to this event. The `listener` argument is the listener to remove. +- `hasListener(listener)` + - : Checks whether a `listener` is registered for this event. Returns `true` if it is listening, `false` otherwise. + +## addListener syntax + +### Parameters + +- `function` + + - : The function called when this event occurs. The function is passed this argument: + + - `port` + - : {{WebExtAPIRef('runtime.Port')}}. An object connecting the current script to the other context. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/runtime/onuserscriptmessage/index.md b/files/en-us/mozilla/add-ons/webextensions/api/runtime/onuserscriptmessage/index.md new file mode 100644 index 000000000000000..dfa2f28aa62103d --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/runtime/onuserscriptmessage/index.md @@ -0,0 +1,89 @@ +--- +title: runtime.onUserScriptMessage +slug: Mozilla/Add-ons/WebExtensions/API/runtime/onUserScriptMessage +page-type: webextension-api-event +browser-compat: webextensions.api.runtime.onUserScriptMessage +--- + +{{AddonSidebar}} + +Use this event to listen for messages sent from one of the extension's [`USER_SCRIPT` worlds](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts/ExecutionWorld). + +In Firefox, this event requires the [`userScripts` permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#permissions). In Chrome, the event is always present, including in extensions that don't declare the `userScripts` permission. + +A user script can only send messages using {{WebExtAPIRef('runtime.sendMessage')}} from a `USER_SCRIPT` world that is configured by {{WebExtAPIRef('userScripts.configureWorld()')}} with `messaging` set to `true`. + +Along with the message, the listener is passed: + +- a `sender` object with details about the message sender. +- a `sendResponse` function the listener can use to send a response back to the sender. + +## Syntax + +```js-nolint +browser.runtime.onUserScriptMessage.addListener(listener) +browser.runtime.onUserScriptMessage.removeListener(listener) +browser.runtime.onUserScriptMessage.hasListener(listener) +``` + +Events have three functions: + +- `addListener(listener)` + - : Adds a listener to this event. +- `removeListener(listener)` + - : Stop listening to this event. The `listener` argument is the listener to remove. +- `hasListener(listener)` + - : Checks whether a `listener` is registered for this event. Returns `true` if it is listening, `false` otherwise. + +## addListener syntax + +### Parameters + +- `listener` + + - : The function called when this event occurs. The function is passed these arguments: + + - `message` + - : `object`. The message. This is a JSON-ifiable object. + - `sender` + - : A {{WebExtAPIRef('runtime.MessageSender')}} object representing the sender of the message. + - `sendResponse` + + - : A function to call, at most once, to send a response to the message. The function takes one argument, which is any JSON-ifiable object. This argument is passed back to the message sender. + + If you have more than one `onUserScriptMessage` listener in the same document, then only one can send a response. + + To send a response synchronously, call `sendResponse` before the listener function returns. To send a response asynchronously, do one of these: + + - keep a reference to the `sendResponse` argument and return `true` from the listener function. You can then call `sendResponse` after the listener function has returned. + - return a [`Promise`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) from the listener function and resolve the promise when the response is ready. + +## Examples + +In this example, a user script in a `USER_SCRIPT` world with the ID `myScriptWorld` sends a message to the extension that registered it: + +```js +// The user script +// Send a message to the extension that registered the user script +browser.runtime.sendMessage("my message"); +``` + +```js +// The extension that registered the user script + +function handleMessage(message, sender) { + // check that the message originated from "myScriptWorld" world + if (sender.userScriptWorldId === "myScriptWorld") { + // process message + console.log(message); + } +} + +browser.runtime.onUserScriptMessage.addListener(handleMessage); +``` + +{{WebExtExamples}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/runtime/port/index.md b/files/en-us/mozilla/add-ons/webextensions/api/runtime/port/index.md index ef731d291cc1ec8..8ec547bdbd37810 100644 --- a/files/en-us/mozilla/add-ons/webextensions/api/runtime/port/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/api/runtime/port/index.md @@ -89,7 +89,7 @@ Values of this type are objects. They contain the following properties: - `postMessage` - : `function`. Send a message to the other end. This takes one argument, which is a serializable value (see [Data cloning algorithm](/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities#data_cloning_algorithm)) representing the message to send. It will be delivered to any script listening to the port's `onMessage` event, or to the native application if this port is connected to a native application. - `sender` {{optional_inline}} - - : {{WebExtAPIRef('runtime.MessageSender')}}. Contains information about the sender of the message. This property will only be present on ports passed to `onConnect`/`onConnectExternal` listeners. + - : {{WebExtAPIRef('runtime.MessageSender')}}. Contains information about the sender of the message. Only present on ports passed to the {{WebExtAPIRef('runtime.onConnect')}}, {{WebExtAPIRef('runtime.onConnectExternal')}}, or {{WebExtAPIRef("runtime.onUserScriptConnect")}} listeners. ## Lifecycle diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/configureworld/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/configureworld/index.md new file mode 100644 index 000000000000000..d9bf72c29bf8cd3 --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/configureworld/index.md @@ -0,0 +1,42 @@ +--- +title: userScripts.configureWorld() +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/configureWorld +page-type: webextension-api-function +browser-compat: webextensions.api.userScripts.configureWorld +--- + +{{AddonSidebar}} + +Configures `USER_SCRIPT` execution environments for the extension. + +Changes to world configurations only apply to new instances of the world: A configuration won't apply to a world initialized by the execution of a user script in a document until the document is reloaded. However, the browser may revoke certain privileges when a configuration is updated. For example, message calls from a `USER_SCRIPT` world may fail if the extension sets `messaging` to `false`. + +World configurations persist until the extension is updated or the configuration is reset by {{WebExtAPIRef("userScripts.resetWorldConfiguration()")}}. + +## Syntax + +```js-nolint +let configuredWorld = browser.userScripts.configureWorld( + properties // object +); +``` + +### Parameters + +- `properties` + + - : {{WebExtAPIRef("userScripts.WorldProperties")}}. Details of the configuration for a `USER_SCRIPT` world. + + When `worldId` is omitted or the string is empty, the update is applied to the default world and all worlds without an explicit configuration. When `worldId` is specified only that world is configured. + + When updating the default world and worlds without an explicit configuration, when properties are omitted the {{WebExtAPIRef("userScripts.WorldProperties")}} defaults are used. + +### Return value + +A {{JSxRef("Promise")}} fulfilled with no arguments if the request is successful. If the request fails, the promise is rejected with an error message. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/executionworld/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/executionworld/index.md new file mode 100644 index 000000000000000..80cb94894c8368f --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/executionworld/index.md @@ -0,0 +1,32 @@ +--- +title: userScripts.ExecutionWorld +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/ExecutionWorld +page-type: webextension-api-type +browser-compat: webextensions.api.userScripts.ExecutionWorld +--- + +{{AddonSidebar}} + +The execution environment for a script injected with {{WebExtAPIRef("userScripts.register()")}} +or {{WebExtAPIRef("userScripts.update()")}}. + +## Type + +Values of this type are a string. Possible values are: + +- `MAIN` + + The web page execution environment. This environment is shared with the web page without isolation. Scripts in this environment don't have access to the APIs that are only available to content scripts. + + > [!WARNING] + > Web pages can detect and interfere with the executed code due to the lack of isolation. Therefore, don't use the `MAIN` world unless it's acceptable for web pages to read, access, or modify the logic or data that flows through the executed code. + +- `USER_SCRIPT` + + The default execution environment for user scripts. This environment is isolated from the page's context and other `USER_SCRIPT` worlds. Extension APIs are unavailable, unlike [`ISOLATED` worlds of content scripts](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld). Several `USER_SCRIPT` worlds can exist when scripts are registered with `worldId`. {{WebExtAPIRef("userScripts.configureWorld()")}} is used to change the configuration of a `USER_SCRIPT` world. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/getscripts/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/getscripts/index.md new file mode 100644 index 000000000000000..0a88640bab5e879 --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/getscripts/index.md @@ -0,0 +1,33 @@ +--- +title: userScripts.getScripts() +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/getScripts +page-type: webextension-api-function +browser-compat: webextensions.api.userScripts.getScripts +--- + +{{AddonSidebar}} + +Returns user scripts registered by the extension. + +## Syntax + +```js-nolint +const gettingUserScripts = await browser.userScripts.getScripts( + filter // object +); +``` + +### Parameters + +- `filter` {{optional_inline}} + - : {{WebExtAPIRef("userScripts.UserScriptFilter")}}. A list of user script IDs to return. + +### Return value + +A {{JSxRef("Promise")}} fulfilled with an array of {{WebExtAPIRef("userScripts.RegisteredUserScript")}} objects. If no matching user scripts are found, the array is empty. If the request fails, the promise is rejected with an error message. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/getworldconfigurations/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/getworldconfigurations/index.md new file mode 100644 index 000000000000000..5881977e71d9ccb --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/getworldconfigurations/index.md @@ -0,0 +1,30 @@ +--- +title: userScripts.getWorldConfigurations() +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/getWorldConfigurations +page-type: webextension-api-function +browser-compat: webextensions.api.userScripts.getWorldConfigurations +--- + +{{AddonSidebar}} + +Returns all the `USER_SCRIPT` world configurations registered by the extension with {{WebExtAPIRef("userScripts.configureWorld()")}}. + +## Syntax + +```js-nolint +const gettingWorldConfigurations = await browser.userScripts.getWorldConfigurations(); +``` + +### Parameters + +This function takes no parameters. + +### Return value + +A {{JSxRef("Promise")}} fulfilled with an array of {{WebExtAPIRef("userScripts.WorldProperties")}} objects. If the request fails, the promise is rejected with an error message. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/index.md index 00ed59016684501..92cbf7ab4df10a7 100644 --- a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/index.md @@ -10,36 +10,76 @@ browser-compat: webextensions.api.userScripts Use this API to register user scripts, third-party scripts designed to manipulate webpages or provide new features. Registering a user script instructs the browser to attach the script to pages that match the URL patterns specified during registration. > [!NOTE] -> This is documentation for the legacy API version, available in Firefox for Manifest V2. A new API has been designed, see [WECG issue 279](https://github.com/w3c/webextensions/issues/279). This new version of the API will be available in Firefox for use in Manifest V3. Development is tracked in [Firefox bug 1875475](https://bugzil.la/1875475). Chrome includes [an implementation of the new API](https://developer.chrome.com/docs/extensions/reference/api/userScripts). Meanwhile, when using Manifest V3 or higher, use {{WebExtAPIRef("scripting.registerContentScripts()")}} to register scripts. +> This is documentation for the new API version, available in Firefox for Manifest V3. See {{WebExtAPIRef("userScripts_legacy","userScripts (legacy)")}} for information on the API available for use in Firefox with Manifest V2. -This API offers similar capabilities to {{WebExtAPIRef("contentScripts")}} but with features suited to handling third-party scripts: +This API offers capabilities similar to {{WebExtAPIRef("scripting")}} but with features suited to handling third-party scripts. -- execution is in an isolated sandbox: each user script is run in an isolated sandbox within the web content processes, preventing accidental or deliberate interference among scripts. -- access to the `window` and `document` global values related to the webpage the user script is attached to. -- no access to WebExtension APIs or associated permissions granted to the extension: the API script, which inherits the extension's permissions, can provide packaged WebExtension APIs to registered user scripts. An API script is declared in the extension's manifest file using the "user_scripts" manifest key. +## Permissions -> [!WARNING] -> This API requires the presence of the [`user_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/user_scripts) key in the manifest.json, even if no API script is specified. For example. `user_scripts: {}`. +To use this API, you need the `userScripts` permission and [`host_permissions`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/host_permissions) for sites where you want to run scripts. However, the approach to enabling the use of this API varies between browsers: -To use the API, call `{{WebExtAPIRef("userScripts.register","register()")}}` passing in an object defining the scripts to register. The method returns a Promise that is resolved with a `{{WebExtAPIRef("userScripts.RegisteredUserScript","RegisteredUserScript")}}` object. +- In Firefox, `userScripts` is an [optional-only permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/optional_permissions#optional-only_permissions) declared in the `optional_permissions` manifest key. Your extension must check that the permission has been granted by checking the availability of the `userScripts` API namespace or using {{WebExtAPIRef("permissions.contains()")}} and, if not, request it using {{WebExtAPIRef("permissions.request()")}}. +- in Chrome, `userScripts` is an install time requested permission declared in the [`permissions` manifest key](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions). However, to enable use of the API, users must [turn on the developer environment in Chrome](https://developer.chrome.com/docs/extensions/reference/api/userScripts#developer_mode_for_extension_users). + +## Execution worlds + +When a user script is registered or updated (using {{WebExtAPIRef("userScripts.register()")}} or {{WebExtAPIRef("userScripts.update()")}}), your extension can set it to run in an isolated `USER_SCRIPT` world or the `MAIN` world. + +A `USER_SCRIPT` world provides an isolated execution environment that isn't accessible to a host page or other extensions. This means a user script can change its JavaScript environment without affecting the host page or other extensions' user and content scripts. In this environment, user scripts aren't visible to the host page or other extensions' user and content scripts. The API also enables an extension to configure a content security policy (CSP) for the `USER_SCRIPT` world using {{WebExtAPIRef("userScripts.configureWorld()")}}. + +In the `MAIN` world, host pages and other extensions can see and access running user scripts. + +These execution world values are defined in {{WebExtAPIRef("userScripts.ExecutionWorld","ExecutionWorld")}}. + +## Messaging + +Like content scripts and other extension scripts, user scripts communicate with other parts of an extension with messages using {{WebExtAPIRef("runtime.sendMessage()")}} and {{WebExtAPIRef("runtime.connect()")}}. However, extensions receive messages using the dedicated {{WebExtAPIRef("runtime.onUserScriptMessage")}} and {{WebExtAPIRef("runtime.onUserScriptConnect")}}. Dedicated handlers are used as they make it easier to identify messages from user scripts, which are a less-trusted context. + +To enable messaging APIs, call {{WebExtAPIRef("userScripts.configureWorld()")}} with the `messaging` argument set to `true` before registering a user script. + +```js +browser.userScripts.configureWorld({ + messaging: true, +}); +``` + +## Extension updates + +When an extension updates, user scripts are cleared. To restore scripts, add code to the extension's {{WebExtAPIRef("runtime.onInstalled")}} event handler that responds to the `"update"` reason. > [!NOTE] -> User scripts are unregistered when the related extension page (from which the user scripts were registered) is unloaded, so you should register a user script from an extension page that persists at least as long as you want the user scripts to stay registered. +> User scripts are unregistered when the related extension page (from which the user scripts were registered) is unloaded, so register user scripts from an extension page that persists as long as you want the user scripts to stay registered. ## Types +- {{WebExtAPIRef("userScripts.ExecutionWorld")}} + - : The execution environment for a script injected with {{WebExtAPIRef("userScripts.register()")}} + or {{WebExtAPIRef("userScripts.update()")}}. - {{WebExtAPIRef("userScripts.RegisteredUserScript")}} - - : The `object` returned by the {{WebExtAPIRef("userScripts.register","register()")}} method. It represents the registered user scripts and is used to deregister the user scripts. + - : An `object` returned by {{WebExtAPIRef("userScripts.getScripts","getScripts()")}} representing registered user scripts and used as input to {{WebExtAPIRef("userScripts.register","register()")}} and {{WebExtAPIRef("userScripts.update","update()")}}. +- {{WebExtAPIRef("userScripts.ScriptSource")}} + - : The code or a file source for a user script. +- {{WebExtAPIRef("userScripts.UserScriptFilter")}} + - : A list of user scripts to be processed by {{WebExtAPIRef("userScripts.getScripts()")}} or {{WebExtAPIRef("userScripts.unregister()")}}. +- {{WebExtAPIRef("userScripts.WorldProperties")}} + - : The configuration of a `USER_SCRIPT` execution environment. ## Methods +- {{WebExtAPIRef("userScripts.configureWorld()")}} + - : Configures a `USER_SCRIPT` execution environment for the extension. +- {{WebExtAPIRef("userScripts.getScripts()")}} + - : Returns user scripts registered by the extension. +- {{WebExtAPIRef("userScripts.getWorldConfigurations()")}} + - : Returns all the extension's registered world configurations. - {{WebExtAPIRef("userScripts.register()")}} - - : Registers user scripts. - -## Events - -- {{WebExtAPIRef("userScripts.onBeforeScript")}} - - : An event available to the API script, registered in[`"user_scripts"`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/user_scripts), that execute before a user script executes. Use it to trigger the export of the additional APIs provided by the API script, so they are available to the user script. + - : Registers user scripts for the extension. +- {{WebExtAPIRef("userScripts.resetWorldConfiguration()")}} + - : Resets the configuration for a `USER_SCRIPT` world registered by the extension. +- {{WebExtAPIRef("userScripts.unregister()")}} + - : Unregisters user scripts registered by the extension. +- {{WebExtAPIRef("userScripts.update()")}} + - : Updates user scripts registered by the extension. ## Browser compatibility @@ -47,5 +87,4 @@ To use the API, call `{{WebExtAPIRef("userScripts.register","register()")}}` pas ## See also -- [Working with `userScripts` (Legacy)](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts_legacy/Working_with_userScripts) -- {{WebExtAPIRef("contentScripts","browser.contentScripts")}} +- {{WebExtAPIRef("scripting","browser.scripting")}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/register/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/register/index.md index 0fa12c80df3d7a7..711565b0a3209db 100644 --- a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/register/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/register/index.md @@ -7,66 +7,44 @@ browser-compat: webextensions.api.userScripts.register {{AddonSidebar}} -This method enables user scripts to be registered from an extension's pages (such as the background page). - -This method is very similar to the {{WebExtAPIRef("contentScripts.register","contentScripts.register()")}} API method (for example, they both return a promise that resolves to an API object with an {{WebExtAPIRef("userScripts.RegisteredUserScript.unregister","unregister()")}} method for unregistering the script). There are, however, differences in the options supported. - -This is an asynchronous method that returns a {{JSxRef("Promise")}}. +Registers user scripts for the extension. ## Syntax ```js-nolint -const registeredUserScript = await browser.userScripts.register( - userScriptOptions // object -); -// … -await registeredUserScript.unregister(); +let registeredUserScript = browser.userScripts.register( + scripts // array of objects +) ``` ### Parameters -- `userScriptOptions` - - - : `object`. Represents the user scripts to register. It has similar syntax to {{WebExtAPIRef("contentScripts.register","contentScripts.register()")}}. - - The `UserScriptOptions` object has the following properties: - - - `scriptMetadata` {{Optional_Inline}} - - : A `JSON` object containing arbitrary metadata properties associated with the registered user scripts. However, while arbitrary, the object must be serializable, so it is compatible with [the structured clone algorithm.](/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) This metadata is used to pass details from the script to the [API script](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/user_scripts). For example, providing details of a subset of the APIs that need to be injected by the [API script](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/user_scripts). The API does not use this metadata, - - `allFrames` {{Optional_Inline}} - - : Same as `all_frames` in the [`content_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts) key. - - `cookieStoreId` {{optional_inline}} - - : An array of cookie store ID strings or a string containing a cookie store ID. Registers the user script in the tabs that belong to the cookie store IDs. This enables scripts to be registered for all default or non-contextual identity tabs, private browsing tabs (if the [extension is enabled in private browsing](https://support.mozilla.org/en-US/kb/extensions-private-browsing)), the tabs of a [contextual identity](/en-US/docs/Mozilla/Add-ons/WebExtensions/Work_with_contextual_identities), or a combination of these. - - `excludeGlobs` {{Optional_Inline}} - - : Same as `exclude_globs` in the [`content_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts) key. - - `excludeMatches` {{Optional_Inline}} - - : Same as `exclude_matches` in the [`content_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts) key. - - `includeGlobs` {{Optional_Inline}} - - : Same as `include_globs` in the [`content_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts) key. - - `js` - - : An array of objects. Each object has either a property named `file`, which is a URL starting at the extension's manifest.json and pointing to a JavaScript file to register, or a property named `code`, which contains JavaScript code to register. - - `matchAboutBlank` {{Optional_Inline}} - - : Same as `match_about_blank` in the [`content_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts) key. - - `matches` - - : Same as `matches` in the [`content_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts) key. - The URL patterns provided in `matches` must be enabled by the host permissions defined in the manifest [`permission`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions) property or enabled by the user from the [`optional_permissions`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/optional_permissions) list. For example, if matches includes `https://mozilla.org/a` a script is only registered if host permissions include, for example, `https://mozilla.org/*`. If the URL pattern isn't enabled, the call to register fails with the error "Permission denied to register a user script for ORIGIN". - - `runAt` {{Optional_Inline}} - - : Same as `run_at` in the [`content_scripts`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts) key. - -Unlike content script options, the userScriptOptions object does not have a CSS property. Use {{WebExtAPIRef("contentScripts.register","contentScripts.register()")}} to dynamically register and unregister stylesheets. +- `scripts` + + - : `array` of {{WebExtAPIRef("userScripts.RegisteredUserScript")}}. Details of user scripts to register. + + Each {{WebExtAPIRef("userScripts.RegisteredUserScript")}} object must contain the `js` property as a non-empty array and a non-empty array in either `matches` or `includeGlobs`. ### Return value -A {{JSxRef("Promise")}} that is fulfilled with a {{WebExtAPIRef("userScripts.RegisteredUserScript","RegisteredUserScript")}} object that is use to unregister the user scripts. +A {{JSxRef("Promise")}} fulfilled with no arguments if all the requested user scripts are registered. If any user scripts fail to register or the request fails for another reason, none of the scripts are registered, and the promise is rejected with an error message. -> [!NOTE] -> User scripts are unregistered when the related extension page (from which the user scripts were registered) is unloaded, so you should register user scripts from an extension page that persists at least as long as you want the user scripts to stay registered. +## Examples -## Browser compatibility +This snippet registers hello world code into the `"myScriptId"` execution world to run in all websites matching `"*://example.com/*"`. -{{Compat}} +```js +await browser.userScripts.register([ + { + worldId: "myScriptId", + js: [{ code: "console.log('Hello world!');" }], + matches: ["*://example.com/*"], + }, +]); +``` -## See also +{{WebExtExamples}} -- {{WebExtAPIRef("contentScripts.register","contentScripts.register()")}} -- {{WebExtAPIRef("userScripts.RegisteredUserScript.unregister","RegisteredUserScript.unregister()")}} +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/registereduserscript/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/registereduserscript/index.md index 1a9096e2711b231..7acc884ad31d8c9 100644 --- a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/registereduserscript/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/registereduserscript/index.md @@ -7,17 +7,32 @@ browser-compat: webextensions.api.userScripts.RegisteredUserScript {{AddonSidebar}} -A `RegisteredUserScript` object is returned by a call to {{WebExtAPIRef("userScripts.register","userScripts.register()")}} and represents the user scripts registered in that call. - -The object defines a single method, {{WebExtAPIRef("userScripts.RegisteredUserScript.unregister","unregister()")}}, which is used to unregister the user scripts. - -> [!NOTE] -> If this object is destroyed (for example because it goes out of scope) then the associated scripts will be unregistered automatically, so you should keep a reference to this object for as long as you want the user scripts to stay registered. - -## Methods - -- {{WebExtAPIRef("userScripts.RegisteredUserScript.unregister","unregister()")}} - - : Unregisters the user scripts represented by this object. +An object representing registered user scripts. Returned by {{WebExtAPIRef("userScripts.getScripts","getScripts()")}} and used as input to {{WebExtAPIRef("userScripts.register","register()")}} and {{WebExtAPIRef("userScripts.update","update()")}}. + +## Type + +Values of this type are an object containing these properties: + +- `allFrames` {{optional_inline}} + - : `boolean`. If `allFrames` is `true`, the script is injected into all of a page's frames. By default, it's `false` and the script is only injected into the top frame. +- `id` + - : `string`. The ID of a user script. This property must not start with a '\_', which is reserved as a prefix for generated script IDs. +- `js` {{optional_inline}} for {{WebExtAPIRef("userScripts.update()")}} calls, required for {{WebExtAPIRef("userScripts.register()")}} + - : `array` of {{WebExtAPIRef("userScripts.ScriptSource")}}. The scripts to inject into matching pages. +- `matches` {{optional_inline}} + - : `array` of `string`. [Match patterns](/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns) for the pages to run the script in. `matches` or `includeGlobs` must be specified in {{WebExtAPIRef("userScripts.register()")}} calls. +- `excludeMatches` {{optional_inline}} + - : `array` of `string`. [Match patterns](/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns) for pages that the script must not be run in. +- `includeGlobs` {{optional_inline}} + - : `string`. Glob patterns for the pages to run the script in. `matches` or `includeGlobs` must be specified in {{WebExtAPIRef("userScripts.register()")}} calls. +- `excludeGlobs` {{optional_inline}} + - : `string`. Glob patterns for pages that the script must not be run in. +- `runAt` {{optional_inline}} + - : {{WebExtAPIRef("extensionTypes.RunAt")}}. The earliest the script is injected into a tab. Defaults to `"document_idle"`. +- `world` {{optional_inline}} + - : {{WebExtAPIRef("userScripts.ExecutionWorld")}}. The execution environment to use to run the scripts. Defaults to `"USER_SCRIPT"`. +- `worldId` {{optional_inline}} + - : `string`. ID of a user script world the script executes in. Only valid if `world` is `USER_SCRIPT` or omitted. If `worldId` is omitted, the script is executed in the default `USER_SCRIPT` world (""). Values with leading underscores (`_`) are reserved. The maximum length is 256 characters. A world can be used by several scripts as their execution environment. To configure the behavior of a world, pass its `worldId` to {{WebExtAPIRef("userScripts.configureWorld")}} before the first script executes in that world. ## Browser compatibility diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/resetworldconfiguration/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/resetworldconfiguration/index.md new file mode 100644 index 000000000000000..7f329cfda9a2c95 --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/resetworldconfiguration/index.md @@ -0,0 +1,35 @@ +--- +title: userScripts.resetWorldConfiguration() +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/resetWorldConfiguration +page-type: webextension-api-function +browser-compat: webextensions.api.userScripts.resetWorldConfiguration +--- + +{{AddonSidebar}} + +Resets the configuration of a `USER_SCRIPT` world set by {{WebExtAPIRef("userScripts.configureWorld")}} to the defaults specified in {{WebExtAPIRef("userScripts.WorldProperties")}}. When the default world is reset, all worlds without an explicit configuration are also reset. + +Changes to world configurations only apply to new instances of the world: A configuration won't apply to a world initialized by the execution of a user script in a document until the document is reloaded. However, the browser may revoke certain privileges when a configuration is updated. For example, message calls from a `USER_SCRIPT` world may fail when `messaging` is reset to `false`. + +## Syntax + +```js-nolint +let resettingWorldConfiguration = browser.userScripts.resetWorldConfiguration( + worldId // optional string +); +``` + +### Parameters + +- `worldId` {{optional_inline}} + - : `string` The ID of the `USER_SCRIPT` world to reset. If omitted or empty, resets the configuration of the default world and all worlds without a configuration set by {{WebExtAPIRef("userScripts.configureWorld")}}. + +### Return value + +A {{JSxRef("Promise")}} fulfilled with no arguments if the world configuration is reset. If the request fails, the promise is rejected with an error message. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/scriptsource/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/scriptsource/index.md new file mode 100644 index 000000000000000..03fbee8c37b4939 --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/scriptsource/index.md @@ -0,0 +1,27 @@ +--- +title: userScripts.ScriptSource +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/ScriptSource +page-type: webextension-api-type +browser-compat: webextensions.api.userScripts.ScriptSource +--- + +{{AddonSidebar}} + +The code or source file for a user script. This describes the object values of the "js" property in {{WebExtAPIRef("userScripts.RegisteredUserScript","RegisteredUserScript")}}. + +## Type + +Values of this type are an object containing these properties: + +- `file` {{optional_inline}} + - : `string`. The path of the file containing the user script code. The path is relative to the extension's root directory. +- `allFrames` {{optional_inline}} + - : `code`. JavaScript code for the user script. + +`file` or `code` must be specified, not both. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/unregister/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/unregister/index.md new file mode 100644 index 000000000000000..86ff593da45da1f --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/unregister/index.md @@ -0,0 +1,33 @@ +--- +title: userScripts.unregister() +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/unregister +page-type: webextension-api-function +browser-compat: webextensions.api.userScripts.unregister +--- + +{{AddonSidebar}} + +Unregisters user scripts registered by the extension. + +## Syntax + +```js-nolint +let unregisteringUserScripts = browser.userScripts.unregister( + filter // optional object +); +``` + +### Parameters + +- `filter` {{optional_inline}} + - : {{WebExtAPIRef("userScripts.UserScriptFilter")}}. A list of user script IDs to unregister. If not specified, all user scripts are unregistered. + +### Return value + +A {{JSxRef("Promise")}} fulfilled with no arguments if the user scripts are unregistered. If the request fails, the promise is rejected with an error message. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/update/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/update/index.md new file mode 100644 index 000000000000000..cab52a74b8213d8 --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/update/index.md @@ -0,0 +1,62 @@ +--- +title: userScripts.update() +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/update +page-type: webextension-api-function +browser-compat: webextensions.api.userScripts.update +--- + +{{AddonSidebar}} + +Updates user scripts registered by the extension. + +## Syntax + +```js-nolint +let updatingUserScript = browser.userScripts.update( + scripts // array of objects +); +``` + +### Parameters + +- `scripts` + + - : `array` of {{WebExtAPIRef("userScripts.RegisteredUserScript")}}. Details of user scripts to update. + + Properties that are `null` or omitted are not changed. Passing an empty array to `matches`, `excludeMatches`, `globs`, and `excludeGlobs` clears these properties. + +### Return value + +A {{JSxRef("Promise")}} fulfilled with no arguments if all the requested user scripts are updated. If any user scripts fail to update or the request fails for another reason, none of the scripts are updated, and the promise is rejected with an error message. + +## Examples + +This snippet shows two examples of updates to a user scripts. The first update fails because it attempts to create an invalid script registration. The second example shows a successful update. + +```js +// Valid registration: +await browser.userScripts.register([ + { + worldId: "myScriptId", + js: [{ code: "console.log('Hello world!');" }], + matches: ["*://example.com/*"], + }, +]); + +// Invalid! Would result in script without matches or includeGlobs! +await browser.userScripts.update([{ matches: [] }]); + +// Valid: replaces matches with includeGlobs. +await browser.userScripts.update([ + { + matches: [], + includeGlobs: ["*example*"], + }, +]); +``` + +{{WebExtExamples}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/userscriptfilter/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/userscriptfilter/index.md new file mode 100644 index 000000000000000..46927cf60763339 --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/userscriptfilter/index.md @@ -0,0 +1,23 @@ +--- +title: userScripts.UserScriptFilter +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/UserScriptFilter +page-type: webextension-api-type +browser-compat: webextensions.api.userScripts.UserScriptFilter +--- + +{{AddonSidebar}} + +A list of user scripts to be processed by {{WebExtAPIRef("userScripts.getScripts()")}} or {{WebExtAPIRef("userScripts.unregister()")}}. + +## Type + +Values of this type are an object containing this property: + +- `ids` {{optional_inline}} + - : `array` of `string`. IDs of user scripts to be processed by {{WebExtAPIRef("userScripts.getScripts()")}} and {{WebExtAPIRef("userScripts.unregister()")}}. This matches scripts by the `id` field of {{WebExtAPIRef("userScripts.RegisteredUserScript","RegisteredUserScript")}}. If not specified, all user scripts are matched. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/api/userscripts/worldproperties/index.md b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/worldproperties/index.md new file mode 100644 index 000000000000000..d95937f4349decf --- /dev/null +++ b/files/en-us/mozilla/add-ons/webextensions/api/userscripts/worldproperties/index.md @@ -0,0 +1,27 @@ +--- +title: userScripts.WorldProperties +slug: Mozilla/Add-ons/WebExtensions/API/userScripts/WorldProperties +page-type: webextension-api-type +browser-compat: webextensions.api.userScripts.WorldProperties +--- + +{{AddonSidebar}} + +The configuration of a `USER_SCRIPT` execution environment. Used in {{WebExtAPIRef("userScripts.configureWorld")}} and {{WebExtAPIRef("userScripts.getWorldConfigurations")}}. + +## Type + +Values of this type are an object containing these properties: + +- `worldId` {{optional_inline}} + - : `string`. The identifier for the world. Values with leading underscores (`_`) are reserved. The maximum length is 256 characters. Defaults to the default `USER_SCRIPT` world (""). +- `csp` {{optional_inline}} + - : `string`. The world's Content Security Policy (CSP). Defaults to the [default CSP for content scripts](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy#csp_for_content_scripts), which prohibits dynamic code execution, such as `eval()`. +- `messaging` {{optional_inline}} + - : `boolean`. Whether the {{WebExtAPIRef("runtime.sendMessage")}} and {{WebExtAPIRef("runtime.connect")}} methods are exposed to the user script world. Defaults to hiding these messaging APIs. The {{WebExtAPIRef("runtime.onUserScriptMessage")}} and {{WebExtAPIRef("runtime.onUserScriptConnect")}} event handlers are triggered when these methods are called. + +{{WebExtExamples("h2")}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/add-ons/webextensions/manifest.json/optional_permissions/index.md b/files/en-us/mozilla/add-ons/webextensions/manifest.json/optional_permissions/index.md index 1ecc507fd48d7ab..8b39c0cc021933a 100644 --- a/files/en-us/mozilla/add-ons/webextensions/manifest.json/optional_permissions/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/manifest.json/optional_permissions/index.md @@ -88,6 +88,7 @@ The optional API permissions are: - `tabHide` - `tabs` - `topSites` +- 'userScripts' ([optional-only](#optional-only_permissions)) - `webNavigation` - `webRequest` - `webRequestBlocking` @@ -108,10 +109,11 @@ These optional permissions are granted silently, without a user prompt: ### Optional-only permissions -Optional permissions are generally available for use in the [`permissions`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#api_permissions) key so they can be requested at install time. However, some browsers support the concept of optional-only permissions, permissions that can only be requested at runtime using the {{webextapiref("permissions.request()")}} API. In addition, optional-only permissions must be requested individually and alone through the {{webextapiref("permissions.request()")}} API. +Optional permissions are generally available for use in the [`permissions`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#api_permissions) key, so they can be requested at install time. However, some browsers support the concept of optional-only permissions, permissions that can only be requested at runtime. For example, in Firefox, optional-only permissions can be granted by the user from the [extension's options page](/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Options_pages) or using {{webextapiref("permissions.request()")}}. Optional-only permissions must be requested individually and alone through the {{webextapiref("permissions.request()")}} API. -> [!NOTE] -> No optional-only permissions were generally available at the time of writing, December 2024. +The optional-only API permissions are: + +- 'userScripts' (see [userScripts permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#permissions)) ## Examples diff --git a/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md b/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md index 31bc59664f12777..2a1c670f9840d42 100644 --- a/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md @@ -132,6 +132,7 @@ These permissions are available in Manifest V2 and above unless otherwise noted: - `theme` - `topSites` - `unlimitedStorage` +- 'userScripts' (see [userScripts permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#permissions)) - `webNavigation` - `webRequest` - `webRequestAuthProvider` (Manifest V3 and above) @@ -196,7 +197,7 @@ The `unlimitedStorage` permission: - Enables extensions to exceed any quota imposed by the {{WebExtAPIRef("storage/local", "storage.local")}} API - In Firefox, enables extensions to create a ["persistent" IndexedDB database](/en-US/docs/Web/API/IndexedDB_API) without the browser prompting the user for permission at the time the database is created. -## Example +## Examples ```json "permissions": ["*://developer.mozilla.org/*"] diff --git a/files/en-us/mozilla/firefox/releases/136/index.md b/files/en-us/mozilla/firefox/releases/136/index.md index 7acd7616a02294f..573f237ff7d8d87 100644 --- a/files/en-us/mozilla/firefox/releases/136/index.md +++ b/files/en-us/mozilla/firefox/releases/136/index.md @@ -77,6 +77,7 @@ This article provides information about the changes in Firefox 136 that affect d ## Changes for add-on developers - {{WebExtAPIRef("menus.update")}} and {{WebExtAPIRef("menus.remove")}} and the aliases {{WebExtAPIRef("contextMenus.update")}} and {{WebExtAPIRef("contextMenus.remove")}} now reject with an error when the menu item doesn't exist. Previously, the error was ignored and the promise fulfilled. ([Firefox bug 1688743](https://bugzil.la/1688743)). +- A new version of the {{WebExtAPIRef("userScripts")}} API is available. This version of the API is for use in Manifext V3 extensions and provides broad compatibility with Chrome, although [permissons mechanisms](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#permissions) differ across the browsers. ([Firefox bug 1943050](https://bugzil.la/1943050)). ### Removals