709 lines
20 KiB
JavaScript
709 lines
20 KiB
JavaScript
"use strict";
|
|
|
|
const PROCESS_COUNT_PREF = "dom.ipc.processCount";
|
|
|
|
const { createAppInfo } = AddonTestUtils;
|
|
|
|
AddonTestUtils.init(this);
|
|
|
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49");
|
|
|
|
const server = createHttpServer();
|
|
server.registerDirectory("/data/", do_get_file("data"));
|
|
|
|
const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
|
|
|
|
add_task(async function setup_test_environment() {
|
|
// Start with one content process so that we can increase the number
|
|
// later and test the behavior of a fresh content process.
|
|
Services.prefs.setIntPref(PROCESS_COUNT_PREF, 1);
|
|
|
|
// Grant the optional permissions requested, without prompting.
|
|
Services.prefs.setBoolPref(
|
|
"extensions.webextOptionalPermissionPrompts",
|
|
false
|
|
);
|
|
registerCleanupFunction(() => {
|
|
Services.prefs.clearUserPref("extensions.webextOptionalPermissionPrompts");
|
|
});
|
|
});
|
|
|
|
// Test that there is no userScripts API namespace when the manifest doesn't include a user_scripts
|
|
// property.
|
|
add_task(async function test_userScripts_manifest_property_required() {
|
|
function background() {
|
|
browser.test.assertEq(
|
|
undefined,
|
|
browser.userScripts,
|
|
"userScripts API namespace should be undefined in the extension page"
|
|
);
|
|
browser.test.sendMessage("background-page:done");
|
|
}
|
|
|
|
async function contentScript() {
|
|
browser.test.assertEq(
|
|
undefined,
|
|
browser.userScripts,
|
|
"userScripts API namespace should be undefined in the content script"
|
|
);
|
|
browser.test.sendMessage("content-script:done");
|
|
}
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
background,
|
|
manifest: {
|
|
permissions: ["http://*/*/file_sample.html"],
|
|
content_scripts: [
|
|
{
|
|
matches: ["http://*/*/file_sample.html"],
|
|
js: ["content_script.js"],
|
|
run_at: "document_start",
|
|
},
|
|
],
|
|
},
|
|
files: {
|
|
"content_script.js": contentScript,
|
|
},
|
|
});
|
|
|
|
await extension.startup();
|
|
await extension.awaitMessage("background-page:done");
|
|
|
|
let url = `${BASE_URL}/file_sample.html`;
|
|
let contentPage = await ExtensionTestUtils.loadContentPage(url);
|
|
|
|
await extension.awaitMessage("content-script:done");
|
|
|
|
await extension.unload();
|
|
await contentPage.close();
|
|
});
|
|
|
|
// Test that userScripts can only matches origins that are subsumed by the extension permissions,
|
|
// and that more origins can be allowed by requesting an optional permission.
|
|
add_task(async function test_userScripts_matches_denied() {
|
|
async function background() {
|
|
async function registerUserScriptWithMatches(matches) {
|
|
const scripts = await browser.userScripts.register({
|
|
js: [{ code: "" }],
|
|
matches,
|
|
});
|
|
await scripts.unregister();
|
|
}
|
|
|
|
// These matches are supposed to be denied until the extension has been granted the
|
|
// <all_urls> origin permission.
|
|
const testMatches = [
|
|
"<all_urls>",
|
|
"file://*/*",
|
|
"https://localhost/*",
|
|
"http://example.com/*",
|
|
];
|
|
|
|
browser.test.onMessage.addListener(async msg => {
|
|
if (msg === "test-denied-matches") {
|
|
for (let testMatch of testMatches) {
|
|
await browser.test.assertRejects(
|
|
registerUserScriptWithMatches([testMatch]),
|
|
/Permission denied to register a user script for/,
|
|
"Got the expected rejection when the extension permission does not subsume the userScript matches"
|
|
);
|
|
}
|
|
} else if (msg === "grant-all-urls") {
|
|
await browser.permissions.request({ origins: ["<all_urls>"] });
|
|
} else if (msg === "test-allowed-matches") {
|
|
for (let testMatch of testMatches) {
|
|
try {
|
|
await registerUserScriptWithMatches([testMatch]);
|
|
} catch (err) {
|
|
browser.test.fail(
|
|
`Unexpected rejection ${err} on matching ${JSON.stringify(
|
|
testMatch
|
|
)}`
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
browser.test.fail(`Received an unexpected ${msg} test message`);
|
|
}
|
|
|
|
browser.test.sendMessage(`${msg}:done`);
|
|
});
|
|
|
|
browser.test.sendMessage("background-ready");
|
|
}
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
manifest: {
|
|
permissions: ["http://localhost/*"],
|
|
optional_permissions: ["<all_urls>"],
|
|
user_scripts: {},
|
|
},
|
|
background,
|
|
});
|
|
|
|
await extension.startup();
|
|
|
|
await extension.awaitMessage("background-ready");
|
|
|
|
// Test that the matches not subsumed by the extension permissions are being denied.
|
|
extension.sendMessage("test-denied-matches");
|
|
await extension.awaitMessage("test-denied-matches:done");
|
|
|
|
// Grant the optional <all_urls> permission.
|
|
await withHandlingUserInput(extension, async () => {
|
|
extension.sendMessage("grant-all-urls");
|
|
await extension.awaitMessage("grant-all-urls:done");
|
|
});
|
|
|
|
// Test that all the matches are now subsumed by the extension permissions.
|
|
extension.sendMessage("test-allowed-matches");
|
|
await extension.awaitMessage("test-allowed-matches:done");
|
|
|
|
await extension.unload();
|
|
});
|
|
|
|
// Test that userScripts sandboxes:
|
|
// - can be registered/unregistered from an extension page (and they are registered on both new and
|
|
// existing processes).
|
|
// - have no WebExtensions APIs available
|
|
// - are able to access the target window and document
|
|
add_task(async function test_userScripts_no_webext_apis() {
|
|
async function background() {
|
|
const matches = ["http://localhost/*/file_sample.html*"];
|
|
|
|
const sharedCode = {
|
|
code: 'console.log("js code shared by multiple userScripts");',
|
|
};
|
|
|
|
const userScriptOptions = {
|
|
js: [
|
|
sharedCode,
|
|
{
|
|
code: `
|
|
window.addEventListener("load", () => {
|
|
const webextAPINamespaces = this.browser ? Object.keys(this.browser) : undefined;
|
|
document.body.innerHTML = "userScript loaded - " + JSON.stringify(webextAPINamespaces);
|
|
}, {once: true});
|
|
`,
|
|
},
|
|
],
|
|
runAt: "document_start",
|
|
matches,
|
|
scriptMetadata: {
|
|
name: "test-user-script",
|
|
arrayProperty: ["el1"],
|
|
objectProperty: { nestedProp: "nestedValue" },
|
|
nullProperty: null,
|
|
},
|
|
};
|
|
|
|
let script = await browser.userScripts.register(userScriptOptions);
|
|
|
|
// Unregister and then register the same js code again, to verify that the last registered
|
|
// userScript doesn't get assigned a revoked blob url (otherwise Extensioncontent.sys.mjs
|
|
// ScriptCache raises an error because it fails to compile the revoked blob url and the user
|
|
// script will never be loaded).
|
|
script.unregister();
|
|
script = await browser.userScripts.register(userScriptOptions);
|
|
|
|
browser.test.onMessage.addListener(async msg => {
|
|
if (msg !== "register-new-script") {
|
|
return;
|
|
}
|
|
|
|
await script.unregister();
|
|
await browser.userScripts.register({
|
|
...userScriptOptions,
|
|
scriptMetadata: { name: "test-new-script" },
|
|
js: [
|
|
sharedCode,
|
|
{
|
|
code: `
|
|
window.addEventListener("load", () => {
|
|
const webextAPINamespaces = this.browser ? Object.keys(this.browser) : undefined;
|
|
document.body.innerHTML = "new userScript loaded - " + JSON.stringify(webextAPINamespaces);
|
|
}, {once: true});
|
|
`,
|
|
},
|
|
],
|
|
});
|
|
|
|
browser.test.sendMessage("script-registered");
|
|
});
|
|
|
|
const scriptToRemove = await browser.userScripts.register({
|
|
js: [
|
|
sharedCode,
|
|
{
|
|
code: `
|
|
window.addEventListener("load", () => {
|
|
document.body.innerHTML = "unexpected unregistered userScript loaded";
|
|
}, {once: true});
|
|
`,
|
|
},
|
|
],
|
|
runAt: "document_start",
|
|
matches,
|
|
scriptMetadata: {
|
|
name: "user-script-to-remove",
|
|
},
|
|
});
|
|
|
|
browser.test.assertTrue(
|
|
"unregister" in script,
|
|
"Got an unregister method on the userScript API object"
|
|
);
|
|
|
|
// Remove the last registered user script.
|
|
await scriptToRemove.unregister();
|
|
|
|
browser.test.sendMessage("background-ready");
|
|
}
|
|
|
|
let extensionData = {
|
|
manifest: {
|
|
permissions: ["http://localhost/*/file_sample.html"],
|
|
user_scripts: {},
|
|
},
|
|
background,
|
|
};
|
|
|
|
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
|
|
|
await extension.startup();
|
|
|
|
await extension.awaitMessage("background-ready");
|
|
|
|
let url = `${BASE_URL}/file_sample.html?testpage=1`;
|
|
let contentPage = await ExtensionTestUtils.loadContentPage(url);
|
|
let result = await contentPage.spawn([], async () => {
|
|
return {
|
|
textContent: this.content.document.body.textContent,
|
|
url: this.content.location.href,
|
|
readyState: this.content.document.readyState,
|
|
};
|
|
});
|
|
Assert.deepEqual(
|
|
result,
|
|
{
|
|
textContent: "userScript loaded - undefined",
|
|
url,
|
|
readyState: "complete",
|
|
},
|
|
"The userScript executed on the expected url and no access to the WebExtensions APIs"
|
|
);
|
|
|
|
info("Test content script are correctly created on a newly created process");
|
|
|
|
await extension.sendMessage("register-new-script");
|
|
await extension.awaitMessage("script-registered");
|
|
|
|
// Update the process count preference, so that we can test that the newly registered user script
|
|
// is propagated as expected into the newly created process.
|
|
Services.prefs.setIntPref(PROCESS_COUNT_PREF, 2);
|
|
|
|
const url2 = `${BASE_URL}/file_sample.html?testpage=2`;
|
|
let contentPage2 = await ExtensionTestUtils.loadContentPage(url2, {
|
|
remote: true,
|
|
});
|
|
let result2 = await contentPage2.spawn([], async () => {
|
|
return {
|
|
textContent: this.content.document.body.textContent,
|
|
url: this.content.location.href,
|
|
readyState: this.content.document.readyState,
|
|
};
|
|
});
|
|
Assert.deepEqual(
|
|
result2,
|
|
{
|
|
textContent: "new userScript loaded - undefined",
|
|
url: url2,
|
|
readyState: "complete",
|
|
},
|
|
"The userScript executed on the expected url and no access to the WebExtensions APIs"
|
|
);
|
|
|
|
await contentPage.close();
|
|
|
|
await contentPage2.close();
|
|
|
|
await extension.unload();
|
|
});
|
|
|
|
// This test verify that a cached script is still able to catch the document
|
|
// while it is still loading (when we do not block the document parsing as
|
|
// we do for a non cached script).
|
|
add_task(async function test_cached_userScript_on_document_start() {
|
|
function apiScript() {
|
|
browser.userScripts.onBeforeScript.addListener(script => {
|
|
script.defineGlobals({
|
|
sendTestMessage(name, params) {
|
|
return browser.test.sendMessage(name, params);
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
async function background() {
|
|
function userScript() {
|
|
this.sendTestMessage("user-script-loaded", {
|
|
url: window.location.href,
|
|
documentReadyState: document.readyState,
|
|
});
|
|
}
|
|
|
|
await browser.userScripts.register({
|
|
js: [
|
|
{
|
|
code: `(${userScript})();`,
|
|
},
|
|
],
|
|
runAt: "document_start",
|
|
matches: ["http://localhost/*/file_sample.html"],
|
|
});
|
|
|
|
browser.test.sendMessage("user-script-registered");
|
|
}
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
manifest: {
|
|
permissions: ["http://localhost/*/file_sample.html"],
|
|
user_scripts: {
|
|
api_script: "api-script.js",
|
|
// The following is an unexpected manifest property, that we expect to be ignored and
|
|
// to not prevent the test extension from being installed and run as expected.
|
|
unexpected_manifest_key: "test-unexpected-key",
|
|
},
|
|
},
|
|
background,
|
|
files: {
|
|
"api-script.js": apiScript,
|
|
},
|
|
});
|
|
|
|
ExtensionTestUtils.failOnSchemaWarnings(false);
|
|
await extension.startup();
|
|
ExtensionTestUtils.failOnSchemaWarnings(true);
|
|
await extension.awaitMessage("user-script-registered");
|
|
|
|
let url = `${BASE_URL}/file_sample.html`;
|
|
let contentPage = await ExtensionTestUtils.loadContentPage(url);
|
|
|
|
let msg = await extension.awaitMessage("user-script-loaded");
|
|
Assert.deepEqual(
|
|
msg,
|
|
{
|
|
url,
|
|
documentReadyState: "loading",
|
|
},
|
|
"Got the expected url and document.readyState from a non cached user script"
|
|
);
|
|
|
|
// Reload the page and check that the cached content script is still able to
|
|
// run on document_start.
|
|
await contentPage.loadURL(url);
|
|
|
|
let msgFromCached = await extension.awaitMessage("user-script-loaded");
|
|
Assert.deepEqual(
|
|
msgFromCached,
|
|
{
|
|
url,
|
|
documentReadyState: "loading",
|
|
},
|
|
"Got the expected url and document.readyState from a cached user script"
|
|
);
|
|
|
|
await contentPage.close();
|
|
await extension.unload();
|
|
});
|
|
|
|
add_task(async function test_userScripts_pref_disabled() {
|
|
async function run_userScript_on_pref_disabled_test() {
|
|
async function background() {
|
|
let promise = (async () => {
|
|
await browser.userScripts.register({
|
|
js: [
|
|
{
|
|
code: "throw new Error('This userScripts should not be registered')",
|
|
},
|
|
],
|
|
runAt: "document_start",
|
|
matches: ["<all_urls>"],
|
|
});
|
|
})();
|
|
|
|
await browser.test.assertRejects(
|
|
promise,
|
|
/userScripts APIs are currently experimental/,
|
|
"Got the expected error from userScripts.register when the userScripts API is disabled"
|
|
);
|
|
|
|
browser.test.sendMessage("background-page:done");
|
|
}
|
|
|
|
async function contentScript() {
|
|
let promise = (async () => {
|
|
browser.userScripts.onBeforeScript.addListener(() => {});
|
|
})();
|
|
await browser.test.assertRejects(
|
|
promise,
|
|
/userScripts APIs are currently experimental/,
|
|
"Got the expected error from userScripts.onBeforeScript when the userScripts API is disabled"
|
|
);
|
|
|
|
browser.test.sendMessage("content-script:done");
|
|
}
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
background,
|
|
manifest: {
|
|
permissions: ["http://*/*/file_sample.html"],
|
|
user_scripts: { api_script: "" },
|
|
content_scripts: [
|
|
{
|
|
matches: ["http://*/*/file_sample.html"],
|
|
js: ["content_script.js"],
|
|
run_at: "document_start",
|
|
},
|
|
],
|
|
},
|
|
files: {
|
|
"content_script.js": contentScript,
|
|
},
|
|
});
|
|
|
|
await extension.startup();
|
|
|
|
await extension.awaitMessage("background-page:done");
|
|
|
|
let url = `${BASE_URL}/file_sample.html`;
|
|
let contentPage = await ExtensionTestUtils.loadContentPage(url);
|
|
|
|
await extension.awaitMessage("content-script:done");
|
|
|
|
await extension.unload();
|
|
await contentPage.close();
|
|
}
|
|
|
|
await runWithPrefs(
|
|
[["extensions.webextensions.userScripts.enabled", false]],
|
|
run_userScript_on_pref_disabled_test
|
|
);
|
|
});
|
|
|
|
// This test verify that userScripts.onBeforeScript API Event is not available without
|
|
// a "user_scripts.api_script" property in the manifest.
|
|
add_task(async function test_user_script_api_script_required() {
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
manifest: {
|
|
content_scripts: [
|
|
{
|
|
matches: ["http://localhost/*/file_sample.html"],
|
|
js: ["content_script.js"],
|
|
run_at: "document_start",
|
|
},
|
|
],
|
|
user_scripts: {},
|
|
},
|
|
files: {
|
|
"content_script.js": function () {
|
|
browser.test.assertEq(
|
|
undefined,
|
|
browser.userScripts && browser.userScripts.onBeforeScript,
|
|
"Got an undefined onBeforeScript property as expected"
|
|
);
|
|
browser.test.sendMessage("no-onBeforeScript:done");
|
|
},
|
|
},
|
|
});
|
|
|
|
await extension.startup();
|
|
|
|
let url = `${BASE_URL}/file_sample.html`;
|
|
let contentPage = await ExtensionTestUtils.loadContentPage(url);
|
|
|
|
await extension.awaitMessage("no-onBeforeScript:done");
|
|
|
|
await extension.unload();
|
|
await contentPage.close();
|
|
});
|
|
|
|
add_task(async function test_scriptMetaData() {
|
|
function getTestCases(isUserScriptsRegister) {
|
|
return [
|
|
// When scriptMetadata is not set (or undefined), it is treated as if it were null.
|
|
// In the API script, the metadata is then expected to be null.
|
|
isUserScriptsRegister ? undefined : null,
|
|
|
|
// Falsey
|
|
null,
|
|
"",
|
|
false,
|
|
0,
|
|
|
|
// Truthy
|
|
true,
|
|
1,
|
|
"non-empty string",
|
|
|
|
// Objects
|
|
["some array with value"],
|
|
{ "some object": "with value" },
|
|
];
|
|
}
|
|
|
|
async function background() {
|
|
for (let scriptMetadata of getTestCases(true)) {
|
|
await browser.userScripts.register({
|
|
js: [{ file: "userscript.js" }],
|
|
runAt: "document_end",
|
|
matches: ["http://localhost/*/file_sample.html"],
|
|
scriptMetadata,
|
|
});
|
|
}
|
|
|
|
browser.test.sendMessage("background-page:done");
|
|
}
|
|
|
|
function apiScript() {
|
|
let testCases = getTestCases(false);
|
|
let i = 0;
|
|
|
|
browser.userScripts.onBeforeScript.addListener(script => {
|
|
script.defineGlobals({
|
|
checkMetadata() {
|
|
let expectation = testCases[i];
|
|
let metadata = script.metadata;
|
|
if (typeof expectation === "object" && expectation !== null) {
|
|
// Non-primitive values cannot be compared with assertEq,
|
|
// so serialize both and just verify that they are equal.
|
|
expectation = JSON.stringify(expectation);
|
|
metadata = JSON.stringify(script.metadata);
|
|
}
|
|
|
|
browser.test.assertEq(
|
|
expectation,
|
|
metadata,
|
|
`Expected metadata at call ${i}`
|
|
);
|
|
if (++i === testCases.length) {
|
|
browser.test.sendMessage("apiscript:done");
|
|
}
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
background: `${getTestCases};(${background})()`,
|
|
manifest: {
|
|
permissions: ["http://*/*/file_sample.html"],
|
|
user_scripts: {
|
|
api_script: "apiscript.js",
|
|
},
|
|
},
|
|
files: {
|
|
"apiscript.js": `${getTestCases};(${apiScript})()`,
|
|
"userscript.js": "checkMetadata();",
|
|
},
|
|
});
|
|
|
|
await extension.startup();
|
|
|
|
await extension.awaitMessage("background-page:done");
|
|
|
|
const pageUrl = `${BASE_URL}/file_sample.html`;
|
|
info(`Load content page: ${pageUrl}`);
|
|
const page = await ExtensionTestUtils.loadContentPage(pageUrl);
|
|
|
|
await extension.awaitMessage("apiscript:done");
|
|
|
|
await page.close();
|
|
|
|
await extension.unload();
|
|
});
|
|
|
|
add_task(async function test_userScriptOptions_js_property_required() {
|
|
function background() {
|
|
const userScriptOptions = {
|
|
runAt: "document_start",
|
|
matches: ["http://*/*/file_sample.html"],
|
|
};
|
|
|
|
browser.test.assertThrows(
|
|
() => browser.userScripts.register(userScriptOptions),
|
|
/Type error for parameter userScriptOptions \(Property \"js\" is required\)/,
|
|
"Got the expected error from userScripts.register when js property is missing"
|
|
);
|
|
|
|
browser.test.sendMessage("done");
|
|
}
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
background,
|
|
manifest: {
|
|
permissions: ["http://*/*/file_sample.html"],
|
|
user_scripts: {},
|
|
},
|
|
});
|
|
|
|
await extension.startup();
|
|
await extension.awaitMessage("done");
|
|
await extension.unload();
|
|
});
|
|
|
|
add_task(async function test_userScripts_are_unregistered_on_unload() {
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
manifest: {
|
|
permissions: ["http://*/*/file_sample.html"],
|
|
user_scripts: {
|
|
api_script: "api_script.js",
|
|
},
|
|
},
|
|
files: {
|
|
"userscript.js": "",
|
|
"extpage.html": `<!DOCTYPE html><script src="extpage.js"></script>`,
|
|
"extpage.js": async function extPage() {
|
|
await browser.userScripts.register({
|
|
js: [{ file: "userscript.js" }],
|
|
matches: ["http://localhost/*/file_sample.html"],
|
|
});
|
|
|
|
browser.test.sendMessage("user-script-registered");
|
|
},
|
|
},
|
|
});
|
|
|
|
await extension.startup();
|
|
|
|
equal(
|
|
// In order to read the `registeredContentScripts` map, we need to access
|
|
// the extension embedded in the `ExtensionWrapper` first.
|
|
extension.extension.registeredContentScripts.size,
|
|
0,
|
|
"no user scripts registered yet"
|
|
);
|
|
|
|
const url = `moz-extension://${extension.uuid}/extpage.html`;
|
|
info(`loading extension page: ${url}`);
|
|
const page = await ExtensionTestUtils.loadContentPage(url);
|
|
|
|
info("waiting for the user script to be registered");
|
|
await extension.awaitMessage("user-script-registered");
|
|
|
|
equal(
|
|
extension.extension.registeredContentScripts.size,
|
|
1,
|
|
"got registered user scripts in the extension content scripts map"
|
|
);
|
|
|
|
await page.close();
|
|
|
|
equal(
|
|
extension.extension.registeredContentScripts.size,
|
|
0,
|
|
"user scripts unregistered from the extension content scripts map"
|
|
);
|
|
|
|
await extension.unload();
|
|
});
|