五味渕 大賀 GOMIBUCHI Taiga
http://www.xuldev.org/

これまでの章で,XUL,JavaScript,CSS,XPCOM といった技術について解説しました.本章ではこれらの技術を組み合わせ,実際に拡張機能開発を行います.

開発環境の準備

拡張機能開発の予備知識

拡張機能開発基礎編

拡張機能開発応用編 ~セッション管理拡張機能~

「拡張機能開発応用編」では,Firefox 2の新機能の1つであるセッションストアAPIを利用し,ユーザが好きなタイミングでセッション(ブラウザウィンドウの状態)を保存したり復元したりすることができる実用的な拡張機能を開発します.

フェーズ①̶テストインストール

 セッション管理拡張機能のGUIを図12に示します.「Session Store」メニュー内には,現在のセッションを保存するための「Save Session」,現在保存されているすべてのセッションを削除するための「Clear Sessions」の2つのメニュー項目があり,両者の間には現在保存されているセッションを復元するためのメニュー項目が新しいものから順番に配置されています.

図12:セッション管理拡張機能のGUI
[図12]

コラム「セッションストアAPI」にあるように,セッションはJSON文字列として表されます.セッション管理拡張機能では,プロファイルフォルダ下にセッション保存用のフォルダ(フォルダ名はsessionstore)を作り,その中に個々のセッションをテキストファイルとして保存します(図13).

図13:セッション保存と復元の動作イメージ
[図13]

ソースファイルの配置

フェーズ①でのフォルダ構成を図14に示します.

図14:ソースファイルのフォルダ構成
[図14]

インストールマニフェストの作成

作業用フォルダを作成し,インストールマニフェストをリスト1を参考に作成してください.今回はem:id を「[email protected]」,em:name を「Session Store」とします.

クロムマニフェストの作成

リスト2を参考にしてクロムマニフェストを作成してください.

オーバーレイのマージポイントを探し出す

オーバーレイのマージポイントおよび要素の追加位置は,Helloworld拡張機能と同じです.

ブラウザウィンドウへのオーバーレイ

ブラウザウィンドウへオーバーレイさせるoverlay.xulをリスト14のように記述してください.

リスト14:overlay.xulへの記述
<?xml version="1.0"?>
<overlay id="sessionstoreOverlay"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <script type="application/x-javascript" src="chrome://sessionstore/content/overlay.js" />
    <menupopup id="menu_ToolsPopup">
        <menu label="Session Store" insertbefore="sanitizeSeparator">
            <menupopup>
                <menuitem label="Save Session" />
                <menuseparator />
                <!-- ここへmenuitemを動的に生成して配置する -->
                <menuseparator />
                <menuitem label="Clear Sessions" />
            </menupopup>
        </menu>
    </menupopup>
</overlay>

テストインストール

「拡張機能開発基礎編」で行ったようにポインタファイルを配置してテストインストールします.「ツール」メニューへ「Session Store」メニューが追加されるはずです.

フェーズ②̶機能を実装する

フェーズ②では,JavaScriptによってセッションストアAPIを利用したセッション保存や復元機能の実装をします.

JavaScript の概要を決める

機能を実装するにあたって必要な処理ごとにメソッド名や変数名を決め,overlay.jsへリスト15のように記述します.

リスト15: overlay.jsへの記述
var gSessionStore = {
// セッション保存用フォルダ(nsILocalFile)
_dir: null,
// 初期化処理
init: function() { },
// 終了処理
uninit: function() { },
// セッションを保存する(イベントハンドラ)
save: function(event) { },
// セッションを復元する(イベントハンドラ)
restore: function(event) { },
// セッションを削除する(イベントハンドラ)
clear: function(event) { },
// メニュー項目を動的に生成する(イベントハンドラ)
createMenu: function(event) { },
// ファイルを読み込む
_readFile: function(aFile) { },
// ファイルへ書き込む
_writeFile: function(aFile, aData) { },
};

window.addEventListener("load", function(){
gSessionStore.init(); }, false);
window.addEventListener("unload", function(){
gSessionStore.uninit(); }, false);
メソッドや変数をオブジェクトのプロパティとしてまとめる

