diff --git a/.eslintrc.json b/.eslintrc.json
index 34bf2d87..22a593cb 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -8,12 +8,15 @@
"es6": true,
"webextensions": true
},
+ "globals": {
+ "globalThis": false
+ },
"extends": [
"eslint:recommended"
],
"rules": {
"no-console": 0,
- "no-unused-vars": ["warn", { "vars": "all", "args": "all" } ],
+ "no-unused-vars": ["warn", { "vars": "all", "args": "after-used" } ],
"no-undef": ["warn"],
"no-proto": ["error"],
"prefer-arrow-callback": ["warn"],
diff --git a/dnr-block-only/README.md b/dnr-block-only/README.md
new file mode 100644
index 00000000..94916c3d
--- /dev/null
+++ b/dnr-block-only/README.md
@@ -0,0 +1,42 @@
+# dnr-block-only
+
+Demonstrates how to block network requests without host permissions using the
+declarativeNetRequest API with the `declarative_net_request` manifest key.
+
+## What it does
+
+This extension blocks:
+
+- network requests to URLs containing "blocksub" (except for top-level
+ navigations).
+- top-level navigation to URLs containing "blocktop".
+- all requests containing "blockall".
+
+Load `testpage.html` to see the extension in action.
+This demo page does not need to be packaged with the extension.
+
+# What it shows
+
+This example shows how to:
+
+- use the declarativeNetRequest API through the `declarative_net_request`
+ manifest key.
+- use the "resourceTypes" and "excludedResourceTypes" conditions of a
+ declarativeNetRequest rule.
+- block network requests without host permissions using the
+ "declarativeNetRequest" permission, which triggers the "Block content on any
+ page" permission warning at install time.
+
+This example is the only cross-browser way to block network requests (at least
+in Firefox, Chrome, and Safari). The webRequest API is an alternative way to
+implement this functionality, but is only available in Firefox (MV2 and MV3)
+and in Chrome (MV2 only). Safari does not support the webRequest API.
+
+## Comparison with Manifest Version 2
+
+While this example uses `"manifest_version": 3`, the functionality is not
+specific to Manifest Version 3.
+
+To create a MV2 version of the extension, modify `manifest.json` as follows:
+
+- Set `manifest_version` to 2.
diff --git a/dnr-block-only/manifest.json b/dnr-block-only/manifest.json
new file mode 100644
index 00000000..ed039746
--- /dev/null
+++ b/dnr-block-only/manifest.json
@@ -0,0 +1,16 @@
+{
+ "manifest_version": 3,
+ "name": "Block only, without host_permissions",
+ "description": "Blocks requests to 'blocksub', 'blocktop', and 'blockall'. Uses the 'declarativeNetRequest' permission, meaning that host_permissions in manifest.json are not needed.",
+ "version": "0.1",
+ "permissions": [
+ "declarativeNetRequest"
+ ],
+ "declarative_net_request": {
+ "rule_resources": [{
+ "id": "ruleset",
+ "enabled": true,
+ "path": "rules.json"
+ }]
+ }
+}
diff --git a/dnr-block-only/rules.json b/dnr-block-only/rules.json
new file mode 100644
index 00000000..5d87193c
--- /dev/null
+++ b/dnr-block-only/rules.json
@@ -0,0 +1,31 @@
+[
+ {
+ "id": 1,
+ "condition": {
+ "urlFilter": "blocksub"
+ },
+ "action": {
+ "type": "block"
+ }
+ },
+ {
+ "id": 2,
+ "condition": {
+ "urlFilter": "blocktop",
+ "resourceTypes": ["main_frame"]
+ },
+ "action": {
+ "type": "block"
+ }
+ },
+ {
+ "id": 3,
+ "condition": {
+ "urlFilter": "blockall",
+ "excludedResourceTypes": []
+ },
+ "action": {
+ "type": "block"
+ }
+ }
+]
diff --git a/dnr-block-only/testpage.html b/dnr-block-only/testpage.html
new file mode 100644
index 00000000..02b1f54f
--- /dev/null
+++ b/dnr-block-only/testpage.html
@@ -0,0 +1,97 @@
+
+
+
+ rule 1 will block this image (containing 'blocksub'):
+
+
+
+ rule 1 will not block this image (does not contain 'blocksub'):
+
+
+
+ rule 1 will not block top-level navigations to
+ https://developer.mozilla.org/favicon.ico?blocksub
+ because top-level navigation ("main_frame") requests are not matched
+ when "resourceTypes" and "excludedResourceTypes" are not specified.
+
+
+
+
diff --git a/dnr-dynamic-with-options/README.md b/dnr-dynamic-with-options/README.md
new file mode 100644
index 00000000..993ca1b2
--- /dev/null
+++ b/dnr-dynamic-with-options/README.md
@@ -0,0 +1,105 @@
+# dnr-dynamic-with-options
+
+Demonstrates a generic way to request host permissions and register
+declarativeNetRequest rules to modify network requests, without any
+install-time permission warnings. The `options_ui` page offers a way to request
+permissions and register declarative net request (DNR) rules.
+
+## What it does
+
+After loading the extension, visit the extension options page:
+
+1. Visit `about:addons`.
+2. Go to the extension at "DNR Dynamic with options".
+3. Click on Preferences to view its options page (options.html).
+
+On the options page:
+
+1. Input the list of host permissions and click on "Grant host permissions".
+2. Input the list of declarativeNetRequest rules and click "Save".
+3. Trigger a network request to verify that the rule matched.
+
+### Example for options page
+
+Host permissions:
+
+```json
+["*://example.com/"]
+```
+
+DNR rules:
+
+```json
+[
+ {
+ "id": 1,
+ "priority": 1,
+ "condition": {
+ "urlFilter": "|https://example.com/",
+ "resourceTypes": [
+ "main_frame"
+ ]
+ },
+ "action": {
+ "type": "block"
+ }
+ }
+]
+```
+
+Manual test case: Visit https://example.com/ and verify that it is blocked.
+
+# What it shows
+
+How to create an extension with no install-time permission warnings and
+request (host) permissions as needed:
+
+- declares the "declarativeNetRequestWithHostAccess" permission, which
+ unlocks the declarativeNetRequest API without install-time warning.
+ In contrast, the "declarativeNetRequest" permission has the same effect,
+ but has the "Block content on any page" permission warning.
+- declares the most permissive match pattern in `optional_host_permissions`.
+- calls `permissions.request` to request host permissions.
+- uses `permissions.getAll` and `permissions.remove` to reset permissions.
+
+How to retrieve and dynamically register declarativeNetRequest rules, using:
+
+- `declarativeNetRequest.getDynamicRules` and
+ `declarativeNetRequest.updateDynamicRules` to manage DNR rules that persist
+ across extension restarts. These rules also persist across browser restarts,
+ unless the extension is loaded temporarily or unloaded.
+- `declarativeNetRequest.getSessionRules` and
+ `declarativeNetRequest.updateSessionRules` to manage DNR rules that are
+ session-scoped, that is, cleared when an extension unloads or the browser
+ quits.
+
+How these registered DNR rules can modify network requests without requiring an
+active extension script in the background, in a cross-browser way (at least in
+Firefox, Chrome, and Safari).
+
+## Note on `optional_host_permissions` and `optional_permissions`
+
+Firefox does not support `optional_host_permissions` permissions, it
+supports host permissions in `optional_permissions`
+(https://bugzilla.mozilla.org/show_bug.cgi?id=1766026).
+
+Chrome recognizes `optional_host_permissions` but does not support host
+permissions in `optional_permissions`.
+
+To support both, include `optional_host_permissions` and `optional_permissions`
+in your manifest.json.
+
+## Comparison with Manifest Version 2
+
+While this example uses `"manifest_version": 3`, the functionality is not
+specific to Manifest Version 3.
+
+To create a MV2 version of the extension, modify `manifest.json` as follows:
+
+- Set `manifest_version` to 2.
+- Use `optional_permissions` instead of `optional_host_permissions` to list
+ optional host permissions.
+ - In this example, `optional_permissions` is present with
+ the same value as `optional_host_permissions` for the reasons explained in
+ the previous section. The latter is MV3-only and can be removed from a MV2
+ manifest.
diff --git a/dnr-dynamic-with-options/manifest.json b/dnr-dynamic-with-options/manifest.json
new file mode 100644
index 00000000..bc16e834
--- /dev/null
+++ b/dnr-dynamic-with-options/manifest.json
@@ -0,0 +1,12 @@
+{
+ "manifest_version": 3,
+ "name": "DNR dynamic with options",
+ "description": "Modify requests according to the rules specified by the user in the options page.",
+ "version": "0.1",
+ "permissions": ["declarativeNetRequestWithHostAccess"],
+ "optional_host_permissions": ["*://*/"],
+ "optional_permissions": ["*://*/"],
+ "options_ui": {
+ "page": "options.html"
+ }
+}
diff --git a/dnr-dynamic-with-options/options.css b/dnr-dynamic-with-options/options.css
new file mode 100644
index 00000000..254fe16e
--- /dev/null
+++ b/dnr-dynamic-with-options/options.css
@@ -0,0 +1,8 @@
+.input-and-buttons legend {
+ font-weight: bold;
+}
+.input-and-buttons textarea {
+ display: block;
+ width: 100%;
+ min-height: 7em;
+}
diff --git a/dnr-dynamic-with-options/options.html b/dnr-dynamic-with-options/options.html
new file mode 100644
index 00000000..bea7c417
--- /dev/null
+++ b/dnr-dynamic-with-options/options.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dnr-dynamic-with-options/options.js b/dnr-dynamic-with-options/options.js
new file mode 100644
index 00000000..ab1471a0
--- /dev/null
+++ b/dnr-dynamic-with-options/options.js
@@ -0,0 +1,90 @@
+"use strict";
+
+if (typeof browser == "undefined") {
+ // `browser` is not defined in Chrome, but Manifest V3 extensions in Chrome
+ // also support promises in the `chrome` namespace, like Firefox. To easily
+ // test the example without modifications, polyfill "browser" to "chrome".
+ globalThis.browser = chrome;
+}
+
+function initializePrefHandlerForHostPermissions() {
+ const textarea = document.getElementById("input-host-permissions");
+ const statusOutput = document.getElementById("status-host-permissions");
+ document.getElementById("grant-host-permissions").onclick = async () => {
+ try {
+ let origins = JSON.parse(textarea.value);
+ statusOutput.value = "Requesting permissions";
+ let ok = await browser.permissions.request({ origins });
+ statusOutput.value = ok ? "Permissions granted" : "Permissions denied";
+ } catch (e) {
+ statusOutput.value = `Failed to grant permissions: ${e}`;
+ }
+ };
+ document.getElementById("reset-host-permissions").onclick = async () => {
+ let permissions = await browser.permissions.getAll();
+ await browser.permissions.remove({ origins: permissions.origins });
+ statusOutput.value = `Removed: ${JSON.stringify(permissions.origins)}`;
+ };
+
+ browser.permissions.getAll().then(
+ permissions => {
+ textarea.value = JSON.stringify(permissions.origins, null, 2);
+ }
+ );
+}
+
+function serializeRules(rules) {
+ // The getDynamicRules and getSessionRules APIs returns the rules, including
+ // optional keys. For readability, we strip all optional keys.
+ // JSON.stringify will drop keys if the replacer function returns undefined.
+ const replacer = (key, value) => value === null ? undefined : value;
+ return JSON.stringify(rules, replacer, 2);
+}
+
+function initializePrefHandlerForDynamicDNR() {
+ const textarea = document.getElementById("input-dynamic-rules");
+ const statusOutput = document.getElementById("status-dynamic-rules");
+ document.getElementById("save-dynamic-rules").onclick = async () => {
+ try {
+ let newRules = JSON.parse(textarea.value);
+ let oldRules = await browser.declarativeNetRequest.getDynamicRules();
+ await browser.declarativeNetRequest.updateDynamicRules({
+ removeRuleIds: oldRules.map(rule => rule.id),
+ addRules: newRules,
+ });
+ statusOutput.value = `Saved ${newRules.length} rules`;
+ } catch (e) {
+ statusOutput.value = `Failed to save rules: ${e}`;
+ }
+ };
+
+ browser.declarativeNetRequest.getDynamicRules().then(rules => {
+ textarea.value = serializeRules(rules);
+ });
+}
+
+function initializePrefHandlerForSessionDNR() {
+ const textarea = document.getElementById("input-session-rules");
+ const statusOutput = document.getElementById("status-session-rules");
+ document.getElementById("save-session-rules").onclick = async () => {
+ try {
+ let newRules = JSON.parse(textarea.value);
+ let oldRules = await browser.declarativeNetRequest.getSessionRules();
+ await browser.declarativeNetRequest.updateSessionRules({
+ removeRuleIds: oldRules.map(rule => rule.id),
+ addRules: newRules,
+ });
+ statusOutput.value = `Saved ${newRules.length} rules`;
+ } catch (e) {
+ statusOutput.value = `Failed to save rules: ${e}`;
+ }
+ };
+
+ browser.declarativeNetRequest.getSessionRules().then(rules => {
+ textarea.value = serializeRules(rules);
+ });
+}
+
+initializePrefHandlerForHostPermissions();
+initializePrefHandlerForDynamicDNR();
+initializePrefHandlerForSessionDNR();
diff --git a/dnr-redirect-url/README.md b/dnr-redirect-url/README.md
new file mode 100644
index 00000000..27bc5276
--- /dev/null
+++ b/dnr-redirect-url/README.md
@@ -0,0 +1,64 @@
+# dnr-redirect-url
+
+Demonstrates multiple ways to redirect requests using the declarativeNetRequest
+API through the `declarative_net_request` manifest key. Demonstrates aspects of
+Manifest Version 3 (MV3): `action`, `host_permissions`, and
+`web_accessible_resources`.
+
+## What it does
+
+This extension redirects requests from the example.com domain to other
+destinations:
+
+- example.com/ to `redirectTarget.html` packaged with the extension.
+- example.com/ew to extensionworkshop.com
+- https://www.example.com/[anything] to the same URL but the domain changed to
+ example.com and `?redirected_from_www=1` appended to the URL.
+- example.com URLs matching regular expression `^https?://([^?]+)$` to the same
+ URL but with the scheme set to `https:` (if it was `http:` before), and with
+ `?redirected_by_regex` appended.
+
+Redirecting requires host permissions for the pre-redirect URLs. In Firefox
+(and Safari), Manifest V3 extensions do not have access to these by default.
+The permission to these can be granted from the extension action popup.
+
+# What it shows
+
+This extension shows how to:
+
+- use the declarativeNetRequest API through the `declarative_net_request`
+ manifest key, along with the "declarativeNetRequestWithHostAccess"
+ permission. This permission does not trigger a permission warning. (Compared
+ to the "declarativeNetRequest" permission, which has the same effect but
+ displays the "Block content on any page" permission warning.)
+- use the `action` API to offer a UI surface with which the user can interact.
+- use the `permissions.contains` API to check whether an extension is granted
+ host permissions.
+- use the `permissions.request` API to request host permissions as needed.
+- redirect requests to another website.
+- redirect requests to a page packaged in the extension and listed in
+ `web_accessible_resources`.
+- redirect requests and transform the URL with the `transform` and
+ `queryTransform` options.
+- redirect a URL matching a regular expression in `regexFilter` to a
+ destination composed from `regexSubstitution` and the matched URL.
+- use "priority" to establish a guaranteed order of precedence between rules.
+ This results in a predictable redirect outcome when there are multiple
+ matching rule conditions for a given request.
+
+## Comparison with Manifest Version 2
+
+While this example uses `"manifest_version": 3`, the functionality is not
+specific to Manifest Version 3.
+
+To create a MV2 version of the extension, modify `manifest.json` as follows:
+
+- Set `manifest_version` to 2.
+- Rename `host_permissions` to `optional_permissions`.
+- Rename `action` to `browser_action`.
+- Set `web_accessible_resources` to `["redirectTarget.html"]`
+
+As an alternative to renaming `host_permissions` to `optional_permissions`,
+add the match patterns in the `host_permissions` array to the
+`permissions` key of the MV2 manifest. Then the user does not need to opt in to
+the host permission, and the extension works immediately after installation.
diff --git a/dnr-redirect-url/manifest.json b/dnr-redirect-url/manifest.json
new file mode 100644
index 00000000..e9417744
--- /dev/null
+++ b/dnr-redirect-url/manifest.json
@@ -0,0 +1,24 @@
+{
+ "manifest_version": 3,
+ "name": "Redirect example.com requests",
+ "description": "Redirects example.com requests. Redirects always require host_permissions.",
+ "version": "0.1",
+ "permissions": ["declarativeNetRequestWithHostAccess"],
+ "host_permissions": ["*://*.example.com/"],
+ "declarative_net_request": {
+ "rule_resources": [
+ {
+ "id": "ruleset",
+ "enabled": true,
+ "path": "redirect-rules.json"
+ }
+ ]
+ },
+ "action": {
+ "default_popup": "popup.html"
+ },
+ "web_accessible_resources": [{
+ "resources": ["redirectTarget.html"],
+ "matches": ["*://example.com/*"]
+ }]
+}
diff --git a/dnr-redirect-url/popup.html b/dnr-redirect-url/popup.html
new file mode 100644
index 00000000..ac92ca5b
--- /dev/null
+++ b/dnr-redirect-url/popup.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
Host permission requirement
+ To redirect requests, the extension needs host permissions.
+ While "Manage Extensions" (about:addons) offers a built-in UI to grant or revoke permissions, this extension uses the permissions API to build the request into the UI:
+
+
+
+
Test cases
+ There are four rules in redirect-rules.json; each rule has a test case here.
+
+
+For the redirect to have succeeded, three conditions must be met:
+
+
+
The declarativeNetRequest (DNR) rule should match the request.
+
The extension must declare the pre-redirect URL in host_permissions (in manifest.json) and the user should grant the permission.
+
The extension path in extensionPath must be declared in web_accessible_resources (in manifest.json), and the pre-redirect URL should match the pattern in matches.
+
+
+See popup.html for the permissions UI and examples.
+
+
diff --git a/examples.json b/examples.json
index 4057a0a2..994dd9ce 100644
--- a/examples.json
+++ b/examples.json
@@ -156,6 +156,43 @@
"javascript_apis": [],
"name": "discogs-search"
},
+ {
+ "description": "Demonstrates how to block network requests without host permissions using the declarativeNetRequest API with the `declarative_net_request` manifest key.",
+ "javascript_apis": [
+ "declarativeNetRequest.Rule",
+ "declarativeNetRequest.RuleAction",
+ "declarativeNetRequest.RuleCondition"
+ ],
+ "name": "dnr-block-only"
+ },
+ {
+ "description": "Demonstrates a generic way to request host permissions and register declarativeNetRequest rules to modify network requests, without any install-time permission warnings. The options_ui page offers a way to request permissions and register DNR rules.",
+ "javascript_apis": [
+ "declarativeNetRequest.Rule",
+ "declarativeNetRequest.getDynamicRules",
+ "declarativeNetRequest.getSessionRules",
+ "declarativeNetRequest.updateDynamicRules",
+ "declarativeNetRequest.updateSessionRules",
+ "permissions.getAll",
+ "permissions.remove",
+ "permissions.request"
+ ],
+ "name": "dnr-dynamic-with-options"
+ },
+ {
+ "description": "Demonstrates multiple ways to redirect requests using the declarativeNetRequest API through the `declarative_net_request` manifest key. Demonstrates aspects of Manifest Version 3 (MV3): action, host_permissions, and web_accessible_resources, and includes a comparison with Manifest Version 2 (MV2).",
+ "javascript_apis": [
+ "declarativeNetRequest.Redirect",
+ "declarativeNetRequest.Rule",
+ "declarativeNetRequest.RuleAction",
+ "declarativeNetRequest.RuleCondition",
+ "declarativeNetRequest.URLTransform",
+ "permissions.contains",
+ "permissions.remove",
+ "permissions.request"
+ ],
+ "name": "dnr-redirect-url"
+ },
{
"description": "Dynamic theme example",
"javascript_apis": [