From 06330a69c2087baba3cde1a9b241d314e37cacc6 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Wed, 17 May 2023 18:13:55 +0200 Subject: [PATCH] Add declarativeNetRequest (DNR) + MV3 examples (#526) These examples are designed to be cross-browser compatible. In particular, these extensions do not use `background` because there is currently no single manifest that can support both Firefox and Chrome, due to the lack of event page support in Chrome, and the lack of service worker support in Firefox. Three examples demonstrating the use of the declarativeNetRequest API: - dnr-block-only: One minimal example demonstrating the use of static DNR rules to block requests. - dnr-redirect-url: One minimal example demonstrating the use of static DNR rules to redirect requests. - dnr-dynamic-with-options: A generic example demonstrating how host permissions can be requested and free forms to input DNR rules. --- .eslintrc.json | 5 +- dnr-block-only/README.md | 42 ++++++++++ dnr-block-only/manifest.json | 16 ++++ dnr-block-only/rules.json | 31 ++++++++ dnr-block-only/testpage.html | 97 +++++++++++++++++++++++ dnr-dynamic-with-options/README.md | 105 +++++++++++++++++++++++++ dnr-dynamic-with-options/manifest.json | 12 +++ dnr-dynamic-with-options/options.css | 8 ++ dnr-dynamic-with-options/options.html | 36 +++++++++ dnr-dynamic-with-options/options.js | 90 +++++++++++++++++++++ dnr-redirect-url/README.md | 64 +++++++++++++++ dnr-redirect-url/manifest.json | 24 ++++++ dnr-redirect-url/popup.html | 29 +++++++ dnr-redirect-url/popup.js | 37 +++++++++ dnr-redirect-url/redirect-rules.json | 65 +++++++++++++++ dnr-redirect-url/redirectTarget.html | 38 +++++++++ examples.json | 37 +++++++++ 17 files changed, 735 insertions(+), 1 deletion(-) create mode 100644 dnr-block-only/README.md create mode 100644 dnr-block-only/manifest.json create mode 100644 dnr-block-only/rules.json create mode 100644 dnr-block-only/testpage.html create mode 100644 dnr-dynamic-with-options/README.md create mode 100644 dnr-dynamic-with-options/manifest.json create mode 100644 dnr-dynamic-with-options/options.css create mode 100644 dnr-dynamic-with-options/options.html create mode 100644 dnr-dynamic-with-options/options.js create mode 100644 dnr-redirect-url/README.md create mode 100644 dnr-redirect-url/manifest.json create mode 100644 dnr-redirect-url/popup.html create mode 100644 dnr-redirect-url/popup.js create mode 100644 dnr-redirect-url/redirect-rules.json create mode 100644 dnr-redirect-url/redirectTarget.html 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 @@ + + + + + + + + +

Block requests containing 'blocksub' (except main_frame)

+Given the following rule: +
+  {
+    "id": 1,
+    "condition": {
+      "urlFilter": "blocksub"
+    },
+    "action": {
+      "type": "block"
+    }
+  },
+
+
+ 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. +
+ +

Block main_frame requests containing 'blocktop'

+Given the following rule: +
+  {
+    "id": 2,
+    "condition": {
+      "urlFilter": "blocktop",
+      "resourceTypes": ["main_frame"]
+    },
+    "action": {
+      "type": "block"
+    }
+  },
+
+
+ rule 2 will block top-level navigation to + https://developer.mozilla.org/favicon.ico?blocktop + because the "resourceTypes" array contains "main_frame". +
+
+ rule 2 will not block this image (containing 'blocktop') + + because "image" is not in the "resourceTypes" array. +
+ +

Block all requests containing 'blockall'

+Given the following rule: +
+  {
+    "id": 3,
+    "condition": {
+      "urlFilter": "blockall",
+      "excludedResourceTypes": []
+    },
+    "action": {
+      "type": "block"
+    }
+  }
+
+
+ rule 3 will block this image (containing 'blockall'): + +
+
+ rule 3 will block top-level navigation to + https://developer.mozilla.org/favicon.ico?blockall + because "excludedResourceTypes" is set to an empty array. +
+ Note: not blocked in Chrome due to https://crbug.com/1432871. +
+ + + 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 @@ + + + + + + + + +
+ Allowed host permissions + Specify the JSON-formatted list of allowed host permissions (documentation: origins). + + + + +
+ +
+ Dynamic declarativeNetRequest rules (persists across restarts) + Specify the JSON-formatted list of dynamic DNR rules (documentation: declarativeNetRequest rules). + + + +
+ +
+ Session-scoped declarativeNetRequest rules (cleared on extension unload/reload) + Specify the JSON-formatted list of session DNR rules (documentation: declarativeNetRequest rules). + + + +
+ + + + 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. + + + + + diff --git a/dnr-redirect-url/popup.js b/dnr-redirect-url/popup.js new file mode 100644 index 00000000..0c2ee8c6 --- /dev/null +++ b/dnr-redirect-url/popup.js @@ -0,0 +1,37 @@ +"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; +} + +const permissions = { + // This origin is listed in host_permissions: + origins: ["*://*.example.com/"], +}; + +const checkbox_host_permission = document.getElementById("checkbox_host_permission"); +checkbox_host_permission.onchange = async () => { + if (checkbox_host_permission.checked) { + let granted = await browser.permissions.request(permissions); + if (!granted) { + // Permission request was denied by the user. + checkbox_host_permission.checked = false; + } + } else { + try { + await browser.permissions.remove(permissions); + } catch (e) { + // While Chrome allows granting of host_permissions that have manually + // been revoked by the user, it fails when revoking them, with + // "Error: You cannot remove required permissions." + console.error(e); + checkbox_host_permission.checked = true; + } + } +}; +browser.permissions.contains(permissions).then(granted => { + checkbox_host_permission.checked = granted; +}); diff --git a/dnr-redirect-url/redirect-rules.json b/dnr-redirect-url/redirect-rules.json new file mode 100644 index 00000000..bc75b95a --- /dev/null +++ b/dnr-redirect-url/redirect-rules.json @@ -0,0 +1,65 @@ +[ + { + "id": 1, + "priority": 4, + "condition": { + "urlFilter": "||example.com/|", + "resourceTypes": ["main_frame"] + }, + "action": { + "type": "redirect", + "redirect": { + "extensionPath": "/redirectTarget.html" + } + } + }, + { + "id": 2, + "priority": 3, + "condition": { + "urlFilter": "||example.com/ew", + "resourceTypes": ["main_frame"] + }, + "action": { + "type": "redirect", + "redirect": { + "url": "https://extensionworkshop.com/" + } + } + }, + { + "id": 3, + "priority": 2, + "condition": { + "urlFilter": "|https://www.example.com/", + "resourceTypes": ["main_frame"] + }, + "action": { + "type": "redirect", + "redirect": { + "transform": { + "host": "example.com", + "queryTransform": { + "addOrReplaceParams": [ + { "key": "redirected_from_www", "value": "1" } + ] + } + } + } + } + }, + { + "id": 4, + "condition": { + "regexFilter": "^https?://([^?]+)$", + "requestDomains": ["example.com"], + "resourceTypes": ["main_frame"] + }, + "action": { + "type": "redirect", + "redirect": { + "regexSubstitution": "https://\\1?redirected_by_regex" + } + } + } +] diff --git a/dnr-redirect-url/redirectTarget.html b/dnr-redirect-url/redirectTarget.html new file mode 100644 index 00000000..a9b1d677 --- /dev/null +++ b/dnr-redirect-url/redirectTarget.html @@ -0,0 +1,38 @@ + + + + + +redirectTarget.html + + + This page is the redirect target of requests matching rule 1 from redirect-rules.json.
+ The pattern ||example.com/| means: (sub)domain of example.com, with path "/" and nothing else before the end of the URL. +
+  {
+    "id": 1,
+    "priority": 4,
+    "condition": {
+      "urlFilter": "||example.com/|",
+      "resourceTypes": ["main_frame"]
+    },
+    "action": {
+      "type": "redirect",
+      "redirect": {
+        "extensionPath": "/redirectTarget.html"
+      }
+    }
+  },
+
+ +For the redirect to have succeeded, three conditions must be met: + + + +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": [