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;
|
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 {
|
this.scripting = class extends ExtensionAPI {
|
||||||
getAPI(context) {
|
getAPI(context) {
|
||||||
const { extension } = context;
|
const { extension } = context;
|
||||||
|
|
@ -20,19 +84,11 @@ this.scripting = class extends ExtensionAPI {
|
||||||
|
|
||||||
let tab = tabManager.get(tabId);
|
let tab = tabManager.get(tabId);
|
||||||
|
|
||||||
let executeScriptDetails = {
|
let executeDetails = {
|
||||||
code: null,
|
|
||||||
file: null,
|
|
||||||
runAt: "document_idle",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (details.files) {
|
|
||||||
// TODO bug 1736576: Support more than one file.
|
|
||||||
executeScriptDetails.file = details.files[0];
|
|
||||||
} else {
|
|
||||||
// Defined in `child/ext-scripting.js`.
|
// Defined in `child/ext-scripting.js`.
|
||||||
executeScriptDetails.code = details.codeToExecute;
|
code: details.codeToExecute,
|
||||||
}
|
files: details.files,
|
||||||
|
};
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
|
|
@ -42,9 +98,9 @@ this.scripting = class extends ExtensionAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const frameId of frameIds) {
|
for (const frameId of frameIds) {
|
||||||
|
const details = { ...executeDetails, frameId };
|
||||||
promises.push(
|
promises.push(
|
||||||
tab
|
execute(tab, context, details, "js", "executeScript")
|
||||||
.executeScript(context, { ...executeScriptDetails, frameId })
|
|
||||||
// We return `null` when the result value is falsey.
|
// We return `null` when the result value is falsey.
|
||||||
.then(results => ({ frameId, result: results[0] || null }))
|
.then(results => ({ frameId, result: results[0] || null }))
|
||||||
.catch(error => ({ frameId, result: null, error }))
|
.catch(error => ({ frameId, result: null, error }))
|
||||||
|
|
|
||||||
|
|
@ -808,6 +808,10 @@ class TabBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
options.wantReturnValue = true;
|
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);
|
return this.queryContent("Execute", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -685,6 +685,131 @@ add_task(async function test_executeScript_with_iframe_srcdoc() {
|
||||||
await extension.unload();
|
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>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue