summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js620
1 files changed, 620 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js b/toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js
new file mode 100644
index 0000000000..ff2cc3c2ac
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js
@@ -0,0 +1,620 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { newURI } = Services.io;
+
+add_task(async function test_WebExtensionPolicy() {
+ const id = "foo@bar.baz";
+ const uuid = "ca9d3f23-125c-4b24-abfc-1ca2692b0610";
+
+ const baseURL = "file:///foo/";
+ const mozExtURL = `moz-extension://${uuid}/`;
+ const mozExtURI = newURI(mozExtURL);
+
+ let policy = new WebExtensionPolicy({
+ id,
+ mozExtensionHostname: uuid,
+ baseURL,
+
+ localizeCallback(str) {
+ return `<${str}>`;
+ },
+
+ allowedOrigins: new MatchPatternSet(["http://foo.bar/", "*://*.baz/"], {
+ ignorePath: true,
+ }),
+ permissions: ["<all_urls>"],
+ webAccessibleResources: [
+ {
+ resources: ["/foo/*", "/bar.baz"].map(glob => new MatchGlob(glob)),
+ },
+ ],
+ });
+
+ equal(policy.active, false, "Active attribute should initially be false");
+
+ // GetURL
+
+ equal(
+ policy.getURL(),
+ mozExtURL,
+ "getURL() should return the correct root URL"
+ );
+ equal(
+ policy.getURL("path/foo.html"),
+ `${mozExtURL}path/foo.html`,
+ "getURL(path) should return the correct URL"
+ );
+
+ // Permissions
+
+ deepEqual(
+ policy.permissions,
+ ["<all_urls>"],
+ "Initial permissions should be correct"
+ );
+
+ ok(
+ policy.hasPermission("<all_urls>"),
+ "hasPermission should match existing permission"
+ );
+ ok(
+ !policy.hasPermission("history"),
+ "hasPermission should not match nonexistent permission"
+ );
+
+ Assert.throws(
+ () => {
+ policy.permissions[0] = "foo";
+ },
+ TypeError,
+ "Permissions array should be frozen"
+ );
+
+ policy.permissions = ["history"];
+ deepEqual(
+ policy.permissions,
+ ["history"],
+ "Permissions should be updateable as a set"
+ );
+
+ ok(
+ policy.hasPermission("history"),
+ "hasPermission should match existing permission"
+ );
+ ok(
+ !policy.hasPermission("<all_urls>"),
+ "hasPermission should not match nonexistent permission"
+ );
+
+ // Origins
+
+ ok(
+ policy.canAccessURI(newURI("http://foo.bar/quux")),
+ "Should be able to access permitted URI"
+ );
+ ok(
+ policy.canAccessURI(newURI("https://x.baz/foo")),
+ "Should be able to access permitted URI"
+ );
+
+ ok(
+ !policy.canAccessURI(newURI("https://foo.bar/quux")),
+ "Should not be able to access non-permitted URI"
+ );
+
+ policy.allowedOrigins = new MatchPatternSet(["https://foo.bar/"], {
+ ignorePath: true,
+ });
+
+ ok(
+ policy.canAccessURI(newURI("https://foo.bar/quux")),
+ "Should be able to access updated permitted URI"
+ );
+ ok(
+ !policy.canAccessURI(newURI("https://x.baz/foo")),
+ "Should not be able to access removed permitted URI"
+ );
+
+ // Web-accessible resources
+
+ ok(
+ policy.isWebAccessiblePath("/foo/bar"),
+ "Web-accessible glob should be web-accessible"
+ );
+ ok(
+ policy.isWebAccessiblePath("/bar.baz"),
+ "Web-accessible path should be web-accessible"
+ );
+ ok(
+ !policy.isWebAccessiblePath("/bar.baz/quux"),
+ "Non-web-accessible path should not be web-accessible"
+ );
+
+ ok(
+ policy.sourceMayAccessPath(mozExtURI, "/bar.baz"),
+ "Web-accessible path should be web-accessible to self"
+ );
+
+ // Localization
+
+ equal(
+ policy.localize("foo"),
+ "<foo>",
+ "Localization callback should work as expected"
+ );
+
+ // Protocol and lookups.
+
+ let proto = Services.io
+ .getProtocolHandler("moz-extension", uuid)
+ .QueryInterface(Ci.nsISubstitutingProtocolHandler);
+
+ deepEqual(
+ WebExtensionPolicy.getActiveExtensions(),
+ [],
+ "Should have no active extensions"
+ );
+ equal(
+ WebExtensionPolicy.getByID(id),
+ null,
+ "ID lookup should not return extension when not active"
+ );
+ equal(
+ WebExtensionPolicy.getByHostname(uuid),
+ null,
+ "Hostname lookup should not return extension when not active"
+ );
+ Assert.throws(
+ () => proto.resolveURI(mozExtURI),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "URL should not resolve when not active"
+ );
+
+ policy.active = true;
+ equal(policy.active, true, "Active attribute should be updated");
+
+ let exts = WebExtensionPolicy.getActiveExtensions();
+ equal(exts.length, 1, "Should have one active extension");
+ equal(exts[0], policy, "Should have the correct active extension");
+
+ equal(
+ WebExtensionPolicy.getByID(id),
+ policy,
+ "ID lookup should return extension when active"
+ );
+ equal(
+ WebExtensionPolicy.getByHostname(uuid),
+ policy,
+ "Hostname lookup should return extension when active"
+ );
+
+ equal(
+ proto.resolveURI(mozExtURI),
+ baseURL,
+ "URL should resolve correctly while active"
+ );
+
+ policy.active = false;
+ equal(policy.active, false, "Active attribute should be updated");
+
+ deepEqual(
+ WebExtensionPolicy.getActiveExtensions(),
+ [],
+ "Should have no active extensions"
+ );
+ equal(
+ WebExtensionPolicy.getByID(id),
+ null,
+ "ID lookup should not return extension when not active"
+ );
+ equal(
+ WebExtensionPolicy.getByHostname(uuid),
+ null,
+ "Hostname lookup should not return extension when not active"
+ );
+ Assert.throws(
+ () => proto.resolveURI(mozExtURI),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "URL should not resolve when not active"
+ );
+
+ // Conflicting policies.
+
+ // This asserts in debug builds, so only test in non-debug builds.
+ if (!AppConstants.DEBUG) {
+ policy.active = true;
+
+ let attrs = [
+ { id, uuid },
+ { id, uuid: "d916886c-cfdf-482e-b7b1-d7f5b0facfa5" },
+ { id: "foo@quux", uuid },
+ ];
+
+ // eslint-disable-next-line no-shadow
+ for (let { id, uuid } of attrs) {
+ let policy2 = new WebExtensionPolicy({
+ id,
+ mozExtensionHostname: uuid,
+ baseURL: "file://bar/",
+
+ localizeCallback() {},
+
+ allowedOrigins: new MatchPatternSet([]),
+ });
+
+ Assert.throws(
+ () => {
+ policy2.active = true;
+ },
+ /NS_ERROR_UNEXPECTED/,
+ `Should not be able to activate conflicting policy: ${id} ${uuid}`
+ );
+ }
+
+ policy.active = false;
+ }
+});
+
+// mozExtensionHostname is normalized to lower case when using
+// policy.getURL whereas using policy.getByHostname does
+// not. Tests below will fail without case insensitive
+// comparisons in ExtensionPolicyService
+add_task(async function test_WebExtensionPolicy_case_sensitivity() {
+ const id = "policy-case@mochitest";
+ const uuid = "BAD93A23-125C-4B24-ABFC-1CA2692B0610";
+
+ const baseURL = "file:///foo/";
+ const mozExtURL = `moz-extension://${uuid}/`;
+ const mozExtURI = newURI(mozExtURL);
+
+ let policy = new WebExtensionPolicy({
+ id: id,
+ mozExtensionHostname: uuid,
+ baseURL,
+ localizeCallback() {},
+ allowedOrigins: new MatchPatternSet([]),
+ permissions: ["<all_urls>"],
+ });
+ policy.active = true;
+
+ equal(
+ WebExtensionPolicy.getByHostname(uuid)?.mozExtensionHostname,
+ policy.mozExtensionHostname,
+ "Hostname lookup should match policy"
+ );
+
+ equal(
+ WebExtensionPolicy.getByHostname(uuid.toLowerCase())?.mozExtensionHostname,
+ policy.mozExtensionHostname,
+ "Hostname lookup should match policy"
+ );
+
+ equal(policy.getURL(), mozExtURI.spec, "Urls should match policy");
+ ok(
+ policy.sourceMayAccessPath(mozExtURI, "/bar.baz"),
+ "Extension path should be accessible to self"
+ );
+
+ policy.active = false;
+});
+
+add_task(async function test_WebExtensionPolicy_V3() {
+ const id = "foo@bar.baz";
+ const uuid = "ca9d3f23-125c-4b24-abfc-1ca2692b0610";
+ const id2 = "foo-2@bar.baz";
+ const uuid2 = "89383c45-7db4-4999-83f7-f4cc246372cd";
+ const id3 = "foo-3@bar.baz";
+ const uuid3 = "56652231-D7E2-45D1-BDBD-BD3BFF80927E";
+
+ const baseURL = "file:///foo/";
+ const mozExtURL = `moz-extension://${uuid}/`;
+ const mozExtURI = newURI(mozExtURL);
+ const fooSite = newURI("http://foo.bar/");
+ const exampleSite = newURI("https://example.com/");
+
+ let policy = new WebExtensionPolicy({
+ id,
+ mozExtensionHostname: uuid,
+ baseURL,
+ manifestVersion: 3,
+
+ localizeCallback(str) {
+ return `<${str}>`;
+ },
+
+ allowedOrigins: new MatchPatternSet(["http://foo.bar/", "*://*.baz/"], {
+ ignorePath: true,
+ }),
+ permissions: ["<all_urls>"],
+ webAccessibleResources: [
+ {
+ resources: ["/foo/*", "/bar.baz"].map(glob => new MatchGlob(glob)),
+ matches: ["http://foo.bar/"],
+ extension_ids: [id3],
+ },
+ {
+ resources: ["/foo.bar.baz"].map(glob => new MatchGlob(glob)),
+ extension_ids: ["*"],
+ },
+ ],
+ });
+ policy.active = true;
+ equal(
+ WebExtensionPolicy.getByHostname(uuid),
+ policy,
+ "Hostname lookup should match policy"
+ );
+
+ let policy2 = new WebExtensionPolicy({
+ id: id2,
+ mozExtensionHostname: uuid2,
+ baseURL,
+ localizeCallback() {},
+ allowedOrigins: new MatchPatternSet([]),
+ permissions: ["<all_urls>"],
+ });
+ policy2.active = true;
+ equal(
+ WebExtensionPolicy.getByHostname(uuid2),
+ policy2,
+ "Hostname lookup should match policy"
+ );
+
+ let policy3 = new WebExtensionPolicy({
+ id: id3,
+ mozExtensionHostname: uuid3,
+ baseURL,
+ localizeCallback() {},
+ allowedOrigins: new MatchPatternSet([]),
+ permissions: ["<all_urls>"],
+ });
+ policy3.active = true;
+ equal(
+ WebExtensionPolicy.getByHostname(uuid3),
+ policy3,
+ "Hostname lookup should match policy"
+ );
+
+ ok(
+ policy.isWebAccessiblePath("/bar.baz"),
+ "Web-accessible path should be web-accessible"
+ );
+ ok(
+ !policy.isWebAccessiblePath("/bar.baz/quux"),
+ "Non-web-accessible path should not be web-accessible"
+ );
+ // Extension can always access itself
+ ok(
+ policy.sourceMayAccessPath(mozExtURI, "/bar.baz"),
+ "Web-accessible path should be accessible to self"
+ );
+ ok(
+ policy.sourceMayAccessPath(mozExtURI, "/foo.bar.baz"),
+ "Web-accessible path should be accessible to self"
+ );
+
+ ok(
+ !policy.sourceMayAccessPath(newURI(`https://${uuid}/`), "/bar.baz"),
+ "Web-accessible path should not be accessible due to scheme mismatch"
+ );
+
+ // non-matching site cannot access url
+ ok(
+ policy.sourceMayAccessPath(fooSite, "/bar.baz"),
+ "Web-accessible path should be accessible to foo.bar site"
+ );
+ ok(
+ !policy.sourceMayAccessPath(fooSite, "/foo.bar.baz"),
+ "Web-accessible path should not be accessible to foo.bar site"
+ );
+
+ // non-matching site cannot access url
+ ok(
+ !policy.sourceMayAccessPath(exampleSite, "/bar.baz"),
+ "Web-accessible path should not be accessible to example.com"
+ );
+ ok(
+ !policy.sourceMayAccessPath(exampleSite, "/foo.bar.baz"),
+ "Web-accessible path should not be accessible to example.com"
+ );
+
+ let extURI = newURI(policy2.getURL(""));
+ ok(
+ !policy.sourceMayAccessPath(extURI, "/bar.baz"),
+ "Web-accessible path should not be accessible to other extension"
+ );
+ ok(
+ policy.sourceMayAccessPath(extURI, "/foo.bar.baz"),
+ "Web-accessible path should be accessible to other extension"
+ );
+
+ extURI = newURI(policy3.getURL(""));
+ ok(
+ policy.sourceMayAccessPath(extURI, "/bar.baz"),
+ "Web-accessible path should be accessible to other extension"
+ );
+ ok(
+ policy.sourceMayAccessPath(extURI, "/foo.bar.baz"),
+ "Web-accessible path should be accessible to other extension"
+ );
+
+ policy.active = false;
+ policy2.active = false;
+ policy3.active = false;
+});
+
+add_task(async function test_WebExtensionPolicy_registerContentScripts() {
+ const id = "foo@bar.baz";
+ const uuid = "77a7b9d3-e73c-4cf3-97fb-1824868fe00f";
+
+ const id2 = "foo-2@bar.baz";
+ const uuid2 = "89383c45-7db4-4999-83f7-f4cc246372cd";
+
+ const baseURL = "file:///foo/";
+
+ const mozExtURL = `moz-extension://${uuid}/`;
+ const mozExtURL2 = `moz-extension://${uuid2}/`;
+
+ let policy = new WebExtensionPolicy({
+ id,
+ mozExtensionHostname: uuid,
+ baseURL,
+ localizeCallback() {},
+ allowedOrigins: new MatchPatternSet([]),
+ permissions: ["<all_urls>"],
+ });
+
+ let policy2 = new WebExtensionPolicy({
+ id: id2,
+ mozExtensionHostname: uuid2,
+ baseURL,
+ localizeCallback() {},
+ allowedOrigins: new MatchPatternSet([]),
+ permissions: ["<all_urls>"],
+ });
+
+ let script1 = new WebExtensionContentScript(policy, {
+ run_at: "document_end",
+ js: [`${mozExtURL}/registered-content-script.js`],
+ matches: new MatchPatternSet(["http://localhost/data/*"]),
+ });
+
+ let script2 = new WebExtensionContentScript(policy, {
+ run_at: "document_end",
+ css: [`${mozExtURL}/registered-content-style.css`],
+ matches: new MatchPatternSet(["http://localhost/data/*"]),
+ });
+
+ let script3 = new WebExtensionContentScript(policy2, {
+ run_at: "document_end",
+ css: [`${mozExtURL2}/registered-content-style.css`],
+ matches: new MatchPatternSet(["http://localhost/data/*"]),
+ });
+
+ deepEqual(
+ policy.contentScripts,
+ [],
+ "The policy contentScripts is initially empty"
+ );
+
+ policy.registerContentScript(script1);
+
+ deepEqual(
+ policy.contentScripts,
+ [script1],
+ "script1 has been added to the policy contentScripts"
+ );
+
+ Assert.throws(
+ () => policy.registerContentScript(script1),
+ e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE,
+ "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to register a script more than once"
+ );
+
+ Assert.throws(
+ () => policy.registerContentScript(script3),
+ e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE,
+ "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to register a script related to " +
+ "a different extension"
+ );
+
+ Assert.throws(
+ () => policy.unregisterContentScript(script3),
+ e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE,
+ "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to unregister a script related to " +
+ "a different extension"
+ );
+
+ deepEqual(
+ policy.contentScripts,
+ [script1],
+ "script1 has not been added twice"
+ );
+
+ policy.registerContentScript(script2);
+
+ deepEqual(
+ policy.contentScripts,
+ [script1, script2],
+ "script2 has the last item of the policy contentScripts array"
+ );
+
+ policy.unregisterContentScript(script1);
+
+ deepEqual(
+ policy.contentScripts,
+ [script2],
+ "script1 has been removed from the policy contentscripts"
+ );
+
+ Assert.throws(
+ () => policy.unregisterContentScript(script1),
+ e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE,
+ "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to unregister a script more than once"
+ );
+
+ deepEqual(
+ policy.contentScripts,
+ [script2],
+ "the policy contentscripts is unmodified when unregistering an unknown contentScript"
+ );
+
+ policy.unregisterContentScript(script2);
+
+ deepEqual(
+ policy.contentScripts,
+ [],
+ "script2 has been removed from the policy contentScripts"
+ );
+});
+
+add_task(async function test_WebExtensionPolicy_static_themes_resources() {
+ const uuid = "0e7ae607-b5b3-4204-9838-c2138c14bc3c";
+ const mozExtURL = `moz-extension://${uuid}/`;
+ const mozExtURI = newURI(mozExtURL);
+
+ let policy = new WebExtensionPolicy({
+ id: "test-extension@mochitest",
+ mozExtensionHostname: uuid,
+ baseURL: "file:///foo/foo/",
+ localizeCallback() {},
+ allowedOrigins: new MatchPatternSet([]),
+ permissions: [],
+ });
+ policy.active = true;
+
+ let staticThemePolicy = new WebExtensionPolicy({
+ id: "statictheme@bar.baz",
+ mozExtensionHostname: "164d05dc-b45b-4731-aefc-7c1691bae9a4",
+ baseURL: "file:///static_theme/",
+ type: "theme",
+ allowedOrigins: new MatchPatternSet([]),
+ localizeCallback() {},
+ });
+
+ staticThemePolicy.active = true;
+
+ ok(
+ staticThemePolicy.sourceMayAccessPath(mozExtURI, "/someresource.ext"),
+ "Active extensions should be allowed to access the static themes resources"
+ );
+
+ policy.active = false;
+
+ ok(
+ !staticThemePolicy.sourceMayAccessPath(mozExtURI, "/someresource.ext"),
+ "Disabled extensions should be disallowed the static themes resources"
+ );
+
+ ok(
+ !staticThemePolicy.sourceMayAccessPath(
+ Services.io.newURI("http://example.com"),
+ "/someresource.ext"
+ ),
+ "Web content should be disallowed the static themes resources"
+ );
+
+ staticThemePolicy.active = false;
+});