Injecting multiple scripts through executeScript in Google Chrome

后端 未结 5 2156
渐次进展
渐次进展 2020-12-04 22:25

I need to programmatically inject multiple script files (followed by a code snippet) into the current page from my Google Chrome extension. The chrome.tabs.executeScript met

相关标签:
5条回答
  • 2020-12-04 22:49

    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);
        });
    });
    
    0 讨论(0)
  • 2020-12-04 22:52

    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

    0 讨论(0)
  • 2020-12-04 23:02

    Given your answer, I expected synchronously injecting the scripts to cause problems (namely, I thought that the scripts might be loaded in the wrong order), but it works well for me.

    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
      });
    });
    

    This assumes you only want one callback at the end and don't need the results of each executed script.

    0 讨论(0)
  • 2020-12-04 23:08

    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();" }
        ])
    });
    
    0 讨论(0)
  • 2020-12-04 23:15

    This is mostly an updated answer (on the other answer) :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.
      }
    )
    
    

    Or return a promise.

    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.
    })
    
    
    0 讨论(0)
提交回复
热议问题