mirror of
https://github.com/mdn/webextensions-examples.git
synced 2026-04-17 23:08:33 +02:00
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.
This commit is contained in:
64
dnr-redirect-url/README.md
Normal file
64
dnr-redirect-url/README.md
Normal file
@@ -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.
|
||||
24
dnr-redirect-url/manifest.json
Normal file
24
dnr-redirect-url/manifest.json
Normal file
@@ -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/*"]
|
||||
}]
|
||||
}
|
||||
29
dnr-redirect-url/popup.html
Normal file
29
dnr-redirect-url/popup.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width"> <!-- mobile-friendly -->
|
||||
<base target="_blank"> <!-- Open links in new tab by default -->
|
||||
</head>
|
||||
<body>
|
||||
<h2>Host permission requirement</h2>
|
||||
To redirect requests, the extension needs host permissions.<br>
|
||||
While "Manage Extensions" (<code>about:addons</code>) offers a built-in UI to grant or revoke permissions, this extension uses the <code>permissions</code> API to build the request into the UI:
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" id="checkbox_host_permission">
|
||||
Allow access to example.com
|
||||
</label>
|
||||
|
||||
<h2>Test cases</h2>
|
||||
There are four rules in <a href="redirect-rules.json">redirect-rules.json</a>; each rule has a test case here.
|
||||
<ul>
|
||||
<li>1: <a href="https://example.com/">example.com/</a> should redirect to <a href="redirectTarget.html">redirectTarget.html</a> packaged in the extension.</li>
|
||||
<li>2: <a href="https://example.com/ew">example.com/ew</a> should redirect to <code>extensionworkshop.com</code></li>
|
||||
<li>3: <a href="https://www.example.com/anything">https://www.example.com/anything</a> should redirect to <code>https://example.com/anything?redirected_from_www=1</code>.</li>
|
||||
<li>4: <a href="http://example.com/no_question">http://example.com/no_question</a> should redirect to <code>https://example.com/no_question?redirected_by_regex</code>.</li>
|
||||
</ul>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
37
dnr-redirect-url/popup.js
Normal file
37
dnr-redirect-url/popup.js
Normal file
@@ -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;
|
||||
});
|
||||
65
dnr-redirect-url/redirect-rules.json
Normal file
65
dnr-redirect-url/redirect-rules.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
38
dnr-redirect-url/redirectTarget.html
Normal file
38
dnr-redirect-url/redirectTarget.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width"> <!-- mobile-friendly -->
|
||||
<title>redirectTarget.html</title>
|
||||
</head>
|
||||
<body>
|
||||
This page is the redirect target of requests matching rule 1 from <a href="redirect-rules.json">redirect-rules.json</a>.<br>
|
||||
The pattern <code>||example.com/|</code> means: (sub)domain of <code>example.com</code>, with path "/" and nothing else before the end of the URL.
|
||||
<pre>
|
||||
{
|
||||
"id": 1,
|
||||
"priority": 4,
|
||||
"condition": {
|
||||
"urlFilter": "||example.com/|",
|
||||
"resourceTypes": ["main_frame"]
|
||||
},
|
||||
"action": {
|
||||
"type": "redirect",
|
||||
"redirect": {
|
||||
"extensionPath": "/redirectTarget.html"
|
||||
}
|
||||
}
|
||||
},
|
||||
</pre>
|
||||
|
||||
For the redirect to have succeeded, three conditions must be met:
|
||||
|
||||
<ul>
|
||||
<li> The declarativeNetRequest (DNR) rule should match the request.</li>
|
||||
<li> The extension must declare the pre-redirect URL in <code>host_permissions</code> (in manifest.json) and the user should grant the permission.</li>
|
||||
<li> The extension path in <code>extensionPath</code> must be declared in <code>web_accessible_resources</code> (in manifest.json), and the pre-redirect URL should match the pattern in <code>matches</code>.</li>
|
||||
</ul>
|
||||
|
||||
See <a href="popup.html">popup.html</a> for the permissions UI and examples.
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user