こんにちは、株式会社ALBERTの太田です。Extensionsが有効になるベータ版のリリースが近づいてきました。そこで今回は、現時点(2009年11月23日)でのExtensionsの作り方をまとめてみます。
Extensionsの開発の下準備
まずは開発環境を整理します。といっても、基本的にHTML/CSS/JavaScriptで作成するので最低限エディタがあればなんとかなります。
ただ、実際に開発/テスト/リリースをする場合には、プロファイルの異なる複数のChromeを起動できると何かと便利です。そういった場合、起動オプションで--user-data-dirを指定することで同時起動ができます(User Data Directory - Custom-Location)。
このようにして、ディレクトリを分けることで複数のプロファイルで開発・テストが行えます。
また、(Chromeではなく)Chromiumの最新のスナップショットも利用すれば異なるバージョンを試すこともできます(今のところ、複数バージョンのChromeを同時にインストールする方法は用意されていないようです)。Chromiumのスナップショットにはsnapshots版とcontinuous版があり、continuous版のほうが自動テストを通っていて、最新版のファイルに固定のURLでアクセスできるのでお勧めです。Windowsであれば、mini_installer.exeを実行すればChromiumが最新版に更新されます。バッチでアップデートするようにしておくとよいでしょう。
起動オプションは不要に
Extensionsを開発/利用するのに以前は --enable-extensionというオプションが必要でしたが、現在は不要となっています。そのほかにもデータベース、ストレージ、Cookieの開発ツールを使用するのに以前は --enable-databasesオプションが必要でしたが、(2009年11月23日時点のDev版では)デフォルトで有効になっています。
開発関連以外でも、HTML5関連のリモートフォント(--enable-remote-fonts)、WebSockets(--enable-web-sockets)、WebDatabase(--enable-databases)、WebStorage(--enable-local-storage)などは(2009年11月23日時点のdev版では)どれもデフォルトで有効になっており、各オプションを指定する必要はありません(ただし、sessionStorageのみ現在は無効のままとなっています)。また、Toolstriptsの廃止に伴い、Extensionsバーを上に表示する --show-extensions-on-topなども廃止予定です。
Extensionsのひな形とmanifest
まず、Extensionsに最低限必要なのはmanifest.jsonというJSONファイルと、HTML/CSS/JavaScriptファイルのどれか1つ以上です。それ以外は、使用するAPIによって必要なファイルが変わり、manifest.jsonの記述内容も同様に変わります。サンプルとしてAPIを一通り使用する場合のmanifest.jsonを見てみます。
nameとdescriptionはそのままExtensionの名前と説明です。versionはドット区切りの数字で、ドットは3つまでです。数字は左から順に比較され、大きいバージョンと判定されれば新しいバージョンがインストールされます。例えば、1.1.Xは、Xの値がいくつであっても1.2をアップデートすることはできません。またバージョンがまったく同じ場合もアップデートされません。
permissionsは拡張のアクセス権限を決定します。クロスオリジン通信を行う場合はアクセス先のオリジンを、タブ/ウィンドウ関連のAPIを使用する場合にはtabsの指定、動的にContent Scriptsを挿入する場合(executeScriptやinsertCSSなどを使用する場合)には挿入対象となるページのオリジンを指定します。なお、セキュリティ/メンテナンスの問題からchromeで始まるオリジンを指定することはできません(Extensionsギャラリーが公開される予定のchrome.google.comも特別に指定できません。Issue 28228)。
update_urlは自動アップデートを行う場合にアップデート情報を書いたXMLのURLを記述します。XMLの中身はUpdate manifestの通りで、appid(拡張のID)とcodebase(crxファイルの場所)にversion(最新のバージョン)を記述します。
options_page、background_page、chrome_url_overridesはそれぞれOptions Page、Background Pages、Override Pagesを使用する場合に記述し、対応するHTMLファイルを用意します。当然ですが、HTMLファイルはCSSやJavaScriptを外部ファイルに分けることができ、jQueryなどのライブラリをそのまま利用できます。
iconsはインストール時や拡張の一覧ページなどで使用されるアイコンを指定できます。サイズは16px、32px、48px、128pxの4つを記述でき(省略も可能です)、画像フォーマットはWebKitがサポートするものであれば使用できます。
content_scriptsは読み込まれたページのURLとmatchする条件を指定し、その際に適用するJavaScriptまたはCSSを指定します。条件は複数記述でき、適用するファイルも複数指定可能です。
page_action、browser_actionは、アドレスバー(Omnibox)やその右隣(ツールバー)に表示するアイコンを指定します。page_actionとbrowser_actionはどちらか1つしか指定できません。1つのExtensionは1つのアイコン領域しか持つことができません。そのアイコンには画像を使用しますが、Canvasで描いたImageDataを使用することも可能なので動的に生成することも可能です。popupでアイコンをクリックした際にhtmlを表示することもできます。
manifest.jsonの内容は以上ですが、このほかにもmanifest.jsonに記述しないHTMLファイルをBrowserActionなどから開いて、通常のタブに開いたページと同じように使用することも可能です。popupはすぐに閉じてしまいますので、Twitterクライアントなどはタブで開いたほうがよいかもしれません。
Extensionsの開発手順
では実際にmanifest.jsonを記述し、それに対応するファイルを用意していきます。今回もSBMカウンタをサンプルとします(そろそろ別のサンプルを出すべきところですが、今回だけお付き合いください)。
Tabs APIとexecuteScriptをすべてのサイトで使用するので、permissionsはほぼフルアクセスの権限を与えています。
このmanifestに対応したHTML、画像、CSSやJavaScriptファイルを用意していきます。なお、前回はpage_actionを使用しましたが、今回はbrowser_actionを使用しています。また、複数のサービスをカウントするためにExtensionsを複数用意する方法を取りましたが、BrowserActionにはブックマーク数の合計数を表示して、Tooltipに各サービスごとのブックマーク数を、popupにブックマーク数と詳細ページ、ブックマーク追加ページへのリンクを表示することにします。
Background Page
では、最初にBackground PageのHTMLを用意します。
DOCTYPEやhtml要素などを省略せずに書きましたが、JavaScriptを動かすだけなので、script要素だけに省略しても問題ありません。
scriptはサービスの定義を記述したServices.js、delicious用のMD5ライブラリ、Background Pageの本体となるbackground.jsの3つに分けました。
このbackground.jsはChromeが起動した際に一度だけ初期化されます。そこで、最初に起動時の初期化処理を記述します。
最初のSBMConfはインストール直後用のデフォルトの設定です。localStorageに値が存在すればその値をJSON.parseで取り出してSBMConfに反映し、存在しなければ逆にJSON.stringifyでlocalStorageに値をセットしています。なお、isInlineCountは真偽値(プリミティブな値)なので、JSONを経由させていません。JSONを使ってもエラーにはならないようなのですが、本来JSONはプリミティブ値だけを取ることはできないので、念のため直接プリミティブ値として扱うようにしました。
最後のusageServiceConfigの処理は、サービスのON/OFFをServicesオブジェクトに反映させています。
続いて、SBMクラスの実装です。tabオブジェクトを受け取り、サービスごとにリクエストを投げ、レスポンスが返ってきたらそれぞれカウントをBrowserActionのアイコンに反映しています。レスポンスは順番通りに返ってくるわけではないので、サービスが増えて順番がまちまちになっても正常に動作するようにcount、titleの初期化を工夫しています。BrowserActionもtabidを指定することでBadgeText、TitleをPageActionのようにタブごとに保持させることができます。
仕上げにchrome.tabs.onUpdatedでタブの更新をチェックし、タブの読み込みが開始した際にURLが有効なものであればSBMクラスをnewしてインスタンスを作ります。これで、合計ブックマーク数の表示と、Tooltipにサービスごとのブックマーク数を表示することができました。
Options Page
オプションページは chrome://extensions/ のオプションボタンをクリックした際に開かれるページです。今のところ純粋なHTMLページで、特にほかのページと異なる部分はありません(OptionsPage用の共通CSSを適用する予定はあります)。ほかのExtensionページと同様、chrome.extension.getBackgroundPageでバックグラウンドページとやり取りをすることができ、localStorageなどを使ってExtensionの設定をカスタマイズする際に使用します。OptionsPageのlocalStorageとBackgroundPageのlocalStorageは同じオブジェクトなので、OptionsPageでlocalStorageを書き換えれば即、BackgroundPageに反映されます。なお、localStorageの値はExtensionのIDをドメインに見立てて保存されます。
BrowserActionのPopup
Popupもやはり基本はHTMLですが、こちらはページ内容に応じて表示領域が動的に変化するようになっています。また、2009年11月24日時点のdev版では、popupに対してDevToolsを開くことができません。そこで、chrome-extension://extension_id/popup.htmlのようにpopupを直接タブに開いたり、BackgroundPageのconsole.logを呼び出してコンソール出力をするといった方法でデバッグする必要があります。
さて、popupでのブックマーク数の表示ですが、BackgroundPageのSBMクラスと処理は概ね同じなので、SBMクラスを継承します。
BackgroundPageのSBMクラスは引数に何も渡さなければinitメソッドを呼ばないようにしてあるので、init、request、updateの各メソッドだけを持ったインスタンスを作ります。そのインスタンスを新しいPSBMクラスのprototypeに入れると、PSBMはSBMクラスを継承したことになります。
PSBMクラスでは継承元のrequestの前に無効なサービスであればその表示を隠す処理を入れ、またupdate時はそのサービスごとに表示を更新するようにしました。
後はpopupがクリックされた際のタブを取得して、PSBMクラスをnewしています。なお、(現状では)popupが開くことをキャンセルすることができないので、無効なURLでは即座にwindow.closeでpopupを閉じるようにしています。
まとめ
現在のSBMカウンタのすべてを説明したわけではありませんが、現時点でのExtensionsの作り方は説明できたのではないかと思います。FirefoxのAdd-onなどに比べれば機能不足は否めませんが、アイデア次第では面白いものが作れる土台はできてきています。正式リリースも着々と近づいてきていますので、この機会にExtensionsを作ってみてはいかがでしょうか?
次回はまだ紹介していないAPI(ドキュメントにないものも含めて)を幾つか紹介したいと思います。