forked from mirrors/gecko-dev
Bug 1736576 - Add support for multiple files in scripting.executeScript(). r=robwu
Instead of trying to make `_execute()` in `ext-tabs-base.js` accept options for both the Scripting and Tabs APIs, we introduced an `execute()` function in `parent/ext-scripting.js` that is similar to `_execute()` but specific to the Scripting API options. Differential Revision: https://phabricator.services.mozilla.com/D133872
This commit is contained in:
parent
0eb950d9a0
commit
a718b52a24
3 changed files with 199 additions and 14 deletions
|
|
@ -8,6 +8,70 @@
|
|||
|
||||
var { ExtensionError } = ExtensionUtils;
|
||||
|
||||
/**
|
||||
* Inserts a script in the given tab, and returns a promise which resolves when
|
||||
* the operation has completed.
|
||||
*
|
||||
* @param {TabBase} tab
|
||||
* The tab in which to perform the injection.
|
||||
* @param {BaseContext} context
|
||||
* The extension context for which to perform the injection.
|
||||
* @param {Object} details
|
||||
* The details object, specifying what to inject, where, and when.
|
||||
* Derived from the ScriptInjection type.
|
||||
* @param {string} kind
|
||||
* The kind of data being injected. Possible choices: "js".
|
||||
* @param {string} method
|
||||
* The name of the method which was called to trigger the injection.
|
||||
* Used to generate appropriate error messages on failure.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* Resolves to the result of the execution, once it has completed.
|
||||
*/
|
||||
const execute = (tab, context, details, kind, method) => {
|
||||
let options = {
|
||||
jsPaths: [],
|
||||
extensionId: context.extension.id,
|
||||
};
|
||||
|
||||
// TODO: Bug 1750765 - Add test coverage for this option.
|
||||
options.hasActiveTabPermission = tab.hasActiveTabPermission;
|
||||
options.matches = tab.extension.allowedOrigins.patterns.map(
|
||||
host => host.pattern
|
||||
);
|
||||
|
||||
if (details.code) {
|
||||
options[`${kind}Code`] = details.code;
|
||||
}
|
||||
|
||||
if (details.files) {
|
||||
for (const file of details.files) {
|
||||
let url = context.uri.resolve(file);
|
||||
if (!tab.extension.isExtensionURL(url)) {
|
||||
return Promise.reject({
|
||||
message: "Files to be injected must be within the extension",
|
||||
});
|
||||
}
|
||||
options[`${kind}Paths`].push(url);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Bug 1736574 - Add support for multiple frame IDs and `allFrames`.
|
||||
if (details.frameId) {
|
||||
options.frameID = details.frameId;
|
||||
}
|
||||
|
||||
options.runAt = "document_idle";
|
||||
options.wantReturnValue = true;
|
||||
|
||||
// TODO: Bug 1736579 - Configure options for CSS injection, e.g., `cssPaths`
|
||||
// and `cssOrigin`.
|
||||
|
||||
// This function is derived from `_execute()` in `parent/ext-tabs-base.js`,
|
||||
// make sure to keep both in sync when relevant.
|
||||
return tab.queryContent("Execute", options);
|
||||
};
|
||||
|
||||
this.scripting = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
const { extension } = context;
|
||||
|
|
@ -20,19 +84,11 @@ this.scripting = class extends ExtensionAPI {
|
|||
|
||||
let tab = tabManager.get(tabId);
|
||||
|
||||
let executeScriptDetails = {
|
||||
code: null,
|
||||
file: null,
|
||||
runAt: "document_idle",
|
||||
};
|
||||
|
||||
if (details.files) {
|
||||
// TODO bug 1736576: Support more than one file.
|
||||
executeScriptDetails.file = details.files[0];
|
||||
} else {
|
||||
let executeDetails = {
|
||||
// Defined in `child/ext-scripting.js`.
|
||||
executeScriptDetails.code = details.codeToExecute;
|
||||
}
|
||||
code: details.codeToExecute,
|
||||
files: details.files,
|
||||
};
|
||||
|
||||
const promises = [];
|
||||
|
||||
|
|
@ -42,9 +98,9 @@ this.scripting = class extends ExtensionAPI {
|
|||
}
|
||||
|
||||
for (const frameId of frameIds) {
|
||||
const details = { ...executeDetails, frameId };
|
||||
promises.push(
|
||||
tab
|
||||
.executeScript(context, { ...executeScriptDetails, frameId })
|
||||
execute(tab, context, details, "js", "executeScript")
|
||||
// We return `null` when the result value is falsey.
|
||||
.then(results => ({ frameId, result: results[0] || null }))
|
||||
.catch(error => ({ frameId, result: null, error }))
|
||||
|
|
|
|||
|
|
@ -808,6 +808,10 @@ class TabBase {
|
|||
}
|
||||
|
||||
options.wantReturnValue = true;
|
||||
|
||||
// The scripting API (defined in `parent/ext-scripting.js`) has its own
|
||||
// `execute()` function that calls `queryContent()` as well. Make sure to
|
||||
// keep both in sync when relevant.
|
||||
return this.queryContent("Execute", options);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -685,6 +685,131 @@ add_task(async function test_executeScript_with_iframe_srcdoc() {
|
|||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_executeScript_with_multiple_files() {
|
||||
let extension = makeExtension({
|
||||
async background() {
|
||||
const tabs = await browser.tabs.query({ active: true });
|
||||
|
||||
browser.test.assertEq(1, tabs.length, "expected 1 tab");
|
||||
|
||||
const results = await browser.scripting.executeScript({
|
||||
target: { tabId: tabs[0].id },
|
||||
files: ["1.js", "2.js"],
|
||||
});
|
||||
|
||||
browser.test.assertEq(
|
||||
1,
|
||||
results.length,
|
||||
"got expected number of results"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"value from 2.js",
|
||||
results[0].result,
|
||||
"got the expected result"
|
||||
);
|
||||
browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
|
||||
|
||||
browser.test.notifyPass("execute-script");
|
||||
},
|
||||
files: {
|
||||
"1.js": function() {
|
||||
return "value from 1.js";
|
||||
},
|
||||
"2.js": function() {
|
||||
return "value from 2.js";
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("execute-script");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_executeScript_with_multiple_files_and_an_error() {
|
||||
let tab = await AppTestDelegate.openNewForegroundTab(
|
||||
window,
|
||||
"https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
|
||||
true
|
||||
);
|
||||
|
||||
let extension = makeExtension({
|
||||
async background() {
|
||||
const tabs = await browser.tabs.query({ active: true });
|
||||
|
||||
browser.test.assertEq(1, tabs.length, "expected 1 tab");
|
||||
|
||||
const results = await browser.scripting.executeScript({
|
||||
target: { tabId: tabs[0].id },
|
||||
files: ["1.js", "2.js"],
|
||||
});
|
||||
|
||||
browser.test.assertEq(
|
||||
1,
|
||||
results.length,
|
||||
"got expected number of results"
|
||||
);
|
||||
browser.test.assertEq(null, results[0].result, "got the expected result");
|
||||
browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
|
||||
|
||||
browser.test.notifyPass("execute-script");
|
||||
},
|
||||
files: {
|
||||
"1.js": function() {
|
||||
throw new Error(`Thrown at ${location.pathname.split("/").pop()}`);
|
||||
},
|
||||
"2.js": function() {
|
||||
return "value from 2.js";
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
consoleMonitor.start([{ message: /Thrown at file_contains_iframe/ }]);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("execute-script");
|
||||
await extension.unload();
|
||||
|
||||
await consoleMonitor.finished();
|
||||
|
||||
await AppTestDelegate.removeTab(window, tab);
|
||||
});
|
||||
|
||||
add_task(async function test_executeScript_with_file_not_in_extension() {
|
||||
let tab = await AppTestDelegate.openNewForegroundTab(
|
||||
window,
|
||||
"https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
|
||||
true
|
||||
);
|
||||
|
||||
let extension = makeExtension({
|
||||
async background() {
|
||||
const tabs = await browser.tabs.query({ active: true });
|
||||
|
||||
browser.test.assertEq(1, tabs.length, "expected 1 tab");
|
||||
|
||||
await browser.scripting.executeScript({
|
||||
target: { tabId: tabs[0].id },
|
||||
files: ["https://example.com/script.js"],
|
||||
});
|
||||
|
||||
browser.test.notifyPass("execute-script");
|
||||
},
|
||||
});
|
||||
|
||||
consoleMonitor.start([{
|
||||
message: /Files to be injected must be within the extension/,
|
||||
}]);
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("execute-script");
|
||||
await extension.unload();
|
||||
|
||||
await consoleMonitor.finished();
|
||||
|
||||
await AppTestDelegate.removeTab(window, tab);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
Loading…
Reference in a new issue