我需要将programmatically inject多个脚本文件(后跟代码段)从我的Google Chrome扩展程序中添加到当前页面中。 chrome.tabs.executeScript
方法允许单个InjectDetails
对象(表示脚本文件或代码片段),以及在脚本之后执行的回调函数。 Current answers建议嵌套executeScript
来电:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, { file: "jquery.js" }, function() {
chrome.tabs.executeScript(null, { file: "master.js" }, function() {
chrome.tabs.executeScript(null, { file: "helper.js" }, function() {
chrome.tabs.executeScript(null, { code: "transformPage();" })
})
})
})
});
但是,回调嵌套变得难以处理。有没有办法抽象出来?
答案 0 :(得分:35)
这是我提议的解决方案:
function executeScripts(tabId, injectDetailsArray)
{
function createCallback(tabId, injectDetails, innerCallback) {
return function () {
chrome.tabs.executeScript(tabId, injectDetails, innerCallback);
};
}
var callback = null;
for (var i = injectDetailsArray.length - 1; i >= 0; --i)
callback = createCallback(tabId, injectDetailsArray[i], callback);
if (callback !== null)
callback(); // execute outermost function
}
随后,可以将InjectDetails
脚本的序列指定为数组:
chrome.browserAction.onClicked.addListener(function (tab) {
executeScripts(null, [
{ file: "jquery.js" },
{ file: "master.js" },
{ file: "helper.js" },
{ code: "transformPage();" }
])
});
答案 1 :(得分:9)
从Chrome v32开始,它支持Promise。我们应该用它来清理代码。
以下是一个例子:
new ScriptExecution(tab.id)
.executeScripts("js/jquery.js", "js/script.js")
.then(s => s.executeCodes('console.log("executes code...")'))
.then(s => s.injectCss("css/style.css"))
.then(s => console.log('done'));
ScriptExecution
来源:
(function() {
function ScriptExecution(tabId) {
this.tabId = tabId;
}
ScriptExecution.prototype.executeScripts = function(fileArray) {
fileArray = Array.prototype.slice.call(arguments); // ES6: Array.from(arguments)
return Promise.all(fileArray.map(file => exeScript(this.tabId, file))).then(() => this); // 'this' will be use at next chain
};
ScriptExecution.prototype.executeCodes = function(fileArray) {
fileArray = Array.prototype.slice.call(arguments);
return Promise.all(fileArray.map(code => exeCodes(this.tabId, code))).then(() => this);
};
ScriptExecution.prototype.injectCss = function(fileArray) {
fileArray = Array.prototype.slice.call(arguments);
return Promise.all(fileArray.map(file => exeCss(this.tabId, file))).then(() => this);
};
function promiseTo(fn, tabId, info) {
return new Promise(resolve => {
fn.call(chrome.tabs, tabId, info, x => resolve());
});
}
function exeScript(tabId, path) {
let info = { file : path, runAt: 'document_end' };
return promiseTo(chrome.tabs.executeScript, tabId, info);
}
function exeCodes(tabId, code) {
let info = { code : code, runAt: 'document_end' };
return promiseTo(chrome.tabs.executeScript, tabId, info);
}
function exeCss(tabId, path) {
let info = { file : path, runAt: 'document_end' };
return promiseTo(chrome.tabs.insertCSS, tabId, info);
}
window.ScriptExecution = ScriptExecution;
})()
如果您想使用ES5,可以使用online compiler将上述代码编译为ES5。
在GitHub上问我:chrome-script-execution
答案 2 :(得分:2)
鉴于你的答案,我期望同步注入脚本以引起问题(即,我认为脚本可能以错误的顺序加载),但它对我来说效果很好。
var scripts = [
'first.js',
'middle.js',
'last.js'
];
scripts.forEach(function(script) {
chrome.tabs.executeScript(null, { file: script }, function(resp) {
if (script!=='last.js') return;
// Your callback code here
});
});
这假设您最后只需要一次回调,并且不需要每个已执行脚本的结果。
答案 3 :(得分:1)
从 Manifest v3 开始,您可以使用承诺链和 async/await:
<块引用>MV3 为 Promise 提供了一流的支持:现在许多流行的 API 都支持 Promise,我们最终将支持所有适当方法的 Promise。
您可以使用承诺链以及 async/await。 [...]
以下应该可以工作。
chrome.browserAction.onClicked.addListener(async (tab) => {
await chrome.scripting.executeScript({ files: ["jquery.js"] });
await chrome.scripting.executeScript({ files: ["master.js"] });
await chrome.scripting.executeScript({ files: ["helper.js"] });
// await chrome.tabs.executeScript({ code: "transformPage();" });
});
请注意,尽管有参数名称,files
必须只指定一个文件。
请注意,您不能再执行任意代码,因此最好将 transformPage();
移动到文件中并执行它。
答案 4 :(得分:0)
这主要是更新的答案(关于另一个答案):P
const executeScripts = (tabId, scripts, finalCallback) => {
try {
if (scripts.length && scripts.length > 0) {
const execute = (index = 0) => {
chrome.tabs.executeScript(tabId, scripts[index], () => {
const newIndex = index + 1;
if (scripts[newIndex]) {
execute(newIndex);
} else {
finalCallback();
}
});
}
execute();
} else {
throw new Error('scripts(array) undefined or empty');
}
} catch (err) {
console.log(err);
}
}
executeScripts(
null,
[
{ file: "jquery.js" },
{ file: "master.js" },
{ file: "helper.js" },
{ code: "transformPage();" }
],
() => {
// Do whatever you want to do, after the last script is executed.
}
)
或返回承诺。
const executeScripts = (tabId, scripts) => {
return new Promise((resolve, reject) => {
try {
if (scripts.length && scripts.length > 0) {
const execute = (index = 0) => {
chrome.tabs.executeScript(tabId, scripts[index], () => {
const newIndex = index + 1;
if (scripts[newIndex]) {
execute(newIndex);
} else {
resolve();
}
});
}
execute();
} else {
throw new Error('scripts(array) undefined or empty');
}
} catch (err) {
reject(err);
}
});
};
executeScripts(
null,
[
{ file: "jquery.js" },
{ file: "master.js" },
{ file: "helper.js" },
{ code: "transformPage();" }
]
).then(() => {
// Do whatever you want to do, after the last script is executed.
})
答案 5 :(得分:0)
有趣的是,脚本是按顺序注入的,您不必等待每个脚本都被注入。
ONLY_ME:
这比手动等待每个要快得多。您可以通过先加载一个巨大的库(例如chrome.browserAction.onClicked.addListener(tab => {
chrome.tabs.executeScript(tab.id, { file: "jquery.js" });
chrome.tabs.executeScript(tab.id, { file: "master.js" });
chrome.tabs.executeScript(tab.id, { file: "helper.js" });
chrome.tabs.executeScript(tab.id, { code: "transformPage();" }, () => {
// All scripts loaded
});
});
)然后再加载一个小文件来验证它们是否已按顺序加载。订单仍将保留。
注意:不会捕获错误,但是如果所有文件都存在,则永远不会发生。
如果您想捕获错误,建议您将Firefox的d3.js
API与their Chrome polyfill一起使用
browser.*