リスト15では,いろいろなメソッドや変数をgSessionStoreという1個のオブジェクトのプロパティとしてまとめて定義していることに着目してください.拡張機能でブラウザウィンドウへオーバーレイする際に気を付けたいことは,browser.xulへオーバーレイさせるXULから読み込まれるJavaScript内で定義したグローバル変数や関数は,すべてbrowser.xulのwindowオブジェクトのプロパティとなることです.したがって,browser.xulのwindowオブジェクトのプロパティには,Firefox 自身や色々な拡張機能によって定義された変数や関数が多く混在した状態となります.独立性を高めて拡張機能どうしの衝突が起きにくくするためには,上記のように1個のオブジェクトにまとめるか,あるいはクラスを定義すると良いでしょう.

ブラウザウィンドウを開いたときに実行する初期化処理

拡張機能では,ブラウザウィンドウを開いたときに何らかの初期化処理を実行したいケースがよくあります.そのようなときは,windowオブジェクトをターゲットとしてload イベントに対するイベントリスナを追加します.リスト15ではウィンドウを開いたときにinitメソッドを実行し,ウィンドウを閉じたときにuninitメソッドを実行します.

イベントハンドラを追加

overlay.jsで定義した4つのイベントハンドラをoverlay.xulへ追加します(リスト16).ここではイベントのバブリング(▽注10)を利用し,menuitemが並ぶ階層よりも1つ上のmenupopup要素にrestoreイベントハンドラを追加しています.それに伴い,save,restore の両イベントハンドラの処理内で,イベントのバブリングを阻止する必要があります.

リスト16:overlay.xulへの修正
<menupopup onpopupshowing="gSessionStore.createMenu(event);"
                oncommand="gSessionStore.restore(event);">
    <menuitem label="Save Session" oncommand="gSessionStore.save(event);" />
    <menuseparator />
    <!-- ここへmenuitemを動的に生成して配置する -->
    <menuseparator />
    <menuitem label="Clear Sessions" oncommand="gSessionStore.clear(event);" />
</menupopup>

メソッドの実装

それでは,gSessionStoreオブジェクトに定義された各メソッドを1つずつ実装していきましょう.なお,誌面の都合上,すべてのソースコードは掲載することができません.下記のURL にて,掲載できなかったソースコードをご覧になることができます.
http://www.xuldev.org/samples/sd/

initメソッド

initメソッドをリスト17のように記述します.initメソッドでは,まずプロファイルフォルダの中にある「sessionstore」フォルダをnsILocalFile形式で取得し,今後使いまわしができるように変数_dirとして保持します(3~6行目).もしフォルダが存在しなければ作成します(7~8行目)(▽注11).

リスト17:initメソッドへの記述
1: init: function()
2: {
3: var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
4:         .getService(Components.interfaces.nsIProperties);
5: this._dir = dirSvc.get("ProfD", Components.interfaces.nsILocalFile);
6: this._dir.append("sessionstore");
7: if (!this._dir.exists())
8:     this._dir.create(this._dir.DIRECTORY_TYPE, 0700);
9: },
uninitメソッド

uninitメソッドをリスト18のように記述します.initメソッドで生成した変数_dirを破棄するだけです.この処理は実際には意味がありません.

リスト18:uninitメソッドへの記述
1: uninit: function()
2: {
3:     this._dir = null;
4: },
saveメソッド

saveメソッドをリスト19のように記述してください.3行目は,イベントのバブリングによってmenuitem要素の親のmenupopup要素のoncommandイベントハンドラが呼び出されてしまうのを防ぐための処理です.4~6行目で,nsISessionStoreインターフェースのgetBrowserStateメソッドを使い,現在開いているすべてのウィンドウの状態を表すJSON文字列を取得します.7~9行目で,_dirプロパティから複製することによって保存先ファイルのnsIFileオブジェクトを生成します.10行目で,後述する_writeFileメソッドを使ってセッションを表すJSON文字列をファイルへ書き込みます.

リスト19:saveメソッドへの記述
 1: save: function(event)
 2: {
 3: event.stopPropagation();
 4: var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
 5:         .getService(Components.interfaces.nsISessionStore);
 6: var state = ss.getBrowserState();
 7: var fileName = "session_" + Date.now() + ".js";
 8: var file = this._dir.clone();
 9: file.append(fileName);
10: this._writeFile(file, state);
11: },
restoreメソッド

