Injecting multiple scripts through executeScript in Google Chrome
This is my proposed solution:
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}
Subsequently, the sequence of InjectDetails
scripts can be specified as an array:
chrome.browserAction.onClicked.addListener(function (tab) { executeScripts(null, [ { file: "jquery.js" }, { file: "master.js" }, { file: "helper.js" }, { code: "transformPage();" } ])});
From Chrome v32, it supports Promise. We should use it for making code clean.
Here is an example:
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
source:
(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;})()
If you would like to use ES5, you can use online compiler to compile above codes to ES5.
Fork me on GitHub: chrome-script-execution
Fun fact, the scripts are injected in order and you don't need to wait for each one to be injected.
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 });});
This is considerably faster than manually waiting for each one. You can verify that they are loaded in order by loading a huge library first (like d3.js
) and then loading a small file after. The order will still be preserved.
Note: errors aren't caught, but this should never happen if all files exist.
If you want to catch the errors, I'd suggest to use the Firefox’ browser.*
APIs with their Chrome polyfill
browser.browserAction.onClicked.addListener(tab => { Promise.all([ browser.tabs.executeScript(tab.id, { file: "jquery.js" }), browser.tabs.executeScript(tab.id, { file: "master.js" }), browser.tabs.executeScript(tab.id, { file: "helper.js" }), browser.tabs.executeScript(tab.id, { code: "transformPage();" }) ]).then(() => { console.log('All scripts definitely loaded') }, error => { console.error(error); });});