Add-on SDK で始めるアドオン開発(Content Script 編)

前回の記事:Add-on SDK で始めるアドオン開発(Panel & Widget 編)

Add-on SDK で作成するアドオンのスクリプトは、主に JavaScript で記述されますが、 Web ページ上で動作する JavaScript とは異なり、このスクリプトから Web ページの DOM に直接アクセスすることができません。
これは、アドオンのスクリプトが、 通常のWeb ページとは異なるコンテクストで動作しているためです。
アドオンのスクリプトから DOM にアクセスするためには、 Content Script という別のスクリプトを利用しなければなりません。

詳細:Two Types of Scripts

今回の記事では、 Content Script を用いて、実際にパネル内の Web ページ の DOM を操作してみます。
また、 Content process と Add-on process との間でデータを送受信する方法も紹介します。

Content Script を用いた Web ページの書き換え

まずは試しに、簡単なContent Script を実行して、パネルに読み込んだ Web ページの内容を書き換えてみます。
前回作成した main.js を、次のように変更します。

…
    var createPanel = function(){
        return panel.Panel({
            width: 800,
            height: 320,
            contentURL: "http://www.google.com/ads/preferences/view?hl=ja",
            contentScript: "document.querySelector('h1').style.color='red';"
        });
    }
…

パネルのコンストラクタで contentScript を指定し、実行するスクリプトを定義します(11行目)。
このスクリプトは、ページ内の h1 要素を選択し、そのスタイルシートの color プロパティを red に変更しています。

このプログラムを 「cfx run」 で実行すると、パネルの中の h1 要素が赤色に表示されます。
※このプログラムは、2011年8月19日現在の Google Ads Preferences のページを前提に作成しているので、内容が古くなっている可能性があります。 Content Script の内容は、適宜書き換えてみてください。

http://www.google.com/ads/preferences/view?hl=ja

Content Script の動作を確認したら、今度は、パネル内に必要な情報だけを残すようなスクリプトを実行してみます。
Google Ads Preferences ページのソースコードを確認してみると、ユーザのインタレスト カテゴリを表示している部分には、 ID 属性として remove-vert-form (インタレスト カテゴリがある場合)、または、 add-interests (インタレスト カテゴリがない場合)が指定されています(2011年8月19日現在)。
そこで、 querySelector を用いて、これらの ID 属性を持つ要素を含まないような div 要素を非表示にするスクリプトを実行してみます。

…
    var createPanel = function(){
        return panel.Panel({
            width: 800,
            height: 320,
            contentURL: "http://www.google.com/ads/preferences/view?hl=ja",
            contentScript: "var divList = document.querySelectorAll('div');" +
                           "for (var i = 0; i < divList.length; i++) {" +
                           "    var div = divList.querySelector('#remove-vert-form, #add-interests');" +
                           "    if (div == null){" +
                           "        divList.style.display='none';" +
                           "    }" +
                           "}"
        });
    }
…

これにより、インタレスト カテゴリを表示する部分のみが、パネル内に残されます。

http://www.google.com/ads/preferences/view?hl=ja

ところで、上のように Content Script の中身が長くなる場合、文字列としてスクリプトを記述すると可読性や保守性が低下します。
そこで、 Content Script を外部ファイルに記述し、それを読み込む方法を紹介します。

まず、アドオンのパッケージ内にある data フォルダの中に、 panelScript.js を作成し、先ほどの Content Script の内容を記述します。

// data/panelScript.js
var divList = document.querySelectorAll('div');
for (var i = 0; i < divList.length; i++) {
    var div = divList.querySelector('#remove-vert-form, #add-interests');
    if (div == null){
        divList.style.display = 'none';
    }
}

次に、 main.js 側で、以下のように self モジュールをインポートします。
このモジュールにより、data フォルダ内のファイルを url 関数を用いて読み込むことができます。

const panel = require("panel");
const widget = require("widget");
const {Hotkey} = require("hotkeys");
const data = require("self").data;

