web-dev-qa-db-ja.com

GoogleのexecuteScriptを使用して複数のスクリプトを挿入するChrome

programmatically inject 複数のスクリプトファイル(続いてコードスニペット)を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();" })
            })
        })
    })
});

ただし、コールバックのネストは扱いにくくなります。これを抽象化する方法はありますか?

24
Douglas

これは私の提案する解決策です:

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

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を使用する場合は、 オンラインコンパイラ を使用して上記のコードをES5にコンパイルできます。

GitHubでフォークしてください: chrome-script-execution

11
Ninh Pham

あなたの答えを考えると、スクリプトを同期的に挿入すると問題が発生すると予想していました(つまり、スクリプトが間違った順序でロードされている可能性があると思いました)が、それは私にとってはうまくいきます。

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

これは、最後にコールバックが1つだけ必要で、実行された各スクリプトの結果を必要としないことを前提としています。

3
willlma

おもしろいことに、スクリプトは順番に注入され、それぞれが注入されるのを待つ必要はありません。

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など)をロードしてから、小さなファイルをロードすることで、それらが順番にロードされていることを確認できます。注文は引き続き保持されます。

注:エラーはキャッチされませんが、すべてのファイルが存在する場合は、これが発生することはありません。


エラーをキャッチしたい場合は、Firefoxのbrowser.* AP​​Iを 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);
    });
});
1
fregante

これは主に更新された回答です(他の回答について):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.
})

0
Aytacworld