拡張機能からページコンテキストで定義された変数と関数にアクセスする 質問する

拡張機能からページコンテキストで定義された変数と関数にアクセスする 質問する

拡張機能で youtube.com のプレーヤーを制御したい:

マニフェスト.json:

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

スクリプト:

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

問題は、コンソールに「開始しました!」と表示されるものの、YouTube ビデオを再生/一時停止したときに「状態が変更されました!」と表示されないことです。

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

ベストアンサー1

根本的な原因:
コンテンツスクリプトがISOLATED"世界"つまり、"world" (ページ コンテキスト) 内の JS 関数や変数にアクセスできず、あなたの場合のメソッドMAINのように、独自の JS 要素を公開することもできません。state()

解決策:
以下に示す方法を使用して、ページの JS コンテキスト ( MAIN「world」) にコードを挿入します。

chromeAPIの使用について
:  •externally_connectableメッセージング<all_urls>許可されたChrome 107以降
 •CustomEvent通常のコンテンツ スクリプトを使用したメッセージングについては、次の段落を参照してください。

通常のコンテンツスクリプトを使用したメッセージングの場合:示されているように
使用しますCustomEventここ、 またはここ、 またはここつまり、挿入されたスクリプトは通常のコンテンツ スクリプトにメッセージを送信し、通常のコンテンツ スクリプトはchrome.storageまたは を呼び出しchrome.runtime.sendMessage、その結果を別の CustomEvent メッセージを介して挿入されたスクリプトに送信します。 は使用しないでください。window.postMessageデータによって、特定の形式のメッセージを期待するリスナーを持つサイトが破損する可能性があります。

注意!
ページは組み込みのプロトタイプまたはグローバルを再定義し、プライベート通信からデータを盗み出したり、挿入されたコードを失敗させたりする可能性があります。これを防ぐのは複雑です (Tampermonkey または Violentmonkey の「vault」を参照)。そのため、受信したすべてのデータを必ず確認してください。

目次

では、何が最善でしょうか? ManifestV3 では、コードを常に実行する必要がある場合は宣言型メソッド (#5) を使用し、chrome.scriptingポップアップやサービス ワーカーなどの拡張スクリプトからの条件付き挿入には (#4) を使用し、それ以外の場合はコンテンツ スクリプト ベースのメソッド (#1 および #3) を使用します。

  • コンテンツ スクリプトは挿入を制御します。

    • 方法 1: 別のファイルを挿入する - ManifestV3 互換
    • 方法 2: 埋め込みコードを挿入する - MV2
    • 方法 2b: 関数を使用する - MV2
    • 方法3: インラインイベントの使用 - ManifestV3互換
  • 拡張スクリプトはインジェクションを制御します (例: バックグラウンド サービス ワーカーまたはポップアップ スクリプト)。

    • 方法 4: executeScript のワールドを使用する - ManifestV3 のみ
  • 宣言的注入:

    • 方法 5: worldmanifest.json で使用する - ManifestV3 のみ、Chrome 111 以降
  • 挿入されたコード内の動的な値

方法 1: 別のファイルを挿入する (ManifestV3/MV2)

特にコードがたくさんある場合に適しています。コードを拡張機能内のファイル(例えば)に入れて、script.jsそれをコンテンツスクリプトこのような:

var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() { this.remove(); };
// see also "Dynamic values in the injected code" section in this answer
(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: 埋め込みコードを挿入する (MV2)

この方法は、小さなコードをすばやく実行したい場合に便利です。(参照: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: 関数を使用する (MV2)

大きなコードの場合、文字列を引用符で囲むのは現実的ではありません。配列を使用する代わりに、関数を使用して文字列化することができます。

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: インライン イベントを使用する (ManifestV3/MV2)

場合によっては、要素が作成される前に何らかのコードを実行するなど、何らかのコードをすぐに実行したいことがあります。これは、 のタグ<head>を挿入することで実行できます(方法 2/2b を参照)。<script>textContent

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

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使用可能なイベントのいずれかを選択します。

方法 4: chrome.scripting API を使用するworld(ManifestV3 のみ)

  • Chrome 95以降chrome.scripting.executeScriptworld: 'MAIN'
  • Chrome 102 以降でchrome.scripting.registerContentScriptsは、ページ スクリプトの早期実行world: 'MAIN'も保証されます。runAt: 'document_start'

他の方法とは異なり、これはコンテンツスクリプトではなく、バックグラウンドスクリプトまたはポップアップスクリプト用です。ドキュメンテーションそして

方法 5: worldmanifest.json で使用する (ManifestV3 のみ)

Chrome 111 以降では、manifest.json に宣言を追加して、デフォルト値を上書きできます"world": "MAIN"content_scriptsスクリプトISOLATEDはリストされた順序で実行されます。

  "content_scripts": [{
    "world": "MAIN",
    "js": ["page.js"],
    "matches": ["<all_urls>"],
    "run_at": "document_start"
  }, {
    "js": ["content.js"],
    "matches": ["<all_urls>"],
    "run_at": "document_start"
  }],

挿入されたコード内の動的な値 (MV2)

場合によっては、挿入された関数に任意の変数を渡す必要があります。例:

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変数が多数ある場合は、読みやすさを向上させるために、次のように 1 回だけ使用するとよいでしょう。

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

挿入されたコード内の動的な値 (ManifestV3)

  • 方法 1 を使用して、次の行を追加します。

    s.dataset.params = JSON.stringify({foo: 'bar'});
    

    すると、挿入された script.js はそれを読み取ることができます:

    (() => {
      const params = JSON.parse(document.currentScript.dataset.params);
      console.log('injected params', params);
    })();
    

    ページスクリプトからパラメータを隠すには、スクリプト要素を閉じたShadowDOM

  • メソッド 4 の executeScript にはargsパラメーターがありますが、registerContentScripts には現在パラメーターがありません (将来追加されることを期待しています)。

おすすめ記事