Firefox拡張機能でコンテンツスクリプトの注入に失敗する

現象

特定のウェブサイトでコンテンツスクリプトに設定したスクリプトが動作していない。

ウェブ全体を対象にしたFirefox用の拡張機能でmanifest.jsoncontent_scripts.matches=<all_urls>などを指定したにも関わらず特定ウェブサイトで挿入予定のコンテンツスクリプトが動作していない。

具体的には、TwitterでFirefox拡張機能のReject Service WorkerのServiceWorker登録関数置き換え処理が動作していなかった。

※同様のコードで実装しているChrome用の拡張機能は意図したを動作する。

原因

拡張機能がサイトのContent-Security-Policy設定の影響を受けて、コンテンツスクリプトで挿入したスクリプトコードがコンテンツセキュリティポリシーの設定に従って動作しなかったため、問題現象が発生していた。

Content-Security-Policy(CSP、コンテンツセキュリティポリシー)では、特定のスクリプト以外の実行を禁止することが可能です。それにより、サイトに直接挿入されたスクリプトの動作を禁止することが可能です。(具体的な例として、サイトでのブックマークレットの実行を禁止することができます)

問題発生時の拡張機能の実装

Chromeと共通コードで実装するため、次の様なコードで実装されていました。

// ページ内スクリプトの実行
const runPageScript = function(text) {
  var script = document.createElement('script');
  script.textContent = text;
  (document.head || document.documentElement).appendChild(script);
  script.remove();
};
runPageScript(scriptCode);

拡張機能の通常処理をコンテンツスクリプト内で実行して、ページのスクリプトに影響のあるコードをページ内のスクリプトタグに記述してページに挿入することで実行します。このページにスクリプトタグを挿入する行為が今回の問題の原因になります。(これだけ見ると、なぜChromeで問題現象が発生しないのかの方が不思議です。うまくやってるのかな?)

対策

Firefoxからページのスクリプトに影響のあるコードは、exportFunction関数を使用してページ内スクリプトを上書きして対応します。もちろんですがexportFunction関数は、Chrome側には存在しないため、ChromeとFirefoxコードを分割して対応します。

対応例exportFunction(
    置き換える関数, 
    置き換え元のスコープ, 
    {defineAs: '置き換えるプロパティ名'});
exportFunction(
    func, 
    ServiceWorkerContainer.prototype, 
    {defineAs: 'register'});