在Google Chrome中通过executeScript注入多个脚本

ecfdbz9o  于 2022-12-06  发布在  Go
关注(0)|答案(7)|浏览(353)

我需要通过编程方式将多个脚本文件(后跟一个代码片段)从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();" })
            })
        })
    })
});

然而,回调嵌套变得很难处理,有没有一种方法可以将其抽象化呢?

vdzxcuhz

vdzxcuhz1#

这是我提出的解决方案:

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

vngu2lb82#

从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

xtfmy6hx

xtfmy6hx3#

有趣的是,脚本是按顺序注入的,您不需要等待每个脚本都被注入。

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

这比手动等待要快得多。您可以先加载一个大型库(如d3.js),然后再加载一个小文件来验证它们是否按顺序加载。顺序仍然保持不变。

**注意:**不会捕获错误,但如果所有文件都存在,则不会发生这种情况.

我编写了一个小模块来进一步简化这个过程,包括正确的错误处理、Promise支持和Manifest v3中的scripting API:

executeScript({
    tabId: tab.id,
    files: ["jquery.js", "master.js", "helper.js"]
}).then(() => {
    // All scripts loaded
});
lrpiutwd

lrpiutwd4#

从Manifest v3开始,您可以使用承诺链和async/await:

承诺

MV 3为以下承诺提供一流的支持:现在许多流行的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();移到一个文件中并执行它。

dluptydi

dluptydi5#

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.

6qqygrtg

6qqygrtg6#

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.
})
j8yoct9x

j8yoct9x7#

使用v3:

chrome.action.onClicked.addListener((tab) => {
  console.log("entering");
  chrome.scripting
    .executeScript({
      target: { tabId: tab.id },
      files: [
        "scripts/jquery.min.js",
        "scripts/bootstrap.min.js",
        "scripts/script.js",
      ],
    })
    .then(() => {
      // All scripts loaded
      console.log("done");
    });
});

相关问题