コンテント・スクリプト

コンテント・スクリプトは表示中のウェブページに適用するJavaScriptを指す。 Document Object Model (DOM)を使うことで、ページの内容をチェックしたり、変更したりすることが出来る。

この機能を使って出来ることには、例えば次のようなものがある。

一方、コンテント・スクリプトには制限もある。以下のようなことは、することが出来ない。

これらの制限は、一見した感じほど不自由なものではない。拡張機能側で chrome.* APIを使った操作を行い、それらの機能とメッセージを使って通信することで、コンテント・スクリプトからも間接的に chrome.* APIを使うことは出来る。また、DOMを共有することでウェブページと対話することも可能。より詳細なコンテント・スクリプトの出来ること/出来ないことについては、後述する。

Manifest

コンテント・スクリプトを使うには、Manifest Fileに以下のように記述する。

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://www.google.com/*"],
      "css": ["mystyles.css"],
      "js": ["jquery.js", "myscript.js"]
    }
  ],
  ...
}

拡張機能は複数のコンテント・スクリプトを内包でき、コンテント・スクリプトは複数のJavaScriptやCSSから構成される。matchesプロパティは、どのページでスクリプトを実行するかを制御する。

各コンテント・スクリプトは、以下のようなプロパティを使って登録することが出来る。

matches

必須。配列でマッチパターンを指定し、スクリプトが実行されるページを制御する。文法についてはマッチパターン参照。

js

省略可能。配列でファイルを指定し、JavaScriptファイルをページに埋め込む。スクリプトは配列に記述した順に読み込まれる。

css

省略可能。配列でファイルを指定し、CSSファイルをページに埋め込む。ファイルは配列に記述した順に、DOMが構築されてページが表示されるより前に読み込まれる。

run_at

省略可能。jsを指定した場合に、読み込みのタイミングを制御する。値は “document_start“、“document_end“、“document_idle“のいずれかを指定。初期値は “document_idle” 。

“document_start“を指定した場合、スクリプトはCSSが読み込まれた直後にロードされる。DOMが構築される前で、ページ内の他のスクリプトも実行される前になる。

“document_end“を指定した場合、DOMは構築されて、しかし画像やフレーム内のコンテンツなどは読み込まれる前のタイミングでロードされる。

“document_idle“を指定した場合、ロードは “document_end” を指定した場合のタイミングから、 window.onload イベントが発生する直後までの、どこかでロードされる。実際にその間のどこでロードされるのかは、表示中のウェブページの複雑さや読み込みにかかる時間、最適化の具合などによる。

注意:document_idleを使う場合、コンテント・スクリプトでは window.onload イベントを受け取ることが出来ない場合がある。なぜなら前述の通り、スクリプトがロードされるタイミングが既に onload イベントの発生後である場合があるためである。殆どの場合、コンテント・スクリプトにおいては onload イベントを受け取る必要は無い。それは document_start を使わない限りDOMが既に構築されていることは保証されているからである。もし、どうしても onload イベントが既に呼ばれた後かどうかを確認したい場合は document.readyState を用いる。

all_frames

省略可能。コンテント・スクリプトをページ内の全てのフレームに適用するか、トップフレームのみに適用するかどうかを指定する。

初期値は false で、トップページのみの適用を意味する。

実行環境

コンテント・スクリプトは “Isolated World”(隔離空間) と呼ばれる特殊な環境で実行される。ここでは読み込まれたページのDOMにはアクセスできるが、該当ページのJavaScriptの変数や関数には出来ない。そのため、各コンテンツ・スクリプトからは、あたかも他のJavaScriptが全く実行されていないページで自分が実行されるように見える。逆もまた真なりで、各ページのスクリプトからもコンテンツ・スクリプトで定義した変数や関数にアクセスすることは出来ない。

例えば、次のようなシンプルなページがあったとする。


  
  

このページに対して、次のようなコンテント・スクリプトを読み込む。

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener("click", function() {
  alert(greeting + button.person_name + ".");
}, false);

ここでボタンをクリックすると、BobとRoberto両方の挨拶を見ることが出来る。

Isolated Worldでは、各コンテント・スクリプトに対して、ページ内のスクリプトや他のコンテンツ・スクリプトと衝突する心配無しに変更を行える環境を提供する。例えばコンテント・スクリプトではjQueryのバージョン1系を使うが、ウェブページではjQueryのバージョン2系を使っているような場合であっても、ここに衝突は起きずに問題なく両方が動作する。

