1. ホーム
  2. javascript

[解決済み] コンテンツスクリプトを使用して、ページコンテキスト変数と関数にアクセスする

2022-03-17 02:39:06

質問

Chromeの拡張機能の作り方を勉強中です。YouTubeのイベントをキャッチするためのものを開発し始めたところです。YouTube flash playerと一緒に使いたいのですが(後でHTML5にも対応させようと思っています)。

manifest.json。

{
    "name": "MyExtension",
    "version": "1.0",
    "description": "Gotta catch Youtube events!",
    "permissions": ["tabs", "http://*/*"],
    "content_scripts" : [{
        "matches" : [ "www.youtube.com/*"],
        "js" : ["myScript.js"]
    }]
}

myScript.jsです。

function state() { console.log("State Changed!"); }
var player = document.getElementById("movie_player");
player.addEventListener("onStateChange", "state");
console.log("Started!");

問題は、コンソールから "Started!"。 がない。 "State Changed!" YouTubeの動画を再生/一時停止する際に

このコードをコンソールに入れると、動作しました。何が間違っているのでしょうか?

どうすればいいですか?

根本的な原因
コンテンツスクリプトは 隔離された世界("isolated world")。 環境である。

解決策 :
への アクセス の関数/変数を使用するには、DOMを使用してページ自体にコードを注入する必要があります。同じことは、もしあなたが 公開する 関数や変数をページコンテキスト(あなたの場合、それは state() メソッド) を使用します。

  • ページスクリプトとの通信が必要な場合の注意事項。
    DOMを使用する CustomEvent ハンドラを使用します。例 1 , 2 そして スリー .

  • 注意事項 chrome APIがページスクリプトで必要です。
    から chrome.* APIはページスクリプトでは使用できないので、コンテンツスクリプトで使用し、DOMメッセージングでページスクリプトに結果を送る必要があります(上記の注釈を参照ください)。

安全に関する警告 :
ページが組み込みのプロトタイプを再定義したり、拡張/フックすることがあるので、ページが互換性のない方法でそれを行った場合、公開したコードが失敗することがあります。もし、公開するコードが安全な環境で実行されることを確認したいのであれば、a) コンテンツスクリプトを "run_at": "document_start" を使用するか、または b) 空の iframe を使用してオリジナルのネイティブビルトインを抽出します。 . ただし document_start を使用する必要がある場合があります。 DOMContentLoaded イベントを公開することで、DOM を待機させることができます。

目次

  • 方法1:別のファイルを注入する - ManifestV3との互換性
  • 方法2:埋め込みコードを注入する
  • 方法2b: 関数を使用する
  • 方法3:インラインイベントを使用する
  • 注入されたコード内の動的な値

方法1:別のファイルをインジェクトする

現時点での唯一のManifestV3対応方法です。 特に、コードがたくさんある場合に有効です。コードをエクステンションの中のファイル、例えば script.js . そして、それを コンテンツスクリプト のようにします。

var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);

js ファイルは web_accessible_resources :

  • ManifestV2用manifest.jsonの例

    "web_accessible_resources": ["script.js"],
    
    
  • ManifestV3用manifest.jsonの例

    "web_accessible_resources": [{
      "resources": ["script.js"],
      "matches": ["<all_urls>"]
    }]
    
    

そうでない場合は、コンソールに以下のエラーが表示されます。

chrome-extension://[EXTENSIONID]/script.js の読み込みを拒否しています。拡張機能外のページで読み込むには、リソースを web_accessible_resources マニフェスト キーにリストアップする必要があります。

方法2:埋め込みコードを挿入する

この方法は、小さなコードを素早く実行したい場合に有効です。(参照 Chromeの拡張機能でfacebookのホットキーを無効にする方法は? ).

var actualCode = `// Code here.
// If you want to use a variable, use $ and curly braces.
// For example, to use a fixed random number:
var someFixedRandomValue = ${ Math.random() };
// NOTE: Do not insert unsafe variables in this way, see below
// at "Dynamic values in the injected code"
`;

var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();

テンプレート・リテラル は、Chrome 41以上でのみサポートされています。Chrome 40〜で拡張機能を動作させたい場合は、使用してください。

var actualCode = ['/* Code here. Example: */' + 'alert(0);',
                  '// Beware! This array have to be joined',
                  '// using a newline. Otherwise, missing semicolons',
                  '// or single-line comments (//) will mess up your',
                  '// code ----->'].join('\n');

方法2b: 関数を使用する

大きなコードの塊の場合、文字列を引用することは不可能です。配列を使う代わりに、関数を使い、文字列化することができる。

var actualCode = '(' + function() {
    // All code is executed in a local scope.
    // For example, the following does NOT overwrite the global `alert` method
    var alert = null;
    // To overwrite a global variable, prefix `window`:
    window.alert = null;
} + ')();';
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();

この方法がうまくいくのは + 演算子で文字列に変換し、関数ですべてのオブジェクトを文字列に変換します。もし、このコードを複数回使うつもりなら、コードの繰り返しを避けるために関数を作成するのが賢明です。実装は次のようになります。

function injectScript(func) {
    var actualCode = '(' + func + ')();'
    ...
}
injectScript(function() {
   alert("Injected script");
});

注意:関数はシリアライズされるため、元のスコープとバインドされたプロパティはすべて失われます。

var scriptToInject = function() {
    console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output:  "undefined"

方法3:インラインイベントを利用する

あるコードをすぐに実行したいとき、たとえば <head> 要素が作成されます。これを実現するには <script> というタグを textContent (方法2/2bを参照)。

代替案です。 ただし、推奨しません は、インラインイベントを使用することです。なぜなら、もしページがインラインスクリプトを禁止するコンテンツセキュリティポリシーを定義しているならば、インラインイベントリスナーはブロックされるからです。一方、拡張機能によって注入されたインラインスクリプトは、そのまま実行されます。 それでもインラインイベントを使用したい場合は、この方法を使用します。

var actualCode = '// Some code example \n' + 
                 'console.log(document.documentElement.outerHTML);';

document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');

注意: このメソッドでは、他のグローバルイベントリスナーが reset イベントが発生します。もしあれば、他のグローバルイベントの一つを選ぶこともできます。JavaScriptコンソールを開き(F12)、次のように入力するだけです。 document.documentElement.on そして、利用可能なイベントを選択する。

注入されたコード内の動的な値

時には、インジェクションされた関数に任意の変数を渡す必要がある場合があります。例えば

var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
    alert(GREETING + NAME);
};

このコードを注入するには、変数を無名関数への引数として渡す必要があります。必ず正しく実装してください! 次のようになります。 ではない が働きます。

var scriptToInject = function (GREETING, NAME) { ... };
var actualCode = '(' + scriptToInject + ')(' + GREETING + ',' + NAME + ')';
// The previous will work for numbers and booleans, but not strings.
// To see why, have a look at the resulting string:
var actualCode = "(function(GREETING, NAME) {...})(Hi, I'm ,Rob)";
//                                                 ^^^^^^^^ ^^^ No string literals!

解決策としては JSON.stringify を渡す前に、引数を指定します。例

var actualCode = '(' + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';

変数が多い場合は JSON.stringify のように、読みやすさを向上させるために一度だけ使用します。

...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';