exports.main = function(){
    var createPanel = function(){
        return panel.Panel({
            width: 800,
            height: 320,
            contentURL: "http://www.google.com/ads/preferences/view?hl=ja",
            contentScriptFile: data.url("panelScript.js")
        });
    }
…

さて、以上までで、 Google AdSence の広告表示設定を手軽に確認できる機能が実装できました。
本来ならば、このパネル内のボタンから、広告表示設定のカテゴリ追加/削除などの管理ができればよいのですが、残念ながらこのままでは上手く機能しません。

そこで、パネルをクリックすると Google Ads Preference の設定ページを開くことができるようにしてみます。

Content Process と Add-on Process 間の通信

パネル内の要素にクリックなどのイベントリスナを設定するためのスクリプトは、 Content Script 内に記述します。
一方、あるページを新規のタブで開くためには、 tabs モジュールの open メソッドを用います。

これらのスクリプトは、それぞれ Content Process と Add-on Process という異なるプロセスで実行されています。
これらのプロセス間で、データをやり取りするためには、port オブジェクトの emit / on メソッドを用います。

詳細:Working with Content Scripts

まず、panelScript.js に、次のスクリプトを追記します。

…
window.addEventListener("click",
    function(){
        self.port.emit("click", location.href);
    },
    false);

このスクリプトは、パネルの window がクリックされたときに、 self.port.emit メソッドを用いて、現在のページのURL (location.href) を、Add-on Process 側に送信します。

次に、 main.js を以下のように変更します。

const panel = require("panel");
const widget = require("widget");
const {Hotkey} = require("hotkeys");
const data = require("self").data;
const tabs = require("tabs");

exports.main = function(){
    var createPanel = function(){
        var newPanel = panel.Panel({
            width: 800,
            height: 320,
            contentURL: "http://www.google.com/ads/preferences/view?hl=ja",
            contentScriptFile: data.url("panelScript.js")
        });
        newPanel.port.on("click", function(url) {
            tabs.open(url);
        });
        return newPanel;
    }
…

15行目からの newPanel.port.on において、”click” イベント(これは、port.emit の第1引数で指定した、ユーザ定義イベントです)が発生したときに、Content Process から受け取った URL を新規タブで開く関数を定義しています。

これにより、パネルのウィンドウをクリックしたときに、新規タブで広告表示設定ページが開かれるようになりました。

jQuery の利用

Content Script では、 jQuery を用いて、より簡単に DOM を操作することができます。
jQuery とは、 CSS に似た比較的短いコードを記述するだけで、高度な DOM 操作を実現する JavaScript ライブラリです。

jQuery を利用するためには、まず jQuery のページから、jquery-xxx.js ファイルをダウンロードして、パッケージ内の data フォルダに保存します。
Content Script として複数のファイルを読み込むためには、contentScriptFile プロパティに、以下のように配列としてファイルを指定します。
※ contentScriptFile に外部ページのファイルは指定できないので、必ずローカルにファイルをダウンロードしてから読み込んでください。

…
        var newPanel = panel.Panel({
            width: 800,
            height: 320,
            contentURL: "http://www.google.com/ads/preferences/view?hl=ja",
            contentScriptFile: [data.url("jquery-1.6.2.min.js"), data.url("panelScript.js")]
        });
…

jQuery を用いると、先ほどの panelScript.js のスクリプトは、次のように書き換えられます。

// panelScript.js
$("div:not(:has(#remove-vert-form, #add-interests))").css("display","none");
$(window).click(function () {
    self.port.emit("click", location.href);
});

このように、 jQuery を用いると、Content Script を非常に簡潔に記述することができます。
したがって、 Web ページ製作などで既に jQuery を活用している場合や、アドオンのプロトタイプを短時間で作成したい場合などに有効です。

次回:コンテクストメニューの拡張

次回の記事では、主に context-menu モジュールと simple-storage モジュールを活用して、コンテクストメニューを拡張し、ユーザのページ遷移に応じて動的にコンテクストメニューを更新する方法を紹介する予定です。

次回の記事:Add-on SDK で始めるアドオン開発(Context-menu 編)