Skip to content

Commit

Permalink
Add declarativeNetRequest (DNR) + MV3 examples (#526)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Rob--W authored May 17, 2023
1 parent 9433f84 commit 06330a6
Show file tree
Hide file tree
Showing 17 changed files with 735 additions and 1 deletion.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
42 changes: 42 additions & 0 deletions dnr-block-only/README.md
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions dnr-block-only/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}]
}
}
31 changes: 31 additions & 0 deletions dnr-block-only/rules.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
97 changes: 97 additions & 0 deletions dnr-block-only/testpage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
div {
margin: 2px 0;
border: 1px solid black;
}
img {
display: block;
}
</style>
</head>
<body>

<h2 id="blocksub">Block requests containing 'blocksub' (except main_frame)</h2>
Given the following rule:
<pre>
{
"id": 1,
"condition": {
"urlFilter": "blocksub"
},
"action": {
"type": "block"
}
},
</pre>
<div>
rule 1 will block this image (containing 'blocksub'):
<img src="https://developer.mozilla.org/favicon.ico?blocksub">
</div>
<div>
rule 1 will not block this image (does not contain 'blocksub'):
<img src="https://developer.mozilla.org/favicon.ico">
</div>
<div>
rule 1 will not block top-level navigations to
<a href="https://developer.mozilla.org/favicon.ico?blocksub">https://developer.mozilla.org/favicon.ico?blocksub</a>
because top-level navigation ("main_frame") requests are not matched
when "resourceTypes" and "excludedResourceTypes" are not specified.
</div>

<h2 id="blocktop">Block main_frame requests containing 'blocktop'</h2>
Given the following rule:
<pre>
{
"id": 2,
"condition": {
"urlFilter": "blocktop",
"resourceTypes": ["main_frame"]
},
"action": {
"type": "block"
}
},
</pre>
<div>
rule 2 will block top-level navigation to
<a href="https://developer.mozilla.org/favicon.ico?blocktop">https://developer.mozilla.org/favicon.ico?blocktop</a>
because the "resourceTypes" array contains "main_frame".
</div>
<div>
rule 2 will not block this image (containing 'blocktop')
<img src="https://developer.mozilla.org/favicon.ico?blocktop">
because "image" is not in the "resourceTypes" array.
</div>

<h2 id="blockall">Block all requests containing 'blockall'</h2>
Given the following rule:
<pre>
{
"id": 3,
"condition": {
"urlFilter": "blockall",
"excludedResourceTypes": []
},
"action": {
"type": "block"
}
}
</pre>
<div>
rule 3 will block this image (containing 'blockall'):
<img src="https://developer.mozilla.org/favicon.ico?blockall">
</div>
<div>
rule 3 will block top-level navigation to
<a href="https://developer.mozilla.org/favicon.ico?blockall">https://developer.mozilla.org/favicon.ico?blockall</a>
because "excludedResourceTypes" is set to an empty array.
<br>
Note: not blocked in Chrome due to <a href="https://crbug.com/1432871">https://crbug.com/1432871</a>.
</div>

</body>
</html>
105 changes: 105 additions & 0 deletions dnr-dynamic-with-options/README.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions dnr-dynamic-with-options/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
8 changes: 8 additions & 0 deletions dnr-dynamic-with-options/options.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.input-and-buttons legend {
font-weight: bold;
}
.input-and-buttons textarea {
display: block;
width: 100%;
min-height: 7em;
}
36 changes: 36 additions & 0 deletions dnr-dynamic-with-options/options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width"> <!-- mobile-friendly -->
<link rel="stylesheet" type="text/css" href="options.css">
</head>
<body>
<fieldset class="input-and-buttons">
<legend>Allowed host permissions</legend>
Specify the JSON-formatted list of allowed host permissions (documentation: <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/permissions/Permissions#origins">origins</a>).
<textarea id="input-host-permissions"></textarea>
<button id="grant-host-permissions">Grant host permissions</button>
<button id="reset-host-permissions">Reset host permissions</button>
<output id="status-host-permissions"></output>
</fieldset>

<fieldset class="input-and-buttons">
<legend>Dynamic declarativeNetRequest rules (persists across restarts)</legend>
Specify the JSON-formatted list of dynamic DNR rules (documentation: <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest#rules">declarativeNetRequest rules</a>).
<textarea id="input-dynamic-rules"></textarea>
<button id="save-dynamic-rules">Save</button>
<output id="status-dynamic-rules"></output>
</fieldset>

<fieldset class="input-and-buttons">
<legend>Session-scoped declarativeNetRequest rules (cleared on extension unload/reload)</legend>
Specify the JSON-formatted list of session DNR rules (documentation: <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest#rules">declarativeNetRequest rules</a>).
<textarea id="input-session-rules"></textarea>
<button id="save-session-rules">Save</button>
<output id="status-session-rules"></output>
</fieldset>

<script src="options.js"></script>
</body>
</html>
Loading

0 comments on commit 06330a6

Please sign in to comment.