Chrome が導入されて以来externally_connectable
、これは Chrome で非常に簡単に行うことができます。まず、manifest.json
ファイルで許可されたドメインを指定します。
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
chrome.runtime.sendMessage
ページからメッセージを送信するために使用します。
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
// ...
});
chrome.runtime.onMessageExternal
最後に、バックグラウンド ページで次のようにリッスンします。
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
// verify `sender.url`, read `request` object, reply with `sednResponse(...)`...
});
サポートにアクセスできない場合externally_connectable
、元の回答は次のとおりです。
ここで説明する原則 (Web ページ スクリプト インジェクション、実行時間の長いバックグラウンド スクリプト、メッセージ パッシング) は、事実上すべてのブラウザー拡張フレームワークに適用できますが、Chrome 中心の観点から回答します。
大まかに言えば、やりたいことはすべての Web ページにコンテンツ スクリプトを挿入することです。これにより、Web ページにアクセスできる API が追加されます。サイトが API を呼び出すと、API はコンテンツ スクリプトをトリガーして、非同期コールバックを介してバックグラウンド ページにメッセージを送信したり、結果をコンテンツ スクリプトに送り返したりします。
ここでの主な問題は、Web ページに「挿入」されたコンテンツ スクリプトが、ページの JavaScript実行環境を直接変更できないことです。これらは DOM を共有するため、イベントとDOM 構造への変更はコンテンツ スクリプトと Web ページの間で共有されますが、関数と変数は共有されません。例:
DOM 操作:コンテンツ スクリプトが<div>
要素をページに追加すると、期待どおりに機能します。コンテンツ スクリプトとページの両方に新しい が表示されます<div>
。
イベント:コンテンツ スクリプトが要素のクリックなどのイベント リスナーを設定すると、イベントが発生したときにリスナーが正常に起動します。ページがコンテンツ スクリプトから起動されたカスタム イベントのリスナーを設定すると、コンテンツ スクリプトがそれらのイベントを起動したときに、それらのイベントが正常に受信されます。
関数:コンテンツ スクリプトが新しいグローバル関数を定義する場合foo()
(新しい API を設定するときに試す可能性があります)。ページの環境ではなく、コンテンツ スクリプトの実行環境にのみ存在するため、ページは を表示または実行できません。foo
foo
では、適切な API を設定するにはどうすればよいでしょうか。答えは多くのステップで得られます。
低レベルでは、API をイベントベースにします。Web ページは を使用してカスタム DOM イベントを発生させdispatchEvent
、コンテンツ スクリプトは を使用してそれらをリッスンしaddEventListener
、受信時にアクションを実行します。これは、ウェブページがデータを保存するための拡張機能を持つために使用できる単純なイベントベースのストレージ API です。
content_script.js (拡張機能内):
// an object used to store things passed in from the API
internalStorage = {};
// listen for myStoreEvent fired from the page with key/value pair data
document.addEventListener('myStoreEvent', function(event) {
var dataFromPage = event.detail;
internalStorage[dataFromPage.key] = dataFromPage.value
});
イベントベースの API を使用した非拡張 Web ページ:
function sendDataToExtension(key, value) {
var dataObj = {"key":key, "value":value};
var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj});
document.dispatchEvent(storeEvent);
}
sendDataToExtension("hello", "world");
ご覧のとおり、通常の Web ページは、DOM を共有しているため、コンテンツ スクリプトが認識して反応できるイベントを発生させています。CustomEvent
イベントには、コンストラクターに追加されたデータが添付されています。ここでの私の例は非常に単純です。コンテンツ スクリプトがページからデータを取得すると、コンテンツ スクリプトでさらに多くのことができることは明らかです (さらに処理するためにバックグラウンド ページに渡す可能性が高い)。
しかし、これは戦いの半分にすぎません。上記の私の例では、通常の Web ページはsendDataToExtension
それ自体を作成する必要がありました。カスタム イベントの作成と起動は非常に冗長です (私のコードは 3 行しかかからず、比較的短いものです)。API を使用するためだけに、難解なイベント起動コードをサイトに強制的に記述させたくありません。解決策はちょっと厄介なハックです<script>
。メイン ページの実行環境にイベント起動コードを追加するタグを共有 DOM に追加します。
content_script.jsの内部:
// inject a script from the extension's files
// into the execution environment of the main page
var s = document.createElement('script');
s.src = chrome.extension.getURL("myapi.js");
document.documentElement.appendChild(s);
で定義されている関数はすべてmyapi.js
、メイン ページからアクセスできるようになります。( を使用している場合は、マニフェストの のリストに"manifest_version":2
含める必要があります)。myapi.js
web_accessible_resources
myapi.js:
function sendDataToExtension(key, value) {
var dataObj = {"key":key, "value":value};
var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj});
document.dispatchEvent(storeEvent);
}
これで、プレーンな Web ページは次のように簡単に実行できます。
sendDataToExtension("hello", "world");
API プロセスにはもう 1 つmyapi.js
問題があります。それは、読み込み時にスクリプトが正確に利用できないということです。代わりに、ページの読み込み時間の後に読み込まれます。したがって、プレーン Web ページは、いつ API を安全に呼び出すことができるかを知る必要があります。myapi.js
これは、ページがリッスンする「API Ready」イベントを発生させることで解決できます。
myapi.js:
function sendDataToExtension(key, value) {
// as above
}
// since this script is running, myapi.js has loaded, so let the page know
var customAPILoaded = new CustomEvent('customAPILoaded');
document.dispatchEvent(customAPILoaded);
API を使用したプレーンな Web ページ:
document.addEventListener('customAPILoaded', function() {
sendDataToExtension("hello", "world");
// all API interaction goes in here, now that the API is loaded...
});
ロード時にスクリプトが使用可能になるという問題に対する別の解決策はrun_at
、マニフェストでコンテンツ スクリプトのプロパティを次の"document_start"
ように設定することです。
マニフェスト.json:
"content_scripts": [
{
"matches": ["https://example.com/*"],
"js": [
"myapi.js"
],
"run_at": "document_start"
}
],
ドキュメントからの抜粋:
「document_start」の場合、css からのファイルの後、他の DOM の構築または他のスクリプトの実行前にファイルが挿入されます。
一部のコンテンツ スクリプトでは、「API ロード」イベントよりも適切で手間がかかりません。
結果をページに送り返すには、非同期コールバック関数を提供する必要があります。イベントの発生/リッスンは本質的に非同期であるため、API から結果を同期的に返す方法はありません (つまり、コンテンツ スクリプトが API 要求でイベントを取得する前に、サイト側の API 関数が終了します)。
myapi.js:
function getDataFromExtension(key, callback) {
var reqId = Math.random().toString(); // unique ID for this request
var dataObj = {"key":key, "reqId":reqId};
var fetchEvent = new CustomEvent('myFetchEvent', {"detail":dataObj});
document.dispatchEvent(fetchEvent);
// get ready for a reply from the content script
document.addEventListener('fetchResponse', function respListener(event) {
var data = event.detail;
// check if this response is for this request
if(data.reqId == reqId) {
callback(data.value);
document.removeEventListener('fetchResponse', respListener);
}
}
}
content_script.js (拡張機能内):
// listen for myFetchEvent fired from the page with key
// then fire a fetchResponse event with the reply
document.addEventListener('myStoreEvent', function(event) {
var dataFromPage = event.detail;
var responseData = {"value":internalStorage[dataFromPage.key], "reqId":data.reqId};
var fetchResponse = new CustomEvent('fetchResponse', {"detail":responseData});
document.dispatchEvent(fetchResponse);
});
通常の Web ページ:
document.addEventListener('customAPILoaded', function() {
getDataFromExtension("hello", function(val) {
alert("extension says " + val);
});
});
一度に複数のreqId
リクエストを送信して、間違ったレスポンスを読み取らないようにする場合に必要です。
そして、それがすべてだと思います!そのため、他の拡張機能もリスナーをイベントにバインドして、ページが API をどのように使用しているかを盗聴できることを考えると、気弱な人向けではなく、価値がない可能性もあります。学校のプロジェクトで概念実証用の暗号化 API を作成した (その後、それに関連する主要なセキュリティの落とし穴を学んだ) ため、私はこれらすべてを知っているだけです。
要約すると、コンテンツ スクリプトは、通常の Web ページからのカスタム イベントをリッスンできます。また、スクリプトは、Web ページがこれらのイベントを起動しやすくする関数を含むスクリプト ファイルを挿入することもできます。コンテンツ スクリプトは、バックグラウンド ページにメッセージを渡すことができます。バックグラウンド ページは、メッセージのデータを保存、変換、または送信します。