Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* -*- 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;
});