コンテンツ スクリプトは、ウェブページのコンテキストで実行されるファイルです。標準のドキュメント オブジェクト モデル(DOM)を使用して、ブラウザがアクセスするウェブページの詳細を読み取り、変更を加え、親拡張機能に情報を渡すことができます。
コンテンツ スクリプトの機能について
コンテンツ スクリプトは、拡張機能とメッセージ
を交換することで、親拡張機能で使用される Chrome API にアクセスできます。また、chrome.runtime.getURL() を使用して拡張機能のファイルの URL にアクセスし、他の URL と同様に結果を使用することもできます。
// Code for displaying EXTENSION_DIR/images/myimage.png:
var imgURL = chrome.runtime.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;
さらに、コンテンツ スクリプトは次の Chrome API に直接アクセスできます。
コンテンツ スクリプトは、他の API に直接アクセスすることはできません。
隔離された環境で作業する
コンテンツ スクリプトは隔離された環境に存在するため、コンテンツ スクリプトはページや追加のコンテンツ スクリプトと競合することなく、JavaScript 環境を変更できます。
拡張機能は、次の例のようなコードを含むウェブページで実行できます。
<html>
<button id="mybutton">click me</button>
<script>
var greeting = "hello, ";
var button = document.getElementById("mybutton");
button.person_name = "Bob";
button.addEventListener("click", function() {
alert(greeting + button.person_name + ".");
}, false);
</script>
</html>
その拡張機能は、次のコンテンツ スクリプトを挿入できます。
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener("click", function() {
alert(greeting + button.person_name + ".");
}, false);
ボタンを押すと、両方のアラートが表示されます。
隔離された環境では、コンテンツ スクリプト、拡張機能、ウェブページが、他のユーザーが作成した変数や関数にアクセスすることはできません。これにより、コンテンツ スクリプトは、ウェブページからアクセスできない機能を使用できるようになります。
スクリプトの挿入
コンテンツ スクリプトは、プログラムで挿入することも、宣言的に挿入することもできます。
プログラムで挿入する
特定の状況で実行する必要があるコンテンツ スクリプトには、プログラムによる挿入を使用します。
プログラムによるコンテンツ スクリプトを挿入するには、マニフェストで activeTab 権限を指定します。これにより、アクティブなサイトのホストへの安全なアクセスと、タブ権限への一時的なアクセスが許可され、コンテンツ スクリプトはクロスオリジン権限を指定せずに現在のアクティブなタブで実行できます。
{
"name": "My extension",
...
"permissions": [
"activeTab"
],
...
}
コンテンツ スクリプトはコードとして挿入できます。
chrome.runtime.onMessage.addListener(
function(message, callback) {
if (message == "changeColor"){
chrome.tabs.executeScript({
code: 'document.body.style.backgroundColor="orange"'
});
}
});
または、ファイル全体を挿入することもできます。
chrome.runtime.onMessage.addListener(
function(message, callback) {
if (message == "runContentScript"){
chrome.tabs.executeScript({
file: 'contentScript.js'
});
}
});
宣言的に挿入する
指定したページで自動的に実行されるコンテンツ スクリプトには、宣言による挿入を使用します。
宣言的に挿入されたスクリプトは、マニフェストの "content_scripts" フィールドに登録されます。
JavaScript ファイル、CSS ファイル、またはその両方を含めることができます。自動実行されるすべてのコンテンツ スクリプトで、
一致パターンを指定する必要があります。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"css": ["myStyles.css"],
"js": ["contentScript.js"]
}
],
...
}
| 名前 | タイプ | 説明 |
|---|---|---|
matches {: #matches } |
必須。 このコンテンツ スクリプトが挿入されるページを指定します。これらの文字列の構文の詳細については、一致パターンをご覧ください。URL を除外する方法については、一致パターンと globをご覧ください。 | |
css {: #css } |
省略可。 一致するページに挿入される CSS ファイルのリスト。これらは、ページの DOM が構築または表示される前に、この配列に表示される順序で挿入されます。 | |
js {: #js } |
省略可。 一致するページに挿入される JavaScript ファイルのリスト。これらは、この配列に表示される順序で挿入されます。 | |
match_about_blank {: #match_about_blank } |
ブール値 | 省略可。 親フレームまたはオープナー フレームが matches で宣言されたパターンのいずれかに一致する場合に、スクリプトを about:blank フレームに挿入するかどうか。デフォルトは false です。 |
一致と glob を除外する
マニフェスト登録に次のフィールドを含めることで、指定したページの一致をカスタマイズできます。
| 名前 | タイプ | 説明 |
|---|---|---|
exclude_matches {: #exclude_matches } |
省略可。 このコンテンツ スクリプトが挿入されるページを除外します。これらの文字列の構文の詳細については、一致パターンをご覧ください。 | |
include_globs {: #include_globs } |
省略可。 matches の後に適用され、この glob にも一致する URL のみが含まれます。Greasemonkey の @include キーワードをエミュレートすることを目的としています。 |
|
exclude_globs {: #exclude_globs } |
省略可。 matches の後に適用され、この glob に一致する URL が除外されます。Greasemonkey の @exclude キーワードをエミュレートすることを目的としています。 |
URL が exclude_matches パターンまたは
exclude_globs パターンにも一致しない限り、コンテンツ スクリプトは、その URL が matches パターンと any
include_globs パターンのいずれかに一致する場合にページに挿入されます。
matches プロパティは必須であるため、exclude_matches、include_globs、exclude_globs
は、影響を受けるページを制限するためにのみ使用できます。
次の拡張機能は、コンテンツ スクリプトを http://www.nytimes.com/ health に挿入しますが、http://www.nytimes.com/ business には挿入しません。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"js": ["contentScript.js"]
}
],
...
}
glob プロパティは、一致パターンとは異なる、より柔軟な構文に従います。使用できる glob 文字列は、ワイルドカードのアスタリスクと疑問符を含む URL です。アスタリスク * は、空の文字列を含む任意の長さの文字列に一致し、疑問符 ? は任意の 1 文字に一致します。
たとえば、glob http:// ??? .example.com/foo/ * は次のいずれかに一致します。
- http:// www .example.com/foo /bar
- http:// the .example.com/foo /
ただし、次のものには一致しません。
- http:// my .example.com/foo/bar
- http:// example .com/foo/
- http://www.example.com/foo
この拡張機能は、コンテンツ スクリプトを http:/www.nytimes.com/ arts /index.html と http://www.nytimes.com/ jobs /index.html に挿入しますが、http://www.nytimes.com/ sports /index.html には挿入しません。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"include_globs": ["*nytimes.com/???s/*"],
"js": ["contentScript.js"]
}
],
...
}
この拡張機能は、コンテンツ スクリプトを http:// history .nytimes.com と http://.nytimes.com/ history に挿入しますが、http:// science .nytimes.com または http://www.nytimes.com/ science には挿入しません。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
これらの一部またはすべてを含めて、適切なスコープを実現できます。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"include_globs": ["*nytimes.com/???s/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
実行日時
JavaScript ファイルがウェブページに挿入されるタイミングは、run_at フィールドによって制御されます。推奨されるデフォルトのフィールドは "document_idle" ですが、必要に応じて "document_start" または
"document_end" として指定することもできます。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}
| 名前 | タイプ | 説明 |
|---|---|---|
document_idle {: #document_idle } |
文字列 | 推奨。 可能な限り "document_idle" を使用してください。ブラウザは、 "document_end" と windowonload イベントの発生直後の間でスクリプトを挿入するタイミングを選択します。挿入の正確なタイミングは、ドキュメントの複雑さと読み込みにかかる時間によって異なり、ページの読み込み速度に合わせて最適化されます。"document_idle" で実行されるコンテンツ スクリプトは、window.onload イベントをリッスンする必要はありません。DOM が完了した後に実行されることが保証されます。スクリプトを window.onload の後に実行する必要がある場合は、拡張機能で document.readyState プロパティを使用して onload がすでに発生しているかどうかを確認できます。 |
document_start {: #document_start } |
文字列 | css のファイルの後、他の DOM が構築される前、または他のスクリプトが実行される前にスクリプトが挿入されます。 |
document_end {: #document_end } |
文字列 | DOM が完了した直後、画像やフレームなどのサブリソースが読み込まれる前にスクリプトが挿入されます。 |
フレームを指定する
"all_frames" フィールドを使用すると、拡張機能は、指定した URL の要件に一致するすべてのフレームに JavaScript ファイルと CSS ファイルを挿入するか、タブの最上位フレームにのみ挿入するかを指定できます。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
| 名前 | タイプ | 説明 |
|---|---|---|
all_frames {: #all_frames } |
ブール値 | 省略可。 デフォルトは false で、最上位フレームのみが一致します。true を指定すると、フレームがタブの最上位フレームでない場合でも、すべてのフレームに挿入されます。各フレームは URL の要件について個別にチェックされます。URL の要件が満たされていない場合、子フレームには挿入されません。 |
埋め込みページとの通信
コンテンツ スクリプトとホストするページの実行環境は互いに隔離されていますが、ページの DOM へのアクセスは共有されます。ページがコンテンツ スクリプトと通信する場合、またはコンテンツ スクリプトを介して拡張機能と通信する場合は、共有 DOM を介して行う必要があります。
たとえば、window.postMessage を使用して実現できます。
var port = chrome.runtime.connect();
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
port.postMessage(event.data.text);
}
}, false);
document.getElementById("theButton").addEventListener("click",
function() {
window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);
拡張機能以外のページ example.html は、自身にメッセージを投稿します。このメッセージはコンテンツ スクリプトによってインターセプトされ、検査された後、拡張機能プロセスに投稿されます。このようにして、ページは拡張機能プロセスとの通信ラインを確立します。同様の方法で逆方向の通信も可能です。
安全の確保
隔離された環境は保護レイヤを提供しますが、コンテンツ スクリプトを使用すると、拡張機能とウェブページに脆弱性が生じる可能性があります。コンテンツ スクリプトが別のウェブサイトからコンテンツを受信する場合(XMLHttpRequest を行うなど)、挿入する前にクロスサイト スクリプティング攻撃をフィルタリングするように注意してください。"man-in-the-middle"
悪意のあるウェブページを必ずフィルタリングしてください。たとえば、次のパターンは危険です。
var data = document.getElementById("json-data")
// WARNING! Might be evaluating an evil script!
var parsed = eval("(" + data + ")")
var elmt_id = ...
// WARNING! elmt_id might be "); ... evil script ... //"!
window.setTimeout("animate(" + elmt_id + ")", 200);
代わりに、スクリプトを実行しない安全な API を使用してください。
var data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
var parsed = JSON.parse(data);
var elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(function() {
animate(elmt_id);
}, 200);