gecko-dev/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js
Luca Greco 292c6a749e Bug 1512741 - Split userScripts xpcshell test and re-enable it on android debug build. r=robwu
This patch move the part of test_ext_userScripts.js that is testing the userScripts' export API helpers
(script.defineGlobals and script.export) into a separate test_ext_userScripts_exports.js test file.

Based on the logs from the intermittent failures of the test_ext_userScripts.js test file
on android debug builds, it seems that this xpcshell test was just timing out while exiting after all
the test tasks were actually completed.

The results got from pushing to try this patch seems to confirm that once the test tasks are splitted
over two test files, the intermittent failures are not being triggered anymore on the android debug
builds.

Differential Revision: https://phabricator.services.mozilla.com/D17374

--HG--
rename : toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js => toolkit/components/extensions/test/xpcshell/test_ext_userScripts_exports.js
extra : moz-landing-system : lando
2019-02-06 11:45:51 +00:00

587 lines
18 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() {
if (ExtensionTestUtils.remoteContentScripts) {
// 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.
function permissionObserver(subject, topic, data) {
if (topic == "webextension-optional-permission-prompt") {
let {resolve} = subject.wrappedJSObject;
resolve(true);
}
}
Services.obs.addObserver(permissionObserver, "webextension-optional-permission-prompt");
registerCleanupFunction(() => {
Services.obs.removeObserver(permissionObserver, "webextension-optional-permission-prompt");
});
// Turn on the userScripts API using the related pref.
Services.prefs.setBoolPref("extensions.webextensions.userScripts.enabled", true);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("extensions.webextensions.userScripts.enabled");
});
});
// 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.jsm
// 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, ExtensionTestUtils.remoteContentScripts ? {remote: true} : undefined);
let result = await contentPage.spawn(undefined, 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");
// If the tests is running with "remote content process" mode, test that the userScript
// are being correctly registered in newly created processes (received as part of the sharedData).
if (ExtensionTestUtils.remoteContentScripts) {
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(undefined, 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 contentPage2.close();
}
await contentPage.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,
},
});
await extension.startup();
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(pageUrl) {
for (let scriptMetadata of getTestCases(true)) {
await browser.userScripts.register({
js: [{file: "userscript.js"}],
runAt: "document_end",
allFrames: true,
matches: ["http://localhost/*/file_sample.html"],
scriptMetadata,
});
}
let f = document.createElement("iframe");
f.src = pageUrl;
document.body.append(f);
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})("${BASE_URL}/file_sample.html")`,
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");
await extension.awaitMessage("apiscript:done");
await extension.unload();
});