summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js283
1 files changed, 283 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js
new file mode 100644
index 0000000000..84464d0cba
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js
@@ -0,0 +1,283 @@
+"use strict";
+
+const server = createHttpServer({ hosts: ["example.com", "restricted"] });
+server.registerPathHandler("/", (req, res) => {
+ res.setHeader("Access-Control-Allow-Origin", "*");
+ res.write("response from server");
+});
+server.registerPathHandler("/style_with_import.css", (req, res) => {
+ res.setHeader("Content-Type", "text/css");
+ res.setHeader("Access-Control-Allow-Origin", "*");
+ res.write("@import url('http://example.com/imported.css');");
+});
+server.registerPathHandler("/imported.css", (req, res) => {
+ res.setHeader("Content-Type", "text/css");
+ res.setHeader("Access-Control-Allow-Origin", "*");
+ res.write("imported_stylesheet_here { }");
+});
+
+add_setup(() => {
+ Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
+ Services.prefs.setBoolPref("extensions.dnr.enabled", true);
+ // The restrictedDomains pref should be set early, because the pref is read
+ // only once (on first use) by WebExtensionPolicy::IsRestrictedURI.
+ Services.prefs.setCharPref(
+ "extensions.webextensions.restrictedDomains",
+ "restricted"
+ );
+});
+
+async function startDNRExtension() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ await browser.declarativeNetRequest.updateSessionRules({
+ addRules: [
+ {
+ id: 1,
+ condition: { resourceTypes: ["xmlhttprequest", "stylesheet"] },
+ action: { type: "block" },
+ },
+ {
+ id: 2,
+ condition: { urlFilter: "blockme", resourceTypes: ["main_frame"] },
+ action: { type: "block" },
+ },
+ ],
+ });
+ browser.test.sendMessage("dnr_registered");
+ },
+ manifest: {
+ manifest_version: 3,
+ permissions: ["declarativeNetRequest"],
+ },
+ });
+ await extension.startup();
+ await extension.awaitMessage("dnr_registered");
+ return extension;
+}
+
+add_task(async function dnr_ignores_system_requests() {
+ let extension = await startDNRExtension();
+ Assert.equal(
+ await (await fetch("http://example.com/")).text(),
+ "response from server",
+ "DNR should not block requests from system principal"
+ );
+ await extension.unload();
+});
+
+add_task(async function dnr_ignores_requests_to_restrictedDomains() {
+ let extension = await startDNRExtension();
+ Assert.equal(
+ await ExtensionTestUtils.fetch("http://example.com/", "http://restricted/"),
+ "response from server",
+ "DNR should not block destination in restrictedDomains"
+ );
+ await extension.unload();
+});
+
+add_task(async function dnr_ignores_initiator_from_restrictedDomains() {
+ let extension = await startDNRExtension();
+ Assert.equal(
+ await ExtensionTestUtils.fetch("http://restricted/", "http://example.com/"),
+ "response from server",
+ "DNR should not block requests initiated from a page in restrictedDomains"
+ );
+ await extension.unload();
+});
+
+add_task(async function dnr_ignores_navigation_to_restrictedDomains() {
+ let extension = await startDNRExtension();
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://restricted/?blockme"
+ );
+ await contentPage.spawn([], () => {
+ const { document } = content;
+ Assert.equal(document.URL, "http://restricted/?blockme", "Same URL");
+ Assert.equal(document.body.textContent, "response from server", "body");
+ });
+ await contentPage.close();
+ await extension.unload();
+});
+
+add_task(async function dnr_ignores_css_import_at_restrictedDomains() {
+ // CSS @import have triggeringPrincipal set to the URL of the stylesheet,
+ // and the loadingPrincipal set to the web page. To verify that access is
+ // indeed being restricted as expected, confirm that none of the stylesheet
+ // requests are blocked by the DNR extension.
+ let extension = await startDNRExtension();
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://restricted/"
+ );
+ await contentPage.spawn([], async () => {
+ // Use wrappedJSObject so that all operations below are with the principal
+ // of the content instead of the system principal (from this ContentTask).
+ const { document } = content.wrappedJSObject;
+ const style = document.createElement("link");
+ style.rel = "stylesheet";
+ // Note: intentionally not at "http://restricted/" because we want to check
+ // that subresources from a restricted domain are ignored by DNR..
+ style.href = "http://example.com/style_with_import.css";
+ style.crossOrigin = "anonymous";
+ await new Promise(resolve => {
+ info("Waiting for style sheet to load...");
+ style.onload = resolve;
+ document.head.append(style);
+ });
+ const importRule = style.sheet.cssRules[0];
+ Assert.equal(
+ importRule?.cssText,
+ `@import url("http://example.com/imported.css");`,
+ "Not blocked by DNR: Loaded style_with_import.css"
+ );
+ // Waiving Xrays here because we cannot read cssRules despite CORS because
+ // that is not implemented for child stylesheets (loaded via @import):
+ // https://searchfox.org/mozilla-central/rev/55d5c4b9dffe5e59eb6b019c1a930ec9ada47e10/layout/style/Loader.cpp#2052
+ const importedStylesheet = Cu.unwaiveXrays(importRule.styleSheet);
+ Assert.equal(
+ importedStylesheet.cssRules[0]?.cssText,
+ "imported_stylesheet_here { }",
+ "Not blocked by DNR: Loaded import.css"
+ );
+ });
+ await contentPage.close();
+ await extension.unload();
+});
+
+add_task(
+ { pref_set: [["extensions.dnr.feedback", true]] },
+ async function testMatchOutcome_and_restrictedDomains() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ await browser.declarativeNetRequest.updateSessionRules({
+ addRules: [{ id: 1, condition: {}, action: { type: "block" } }],
+ });
+ const type = "other"; // matches the condition of the above rule.
+
+ browser.test.assertDeepEq(
+ { matchedRules: [] },
+ await browser.declarativeNetRequest.testMatchOutcome({
+ url: "http://restricted/",
+ type,
+ }),
+ "testMatchOutcome ignores restricted url"
+ );
+ browser.test.assertDeepEq(
+ { matchedRules: [] },
+ await browser.declarativeNetRequest.testMatchOutcome({
+ url: "http://example.com/",
+ initiator: "http://restricted/",
+ type,
+ }),
+ "testMatchOutcome ignores restricted initiator"
+ );
+ browser.test.sendMessage("done");
+ },
+ manifest: {
+ manifest_version: 3,
+ permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"],
+ },
+ });
+ await extension.startup();
+ await extension.awaitMessage("done");
+ await extension.unload();
+ }
+);
+
+add_task(
+ // In debug builds, any attempt to load data:-URLs in the parent process
+ // results in a crash or at least a logged error, via
+ // nsContentSecurityUtils::ValidateScriptFilename.
+ //
+ // Xpcshell tests use loadFrameScript with data:-URLs, which could trigger the
+ // above error / crash, when a page is loaded in the parent process.
+ // For example, the following error message (or crash),
+ // "InternalError: unsafe filename: data:text/javascript,//"
+ // "Hit MOZ_CRASH(Blocking a script load data:text/javascript,// from file (None))"
+ // is triggered because of the loadFrameScript call at
+ // https://searchfox.org/mozilla-central/rev/11dbac7f64f509b78037465cbb4427ed71f8b565/testing/modules/XPCShellContentUtils.sys.mjs#308
+ //
+ // This test loads about:logo in the parent, because nsAboutRedirector.cpp
+ // registers about:logo without nsIAboutModule::URI_MUST_LOAD_IN_CHILD.
+ // When about:logo is loaded, the ContentPage test helper also triggers the
+ // above error/crash at:
+ // https://searchfox.org/mozilla-central/rev/11dbac7f64f509b78037465cbb4427ed71f8b565/testing/modules/XPCShellContentUtils.sys.mjs#224,242
+ //
+ // Opt out of the check/crash from ValidateScriptFilename:
+ { pref_set: [["security.allow_parent_unrestricted_js_loads", true]] },
+ async function non_system_request_with_disallowed_scheme() {
+ let extension = await startDNRExtension();
+ Assert.equal(
+ await (await fetch("http://example.com/")).text(),
+ "response from server",
+ "DNR should not block requests from system principal"
+ );
+ // We are loading about:logo for the following reasons:
+ // - It is a regular content principal, NOT a system principal.
+ // - It is an about:-URL that resolves across all builds (part of toolkit/).
+ // - It does not have a CSP (intentional - bug 1587417). That enables us to
+ // send a fetch() request below.
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "about:logo?blockme"
+ );
+ await contentPage.spawn([], async () => {
+ const { document } = content;
+ // To make sure that the test does not pass trivially, we verify that it
+ // is not the system principal (because dnr_ignores_system_requests
+ // already tests that) and not a null principal (because that translates
+ // to a void "initiator" in the DNR API, which would pass access checks).
+ Assert.ok(
+ document.nodePrincipal.isContentPrincipal,
+ "about:logo has content principal (not system or NullPrincipal))"
+ );
+ Assert.equal(document.URL, "about:logo?blockme", "Same URL");
+ Assert.equal(
+ await (await content.fetch("http://example.com/")).text(),
+ "response from server",
+ "fetch() at about:logo not blocked by DNR"
+ );
+ });
+ await contentPage.close();
+ await extension.unload();
+ }
+);
+
+add_task(
+ { pref_set: [["extensions.dnr.feedback", true]] },
+ async function testMatchOutcome_non_system_request_with_disallowed_scheme() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ await browser.declarativeNetRequest.updateSessionRules({
+ addRules: [{ id: 1, condition: {}, action: { type: "block" } }],
+ });
+ const type = "other"; // matches the condition of the above rule.
+
+ browser.test.assertDeepEq(
+ { matchedRules: [] },
+ await browser.declarativeNetRequest.testMatchOutcome({
+ url: "about:logo",
+ type,
+ }),
+ "testMatchOutcome ignores url with disallowed schema"
+ );
+ browser.test.assertDeepEq(
+ { matchedRules: [] },
+ await browser.declarativeNetRequest.testMatchOutcome({
+ url: "http://example.com/",
+ initiator: "about:logo",
+ type,
+ }),
+ "testMatchOutcome ignores initiator with disallowed schema"
+ );
+ browser.test.sendMessage("done");
+ },
+ manifest: {
+ manifest_version: 3,
+ permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"],
+ },
+ });
+ await extension.startup();
+ await extension.awaitMessage("done");
+ await extension.unload();
+ }
+);