javascript 在谷歌浏览器中通过executeScript注入多个脚本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21535233/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 21:06:51  来源:igfitidea点击:

Injecting multiple scripts through executeScript in Google Chrome

javascriptgoogle-chromegoogle-chrome-extensioncontent-script

提问by Douglas

I need to programmatically injectmultiple script files (followed by a code snippet) into the current page from my Google Chrome extension. The chrome.tabs.executeScriptmethod allows for a single InjectDetailsobject (representing a script file or code snippet), as well as a callback function to be executed after the script. Current answerspropose nesting executeScriptcalls:

我需要通过我的 Google Chrome 扩展程序编程方式将多个脚本文件(后跟代码片段)注入当前页面。该chrome.tabs.executeScript方法允许单个InjectDetails对象(代表脚本文件或代码片段)以及在脚本之后执行的回调函数。当前的答案建议嵌套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();" })
            })
        })
    })
});

However, the callback nesting gets unwieldy. Is there a way of abstracting this?

然而,回调嵌套变得笨拙。有没有办法抽象这个?

回答by Douglas

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 InjectDetailsscripts can be specified as an array:

随后,InjectDetails脚本序列可以指定为一个数组:

chrome.browserAction.onClicked.addListener(function (tab) {
    executeScripts(null, [ 
        { file: "jquery.js" }, 
        { file: "master.js" },
        { file: "helper.js" },
        { code: "transformPage();" }
    ])
});

回答by Ninh Pham

From Chrome v32, it supports Promise. We should use it for making code clean.

从 Chrome v32 开始,它支持Promise。我们应该使用它来使代码干净。

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

ScriptExecutionsource:

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

If you would like to use ES5, you can use online compilerto compile above codes to ES5.

如果你想使用 ES5,你可以使用在线编译器将上述代码编译为 ES5。

Fork me on GitHub: chrome-script-execution

在 GitHub 上 fork 我:chrome-script-execution

回答by willlma

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.

这假设您最后只需要一个回调并且不需要每个执行脚本的结果。

回答by fregante

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.

这比手动等待每个人要快得多。您可以通过首先加载一个巨大的库(如d3.js)然后加载一个小文件来验证它们是否按顺序加载。订单仍将保留。

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

如果您想捕获错误,我建议将 Firefox 的browser.*API 与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);
    });
});

回答by Aytacworld

This is mostly an updated answer (on the other answer) :P

这主要是更新的答案(在另一个答案上):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.
})