Firefoxの拡張機能を作ろう~第1回

拡張機能の正体を知る

(2006年7月1日、日経Linux)

新鋭のWebブラウザ「Mozilla Firefox」
カスタマイズ自在な拡張機能

急速にシェアを伸ばしつつある新鋭のWebブラウザ「Mozilla Firefox」。最大の特徴は、カスタマイズの自由度の高さです。Firefoxは、ユーザーが自由に機能を追加・削除できる「拡張機能」という仕組みを備えています。オリジナルの拡張機能を作って、Firefoxを高度にカスタマイズしてみましょう。

Firefoxの拡張機能とは

Mozilla Firefox(以下、Firefox)は、ソースコードを書き換えて再コンパイルしなくても、機能を変更したり拡張したりできます。このような機能変更・拡張用のモジュールを「拡張機能」と呼びます。

拡張機能のダウンロード
拡張機能の自作も可能

Firefoxでは、拡張機能を作成するための仕組みが公開されています。既に、FirefoxのGUIを変更する、タブを操作する、通販サイトと連携するなど多様な拡張機能が公開されています。多くは開発元のMozilla Foundationが運営するWebサイト(https://addons.mozilla.org/)からダウンロードできます。また、ユーザー自身が新しい拡張機能を作成することも可能です。

拡張機能を作成するには
XML関連技術

しかしながら、自分で拡張機能を作成するには、XML関連技術とプログラミングについてある程度の予備知識が必要です。本連載では、拡張機能を開発するための基礎知識をおさえた上で、簡単な拡張機能を作ることを目標とします。

拡張機能の作成手順
Firefoxの構造

拡張機能の作成手順は、C言語などを用いた通常のプログラム開発とは大きく異なります。まずは、Firefoxというアプリケーションそのものの構造を理解しましょう。第1回は、前半でFirefoxの構造を学び、後半で非常に単純な拡張機能のテンプレートを作ります。

Firefoxの構造を学ぶ
Gecko Runtime Environment(GRE)とXUL(ズール)アプリケーション

Firefoxは、大別して2つの層に分けて実装されています(図1)。一つは、OSに依存しないクロス・プラットフォームな環境を提供するGecko Runtime Environment(GRE)*1。もう一つは、その環境上で動作するクロス・プラットフォームなアプリケーションです。このアプリケーションのことを「XUL(ズール)アプリケーション」と呼ぶこともあります*2。

XULアプリケーション

では、XULアプリケーションとは一体どのようなソフトなのでしょうか。

Ajaxアプリケーション

GMail(http://mail.google.com/)やlivedoor Reader(http://reader.livedoor.com/)といったAjax(後述)アプリケーションは、CSS(Cascading Style Sheets)で整形・装飾されたHTML文書とスプリクト言語のJavaScriptで記述されたコードで構成されています。

Windowsのフォルダの表示

Windowsではフォルダの表示や「プログラムの追加と削除」といった部分で、HTMLとJScript*3から作られたコンテンツをアプリケーションのように使用しています*4。

XML User interface Language(XUL)
XML応用言語

XULアプリケーションも構造的には、AjaxアプリケーションやWindows固有のHTMLアプリケーションとよく似ています。最大の違いは、HTMLの代わりにXML User interface Language(XUL)と呼ばれるXML応用言語を用いている点です。

レンダリング・エンジン「Gecko」
Geckoの役割

GREにはHTML/XMLを解釈して画面を生成するレンダリング・エンジン「Gecko」が含まれています。CSSで整形・装飾されたHTML/XML文書の表示やJavaScriptの実行はすべてGeckoが担っています。Firefoxではまた、Firefoxのユーザー・インタフェース自体の描画と制御も、Geckoが処理します。

Firefoxの長所と短所
非常に高い拡張性

このような構造のためFirefoxは、動作速度や各プラットフォーム固有のインタフェースへの準拠度などの点で、ネイティブ・アプリケーションに劣る場合があります。その一方で、非常に高い拡張性を備えることができたわけです。

プラグインと拡張機能の違い
アドオン・ソフト

アプリケーションの機能を強化するアドオン・ソフトとしては、「プラグイン」が有名でしょう。Firefoxにも、Flash形式の動画やPDF文書、QuickTimeムービーなどを表示するためのさまざまなプラグインが存在します。拡張機能とプラグインは一見同じように思えます。一体何が違うのでしょうか。

実装方法

プラグインと拡張機能では、実装方法が大きく異なります。

プラグイン
一種の小型アプリケーション

プラグインは、本体となるアプリケーションが要求、提供するルールに則って通信を行い、データ処理を肩代わりします。結果は元のアプリケーションに返します。つまり、本体アプリケーションと連携して動作する一種の小型アプリケーションと言えるでしょう*5。

Firefoxの拡張機能
動的に組み込まれるパッチ

一方、Firefoxの拡張機能は、言うなれば、Firefox自体に動的に組み込まれるパッチのようなものです。基本的に拡張機能だけでは動作せず、Firefoxの内部のコードに置き換わる、あるいは追加されることで新規機能を実現します。

プラグインの機能は限定的

プラグインは基本的に、本体アプリケーションとの間で取り決められた通信ルールの範囲の中でしか連携できません。そのため、できることはおのずと限られてきます。

拡張機能は広範囲な連携が可能

その点、拡張機能はFirefoxの内部の奥深くまで自由にアクセスできるため、はるかに広範囲な連携が可能です。さらにFirefox自体を全く別のアプリケーションに変えることもできます*6。

セキュリティ・ホール
個人情報を盗み出される

拡張機能はFirefoxやその他の一般的なアプリケーションと同じ権限で動作します。もし拡張機能にセキュリティ・ホールがあれば、ファイルを破壊される、個人情報を盗み出されるといった危険が伴います。そもそも悪意に基づいて作られた拡張機能によって被害を受ける可能性もあります。

拡張機能の導入には危険が伴う
セキュリティ意識

いくら簡単に導入できるといっても、「拡張機能の導入は、通常のアプリケーションのインストールと同じくらいの危険が伴う」と常に意識しておきましょう。同時に開発者側も、セキュリティ意識を持っている必要があります。

Firefoxを構成する技術とその特徴
クロス・プラットフォーム

拡張機能は「Firefoxに対する動的なパッチのようなものだ」と説明しました。しかしながら、一般のアプリケーションでは動的なパッチは使えません。Firefoxはどのような仕組みで拡張機能を、それもクロス・プラットフォームで動くようにしているのでしょうか。

4つの技術
XULアプリケーション

その答えは、FirefoxというXULアプリケーションを形作っている4つの技術にあります。

CSSとJavaScript
XULとXPCOM

まず、(1)外観をきれいに装飾したり、パッと見ただけで機能が分かるようにデザインを凝らしたスキン(皮膚)のように働く「CSS」。(2)骨格とスキンを自由自在に操作してアプリケーションを動かす筋肉として働く「JavaScript」。(3)アプリケーションとしての形を整えて全体の骨格となる「XUL」。そして、(4)JavaScriptではできないような難しいやっかいな処理を、内臓のようにブラック・ボックスとして処理する「XPCOM」です。

インタプリタによって動的に解釈
動的なパッチ

これらの技術はXPCOMを除きいずれも、コンパイルせずに利用できるインタプリタによって動的に解釈されます。そのためコンパイルされたプログラムに比べて容易にパッチを作れます。それぞれが言語仕様の中に、動的なパッチとして使えるような機能もあらかじめ備えています。順を追って、技術の特徴を見ていきましょう。

骨組みとなって各技術をつなぐXUL
GUIウィジット

XULは、ウインドウやボタン、メニューなどのさまざまなGUIウィジットによって、アプリケーションの骨組みを形成します。

アプリケーション開発の機能
切り替え可能なタブ

HTMLアプリケーションだけでは、HTMLに元から備わっている「テキスト入力欄」、「テキストエリア」、「セレクトボックス」、「ラジオボタン」などのごく限られた機能しかアプリケーションのGUI開発に使えません*7。XULはこれらの機能をベースにしつつ、切り替え可能なタブやドロップダウン・メニューの自動表示、キーボード・ショートカットや状態の一括管理、カスタマイズ可能なツールバーのひな形など、アプリケーション開発に適したさまざまな機能を備えています。

XULのドロップダウン・メニュー
FirefoxのGUIもXULで構成

例として、「sample.xul」というテキスト・ファイルを保存し、Firefoxに読み込ませてみましょう。XULではたったこれだけの記述で、今までならJavaScriptで複雑な処理を記述しなければならなかったようなドロップダウン・メニューを実現できます。FirefoxというWebブラウザのGUI自体もXULで構成されているのです。

各技術を“接着”
XULドキュメント

XULはアプリケーション内の各技術を“接着”する働きをします。CSSによるスタイル・シートもJavaScriptも、すべてXULドキュメントから読み込みます。具体例を見てみましょう。2行目でスタイル・シートのファイル(sample.css)を、4行目でJavaScript(sample.js)を読み込んでいます。

オーバーレイ機能が重要
XULに備わった機能

拡張機能の仕組みを実現する上では、XULに備わった機能の1つ「オーバーレイ」が鍵(かぎ)になります。

XInclude
XULドキュメントを統合

元々XMLには、あるXMLドキュメントの中に別のXMLドキュメントを埋め込むXInclude*8という技術が用意されています。XULのオーバーレイはこれと似たXUL独自の仕様で、パーツごとに分割されたXULドキュメント同士を統合して1つのXULドキュメントとして扱えます。

XML処理命令

具体的には、XULではXML処理命令をXML宣言の直後に書くことで、参照先のXULドキュメントを参照元のXULドキュメントに統合できます。

XULのオーバーレイ
同じID名を持つ要素同士を統合

XULドキュメントを読み込むと、オーバーレイ用として指定されたXULドキュメントを同時に読み込みます。そして同じID名(この場合は、messages-box)を持つ要素同士を統合します。結果として、ドキュメントを読み込んだときと同じ表示になります。

特殊な定義ファイル

特殊な定義ファイルを用いれば、オーバーレイ用のXML処理命令を含んでいないXULドキュメントに対してもオーバーレイを施せます。拡張機能の多くはこちらの機能を使って、新しいボタンやメニュー項目などをFirefoxに追加しています。詳しい手順については後述します。

ID名をキー

以上のように、XULではID名をキーとしてGUIの要求をあとから追加・変更できるのです。

外観を決めるCSS
XUL要素の外観を定義

CSSは、Webページ(HTML)やXMLドキュメントの外観を調整する技術です。最近のWebサイトの多くは、CSSでレイアウト情報を定義しています。HTMLアプリケーションとよく似た構造を持つXULアプリケーションでも、XUL要素の外観を定義するためにCSSを使います。

カスケーディング
複数のシートでの指定

CSSには、複数のシート間での指定の競合を解決して最終的なスタイル指定を導く「カスケーディング」という仕組みがあります。例えば元々のXULドキュメントから、2つのスタイル・シートが読み込まれていたとしましょう。

指定の優先順位

このときFirefoxは、CSSの仕様*9に基づいてそれぞれの指定の優先順位を計算し、カスケーディングを進めます。

Firefox用の「テーマ」
スタイル・シートだけを含んだ拡張機能

XULアプリケーションではこのような仕組みを用いて、元々の外観を拡張機能から自由に変えられます。Firefox用の「テーマ」は、このようなスタイル・シートだけを含んだ拡張機能と言えます。

動作の中核となるJavaScript
プロトタイプ・ベースのオブジェクト指向言語

JavaScriptはWebページで広く使われているプロトタイプ・ベースのオブジェクト指向言語の一つです。XULアプリケーションでも動的処理に利用されています。XULアプリケーション開発でのJavaScriptの重要性は高く、極端に言えば、XULアプリケーションとは「HTMLの代わりにXULを用いたJavaScriptアプリケーション」と言ってもよいほどです。

JavaScript
利便性と高い柔軟性が再評価

これまでJavaScriptというと、Webページにちょっとした飾りを付ける程度にしか使われていませんでした。このため、あまり使い道のない言語と考えられていました。しかし近年Ajax*10に注目が集まるに従い、利便性と高い柔軟性が再評価されています。

オブジェクトとプロパティ

JavaScriptでは、ほとんどのデータをオブジェクトとその属性であるプロパティという形で表現します。基本的にほとんどのプロパティは自由に置き換えられます。

関数型のオブジェクト
オブジェクトのメソッド

関数は「関数型のオブジェクト」として扱われ、こちらも他の変数やプロパティに好きなように代入できます。オブジェクトのプロパティとして代入した関数をそのまま実行すれば、そのオブジェクトのメソッドになります。メソッドの置き換えも同様に行えます。

文字列に変換
関数リテラル

関数は文字列に変換できます。さらに、その文字列をeval()関数で実行すれば、関数リテラル(ラムダ関数)としても使えます。これを応用すれば、メソッドの内容を部分的に変更することも可能です。

FirefoxのGUI
JavaScriptによって記述

FirefoxのGUIに関する大部分の処理は、JavaScriptによって記述されています。以上のようなJavaScriptの仕組みを利用すれば、Firefoxの挙動を好きなように変更できることが分かるでしょう。

JavaScriptを補うXPCOMとXPConnect
ファイル入出力

XULアプリケーションはJavaScriptアプリケーションのようなものだと説明しました。ただし、JavaScriptだけではできないこともあります。例えばJavaScriptにはファイル入出力のための仕組みがもともとありませんし、バイナリ・ファイルのやり取りもできません。

XPCOM(Cross-Platform Component Object Model)
JavaScriptでは不可能な処理

このためXULアプリケーションでは、JavaScriptでは不可能な処理や低レベル*11な処理を進めるためのライブラリとして、XPCOM(Cross-Platform Component Object Model)を使用します。

XPCOMコンポーネント
プラットフォームに依存しないAPI

XPCOMはプラットフォームに依存しないAPI(Application Programming Interface)の仕様*12で、この仕様に則って作られたコンポーネントをXPCOMコンポーネントと呼びます。

XPConnect
完全なブラック・ボックス

XULアプリケーションではXPConnectという技術を使うことで、JavaScriptからXPCOMコンポーネントの機能を利用できます。このとき、XPCOMコンポーネントは、JavaScriptにとって完全なブラック・ボックスとして動きます。

C++、JavaScript、Java
Googleツールバー

XPCOMコンポーネントはインタフェースだけが統一されていればよいため、C++、JavaScript、Javaなど、さまざまな言語で開発できます。JavaScriptや既存のXPCOMコンポーネントではどうしても実現できない機能を拡張機能として加えたい場合、新たにC++などを用いてXPCOMコンポーネントを作成します。Googleツールバーなど、独自のXPCOMコンポーネントを含んでいる拡張機能もいくつかあります。

JavaScriptの機能
Firefoxの挙動を変更

XPCOMコンポーネントの動的な変更はできませんが、JavaScriptによる呼び出し処理の変更は可能です。先ほど解説したJavaScript自体の機能を用いて、XPCOM呼び出し部分の記述を書き換えてしまえば、Firefoxに備わる元々のXPCOMコンポーネントの代わりに独自のコンポーネントを使うようFirefoxの挙動を変更できます。

JavaScriptで実現できる機能
C++などで記述した高度な機能

結論として、JavaScriptで実現できる機能だけでなく、C++などで記述した高度な機能を追加、変更できることが分かります。

拡張機能のひな形を作る

後半では、次回の下準備として拡張機能のひな形を作ってみましょう。ごく簡単な例を扱います。Webブラウザのメイン・ウインドウの一番下に「Hello, world!」というメッセージを追加してみましょう。

Firefox 1.0系とFirefox 1.5系
手軽に開発できるFirefox 1.5

Firefoxの拡張機能の仕組みは、Firefox 1.0系とFirefox 1.5系*13で若干異なります。以下では、より手軽に開発できるFirefox 1.5の仕様に合わせて1.5系のみで動作する拡張機能を作ります。

準備を整える
拡張機能の開発

拡張機能の開発にあたっては、いくつかツールをそろえておきましょう。

UTF-8を扱えるテキスト・エディタ
TeraPadやGNOMEテキストエディタ

重要なのは、UTF-8を正しく扱えるテキスト・エディタです。Windows用であれば「TeraPad」*14があればひとまず事足りるでしょう。LinuxなどのGNOME環境であれば、「GNOMEテキストエディタ」でも大丈夫です。

ZIP書庫(アーカイブ)
7-Zip

ZIP書庫(アーカイブ)を作成・展開できるツールも必要です。筆者のお薦めはWindowsであれば「7-Zip」*15です。7-Zip付属のファイル・マネージャを使うと、ZIP書庫を展開しなくても直接内容を編集できます*16。他の圧縮ツールに比べて圧縮率が高いため、完成した拡張機能のパッケージなどを配布する際にも活用できます。

専用のフォルダを作る
ユーザー・プロファイル内に作成

拡張機能を作るには、最初に専用のフォルダをユーザー・プロファイル内に作成します。

ユーザー固有の情報
ユーザープロファイル

Webブラウザの履歴やブックマーク、インストール済みの拡張機能といったユーザー固有の情報は、「ユーザープロファイル」という特殊なフォルダの中に保存されています。拡張機能の開発は、この中に自分でフォルダを作り適切な位置にファイルを置くことで進めます。

ユーザー・プロファイルの位置
プロファイルの実体

ユーザー・プロファイルの位置はプラットフォームによって異なります。インストール後そのままFirefoxを使っている場合、この中に「(ランダムな文字列).default」という名前のフォルダがあります*17。このフォルダ中にプロファイルの実体が保存されています。

「extensions」フォルダ
インストール済みの拡張機能

プロファイルの中にはいろいろなファイルやフォルダがありますが、「extensions」フォルダの中に、インストール済みの拡張機能が保存されています。

拡張機能の内部的なID名
UUID(GUID)形式

extensionsフォルダを開いてみると、「〔972ce4c6-7e08-4474-a285-3208198ce6fd〕」といった名前のサブフォルダがいくつか保存されているのが分かります。これらのフォルダ名はそれぞれの拡張機能の内部的なID名で、UUID(GUID)*18形式になっています。

拡張機能名@ドメイン名

これから作る拡張機能にも、同様に内部的なID名を付ける必要があります*19。正式なID名は他の拡張機能と同様にUUIDで付ける必要がありますが、簡易的に「拡張機能名@ドメイン名」という形式のID名を使うこともできます。ここでは「helloworld@example.com」としておきましょう。extensionsフォルダの中に「helloworld@example.com」フォルダを作ってください。

ファイルの作成
「chrome」フォルダ

helloworld@example.comフォルダができたら、その中に「chrome」という名前のフォルダを作成しましょう。拡張機能として組み込むXULドキュメントやJavaScriptのコードはすべてこのchromeフォルダの中に置きます。

chromeフォルダに保存
UTF-8

テキスト・ファイルを作成し、helloworld.xulという名前でchromeフォルダに保存してください。文字コードは必ずUTF-8にしてください。

拡張機能としての登録情報

XULファイルだけでは拡張機能としては認識されません。FirefoxにXULファイルの内容を組み込むためには、拡張機能としての登録情報を準備する必要があります。

install.rdfとchrome.manifest

登録情報は、install.rdfとchrome.manifestという2つのファイルに記述します。まずinstall.rdfから準備しましょう。

install.rdf

テキスト・ファイルを作成し、install.rdfという名前でhelloworld@example.comフォルダに保存してください。

拡張機能の名前やバージョン情報

install.rdfは拡張機能の名前やバージョン情報、作者、対応するFirefoxのバージョンといった情報を提供するファイルです。em:idプロパティには、先ほど作成したフォルダに付けたのと同じID名を記述しておく必要があります。em:name(名前)やem:description(説明)、em:creator(作者)などの情報は好きなように書き換えられます。

em:targetApplication

em:targetApplicationは、その拡張機能が対応したアプリケーションに関する情報です。ここではFirefox 1.5と1.5のバージョンアップ版に対応すると記述しました(〔ec8030f7-c20a-464f-9b0e-13a3a9e97384〕は、Firefox自体のID名です)。

chrome.manifest

次に、テキスト・ファイルを作成し、chrome.manifestという名前でinstall.rdfと同じ位置に保存してください。「content」と「helloworld」の間など、それぞれの項はタブ文字で区切ります。

Chrome URL
特殊な形式のURL

Firefoxでは通常のURLのほかに「Chrome URL」という特殊な形式のURLを使用します。「chrome://browser/content/browser.xul」という記述も、Chrome URLの一例です。

Chrome URLを割り当てる定義
オーバーレイの定義

chrome.manifestには、拡張機能のリソースにChrome URLを割り当てる定義やオーバーレイの定義が記述されています。

Chrome URLを登録

1行目はChrome URLを登録している行です。この場合は「chrome://helloworld/content/helloworld.xul」にアクセスすれば「~/helloworld@example.com/chrome/helloworld.xul」が読み込まれます。

Firefoxの内部のファイル

2行目は、先ほど作成したファイルをFirefoxの内部のファイルにオーバーレイとして読み込ませるための定義です。

overlay

最初にこの行がオーバーレイの定義行であることを「overlay」によって示し、次にオーバーレイのベースとなる側のリソースのChrome URLを、最後にオーバーレイで読み込まれる側のリソースのChrome URLを示しています。

自動的に統合

「chrome://browser/content/browser.xul」のリソースにアクセスした場合に「chrome://helloworld/content/helloworld.xul」の内容が自動的に統合されることになります。

拡張機能を動かす
Firefoxを再起動

以上ですべての準備は整いました。作成した内容をFirefoxに読み込ませるため、Firefoxを起動しましょう。Firefoxを起動していた場合はいったん終了させてから再起動しなければなりません。

本格的な拡張機能の開発
テストは成功

ウインドウの最下部、ステータスバーの下に「Hello, world!」と表示されていれば、テストは成功です。本格的な拡張機能の開発でも以上と同じ要領でさまざまな機能をFirefoxに加えていきます。

Firefoxと拡張機能を支える仕組みの概要
Firefoxのツールバー

今回はFirefoxと拡張機能を支える仕組みの概要を解説しました。次回は、Firefoxのツールバーに独自のボタンを追加する方法などを通じて、実用的な拡張機能の作り方と配布手順を解説します。

Chrome URLとは何か

XULアプリケーションや拡張機能では通常、内部のリソース(XULファイルやJavaScript、画像など)へアクセスするためにChrome URLを使用します。

chrome.manifestでChrome URLを割り当て

そのためには、読み込みたいファイルに対してchrome.manifestでChrome URLを割り当てなくてはなりません。なぜこのような手間をかけてまでChrome URLを使うのでしょうか。

絶対パスとしてChrome URL

一つは、ファイルまでのフルパスに代わる絶対パスとしてChrome URLを使うためです。

プラットフォームによって異なる

プロファイルのあるフォルダの位置はプラットフォームによって異なりますし、Firefox自体についても、どのフォルダ(ディレクトリ)に置かれるかは環境次第です。Chrome URLを定義しておけば、どのような環境でもChrome URLで必ず同じリソースを参照できます。

XPConnectを使う

もう1つの理由は、XPConnectを使うためです。XPConnectはJavaScriptからXPCOMコンポーネントの機能を呼び出す技術でした。通常のWebページに埋め込まれたJavaScriptから自由にXPConnectを使えてしまうと、セキュリティ上大きな問題となります。そのため、スクリプトに「XPConnect特権」と呼ばれる特殊な権限がある場合に限って、XPConnectを利用できるように設計されています。Chroem URLでアクセスされたリソースには、自動的にこのXPConnect特権が与えられます。

*1 Firefoxは動作の高速化などの理由から実際にはGREを内蔵している。Mozilla SuiteなどではGREは分離されており、他のアプリケーションから利用できるようになっている。

*2 Firefox 1.5はプラットフォーム依存の部分を含んでおり、完全なXULアプリケーションではない。Firefox 3.0は完全なXULアプリケーションを目指して開発されている。

*3 JScriptは、MicrosoftによるECMAScriptの実装。JavaScriptによく似ているが若干仕様が異なる。

*4 こういった簡易アプリケーションを「HTMLアプリケーション」と呼ぶ。

*5 本体アプリケーションの機能に対するパラメータだけを定義するプラグインもある。例えば画像編集ツールにおけるブラシの追加データや、Firefoxの検索プラグインなどである。

*6 例えばFireFTP(http://fireftp.mozdev.org/)を使うと、Firefoxに本格的なFTPクライアント機能を追加できる。また、Mozilla Thunderbirdは、Firefoxにメール送受信機能を加えた巨大な拡張機能と考えることもできる。

*7 本文に挙げた以上の機能はJavaScriptなどを使い、自力で実装する必要がある。

*8 XIncludeについては、W3C(http://www.w3.org/TR/xinclude/)に詳しい。http://www.w3.org/TR/xlink/も参照。

*9 CSSの仕様についてはW3C(http://www.w3.org/TR/REC-CSS2/)に詳しい。

*10 Ajaxとは、Asynchronous JavaScriptとXMLから導かれた造語。主にJavaScriptで非同期通信を処理する。画面遷移のない使いやすいWebアプリケーションのユーザー・インタフェースを作る技術。

*11 ここで言う低レベルとは抽象度の低いという意味。処理内容が単純だという意味ではない。

*12 実際のXPCOMコンポーネントは、インタフェースがプラットフォーム非依存で統一されているだけで、コンポーネント自体はプラットフォーム依存のバイナリである。クロス・プラットフォーム環境でコンポーネントを利用するためには、Windows用のコンポーネント(DLL)、Linux用やMac OS X用のコンポーネント(so)などのバイナリをあらかじめ用意しておく必要がある。

*13 現在開発中のFirefox 2.0は、Firefox 1.5と同様の仕様である。

*14 TeraPadは、http://www5f.biglobe.ne.jp/~t-susumu/library/tpad.htmlから入手できる。

*15 7-Zipは、http://www.7-zip.org/ja/から入手できる。

*16 LinuxなどのGNOME環境でも「書庫マネージャ」で同様の操作ができる。

*17 自分で新しくプロファイルを作成した場合や、前のバージョンのFirefoxからプロファイルを引き継いだ場合、フォルダ名が異なる場合がある。

*18 UUID(Universally Unique Identifier)またはGUID(Global Unique Identifier)とは、世界中で一意な識別子として機能する文字列。

*19 新しいUUIDはGuidGen(http://www.guidgen.com/)などで生成できる。

Firefoxソフトウエアの構造
複数のOSで動作するために、GRE(Gecko Runtime Environment)とFirefox本体に分かれている。GRE上ではメール・クライアント・ソフトのThunderbirdなども動作する。
Firefoxの構造とAjaxアプリケーションなどとの比較
FirefoxはXUL、JavaScript、XPCOMの3つが連携している。Dynamic HTMLやAjaxアプリケーションと構造が似ているが、HTMLではなくXULを用いる点で異なる。
プラグインの動作概念図
本体アプリケーションと通信しながら動作している。
拡張機能の概念図
拡張機能はFirefoxの内部に入り込むことにより、機能を追加、変更できる。
JavaScriptのサンプル・コードプロパティ
好きな値を入力できる。
JavaScriptの関数
関数にも好きな値を入力できる。
ファイル操作を例としたXPCOMとJavaScriptの関係
JavaScriptからはXPCOM(nsIFile)をブラック・ボックスとして扱う。
アクセス時のURIでXPConnect特権の有無が決まる
http、https、ftp、fileなどのスキーマでアクセスしたリソースからはXPConnectが使えず、chromeでアクセスしたときのみXPConnectが使える。

Firefoxの拡張機能を作ろう~第2回

実用的な拡張機能を作ってみよう

(2006年8月1日、日経Linux)

実用的な拡張機能の作り方
プログラミング言語とデータ構造

前回は、Mozilla Firefoxの拡張機能を実現する仕組みについて解説しました。Firefox自体が拡張を前提としたプログラミング言語とデータ構造を採用しているため、ごく自然に新機能を追加できます。前回の内容をふまえて、実用的な拡張機能の基本的な作り方から配布までの手順を紹介します。

サイドバー用の自作パネル
簡単に追加

Firefoxは、拡張機能を比較的簡単に作成できるように設計されています。例えば、サイドバー用の自作パネルやカスタマイズ可能なツールバー用の自作ボタンを簡単に追加できます。

今回の要点
独自のサイドバー・パネルを作成

今回の要点は3つあります。まず、独自のサイドバー・パネルを作成します*1。次に、サイドバー・パネルを開閉するボタンを作ります。最後に、作成した拡張機能を配布する方法を説明します。

サイドバー・パネルの組み込み方

早速、サイドバー・パネルの組み込み方から見ていきましょう。

サイドバー・パネルを組み込む
インライン・フレーム

Firefoxでブックマークや履歴を表示する際には「サイドバー」を使います。サイドバーはWebページのインライン・フレームと同じような形で実装されています。まずは、サイドバー・パネルとして表示するためのXUL*2ドキュメントを作成しましょう。

panel.xul

helloworld@example.com/chrome/フォルダに「panel.xul」という名前のテキスト・ファイルとして保存してください*3。

xml-stylesheet
Firefoxの標準的な表示

2行目の「xml-stylesheet …」という部分は、パネルの表示をFirefoxの標準的な表示に合わせるための指定です。オーバーレイ*4で読み込むXULドキュメントではこの指定は必要ありませんが、サイドバー・パネルのように単独で表示するXULドキュメントでは、忘れずに指定しておきましょう。

表示させるための定義

次に、このXULドキュメントをサイドバー・パネルとして表示させるための定義を記述します。前回作成したhelloworld.xulをテキスト・エディタで開き、内容を書き換えてください。

Firefoxのブラウザ・ウインドウ

Firefoxのブラウザ・ウインドウにおいて、「viewSidebarMenu」というIDを持つmenupopup要素と、「mainBroadcasterSet」というIDのbroadcasterset要素のそれぞれに、自作サイドバー・パネル用の要素を追加しています。

broadcaster要素

broadcaster要素は、メニューやボタンなどの複数の要素の属性を一括して指定・変更するための要素です。この要素のIDをほかの要素から「observes」属性で参照すれば、broadcaster要素に指定されたすべての属性を継承できます*5。

group="sidebar"
Firefoxでは、「group="sidebar"」と指定されたbroadcaster要素はサイドバー・パネルの定義だと見なされます。このとき同時に、sidebarurl属性でサイドバー・パネルのXULドキュメントのURIを指定し、sidebartitle属性でサイドバー・パネルの名前を設定しておく必要があります。

Firefox内部の関数

oncommandイベント・ハンドラ*6にある「toggleSidebar(fviewHelloWorldSidebarf);」という内容は、Firefox内部の関数を使ってサイドバーを開閉するための処理です。

Firefoxを再起動

さて、変更を保存してからFirefoxを再起動し、[表示]-[サイドバー]を開いてみましょう。リストの中に、「Hello, world!」という項目が加わっているはずです。

サイドバー内に表示

この項目を選択してください。サイドバーが開き(他のサイドバー・パネルを表示していた場合、そのパネルと入れ替わる形で)、「Welcome to the Sidebar!」という内容がサイドバー内に表示されました。

実用的な拡張機能

これを「サイドバー・パネルを追加する拡張機能」のひな形として、実用的な拡張機能に仕立てていきましょう。

サイドバー・パネルによる拡張機能
2つのページを読み比べる

例えば「用語集を見ながら本文を読み進める」「2つのページを読み比べる」といった場合、2カ所のページを並べて表示できると便利です。そこで、今回は「今見ているページをそのままサイドバー内にも読み込む」拡張機能を作ります。拡張機能の名前は「Page Holder」にします。

拡張機能の名前

まずは、拡張機能の名前を「Hello, world!」から「PageHolder」に変更しましょう。

拡張機能を格納するフォルダ

すべてのフォルダやファイルを、古い名前から新しい名前に変更します。拡張機能全体を格納する「helloworld@example.com」フォルダは「pageholder@nikkeibp.co.jp」*7に、chromeフォルダ内の「helloworld.xul」は「pageholder.xul」に変更しましょう。

ファイルの内容を修正

次に、ファイルの内容を修正します。install.rdf、chrome.manifest、pageholder.xul(helloworld.xul)を順にテキスト・エディタで開き、insatll.rdfの「em:id」欄はフォルダ名と同じ値に変えてください。それ以外の「helloworld」とある場所もすべて「pageholder」に書き換えます。「Hello, world!」という部分も「Page Holder」に直しておきます*8。

拡張機能一覧

すべての名前を変更し終えたら、Firefoxを再起動してみましょう。[ツール]-[拡張機能]の拡張機能一覧に表示された拡張機能の名前と、[表示]-[サイドバー]の中にある項目の両方とも「Page Holder」になっていれば、うまく変更できています。

ファイル名やパス名などを修正し忘れ

もしもエラーになったり、[表示]-[サイドバー]の項目を選択しても何も起こらない場合、どこかでファイル名やパス名などを修正し忘れている可能性があります。JavaScriptコンソールなどを使って調べてください。

パネルのUIを定義する
ユーザー・インタフェース(UI)の設定

現状では、サイドバー・パネル内に文字が表示されるだけです。続けて、ユーザー・インタフェース(UI)の設定を行います。

サイドバー・パネルのUI

「Page Holder」サイドバー・パネルのUIに最低限必要なのは、以下の2つの要素です。

  • 現在のページを読み込む(表示しているページを更新する)ボタン
  • ページを表示する領域
ひな形の中に組み込む

これをひな形の中に組み込んでみましょう。panel.xulをひな形として作成したPage Holderサイドバー・パネルの内容です。

button要素
最も基本的なXUL要素

button要素は、ユーザーがクリックできるボタンを定義する、最も基本的なXUL要素の一つです。label属性でボタンの文字列を指定できます。ボタンをクリックする、あるいはキーボード操作で選択すると、oncommandイベント・ハンドラに書かれた内容を実行します。4行目にはonloadイベント・ハンドラにも同じ内容を指定しています。こうすると、サイドバー・パネルが読み込まれた段階で自動的に、現在見ているページをサイドバー内に読み込みます。

iframe要素
flex属性

iframe要素は、HTMLのiframe要素と同じ要領で記述します。ここではスクリプトで要素を操作するためにIDを指定しているほか、サイドバー内の領域全体にiframe要素が広がるように、flex属性*9を指定しています。

script要素
XULドキュメント専用の場合

script要素も、HTMLのscript要素と同じ要領で記述します。XULドキュメントから読み込むスクリプトが1つだけで、そのXULドキュメント専用の場合は、スクリプトファイルを「XULドキュメントと同じファイル名!).js」という名前にするのが一般的です。

XMLパースエラー

ファイルを保存してFirefoxを再起動し、[表示]-[サイドバー]で「Page Holder」を選択してみましょう。パネル内の上の端に「Load」というボタンが表示されていれば正常に設定できています。もし「XMLパースエラー」といった表示になる場合は、タグやかっこ、引用符などの閉じ忘れがないかを確認してください。

パネルの挙動を定義する
スクリプト・ファイル

次に、panel.xulの挙動を定義するスクリプト・ファイル「panel.js」を作成します。内容を入力し、panel.jsとして保存しましょう。

JavaScriptの書き方
Webページで使用する場合

JavaScriptの書き方は、Webページで使用する場合と同じです。1行目から始まるブロックは、panel.xul内の2カ所で呼び出している関数「LoadContent」を定義しています。

サイドバーやブラウザ領域などもすべてフレーム
Windowオブジェクト

2行目では、変数varに現在閲覧中のページのURI文字列を格納しています。Firefoxはサイドバーやブラウザ領域などもすべてフレームとして扱うため、「window.parent.content(サイドバー内から見た親フレームの、contentフレーム)」と書くことで、閲覧中のページのWindowオブジェクトにアクセスできます。

XULドキュメント中の要素
W3C DOM

3行目と4行目では、XULドキュメント中の要素を操作しています。W3C DOM*10の基本機能を使ってiframe要素のsrc属性を変更し、フレームの中にWebページを読み込ませています。

Firefoxを再起動

panel.jsを保存したら、Firefoxを再起動して適当なページを読み込み、「Page Holder」サイドバーを開き直してみましょう。現在のページがサイドバー内に読み込まれていれば、テストは成功です。

ボタンを追加する
ワンクリックで呼び出せるボタン

次に、ツールバーに自作ボタンを追加してみましょう。Page Holderサイドバーをワンクリックで呼び出せるボタンを作ります。

toolbarpalette要素

Firefoxでは、toolbarpalette要素に追加した内容が、ツールバーのカスタマイズ・パレット内に列挙されます。pageholder.xulをテキスト・エディタで開いて、overlay要素の末尾に追記してください。

ツールバーの内容が定義

Firefoxのブラウザ・ウインドウでは、「BrowserToolbarPalette」というIDを持つtoolbarpalette要素によってツールバーの内容が定義されています。ここに自作ボタンの定義を追加しています。

toolbarbutton要素

ツールバー用のボタンは、toolbarbutton要素として記述します。class属性の値「chromeclass-toolbar-additional」は、カスタマイズ可能な項目用にFirefox内で使われている指定です。observes属性に「viewPageHolderSidebar」を指定すると、このボタンはサイドバーを定義するbroadcaster要素から、ラベル文字列やoncommandイベント・ハンドラなどの定義を継承します。

image属性

image属性を使うと、ボタンのアイコン画像を指定できます。「chrome://pageholder/content/icon.png」というChrome URLでアイコン画像を読み込めるように、icon.pngをchromeフォルダの中にコピーしておきましょう。

カスタマイズ・パレットを表示
ボタンをツールバーにドラッグ

pageholder.xulを編集したら、ファイルを上書き保存してFirefoxを再起動し、[表示]-[ツールバー]-[カスタマイズ]を選択して、ツールバーのカスタマイズ・パレットを表示しましょう。パレットの最後の方に、先ほど書き加えた「Page Holder」ボタンがあるはずです。試しにボタンをパレットからツールバーにドラッグして、ツールバー内の適当な位置にドロップしてみましょう。自作のボタンがツールバーの中に追加された様子が確認できるはずです。

ボタンを何度かクリック

ボタンを何度かクリックしてみて、サイドバーが正常に開閉することも確認しておきましょう。

キーボード・ショートカットの定義

ボタン操作だけではなく、Page Holderサイドバーをキーボード操作でも簡単に呼び出せるように、オリジナルのキーボード・ショートカットを定義してみましょう。

key要素

XULでは、key要素を使ってキーボード・ショートカットを定義します。pageholder.xulをテキスト・エディタで開いて、overlay要素の末尾に内容を追記してください。

主なキーボード・ショートカットの定義
mainKeyset

Firefoxのブラウザ・ウインドウでは、「mainKeyset」というIDを持つkeyset要素の中に、主なキーボード・ショートカットの定義がまとめられています。ここでは、その中に自作のキーボード・ショートカットの定義を追加しています。

key属性

key要素ではkey属性に「a」や「f」といったアルファベットを指定しておくと、大文字小文字に関係なく、そのキーが入力されたときに反応します。

特殊なキー
keycode属性

F1やPageUpなどの特殊なキーを認識させたい場合は、key属性の代わりにkeycode属性と、「VK_F1」「VK_PAGE_UP」といった既定のキー名を使います。

CtrlキーやAltキー
modifiers属性

CtrlキーやAltキー(モディファイア・キー)との組み合わせを定義するにはmodifiers属性を使います。ctrl、alt、shift、meta(Mac OS XではCommandキー)、accel(WindowsやLinuxではCtrlキー、Mac OS XではCommandキーとして解釈)の5種類が指定でき、カンマで区切れば複数のキーの組み合わせにも対応できます。

WindowsやLinux

WindowsやLinuxでは「Ctrl+Shift+H」、Mac OS Xでは「Command+Shift+H」で機能を呼び出せます。

command属性
oncommandイベント・ハンドラ

command属性には、oncommandイベント・ハンドラで挙動を定義している要素(ここでは、サイドバーを定義しているbroadcaster要素)のIDを指定します*11。すると、このkey要素で定義されたキーボード・ショートカットを入力したとき、参照先の要素で定義されたコマンドを実行するようになります。

テスト
Ctrl+Shift+Hキー

ファイルを上書き保存してFirefoxを再起動し、Ctrl(Mac OS XではCommand)とShiftを押しながらHキーを押してみましょう。Page Holderサイドバーがキー入力に応じて開閉すれば、テストは成功です。

key要素にIDを指定

なお、key要素にIDを指定しておき、同じ機能を持つメニュー項目(この拡張機能の場合、[表示]-[サイドバー]-[Page Holder]となるmenuitem要素)のkey属性からそのIDを参照しておくと、キーボード・ショートカットがメニュー項目名の横に現れます。使い勝手が向上します。

拡張機能を配布する
配布用のインストーラ形式のファイル

ここまでの工程で作成した拡張機能「Page Holder」は、開発したPC上では問題なく使えますが、他のユーザーに配布できません。今回の締めくくりとして、配布用のインストーラ形式のファイルを作ってみましょう。

インストール定義の手直し

インストーラを作成する前に、前回作成したインストール定義(install.rdf)を配布用に手直しします。配布用に修正した部分です。

拡張機能に関する簡単な説明文
em:description

em:descriptionは、拡張機能に関する簡単な説明文です。今回は「mirrors the current page into the sidebar.(この拡張機能は、現在のページをサイドバーの中に複製します)」としてみました。

拡張機能マネージャで表示されるアイコン画像
em:iconURL

em:iconURLでは、拡張機能マネージャで表示されるアイコン画像を指定します。今回はアイコン作成の手間を省略するため、ボタンのアイコン画像をそのまま流用しましょう。ここはChrome URLで指定します。

XPIパッケージを作成する
自動的にインストール

Firefoxの拡張機能を一般向けに配布する際に使うのが、XPI*12パッケージという形式です。XPIパッケージを作成しておくと、Firefoxから読み込むだけで自動的にインストールが始まります。

XPIパッケージの実体
ZIPアーカイブ

XPIパッケージの実体はただのZIPアーカイブです。まず、pageholder@nikkeibp.co.jpフォルダの内容をそのままZIP形式で圧縮します。出来上がったファイルをpageholder.xpiのような名前に変更すれば、XPIパッケージとして利用できます。今回作成した拡張機能「Page Holder」では、install.rdfとchrome.manifest、chromeフォルダの3つを圧縮します。

GNOME環境
Nautilus

Nautilusを使っているGNOME環境などでは、以下のように操作します。

(1)Nautilus上でinstall.rdf、chrome.manifest、chromeフォルダを選択
(2)右クリックメニューから「書庫の作成...」を選択
(3)「書庫」欄に「pageholder」と入力し、その右のドロップダウン・リストで「.zip」を選択して、「作成」ボタンをクリック
(4)作成された書庫ファイルの拡張子を「zip」から「xpi」に変更

コンソールからも操作できます。

フリーソフト「7-Zip」

Windowsではフリーソフトとして公開されている7-Zip*13を使うとよいでしょう。以下の手順でXPIパッケージを作成できます。

(1)フォルダビューでinstall.rdf、chrome.manifest、chromeフォルダを選択
(2)右クリックメニューから[7-Zip]-[圧縮」を選択
(3)「圧縮先」欄に「pageholder.xpi」と入力、「書庫形式」で「Zip」を選択して「OK」ボタンをクリック

XPIパッケージを作成

XPIパッケージを作成するときには、これらのファイルが必ずアーカイブに直接含まれるようにしてください。例えばpageholder@nikkeibp.co.jpフォルダ自体をそのまま圧縮すると、XPIパッケージとしては機能しなくなります。

XPIパッケージを配布する
ファイルをアップロード

最も簡単なXPIパッケージの配布方法は、自分のWebサイトやブログなどにファイルをアップロードし、配布ページからそのファイルへリンクする方法でしょう。

オンラインでインストール
Content-Type

XPIパッケージをオンラインでインストールできるようにするためには、Content-Typeとして「application/x-xpinstall」を設定しておく必要があります。例えばWebサーバーがApacheであれば、「.htaccess」や「httpd.conf」などに以下の記述を追加するとよいでしょう。

AddType application/x-xpinstall.xpi

ダウンロードさせる

直接インストールさせるのではなく、XPIパッケージをダウンロードさせるだけでよい場合は、Content-Typeとして「application/octet-stream」を指定します。

自動更新について
新バージョンが公開された場合

Firefoxには、拡張機能の自動更新機能が備わっています。インストールが済んだ拡張機能に対して、新バージョンが公開された場合、Firefoxは更新を自動的に検知します。その結果、ユーザーに拡張機能の更新を促すメッセージを表示する、自動的に新バージョンをダウンロードして適用するといった動作が可能になります。

拡張機能の自動更新
RDFデータ・ソース

拡張機能の自動更新を有効にするためには、更新情報を提供するRDFデータ・ソースを作成し、Webサイトに置く必要があります。

更新情報
XPIパッケージのURI

更新情報には、拡張機能のID、新バージョンのバージョン番号、対応アプリケーションに関する情報(ここではFirefox 1.5のみとしています)、最後に、バージョンごと、アプリケーションごとに該当するXPIパッケージのURIが含まれます。

CGIスクリプトで動的に生成

RDFデータ・ソースは、ファイルとして作成してWebサイトに置くか、CGIスクリプトで動的に生成するようにしておきます。そして、そのURIを拡張機能のインストール定義「install.rdf」内の「urn:mozilla:install-manifest」に「em:updateURL」プロパティとして記述すれば(em:iconURLなどと同じ扱いです)、Firefoxが定期的にそのURIにアクセスして情報を取得し、拡張機能の新バージョンが公開されているかどうかを調べてくれるようになります。

更新情報のRDFデータ・ソース
Content-Typeを「text/rdf」

なお、更新情報のRDFデータ・ソースはContent-Typeを「text/rdf」として送信する必要があります。これ以外のContent-Typeでは、FirefoxがリソースをRDFデータ・ソースとして解釈できず更新情報の取得に失敗することがあります。

拡張機能のごく基本的な作り方

今回は拡張機能のごく基本的な作り方と、配布までの手順を紹介しました。次回は拡張機能の多言語対応(国際化)など、より広く配布するのに適した拡張機能を作るための方法について解説する予定です。

拡張機能のデバッグ
Firefoxをセーフモードで起動

拡張機能を開発していると、「エラーが起きてFirefoxを起動できなくなった」、「起動するものの動作がおかしくなった」といったトラブルに見舞われることがあります。Firefoxが使えないなどの致命的な場合は、Firefoxをセーフモードで起動してみましょう。LinuxやMac OS Xなどでは、コンソールから「-safe-mode」というオプションを付けて起動します。Windows版ではスタート・メニューの「Mozilla Firefox」グループに「セーフモード」という項目があります。

拡張機能が一時的にすべて無効
JavaScriptコンソール

セーフモードで起動すると、インストールされた拡張機能が一時的にすべて無効になります。設定が原因の場合は設定を変更する、問題の原因となった拡張機能を無効にする、などして原因の究明に役立ててください。JavaScriptコンソールを使うと、拡張機能の中で発生したエラーの詳細を表示できます。まず、ロケーションバーに「about:config」と入力して詳細設定の画面を開き、「javascript.options.showInConsole」の値を「true」に変更してください。その後、Firefoxの「ツール」メニューから「JavaScriptコンソール」を開きましょう。起動時やボタンなどを操作した際に発生したJavaScriptエラー、XULドキュメントやCSSの文法エラーなどの詳しい内容を表示できます。

*1 今回作成する拡張機能は、Firefox 1.5以降の仕様に基づいている。Firefox 1.0系では利用できない。作成したサンプルがうまく動かない場合は、付録メディアに収録した完成版のソース・コードと比較し、間違いがないかを確認しよう。

*2 XUL(XML User interface Language)とは、XMLを応用した言語。スタイルシートやJavaScriptのコードは、XULドキュメントからFirefoxに読み込まれる。

*3 拡張機能の作成には、連載第1回(2006年7月号)で使った拡張機能のサンプルを前提とした。第1回の操作を実行していない場合は、付録メディアに収録したサンプル「sample.zip」を展開し、「helloworld@example.com」フォルダを取り出し、Firefoxのユーザー・プロファイル・フォルダにある「extensions」フォルダの中に移動しておく必要がある。ユーザー・プロファイル・フォルダは、Linuxなどの場合は「~/.mozilla/firefox/Profiles/<ランダムな文字列>.default/」、Mac OS Xの場合は「~/Library/Application Support/Firefox/Profiles/<ランダムな文字列>.default/」、Windows XPの場合は「C:\Documents and Settings\<ユーザー名>\Application Data\Mozilla\Firefox\Profiles\(ランダムな文字列).default」となる。

*4 XULドキュメントは、他のXULドキュメントを読み込んで、ドキュメント内部の記述内容を結合できる。これをオーバーレイと呼ぶ。

*5 ここでは、「viewSidebarMenu」というIDを持つメニューに追加された項目が、label属性やtype属性などを継承する。

*6 oncommandイベント・ハンドラは、ボタンのクリック、メニューの選択、キーボード操作、といったさまざまな方法で項目が選択・実行された際に呼ばれる。汎用性は高いものの、XULドキュメントでのみ利用可能。

*7 フォルダ名の「@」以降の部分は、独自ドメインを持っている場合はそのドメイン名を、ドメインを持っていない場合はメール・アドレスを基にしたドメイン風の文字列(例えば「shimoda@good-day.co.jp」であれば、「shimoda.at.good-day.co.jp」など)を書くとよい。ID文字列の「@」以降は、実在するドメイン名でなくても認識される。

*8 大文字小文字を区別して一括置換できるテキスト・エディタを使うとよい。

*9 flexは、要素を伸縮可能にする属性。flex属性が指定されていると、内容幅やウインドウ幅に応じて要素自体が自動的に伸び縮みする。

*10 http://www.w3.org/DOM/などを参照。FirefoxではDOM2までのほとんどと、DOM3の一部をサポートしている。

*11 あるいは、oncommandイベント・ハンドラをkey要素に直接記述してもよい。

*12 XPIとはCross(X) Platform Installerの略。

*13 7-Zipは、http://www.7-zip.org/ja/から入手できる。

Firefoxの拡張機能を作ろう~最終回

気の利いた拡張機能を作ってみよう

(2006年9月1日、日経Linux)

拡張機能を広く一般に普及させるためのポイント
サイトへの登録方法

前回は、実用的な拡張機能を作るための基本手順を紹介しました。連載の締めくくりとなる今回は、作成した拡張機能を広く一般に普及させるためのポイントである国際化やサイトへの登録方法などを具体的に紹介します。

自分で作成した拡張機能
国際化(多言語対応)

自分で作成した拡張機能を広く一般に使ってもらうにはどうしたらよいのでしょうか。それには3つのポイントがあります。(1)国際化(多言語対応)、(2)拡張機能へのテーマの組み込み、(3)拡張機能のポータル・サイトへの登録、です。

国際化の方法

まずは、国際化の方法から解説しましょう*1。

拡張機能を国際化する
30以上の言語に対応

Firefox自体は日本語を含む30以上の言語に対応しています。ダウンロード・ページからリンクをたどると、英語版のほかに中国語版やフランス語版、ドイツ語版など、各言語圏向けにローカライズ*2されたFirefoxを入手できます。

分離された言語リソース
さまざまな言語への対応

さまざまな言語への対応を容易にするために、Firefoxの内部では、メニューに表示するラベルなどの文字列のほとんどすべてがプログラム本体から分離されています。このため、日本語や英語などの言語を簡単に切り替えられます。このように分離された言語リソースのことを「ロケール」や「言語パック」などと呼びます。

ロケール
DTD(Document Type Definition)

ロケールには大きく分けて、DTD(Document Type Definition)*3による言語リソースと、JavaScriptから直接参照するためのプロパティ・ファイルによる言語リソースという2種類のリソースが含まれます。DTDはXULドキュメント内に文字列を埋め込むために、一方のプロパティ・ファイルは警告ダイアログなどに表示するメッセージを定義するためなどに使われています。

言語リソースを分割する
エンティティ宣言

DTDによる言語リソースでは、文字列を「エンティティ宣言」を使って定義します。まずはエンティティ宣言を用いて、現在XULドキュメント内に埋め込まれている文字列を分離してみましょう。

DTDファイル

「エンティティ宣言」は、このような書式で記述します。このようにエンティティ宣言だけが書かれたテキスト・ファイルをDTDファイルと呼びます。定義されたエンティティは、XULドキュメントの中で参照します。このXULドキュメントをFirefoxに読み込むとエンティティが展開されて、同等に扱われます。

エンティティ宣言に変える

前回までに作成した「Page Holder」拡張機能の中に埋め込まれていた文字列を抜き出してエンティティ宣言に変えたものです。これをpageholder.xulなどと同じフォルダに「pageholder.dtd」というファイル名で保存しましょう。

文書型宣言
XMLエラー

pageholder.xul、panel.xulの該当部分をそれぞれエンティティ参照に置き換えたものです。冒頭に追加された行は文書型宣言と呼び、この行が外部ファイルとして用意したDTDを読み込みます*4。以上の変更を保存してFirefoxを再起動し、Page Holderの動作を確認してみましょう。エンティティ宣言にもXULドキュメント内のエンティティ参照にも間違いがなければ、今まで通り正常に動作するはずです。XMLエラーが起きてしまったときには、pageholder.xulやpanel.xulをFirefoxに読み込んでエラー・メッセージを確認し、変更した部分をよく見直してください。

日本語の言語リソースを作る
表示言語を切り替え

作成した言語リソースは、英語のラベル文字列だけを含んでいます。国際化の練習として、日本語版の言語リソースを作成し、Firefoxの利用環境に合わせて表示言語を切り替えられるようにしてみましょう。

フォルダ構成を変更
フォルダ・ツリー

日本語版の言語リソースを作成する前に、フォルダ構成を変更します。フォルダ・ツリーを参考に、chromeフォルダの中に「content」フォルダを作成し、その中に拡張機能の核となる部分を移動します。

「locale」フォルダ
「en-US」と「ja」
エンコーディングをUTF-8

編集後、エンコーディングをUTF-8にして*7、ファイルを上書き保存します。

ロケールを登録する
Chrome URLを割り当て

作成したロケール用のフォルダにChrome URL*8を割り当てるために、マニフェスト・ファイルを編集する必要があります。chrome.manifestをテキスト・エディタで開き、書き換えましょう。

言語コード名

最初の行は前回も使いました。フォルダの構成の変更に合わせて記述を修正しています。2行目と3行目はロケールに対してChrome URLを割り当てるための定義文です。他の行と同様にタブ区切りで記述しますが、最初の部分が「content」ではなく「locale」になっている点と、実体となるフォルダのパスの前に言語コード名を記述している点が異なります。

日本語版Firefox
jaフォルダ

2行目と3行はよく似ていますが、これによって、「chrome://pageholder/locale/」というChrome URLに対してen-USフォルダとjaフォルダの両方が結び付くわけです。このChrome URL一つで、英語版Firefoxでは「chrome/locale/en-US/」が、日本語版では「chrome/locale/ja」が自動的に参照されます。

XULドキュメント
DTDファイルを読み込む

最後に、XULドキュメントからDTDファイルを読み込むために記述を修正します。pageholder.xulとpanel.xulをテキスト・エディタで開き、冒頭のDTDファイルを参照している文書型宣言を、Chrome URLを使って書き換えましょう。

言語リソースの登録
マニフェスト・ファイルの記述ミス

以上で言語リソースの登録は完了です。Firefoxを再起動して、どのように変わったのかを確認してみましょう。ファイルの配置ミスやマニフェスト・ファイルの記述ミス、言語リソースのエンコーディングなどの間違いがなければ、「Page Holder」のメニューのラベルやサイドバー内のボタンのラベルが日本語で表示されるはずです*9。

説明文も国際化する
拡張機能マネージャ

Firefoxのメニューにある「拡張機能マネージャ」で表示される名前や説明文は、isntall.rdfに記述したものです。拡張機能マネージャは、Firefoxが内蔵する拡張機能を管理するための標準の機能です。管理画面を開くには[ツール]-[拡張機能]と操作します。

プロパティ・ファイル
言語リソース

プロパティ・ファイルという形式の言語リソースを使うと、ここでの説明文なども言語に応じて切り替わります。プロパティ・ファイルは、「プロパティ名」と「値」が対になった形式のテキスト・ファイルです。プロパティ名の後に「=」が続き、その後に対応する値が書かれています。具体的には次のような形式です。

test_menu_label=Test Menu

拡張機能名と説明文
pageholder.properties

まず、拡張機能名と説明文を記述したプロパティ・ファイルを、英語用と日本語用に用意します。それぞれ、en-USフォルダとjaフォルダに「pageholder.properties」というファイル名で保存してください。

プロパティ名

プロパティ名の部分は「extensions.<拡張機能のID>.」とします。

日本語用のプロパティ・ファイル
Unicodeエスケープ

日本語用のプロパティ・ファイルの値部分がUnicodeエスケープ*10になっていることに注意してください。Firefoxでは、プロパティ・ファイルやJavaScriptのファイルを文字列値についてはUTF-16で、それ以外の部分はUTF-8で記述しなければなりません。ここではUTF-16となる部分をUnicodeエスケープで表現し、ファイル全体はUTF-8で提供できるようにしました。それぞれの値のエスケープ前の状態は、「ページホルダー」と「現在のページをサイドバーの中に複製します」です。

JavaScriptファイル

次に、プロパティ・ファイルを読み込むためのJavaScriptファイルを用意します。chromeフォルダと同じ階層に「defaults」という名前のフォルダを作成します。さらにその中に「preferences」という名前でフォルダを作り、「pageholder.js」という名前で保存しておきましょう。

拡張機能のデフォルト設定

このフォルダに置いたJavaScriptファイルは、拡張機能のデフォルト設定を定義しています。ファイルの書式は、設定キー名と値を対にして記述します。このように書けば、内部で使用できる設定情報として解釈されます。今回はこのデフォルト設定ファイルを、拡張機能の名前と説明文を国際化するために使用しています。

拡張機能の名前と説明文

「extensions.pageholder@nikkeibp.co.jp.name」と「extensions.pageholder@nikkeibp.co.jp.description」は、それぞれ拡張機能の名前(name)と説明文(description)を保持しておく設定キー名です。この設定キー名と対になっている値は、プロパティ・ファイルのChrome URLです。プロパティ・ファイル内のプロパティの値がキー名ごとに自動的に読み込まれて、設定値として使われます。

拡張機能の説明文

デフォルト設定ファイルを保存したら、Firefoxを再起動して拡張機能マネージャを開き、拡張機能の説明文が日本語に変わったことを確認しましょう。拡張機能の名前はここでは変化していませんが、項目を右クリックして「(拡張機能の名称)について...」を選択して表示される情報ダイアログでは、いずれもきちんと日本語で表示されます。

拡張機能にテーマを含める
アイコン画像

前回用意したツールバー・ボタン用のアイコン画像は、白や灰色などの明るい背景の場合を想定して作成したものです。しかし背景が黒に近いテーマだと、ボタンの図柄が見えにくいという問題があります。例えば「BlackJapan」というテーマ*11では、矢印以外の部分が見えにくくなりますし、ボタンの形も全体から浮いてしまいます。

複数のテーマを内蔵

このような場合に備えて、拡張機能側にも複数のテーマを内蔵できます。ボタンのアイコン画像などはコア部分ではなくテーマの一部に含めることで、テーマ(背景色)の変化に対応できます。標準のテーマに合わせたテーマとBlackJapan専用テーマの2種類を内蔵する場合を例に、手順を紹介しましょう。

テーマを用意する
フォルダ構成を変更

テーマを用意する前に、フォルダ構成を再び変更します。フォルダ・ツリーを参考に、contentフォルダと同じ階層に「skin」フォルダを作り、その中に「classic」と「blackjapan」という2つのフォルダを作成しましょう。classicフォルダには標準のテーマ用のファイルを格納します。contentフォルダからclassicフォルダへ、アイコン画像(icon.png)をコピーしておいてください*12。

テーマ用のスタイル・シート

次に、テーマ用のスタイル・シートを用意します。テキスト・エディタで入力後、「pageholder.css」という名前でclassicフォルダの中に保存しましょう。

ツールバー・ボタンの外観

「#pageholder-button [ … ]」と記述することで、IDが「pageholder-button」である要素(ツールバー・ボタン)の外観に関して指定しています。

XUL要素の外観
テーマ内で画像を指定

XUL要素の外観に関するほとんどの指定は、CSSによって代用できます。ボタンやメニュー項目のアイコン画像を指定するには、image属性の代わりにlist-style-imageプロパティを使って定義します。ここではテーマ内で画像を指定するように変更しましたので、pageholder.xul内でimage属性を用いてアイコンを指定している部分は削除しておいてください。

BlackJapanテーマ用

次に、同様のファイルをBlackJapanテーマ用にも用意します。icon.pngをblackjapanフォルダへコピーしてください。

スタイル・シートのファイル名は共通

テーマ間での混乱を避けるために、テーマが異なってもスタイル・シートのファイル名は共通にしておくことを勧めます。今回は画像の中身だけが異なるので、classicフォルダ内のpageholder.cssをblackjapanフォルダにそのままコピーするだけで構いません。もしテーマごとに細かく表示を変えたい場合は、スタイル・シートをテーマごとに調整してみましょう。

テーマを登録する
chrome.manifest

ロケールの場合と同様に、先ほど作成したフォルダにChrome URLを割り当てます。chrome.manifestをテキスト・エディタで開き、書き加えてください。

Chrome URLを割り当て

「style」から始まっている行が、テーマに対してChrome URLを割り当てるための定義文です。この行の内容もタブ区切りで記述します。

テーマ名を記述

テーマの場合もロケールと同様、実体となるフォルダのパスの前にテーマ名を記述します。これによって、「chrome://pageholder/skin/」というChrome URLに対してclassicフォルダとblackjapanフォルダの両方が結び付けられます。現在選択されているテーマと同じ名前のテーマがある場合は、そのテーマにアクセスするように、同じ名前のテーマがない場合は「classic/1.0」テーマにアクセスするようになっています。

スタイル・シートとXULドキュメントを関連付け

最後の2行は、スタイル・シートとXULドキュメントを関連付けるためのものです。xml-stylesheet処理命令を使わずにスタイル・シートをXULドキュメントに読み込ませることが可能になります。ツールバー・ボタンのアイコン画像に関する指定を含んだスタイル・シートですから、Firefoxのブラウザ・ウインドウと、ツールバーのカスタマイズ・パレットのウインドウの2つに関連付けています。

テーマの登録が完了

以上でテーマの登録が終わりました。Firefoxを再起動して、今まで通りのアイコンがボタンに表示されているかどうかを確認しましょう。また、BlackJapanテーマをインストールしてそのテーマに切り替え、Page Holder用のボタンのアイコンもそれに合わせて変化することも確認しておきましょう。もしアイコン画像が表示されない場合は、スタイル・シートやマニフェスト・ファイルの記述が間違っている可能性があります。

Mozilla Add-onsに登録する
作成した拡張機能

作成した拡張機能を広く使ってもらうには、Mozilla Add-ons(https://addons.mozilla.org/)に登録するのが一番です*13。

Mozilla Add-ons
FirefoxやThunderbird用の拡張機能、テーマ、検索プラグイン

Mozilla Add-onsは、FirefoxやThunderbird用の拡張機能、テーマ、検索プラグインなどの総合Webサイトです。自分が作った拡張機能を登録してアップロードでき、Webサイトを持っていなくても配布できます。旧バージョンのファイルの保管や、拡張機能を更新した際のユーザーへの通知機能もありますので、拡張機能の開発者にとっては非常に有用なWebサイトです。

アカウントを取得する
Create an Account

Mozilla Add-onsに初めて拡張機能を登録する際は、まずアカウントを作成します。Mozilla Add-onsの開発者向けのWebページ(https://addons.mozilla.org/developers/)を開き、「Create an Account」と書かれたところにある「Join Mozilla Update!」というリンクをたどって、アカウント作成ページを表示しましょう。

メール・アドレス、作者名

アカウント作成ページでは、メール・アドレス、作者名として表示される名前(ニックネームも可能)、自分のWebサイトのURI(省略可能)、ログイン用のパスワードという4項目を入力します。必要事項を記入して「Join Mozilla Update」ボタンを押すと、「Processing New Account Request、 Please Wait...(アカウントを作成中です、しばらくお待ちください)」と書かれたページに切り替わります。

アカウントの有効化

しばらく待つと、フォームに入力したメール・アドレスあてに確認のためのメールが届きますので、その中に書かれている「https://addons.mozilla/org/developers/createaccount.php?...」という長いURIを開きます。「Activate Your Mozilla Update Account(アカウントが有効化されました)」と書かれたページが表示されれば、アカウントが作成できました。

Mozilla Add-onsの開発者向けトップ・ページ
ログイン

Mozilla Add-onsの開発者向けトップ・ページに戻り、「Developers Login」と書かれたフォームに登録時に使用したメール・アドレスとパスワードを入力して「Login」ボタンを押し、ログインしてください。

ファイルをアップロードする
拡張機能ファイルを登

ここで、拡張機能ファイルを登録します。

登録済みの拡張機能一覧

Mozilla Add-onsにログインすると、登録済みの拡張機能を一覧するページが現れます。

拡張機能のアップロード画面

ここで「Add New Extension...」と書かれたリンクをたどると、拡張機能のアップロード画面に移ります。ファイル選択用のフォームが表示されるので、作成したXPIパッケージ*14のファイルを指定して、「Next」ボタンを押しましょう。XPIパッケージがきちんと様式に則って作成されていれば、拡張機能の詳細情報を記入する画面に移ります。

拡張機能の名前や開発者名

この画面では拡張機能の名前や開発者名などを入力します。install.rdfを正しく記述していた場合、install.rdfに書き込んだ拡張機能名や、対応するFirefoxのバージョンの範囲、簡単な説明文などが自動的に読み込まれます。フォームの右側にはその拡張機能が属するジャンル(開発者向けや、タブ・ブラウズ機能の拡張、サイドバーの機能追加、など)がリスト表示されます。該当するジャンルを選んでおきましょう。Ctrlキー(Mac OS XではCommandキー)を押しながら項目をクリックすると、複数の項目を選択できます。

登録作業完了

以上、内容に問題がなければ「Next」ボタンを押します。登録情報に間違いがなければ、これで登録作業は完了です。ログイン直後の画面(Overviewページ)に戻って表示を更新すれば、登録した拡張機能が一覧に表示されます。

スタッフによる動作テスト
不具合の指摘やアドバイスが

ただし、アップロード完了直後にはまだ公開されません。スタッフによって順次レビュー(動作テスト)が進み、大きな問題がないと判断されてはじめて公開されます。このとき、「[拡張機能名] Approval Granted」という表題のメールが登録アドレスあてに届き、レビューが完了したことが分かります。メールには担当者からのコメント(英語)の欄もあり、自分では気付かなかった不具合の指摘やアドバイスが書かれていることがあります。

拡張機能について告知

Overviewページでそれぞれの拡張機能のリンクを見ると、リンク先のURIに「id=****」という形で何けたかの数字が埋め込まれています。この数字は拡張機能に付けられたMozilla Add-onsでの管理用番号です。レビューが完了した後で「https://addons.mozilla.org/firefox/<番号>/」というURIにアクセスすれば、その拡張機能のダウンロード用ページを表示できます。拡張機能について告知する場合は、このURIを紹介するとよいでしょう。

改良やバグ修正
アップデートしたい場合

改良やバグ修正などで、登録済みの拡張機能をアップデートしたい場合は、ログイン直後の画面で拡張機能の名前の下にある「Edit」と書かれたリンクをたどってください。コメントの追記やプレビュー画像の登録フォームと一緒に、アップデート向けのファイル送信フォームが表示されます。ファイルのアップロードからレビュー完了までの手順は、新しい拡張機能の登録と同様です。

Firefoxの拡張機能の作り方

3回に分けて駆け足でFirefoxの拡張機能の作り方を紹介してきましたが、いかがだったでしょうか。大まかな枠組みの紹介だけになってしまいましたが、皆さんがFirefoxやThunderbird用の拡張機能を開発してみたいと思ったときに、いくらかでも参考にしていただければ幸いです。

*1 今回の作業は、前回作成した拡張機能のサンプルを使う。サンプルが手元にない場合は、「sample.zip」を展開して「pageholder@nikkeibp.co.jp」フォルダを取り出し、Firefoxのユーザー・プロファイル・フォルダにある「extensions」フォルダの中に移動する。Linuxなどの場合、プロファイル・フォルダは「~/.mozilla/firefox/Profiles/(ランダムな文字列).default/」となる。今回作成する拡張機能はFirefox 1.5以降の仕様に基づいたものであり、Firefox 1.0系では利用できない。作成したサンプルがうまく動かない場合は、完成した拡張機能のソース・コードと照らし合わせてみよう。

*2 ローカライズとはソフトウエアの地域化を意味する。具体的には、メニューのラベルなどの文字列をターゲットの言語に翻訳するなどの作業を指す。

*3 DTDとは文書型定義のこと。XMLドキュメントの形式を定義するための仕組みである。

*4 DTDファイルを1つだけ読み込む場合の例を示した。2つ以上のDTDファイルを同時に参照したい場合、単純に文書型宣言を複数記述するだけでは文法エラーとなる。

*5 「en-US」は、英語(アメリカ英語)の言語コード名。

*6 「ja」は、日本語の言語コード名。

*7 外部のDTDファイルを参照する場合、Shift_JISやEUC-JPなどのエンコーディングは利用できず、エラーになる。

*8 Chrome URLは、拡張機能などがFirefox内部のリソースにアクセスする際の特殊な形式のURL。プラットフォームごとのパス名の違いを吸収するほか、セキュリティを強固にする役割がある。

*9 Firefoxの表示言語が日本語になっている場合にのみ日本語化される。英語のUIで使用している場合は変化しない。変更が機能しているかどうかを確認するには、ロケーション・バーに「about:config」と入力して詳細設定を表示し、キー名「general.useragent.locale」の値を「ja」に変更してFirefoxを再起動する必要がある。

*10 Unicodeエスケープとは、「\u」に続いてUTF-16の4けたのキャラクタ・コードを書く形式のエスケープ方法。

*11 次のWebページから入手できる(https://addons.mozilla.org/firefox/1390/)。

*12 拡張機能マネージャ用のアイコン画像として使うため、contentフォルダ内にもicon.pngを残しておく。

*13 以下ではファイルのアップロード手順を紹介するが、記事内で作成した拡張機能は解説用のサンプルであるため、Mozilla Add-onsに実際に登録したり、アップロードしたりしないようにする。

*14 XPIパッケージとはFirefoxの拡張機能を配布する際の形式。pageholder@nikkeibp.co.jpフォルダの内容をZIP形式で圧縮し、「.xpi」という拡張子に変更したものである。