後述するcreateMenuメソッドによって動的に生成されたメニュー項目に対するイベントハンドラです(リスト20).3~6行目で,createMenuメソッドにて付加された独自の属性「fileName」からファイル名を取得し,後述する_readFileメソッドによってファイルからJSON文字列を読み込みます.7~9行目で,nsISessionStoreインターフェースのsetWindowStateメソッドを使って,現在のウィンドウを基点にセッション復元を実行します.その際,現在開いているタブを上書きしてセッション復元します.

リスト20:restoreメソッドへの記述
 1: restore: function(event)
 2: {
 3: var fileName = event.target.getAttribute("fileName");
 4: var file = this._dir.clone();
 5: file.append(fileName);
 6: var state = this._readFile(file);
 7: var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
 8:         .getService(Components.interfaces.nsISessionStore);
 9: ss.setWindowState(window, state, false);
10: },
clearメソッド

本特集4章にて説明した「フォルダの辿り方」を参考に,nsIFileインターフェースのdirectoryEntriesプロパティを使って取得したフォルダ内の全ファイルを削除します.

_readFileメソッド

引数で渡したnsIFile型オブジェクトのファイルを読み込み,その内容を文字列として返します.また,ファイルを読み込んだ際に,文字コードをUTF-8からUnicodeへ変換します.

_writeFileメソッド

引数で渡したnsIFile型オブジェクトのファイルを新規作成し,引数で渡した文字列を書き込みます.また,書き込む際に文字コードをUnicodeからUTF-8へ変換します.

createMenuメソッド

「Session Store」メニューのポップアップを開くタイミングで呼び出されるイベントハンドラです.まず,前回ポップアップを開いたときに動的に生成したまま残っているmenuitem要素を削除します.その後,セッション保存先フォルダ内の全ファイル名を元に,menuitem要素を動的に生成して挿入しています.

動作確認

ここまでで機能の実装は完了です.新しいウィンドウを開き,セッション保存,セッション復元,セッション削除の3つの機能が正常に動作するか確認してください.

フェーズ③̶設定パネルを作る

拡張機能の設定をユーザが変更可能にするために,iniファイルやレジストリなどを利用する必要はありません.Firefoxには設定値を読み書きするための便利なXPCOMサービスとGUIの部品が揃っています.フェーズ③ではこれらを実際に使います(図15).

図15: 設定パネルの完成イメージ
[図15]

コラム

XPCOMのインタフェースの詳細を知るには?

今回紹介したnsISessionStore インターフェースについては,幸いMozilla Development Center に詳しいドキュメントがあります(▽注A).しかし,一般的には,XPCOMのインターフェースの詳細(IDL)は,XULPlanet(▽注B)Mozilla Cross-Reference(▽注C)でのソースコード全文検索にて見つけることができます.

