Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'win' && socketprocess_networking && fission OR os == 'mac' && socketprocess_networking && fission OR os == 'mac' && debug OR os == 'linux' && socketprocess_networking
- Manifest: toolkit/components/extensions/test/xpcshell/xpcshell-remote.toml includes toolkit/components/extensions/test/xpcshell/xpcshell-common.toml
- Manifest: toolkit/components/extensions/test/xpcshell/xpcshell.toml includes toolkit/components/extensions/test/xpcshell/xpcshell-common.toml
"use strict";
add_setup(() => {
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
Services.prefs.setBoolPref("extensions.dnr.enabled", true);
Services.prefs.setBoolPref("extensions.dnr.feedback", true);
});
// This function is serialized and called in the context of the test extension's
// background page. dnrTestUtils is passed to the background function.
function makeDnrTestUtils() {
const dnrTestUtils = {};
const dnr = browser.declarativeNetRequest;
const DUMMY_ACTION = {
// "modifyHeaders" is the only action that allows multiple rule matches.
type: "modifyHeaders",
responseHeaders: [{ operation: "append", header: "x", value: "y" }],
};
async function testMatchesRequest(request, ruleIds, description) {
browser.test.assertDeepEq(
ruleIds,
(await dnr.testMatchOutcome(request)).matchedRules.map(mr => mr.ruleId),
description
);
}
async function testMatchesUrlFilter({
urlFilter,
isUrlFilterCaseSensitive,
urls = [],
urlsNonMatching = [],
}) {
// Sanity check: verify that there are no unexpected escaped characters,
// because that can surprise.
function sanityCheckUrl(url) {
const normalizedUrl = new URL(url).href;
if (normalizedUrl.split("%").length !== url.split("*").length) {
// ^ we only check for %-escapes and not exact URL equality because the
// tests imported from Chrome often omit the "/" (path separator).
browser.test.assertEq(normalizedUrl, url, "url should be canonical");
}
}
await dnr.updateSessionRules({
addRules: [
{
id: 12345,
condition: { urlFilter, isUrlFilterCaseSensitive },
action: DUMMY_ACTION,
},
],
});
for (let url of urls) {
sanityCheckUrl(url);
const request = { url, type: "other" };
const description = `urlFilter ${urlFilter} should match: ${url}`;
await testMatchesRequest(request, [12345], description);
}
for (let url of urlsNonMatching) {
sanityCheckUrl(url);
const request = { url, type: "other" };
const description = `urlFilter ${urlFilter} should not match: ${url}`;
await testMatchesRequest(request, [], description);
}
await dnr.updateSessionRules({ removeRuleIds: [12345] });
}
Object.assign(dnrTestUtils, {
DUMMY_ACTION,
testMatchesRequest,
testMatchesUrlFilter,
});
return dnrTestUtils;
}
async function runAsDNRExtension({ background, manifest }) {
let extension = ExtensionTestUtils.loadExtension({
background: `(${background})((${makeDnrTestUtils})())`,
manifest: {
manifest_version: 3,
permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"],
// While testing urlFilter itself does not require any host permissions,
// we are asking for host permissions anyway because the "modifyHeaders"
// action requires host permissions, and we use the "modifyHeaders" action
// to ensure that we can detect when multiple rules match.
host_permissions: ["<all_urls>"],
granted_host_permissions: true,
...manifest,
},
temporarilyInstalled: true, // <-- for granted_host_permissions
});
await extension.startup();
await extension.awaitFinish();
await extension.unload();
}
// This test checks various urlFilters with a possibly ambiguous interpretation.
// In some cases the semantic difference in interpretation can have different
// outcomes; in these cases we have chosen the behavior as observed in Chrome.
add_task(async function ambiguous_urlFilter_patterns() {
await runAsDNRExtension({
background: async dnrTestUtils => {
const { testMatchesUrlFilter } = dnrTestUtils;
// Left anchor, empty pattern: always matches
// Ambiguous with Right anchor, but same result.
await testMatchesUrlFilter({
urlFilter: "|",
urlsNonMatching: [],
});
// Domain anchor, empty pattern: always matches.
// Ambiguous with Left anchor + Right anchor, the latter would not match
// anything (only an empty string, but URLs cannot be empty).
await testMatchesUrlFilter({
urlFilter: "||",
urlsNonMatching: [],
});
// Domain anchor plus Right separator: never matches.
// Ambiguous with Left anchor + | + Right anchor, that is no match either.
await testMatchesUrlFilter({
urlFilter: "|||",
urls: [],
});
// Repeated separator: ^^^^ matches separator chars (=everything except
// alphanumeric, "_", "-", ".", "%"), but when at the end of a string,
// the last "^" can also be interpreted as a right anchor (like ^^^|).
// Ambiguous: while "^" is defined to match the end of URL, it could also
// be interpreted as "^^^^" matching the end of URL 4x, i.e. always.
await testMatchesUrlFilter({
urlFilter: "^^^^",
urls: [
// Note: "^" is escaped "%5E" when part of the URL, except after "#".
// ^ Note that "^" is after "#" and therefore not %5E. If "^" were to
// somehow be %-encoded to "%5E", then the end would become "/#%5E"
// and the "/#%" would only be 3 separators followed by alphanum. The
// test matching shows that the canonical representation of "^" after
// a "#" is "^" and can be matched.
],
urlsNonMatching: [
],
});
// Not ambiguous, but for comparison with "^^^^": all http(s) match.
await testMatchesUrlFilter({
urlFilter: "^^^",
// Not seen by DNR in practice, but could be passed to testMatchOutcome:
urlsNonMatching: ["file:hello/no/three/consecutive/special/characters"],
});
// Separator plus Right anchor: always matches.
// Ambiguous: "^" is defined to match the end of URL once, but a right
// domain anchor already matches that. A potential interpretation is for
// "^" to be required to match a non-alphanumeric (etc.), but in practice
// "^" is allowed to match the end of the URL. Effectively "^|" = "|".
await testMatchesUrlFilter({
urlFilter: "^|",
urls: [
],
urlsNonMatching: [],
});
// Domain anchor plus separator: "^" only matches non-alphanum (etc.)
// Ambiguous: "||" is defined to match a domain anchor. There is no
// domain part after the trailing "." of a FQDN. Still, "." matches.
await testMatchesUrlFilter({
urlFilter: "||^",
});
browser.test.notifyPass();
},
});
});
add_task(async function urlFilter_domain_anchor() {
await runAsDNRExtension({
background: async dnrTestUtils => {
const { testMatchesUrlFilter } = dnrTestUtils;
await testMatchesUrlFilter({
// Not a domain anchor, but for comparison with "||ps" below:
urlFilter: "ps",
urls: [
],
urlsNonMatching: [
],
});
await testMatchesUrlFilter({
urlFilter: "||ps",
urls: [
],
urlsNonMatching: [
],
});
await testMatchesUrlFilter({
urlFilter: "||1",
urls: [
],
urlsNonMatching: [
],
});
await testMatchesUrlFilter({
urlFilter: "||^1",
urls: [
],
urlsNonMatching: [
],
});
browser.test.notifyPass();
},
});
});
// Extreme patterns that should not be used in practice, but are not explicitly
// documented to be disallowed.
add_task(
{ skip_if: () => mozinfo.ccov },
async function extreme_urlFilter_patterns() {
await runAsDNRExtension({
background: async dnrTestUtils => {
const { testMatchesRequest, DUMMY_ACTION } = dnrTestUtils;
await browser.declarativeNetRequest.updateSessionRules({
addRules: [
{
id: 1,
condition: {
urlFilter: "*".repeat(1e6),
},
action: DUMMY_ACTION,
},
{
id: 2,
condition: {
urlFilter: "^".repeat(1e6),
},
action: DUMMY_ACTION,
},
{
id: 3,
condition: {
// Note: 2 chars repeat 5e5 instead of 1e6 because newURI limits
// the length of the URL (to network.standard-url.max-length),
// so we would not be able to verify whether the URL is really
// that long.
urlFilter: "*^".repeat(5e5),
},
action: DUMMY_ACTION,
},
{
id: 4,
condition: {
// Note: well beyond the maximum length of a URL. But as "*" can
// match any char (including zero length), this still matches.
urlFilter: "h" + "*".repeat(1e7) + "endofurl",
},
action: DUMMY_ACTION,
},
],
});
await testMatchesRequest(
[1],
"urlFilter with 1M wildcard chars matches any URL"
);
await testMatchesRequest(
[1],
"urlFilter with 1M wildcards matches, other '^' do not match alpha"
);
await testMatchesRequest(
[1, 2, 3],
"urlFilter with 1M wildcards, ^ and *^ all match URL with 1M '/' chars"
);
await testMatchesRequest(
[1, 3],
"urlFilter with 1M wildcards and *^ match URL with 1M 'x/' chars"
);
await testMatchesRequest(
[1, 4],
"urlFilter with 1M and 10M wildcards matches URL"
);
browser.test.notifyPass();
},
});
}
);
add_task(async function test_isUrlFilterCaseSensitive() {
await runAsDNRExtension({
background: async dnrTestUtils => {
const { testMatchesUrlFilter } = dnrTestUtils;
await testMatchesUrlFilter({
urlFilter: "AbC",
isUrlFilterCaseSensitive: true,
urls: [
],
urlsNonMatching: [
],
});
await testMatchesUrlFilter({
urlFilter: "AbC",
isUrlFilterCaseSensitive: false,
urls: [
],
urlsNonMatching: [
],
});
// Chrome's initial DNR API specified isUrlFilterCaseSensitive to be true
// by default. Later, it became false by default.
await testMatchesUrlFilter({
urlFilter: "AbC",
// isUrlFilterCaseSensitive: false, // is implied by default.
urls: [
],
urlsNonMatching: [
],
});
browser.test.notifyPass();
},
});
});
// Imported tests from Chromium from:
// kAnchorNone -> "" (anywhere in the string)
// kBoundary -> | (start or end of string)
// kSubdomain -> || (start of (sub)domain)
// kMatchCase -> isUrlFilterCaseSensitive: true
// kDonotMatchCase -> isUrlFilterCaseSensitive: false (this is the default).
// proto::URL_PATTERN_TYPE_WILDCARDED / proto::URL_PATTERN_TYPE_SUBSTRING -> ""
//
// Minus two tests ("", kBoundary, kBoundary) because the resulting pattern is
// "||" and ambiguous with ("", kSubdomain, "").
add_task(async function test_chrome_parity() {
await runAsDNRExtension({
background: async dnrTestUtils => {
const { testMatchesUrlFilter } = dnrTestUtils;
const testCases = [
// {"", proto::URL_PATTERN_TYPE_SUBSTRING}
{
urlFilter: "*",
expectMatch: true,
},
// // {"", proto::URL_PATTERN_TYPE_WILDCARDED}
// { // Already tested before.
// urlFilter: "*",
// expectMatch: true,
// },
// {"", kBoundary, kAnchorNone}
{
urlFilter: "|",
expectMatch: true,
},
// {"", kSubdomain, kAnchorNone}
{
urlFilter: "||",
expectMatch: true,
},
// // {"", kSubdomain, kAnchorNone}
// { // Already tested before.
// urlFilter: "||",
// expectMatch: true,
// },
// {"^", kSubdomain, kAnchorNone}
{
urlFilter: "||^",
expectMatch: false,
},
// {".", kSubdomain, kAnchorNone}
{
urlFilter: "||.",
expectMatch: false,
},
// // {"", kAnchorNone, kBoundary}
// { // Already tested before.
// urlFilter: "|",
// expectMatch: true,
// },
// {"^", kAnchorNone, kBoundary}
{
urlFilter: "^|",
expectMatch: true,
},
// {".", kAnchorNone, kBoundary}
{
urlFilter: ".|",
expectMatch: false,
},
// // {"", kBoundary, kBoundary}
// { // "||" is ambiguous, cannot mean Left anchor + Right anchor
// urlFilter: "||",
// expectMatch: false,
// },
// {"", kSubdomain, kBoundary}
{
urlFilter: "|||",
expectMatch: false,
},
// {"com/", kSubdomain, kBoundary}
{
urlFilter: "||com/|",
expectMatch: true,
},
// {"xampl", proto::URL_PATTERN_TYPE_SUBSTRING}
{
urlFilter: "xampl",
expectMatch: true,
},
// {"example", proto::URL_PATTERN_TYPE_SUBSTRING}
{
urlFilter: "example",
expectMatch: true,
},
// {"/a?a"}
{
urlFilter: "/a?a",
expectMatch: true,
},
// {"^abc"}
{
urlFilter: "^abc",
expectMatch: true,
},
// {"^abc"}
{
urlFilter: "^abc",
expectMatch: true,
},
// {"^abc"}
{
urlFilter: "^abc",
expectMatch: true,
},
// {"^abc^abc"}
{
urlFilter: "^abc^abc",
expectMatch: true,
},
// {"^com^abc^abc"}
{
urlFilter: "^com^abc^abc",
expectMatch: false,
},
{
expectMatch: true,
},
{
expectMatch: true,
},
// {"mple.com/", kAnchorNone, kBoundary}
{
urlFilter: "mple.com/|",
expectMatch: true,
},
// {"mple.com/", kAnchorNone, kAnchorNone}
{
urlFilter: "mple.com/",
expectMatch: true,
},
// {"mple.com/", kSubdomain, kAnchorNone}
{
urlFilter: "||mple.com/",
expectMatch: false,
},
// {"ex.com", kSubdomain, kAnchorNone}
{
urlFilter: "||ex.com",
expectMatch: false,
},
// {"ex.com", kSubdomain, kAnchorNone}
{
urlFilter: "||ex.com",
expectMatch: true,
},
// {"ex.com", kSubdomain, kAnchorNone}
{
urlFilter: "||ex.com",
expectMatch: true,
},
// {"ex.com", kSubdomain, kAnchorNone}
{
urlFilter: "||ex.com",
expectMatch: false,
},
// {"example.com^", kSubdomain, kAnchorNone}
{
urlFilter: "||example.com^",
expectMatch: true,
},
{
expectMatch: true,
},
// {"mpl*com/", kAnchorNone, kBoundary}
{
urlFilter: "mpl*com/|",
expectMatch: true,
},
// {"example^com"}
{
urlFilter: "example^com",
expectMatch: false,
},
// {"example^com"}
{
urlFilter: "example^com",
expectMatch: true,
},
// {"example.com^"}
{
urlFilter: "example.com^",
expectMatch: true,
},
// {"http*.com/", kBoundary, kBoundary}
{
urlFilter: "|http*.com/|",
expectMatch: true,
},
// {"http*.org/", kBoundary, kBoundary}
{
urlFilter: "|http*.org/|",
expectMatch: false,
},
// {"/path?*&p1=*&p2="}
{
urlFilter: "/path?*&p1=*&p2=",
expectMatch: false,
},
// {"/path?*&p1=*&p2="}
{
urlFilter: "/path?*&p1=*&p2=",
expectMatch: true,
},
// {"/path?*&p1=*&p2="}
{
urlFilter: "/path?*&p1=*&p2=",
expectMatch: true,
},
// {"/path?*&p1=*&p2="}
{
urlFilter: "/path?*&p1=*&p2=",
expectMatch: true,
},
// {"/path?*&p1=*&p2="}
{
urlFilter: "/path?*&p1=*&p2=",
expectMatch: false,
},
// {"/path?*&p1=*&p2="}
{
urlFilter: "/path?*&p1=*&p2=",
expectMatch: false,
},
// {"abc*def*ghijk*xyz"}
{
urlFilter: "abc*def*ghijk*xyz",
expectMatch: true,
},
// {"abc*cdef"}
{
urlFilter: "abc*cdef",
expectMatch: false,
},
// {"^^a^^"}
{
urlFilter: "^^a^^",
expectMatch: true,
},
// {"^^a^^"}
{
urlFilter: "^^a^^",
expectMatch: true,
},
// {"^^a^^"}
{
urlFilter: "^^a^^",
expectMatch: false,
},
// {"^^a^^"}
{
urlFilter: "^^a^^",
expectMatch: true,
},
// {"ex.com^path^*k=v^"}
{
urlFilter: "ex.com^path^*k=v^",
expectMatch: true,
},
// {"ex.com^path^*k=v^"}
{
urlFilter: "ex.com^path^*k=v^",
expectMatch: false,
},
// {"a^a&a^a&"}
{
urlFilter: "a^a&a^a&",
expectMatch: true,
},
// {"abc*def^"}
{
urlFilter: "abc*def^",
expectMatch: true,
},
{
expectMatch: false,
},
// {"example.com/", kSubdomain, kAnchorNone}
{
urlFilter: "||example.com/",
expectMatch: true,
},
// {"examp", kSubdomain, kAnchorNone}
{
urlFilter: "||examp",
expectMatch: true,
},
// {"xamp", kSubdomain, kAnchorNone}
{
urlFilter: "||xamp",
expectMatch: false,
},
// {"examp", kSubdomain, kAnchorNone}
{
urlFilter: "||examp",
expectMatch: true,
},
// {"t.examp", kSubdomain, kAnchorNone}
{
urlFilter: "||t.examp",
expectMatch: false,
},
// {"com^", kSubdomain, kAnchorNone}
{
urlFilter: "||com^",
expectMatch: true,
},
// {"com^x", kSubdomain, kBoundary}
{
urlFilter: "||com^x|",
expectMatch: true,
},
// {"x.com", kSubdomain, kAnchorNone}
{
urlFilter: "||x.com",
expectMatch: false,
},
// {"ex.com/", kSubdomain, kBoundary}
{
urlFilter: "||ex.com/|",
expectMatch: true,
},
// {"ex.com^", kSubdomain, kBoundary}
{
urlFilter: "||ex.com^|",
expectMatch: true,
},
// {"ex.co", kSubdomain, kBoundary}
{
urlFilter: "||ex.co|",
expectMatch: false,
},
// {"ex.com", kSubdomain, kBoundary}
{
urlFilter: "||ex.com|",
expectMatch: false,
},
// {"ex.com/", kSubdomain, kBoundary}
{
urlFilter: "||ex.com/|",
expectMatch: true,
},
// {"http", kSubdomain, kBoundary}
{
urlFilter: "||http|",
expectMatch: false,
},
// {"http", kSubdomain, kAnchorNone}
{
urlFilter: "||http",
expectMatch: true,
},
// {"/example.com", kSubdomain, kBoundary}
{
urlFilter: "||/example.com|",
expectMatch: false,
},
// {"/example.com/", kSubdomain, kBoundary}
{
urlFilter: "||/example.com/|",
expectMatch: false,
},
// {".", kSubdomain, kAnchorNone}
{
urlFilter: "||.",
expectMatch: true,
},
// {"^", kSubdomain, kAnchorNone}
{
urlFilter: "||^",
expectMatch: false,
},
// {".", kSubdomain, kAnchorNone}
{
urlFilter: "||.",
expectMatch: false,
},
// {"^", kSubdomain, kAnchorNone}
{
urlFilter: "||^",
expectMatch: true,
},
// {".", kSubdomain, kAnchorNone}
{
urlFilter: "||.",
expectMatch: true,
},
// {"^", kSubdomain, kAnchorNone}
{
urlFilter: "||^",
expectMatch: true,
},
// {"/path", kSubdomain, kAnchorNone}
{
urlFilter: "||/path",
expectMatch: true,
},
// {"^path", kSubdomain, kAnchorNone}
{
urlFilter: "||^path",
expectMatch: true,
},
// {"/path", kSubdomain, kBoundary}
{
urlFilter: "||/path|",
expectMatch: true,
},
// {"^path", kSubdomain, kBoundary}
{
urlFilter: "||^path|",
expectMatch: true,
},
// {"path", kSubdomain, kBoundary}
{
urlFilter: "||path|",
expectMatch: false,
},
// {"path", proto::URL_PATTERN_TYPE_SUBSTRING, kDonotMatchCase}
{
urlFilter: "path",
isUrlFilterCaseSensitive: false,
expectMatch: true,
},
// {"path", proto::URL_PATTERN_TYPE_SUBSTRING, kMatchCase}
{
urlFilter: "path",
isUrlFilterCaseSensitive: true,
expectMatch: false,
},
// {"path", proto::URL_PATTERN_TYPE_SUBSTRING, kDonotMatchCase}
{
urlFilter: "path",
isUrlFilterCaseSensitive: false,
expectMatch: true,
},
// {"path", proto::URL_PATTERN_TYPE_SUBSTRING, kMatchCase}
{
urlFilter: "path",
isUrlFilterCaseSensitive: true,
expectMatch: true,
},
// {"abc*def^", proto::URL_PATTERN_TYPE_WILDCARDED, kMatchCase}
{
urlFilter: "abc*def^",
isUrlFilterCaseSensitive: true,
expectMatch: true,
},
// {"abc*def^", proto::URL_PATTERN_TYPE_WILDCARDED, kMatchCase}
{
urlFilter: "abc*def^",
isUrlFilterCaseSensitive: true,
expectMatch: false,
},
// {"abc*def^", proto::URL_PATTERN_TYPE_WILDCARDED, kDonotMatchCase}
{
urlFilter: "abc*def^",
isUrlFilterCaseSensitive: false,
expectMatch: true,
},
// {"abc*def^", proto::URL_PATTERN_TYPE_WILDCARDED, kDonotMatchCase}
{
urlFilter: "abc*def^",
isUrlFilterCaseSensitive: false,
expectMatch: true,
},
// {"abc^", kAnchorNone, kAnchorNone}
{
urlFilter: "abc^",
expectMatch: true,
},
// {"abc^", kAnchorNone, kAnchorNone}
{
urlFilter: "abc^",
expectMatch: true,
},
// {"abc^", kAnchorNone, kAnchorNone}
{
urlFilter: "abc^",
expectMatch: false,
},
// {"abc^", kAnchorNone, kBoundary}
{
urlFilter: "abc^|",
expectMatch: true,
},
// {"abc^", kAnchorNone, kBoundary}
{
urlFilter: "abc^|",
expectMatch: true,
},
// {"abc^", kAnchorNone, kBoundary}
{
urlFilter: "abc^|",
expectMatch: false,
},
{
expectMatch: true,
},
{
expectMatch: true,
},
{
expectMatch: true,
},
{
expectMatch: true,
},
{
expectMatch: true,
},
{
expectMatch: false,
},
// {"abc.com^", kSubdomain, kAnchorNone}
{
urlFilter: "||abc.com^",
expectMatch: true,
},
// {"abc.com^", kSubdomain, kAnchorNone}
{
urlFilter: "||abc.com^",
expectMatch: true,
},
// {"abc.com^", kSubdomain, kAnchorNone}
{
urlFilter: "||abc.com^",
expectMatch: false,
},
// {"abc.com^", kSubdomain, kBoundary}
{
urlFilter: "||abc.com^|",
expectMatch: false,
},
// {"abc.com^", kSubdomain, kBoundary}
{
urlFilter: "||abc.com^|",
expectMatch: true,
},
// {"abc.com^", kSubdomain, kBoundary}
{
urlFilter: "||abc.com^|",
expectMatch: false,
},
// {"abc*^", kAnchorNone, kAnchorNone}
{
urlFilter: "abc*^",
expectMatch: true,
},
// {"abc*^", kAnchorNone, kAnchorNone}
{
urlFilter: "abc*^",
expectMatch: true,
},
// {"abc*^", kAnchorNone, kBoundary}
{
urlFilter: "abc*^|",
expectMatch: true,
},
// {"abc*^", kAnchorNone, kBoundary}
{
urlFilter: "abc*^|",
expectMatch: true,
},
// {"abc*", kAnchorNone, kBoundary}
{
urlFilter: "abc*|",
expectMatch: true,
},
// {"*google.com", kBoundary, kAnchorNone}
{
urlFilter: "|*google.com",
expectMatch: true,
},
// {"*", kBoundary, kBoundary}
{
urlFilter: "|*|",
expectMatch: true,
},
// // {"", kBoundary, kBoundary}
// { // "||" is ambiguous, cannot mean Left anchor + Right anchor
// urlFilter: "||",
// expectMatch: false,
// },
];
for (let test of testCases) {
let { urlFilter, url, expectMatch, isUrlFilterCaseSensitive } = test;
if (expectMatch) {
await testMatchesUrlFilter({
urlFilter,
isUrlFilterCaseSensitive,
urls: [url],
});
} else {
await testMatchesUrlFilter({
urlFilter,
isUrlFilterCaseSensitive,
urlsNonMatching: [url],
});
}
}
browser.test.notifyPass();
},
});
});