Isolated Worldの提供するもうひとつの重要なメリットは、ページ上のJavaScriptと拡張機能が含むJavaScriptを完全に分離することにある。これにより開発者は、ページ側から誤ってアクセスされてしまう心配無しにコンテント・スクリプトに機能を付加していくことが可能になる。

埋め込みページとの対話

コンテント・スクリプトの実行環境とページ上のスクリプトがお互いに完全に隔離されているにせよ、両者がアクセスするDOMは共有されている。もしページとコンテント・スクリプト(あるいは、それを通じて拡張機能と)の間で対話したい場合は、共有されているDOMを通じて行う。

以下に、カスタムDOMイベントと、決められた位置に入れておいたデータを使うことで、対話を行っている例を挙げる。

/* example.html */
var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);

function fireCustomEvent(data) {
  hiddenDiv = document.getElementById('myCustomEventDiv');
  hiddenDiv.innerText = data
  hiddenDiv.dispatchEvent(customEvent);
}
/* content script */
var port = chrome.extension.connect();

document.getElementById('myCustomEventDiv').addEventListener('myCustomEvent', function() {
  var eventData = document.getElementById('myCustomEventDiv').innerText;
  port.postMessage({message: "myCustomEvent", values: eventData});
});

この例では、example.html(拡張機能の一部ではなく、通常のウェブページ)でカスタムイベントを定義し、DOM上のある場所にデータを保存している。コンテント・スクリプト側でそのイベントに対するリスナーを作成し、そのタイミングで保存されたデータへアクセスし、その値をメッセージ機能を使って拡張機能本体に送っている。これにより、ウェブページから拡張機能までのコミュニケーションラインが確立されたことになる。同様の方法で、逆方向の通信も可能になる。

セキュリティへの配慮

コンテント・スクリプトを書く場合、セキュリティに関する2つの点に注意しなければならない。

まず、コンテント・スクリプトをページに埋め込むことによって脆弱性を発生させてしまわないよう注意する必要がある。例えばコンテント・スクリプトが他のサイトからデータを受け取って表示するような場合、XSS攻撃を受けることにならないよう注意しなければならない。これには例えば innerHTML ではなく innerText を使うようにするなどの措置をとる。特に気をつけなければいけないのは、HTTPで取得したコンテンツをHTTPSページ上に表示させる場合だ。ユーザが悪意のあるネットワーク上にいる場合、HTTPで得たコンテンツは攻撃を受けて改ざんされている可能性がある。それをHTTPSで守られたページ上に展開することは man-in-the-middle攻撃 に繋がってしまう。

次に、コンテント・スクリプトがIsolated Worldで実行されているにもかかわらず、やはり悪意あるページから実行される危険性のある攻撃は存在することに留意しなければならない。例えば、次のようなコードは危険である。

var data = document.getElementById("json-data");
var parsed = eval("(" + data + ")");

ページ内のjson-data要素に悪意のあるコードが埋め込まれていた場合、これを実行してしまう。
これを回避するには、安易に eval を使わずに JSON.parse を用いると良い。

var data = document.getElementById("json-data");
var parsed = JSON.parse(data);

もうひとつ、例を挙げておく。

var elmt_id = ...
window.setTimeout("animate(" + elmt_id + ")", 200);

これはページから要素のid属性を取得し、それにあわせたアニメーションを実行するようなサンプルである。しかし、ページ側でidの値に “); attack…(” のように記述されていた場合、予期しない攻撃が実行されてしまう。このケースであれば、文字列としてコードを生成するのではなく、クロージャを使うことで危険を回避できる。

var elmt_id = ...
window.setTimeout(function() {
  animate(elmt_id);
}, 200);

拡張機能が持つファイルの参照

拡張機能のURLを extension.getURL で取得すれば、拡張機能に同梱された画像などのファイルを通常のURL同様に使うことが出来る。

var imgURL = chrome.extension.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;

メッセージに関するシンプルな例を examples/api/messaging から閲覧することができる。それ以外のサンプルは から探す。

動画

以下の動画は、コンテント・スクリプトに関する重要な話題を扱っている。最初の動画は、コンテント・スクリプトとIsolated Worldについて説明している。

次の動画は、コンテント・スクリプトが親の拡張機能へリクエストを送るサンプルについて説明している。

関連項目

  • マッチパターン : コンテント・スクリプトを適用するURLの記述方法
生成元横断XMLHttpRequest »