△注A「nsISessionStore - MDC」(https://developer.mozilla.org/ja/
△注B「XULPlanet」(http://www.xulplanet.com/
△注C「Mozilla Cross-Reference」(http://mxr.mozilla.org/

フォルダ構成

フェーズ③でのフォルダ構成を図16に示します.また,フェーズ③で新たに作成するファイルの役割を,表5に示します.

図16:ソースファイルのフォルダ構成
[図16]
表5:フェーズ③で作成する各ファイルの役割
ファイル名役割
sessionstore-prefs.jsデフォルトの設定値を定めたファイル. 必ずdefaults\preferencesフォルダへ配置してください.
prefs.xul設定パネル

設定値の仕様を決める

まず,追加する設定の設定名/型/値といった仕様を決めます(表6).設定名は,拡張機能固有の接頭辞(ここでは「extensions.sessionstore.」)を付加しましょう.

表6:設定値の仕様
設定名
extensions.sessionstore
.warnOnClear
真偽値 セッション削除時の動作。
true: 確認ダイアログを表示してから実行する
false: すぐに実行する
extensins.sessionstore
.replaceTabs
数値 セッション復元時の動作。
0: 現在のタブを残す
1: 現在のタブへ上書きする
2: 毎回確認する

デフォルト設定値定義ファイル

defaults¥preferences¥ フォルダ内のsessionstoreprefs.jsへ,表6の設定値およびデフォルト値の定義を記述します(リスト21).

リスト21:sessionstore-prefs.jsへの記述
pref("extensions.sessionstore.warnOnClear", true);
pref("extensions.sessionstore.replaceTabs", 2);

設定パネルを作成

prefs.xulへリスト22のように記述してください.prefwindow要素は設定パネルを作るための特殊なルート要素で,その下にprefpane要素を配置することができます.prefpane要素を複数配置すると,Firefoxのオプションウィンドウのように,ボタンを押してパネルを切り替えることができるGUIを作ることができます.preference要素は,設定パネルによって読み書きする設定値で,name属性が設定名,type属性が型となります.また,preference要素のidと,checkboxやradiogroupのpreference属性とを一致させることで,チェックボックスのオンオフやラジオグループの選択によって設定値を変更できるようになります.インストールマニフェストへリスト23の内容を追加し,アドオンマネージャから設定パネルを開くことができるようにします.

リスト22:prefs.xulへの記述
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<prefwindow id="sessionstorePrefs"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="Session Store Preferences">
    <prefpane>
        <preferences>
            <preference id="extensions.sessionstore.warnOnClear"
                  name="extensions.sessionstore.warnOnClear" type="bool" />
            <preference id="extensions.sessionstore.replaceTabs"
                  name="extensions.sessionstore.replaceTabs" type="int" />
        </preferences>
        <checkbox label="Confirm before clearing sessions"
                  preference="extensions.sessionstore.warnOnClear" />
        <groupbox>
            <caption label="When restoring session:" />
            <radiogroup preference="extensions.sessionstore.replaceTabs">
                <radio value="0" label="Keep current tabs" />
                <radio value="1" label="Replace current tabs" />
                <radio value="2" label="Ask me every time" />
            </radiogroup>
        </groupbox>
    </prefpane>
</prefwindow>
リスト23:install.rdfへの追加
<em:optionsURL>chrome://sessionstore/content/prefs.xul</em:optionsURL>

設定値によって動作を変更する

設定値によってはセッションを削除しようとしたときに確認ダイアログを表示させるように処理を追加します(リスト24).JavaScriptから設定値を取得するには,XPCOMサービスであるnsIPrefServiceインターフェースを利用し,設定値の型に合わせてgetBoolPref,getIntPrefなどのメソッドを使います.リスト24では,設定名が接頭辞「extensions.sessionstore.」で始まるすべての設定値を読み書きするためのnsIPrefBranchオブジェクトを取得してから,getBoolPrefメソッドで設定値の取得をしています.

リスト24:overlay.jsのclearメソッドへの追加
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
        .getService(Components.interfaces.nsIPrefService);
var prefBranch = prefSvc.getBranch("extensions.sessionstore.");
if (prefBranch.getBoolPref("warnOnClear") == true) {
    if (window.confirm("Really?") == false)
        return;
}

動作確認

今回は,インストールマニフェストを修正したので,拡張機能を再インストールする必要があります(コラム「修正したソースファイルの動作を確認するには」参照).再インストールしたら,まず「about:config」を開き,表6の設定値が既定値で設定されていることを確認します.次に,アドオンマネージャから「Session Store」を選択し,「設定」ボタンをクリックして設定パネルが表示されることを確認します.さらに,設定を変更することでセッション復元時などの動作が変化することを確認します.

フェーズ④̶ XPI パッケージング

手順は「拡張機能開発基礎編」のフェーズ⑤と変わりありませんが,defaultsフォルダ以下も含める点に気を付けてください.

まとめ

本章では拡張機能開発のごく基本的な部分を解説しましたが,拡張機能やXULにはまだまだ多くの魅力的な機能があります.この機会にぜひ拡張機能開発の世界へと足を踏み入れてみてはいかがでしょうか.

△注10ある要素上で発生したイベントが,その親要素からルート要素までを伝播していくしくみを,イベントのバブリングと言います.
△注11プロファイルフォルダのような特殊なフォルダを取得する方法については,「Code snippets:File I/O - MDC」(https://developer.mozilla.org/En/Code_snippets:File_I/O#Getting_special_files)を参照してください.