Firefox/Thunderbird用拡張機能の開発ノウハウ紹介 ここではFirefoxを中心に据えて話すが、内容はThunderbirdでも通じる。 環境については、最新の公式リリースであるFirefox 1.5/Thunderbird 1.5を前提とする。 ■基本的な話 Firefoxの改変・拡張機能の開発に必要となる基礎的な知識に関する解説。 ■■Firefoxの構造を理解する ■■■Firefoxを構成する技術 XULやJavaScriptの関係をDHTMLと比較。 XULアプリケーションはDHTMLによって作られたWebアプリケーション、HTMLアプリケーションとよく似ている。 ・XUL⇔XML、HTML(フォームコントロール) ・JavaScript ・XPCOM⇔ActiveXObject、サーバで動作するバックエンド それぞれのキーワードとなっている技術自体は、ほとんどが「枯れた技術」である。 ・Windows XPの一部のUIはHTMLアプリケーションで作られていて、ネイティブアプリケーションの代替になっている。 ・WebアプリケーションにはAjaxのようなダイナミックな活用事例がある。 XULアプリケーションも、ネイティブアプリケーションのような高機能なもの、Ajaxのようなダイナミックなものを実装できる。 ■■■各技術の役割 Firefoxの構造を人体に例えて理解 ・骨組み→XUL(構造を形作る) ・内臓や器官→XPCOM(特殊な機能を提供するブラックボックス) ・筋肉→JS(全体を動かす) ・皮膚→CSS(外観を整える) ■■Firefoxを支える技術 ■■■XUL XMLベースの構造記述言語 HTML(特にフォーム関連要素)に似通っている ■■■CSS XML文書を整形するのに適したスタイル定義用言語 比較的人間に理解しやすい書式でスタイル定義を行うことができる GeckoはCSS Level2(2.1)とCSS3に部分的に対応している。 部分的というのは、音声再生のための機能をサポートしていないということも含めての話で、視覚的メディアのための機能についてはかなりの部分をサポートしている。 詳しくはW3Cの仕様を見ること。 ■■■XULとCSS XUL要素はそのままでは表示スタイルが定義されていないので、「ボタンらしく」「メニューらしく」見えるように、標準でスタイル定義がなされている。 また、色やフォント、背景画像、アイコンなど、特に表面的な部分については「テーマ」という形で追加インストールや切り替えが可能である。 属性とCSSプロパティの対応。 XULの属性による表示の制御は、CSSのプロパティや独自プロパティで実現している部分もある。 flex⇔-moz-box-flex orient⇔-moz-box-orient ordinal⇔-moz-box-ordinal-group pack⇔-moz-box-pack など。 ■■■JavaScript ECMAScript(ECMA-262)第3版の仕様に準拠したスクリプト言語(JavaScript 1.6) プロトタイプベースのオブジェクト指向言語 関数の引数や変数の型などに関して制限が非常に緩い、自由度の高い言語 E4X(XMLを簡単に扱えるようにするJavaScriptの拡張仕様)にも対応 ■■■XULとJavaScript XULとJavaScriptは、DOMを介して相互に影響しあう。 JavaScriptからDOMを使ってXULの構造を変更できるし、 XULの構造の変化をDOMを使ってJavaScriptから知ることができる。 Geckoは現在、 DOM2 Core DOM2 Event DOM2 Range DOM2 Traversal DOM3 XPath などに対応している。詳しくはW3Cの仕様を見ること。 ■■■XPCOM プラットフォームに依存しないコンポーネントのフレームワーク、および、 そのフレームワークに基づいて作られたコンポーネント(XPCOMコンポーネント)。 ファイル操作、詳細な通信機能など、様々な機能を提供するライブラリである。 例えばファイル操作についてのXPCOMコンポーネントは、XULアプリケーションからは ・ファイル操作コンポーネント という単一の存在として見えるが、実際には、 ・ファイル操作コンポーネントの実装 for Windows ・ファイル操作コンポーネントの実装 for Mac OS X ・ファイル操作コンポーネントの実装 for Linux という風に、環境ごとに異なる実装(バイナリ)が用意されることになる。 プラットフォームごとの差異を吸収して、一種類のAPIで複数のプラットフォームに対応できるようにするのが、XPCOMの意義。 ■■■XPCOMとJavaScript JavaScriptとXPCOMの関係。 JavaScriptからは、XPCOMはブラックボックスに見える。 XPConnectという仕組みを利用してXPCOMコンポーネントのオブジェクトを取得し、メソッドやプロパティを介して結果を得ることができる。 var obj = Components.classes[***].getService(Components.itnerfaces.nsI***); obj.someMethod(); ■■■Gecko ここまでに述べた技術をサポートした「XMLレイアウト/レンダリングエンジン」がGeckoである。 ・XMLパーサ ・CSSレンダラ ・JavaScript実行エンジン(SpiderMonkey:C言語によるJavaScript実行エンジン) ・画面表示やファイル入出力など、システムレベルの機能へのアクセスを行う機能(NSPR:Netscape Portable Rutime) などの機能を持っている。 ■■■GeckoとGRE Firefoxではパフォーマンス向上のために、Geckoを静的リンクで統合している。 Geckoをライブラリとして利用するアプリケーションの存在を考えたとき、アプリケーションごとにGeckoを内蔵するのはディスク領域の無駄になる。GTKやJREなどのように、Geckoのコア部分をランタイムエンジンとしてまとめたものが存在する。それがGecko Runtime Environment、GRE。 Geckoを利用するネイティブアプリケーションは、GeckoをFirefoxのように内蔵することもできるし、外部に置いたGREとして利用することもできる。 ■■■XULRunner XULRunnerは、XULアプリケーションをネイティブアプリケーションのように使うための実行環境で、Geckoを内蔵している。 Firefoxは、Webブラウザ用途に特化したXULRunnerと言うこともできる。 ■■Firefoxの動的な改変 ■■■JavaScriptの関数の上書き JavaScriptでは関数や変数の上書きができる ・普通の関数はwindowオブジェクトのメソッドになっている ・オブジェクトのメソッドは関数オブジェクトが代入されたプロパティにすぎない このため、メソッドとなっているプロパティに新たな関数オブジェクトを代入すれば、メソッドを再定義することができる。 また、関数の書き換えもできる。 ・関数の内容を文字列に変換して文字列化 ・文字列置換等で関数の内容を改変 ・改変された関数の文字列をevalで解釈するなどして関数オブジェクトを再定義 など。 ■■■CSSのカスケーディング CSSにはカスケーディングという機能がある。 ・後に書かれた(後に読み込まれた)宣言で前の宣言を上書きできる ・セレクタの詳細度、!importantルールなどによって上書きの優先順位が変わる ■■■XULのオーバーレイ XMLではXML文書片の動的な埋め込みの機能が検討されているが、まだ正式には仕様化されていない。 XULでは「オーバーレイ」という独自の機能で、XUL文書片の動的な組み込みをサポートしている。 ■■■XPCOMコンポーネントの追加 既に存在するXPCOMコンポーネントの内容は改変できない。 しかし新たなXPCOMコンポーネントを追加することは可能。 既にあるXPCOMコンポーネントの代わりに新たなXPCOMコンポーネントを使うようにすることができる。 XPCOMコンポーネントの開発にはC++やJavaScriptなどが使える。 ■■■「拡張機能」の実態 以上のように、Firefoxを構成する各技術はそれぞれ「後からの上書き」「機能の追加」をサポートしている。 「拡張機能」の正体は、これらの「上書き機能」「追加機能」を使ってXULアプリケーションに変更を組み込む「動的なパッチ」である。 アプリケーションと特定のルールに基づいてデータをやりとりするだけの「プラグイン」とは根本的に異なる。 できることには事実上、制限はない。 Firefoxに拡張機能としてThunderbirdを組み込んだり、その逆をやったりといったことも、理論的には可能。 ■開発環境を整える ・UTF-8に対応したテキストエディタ(秀丸エディタ、TeraPadなど) ・ZIP形式を扱えるアーカイバ(7-Zipなど) ・dumpの有効化、内部エラー表示の有効化 プロファイルフォルダに「user.js」というファイルを作成し、以下の内容で保存する。 user_pref("browser.dom.window.dump.enabled", true); user_pref("javascript.options.showInConsole", true); ■拡張機能の例:新しい機能を加える 実際に、拡張機能を開発する手順を見ながらチュートリアル形式でノウハウを紹介。 ■■方針の決定 ツールバーに「すべてのタブを閉じる」ボタンを追加する。 仕様は以下の通り。 ・空(about:blank)のタブを一つだけ残して、現在表示しているタブを全て閉じる。 ・ツールバーにボタンを加える。 ・メニューバーのメニューに項目を加える(「ファイル」メニュー内)。 ・キーボードショートカットも追加する(Ctrl-Q)。 ■■IDを決める UUID(GUID)もしくは<拡張機能名@ドメイン名> ここではclosealltabs@piro.sakura.ne.jpとする。 ■■フォルダの作成とファイルの配置 プロファイルフォルダの「extensions」フォルダに、以下のような厚生でフォルダと空のテキストファイルを作成する。 まずextensionsフォルダの中に、ID名のフォルダを作成する。今回は「closealltabs@piro.sakura.ne.jp」である。 extensions//chrome/content/closealltabs/closealltabs.xul extensions//chrome/content/closealltabs/closealltabs.js extensions//chrome/skin/classic/closealltabs/closealltabs.css extensions//install.rdf extensions//chrome.manifest ■■改変する箇所の特定 DOMインスペクタを使って、Firefoxの内部の要素構造を覗いてみる。 ・コマンドを定義している部分() ・ツールバー用のボタンを定義している部分() ・メニューを定義している部分() ・キーボードショートカットを定義している部分() DOMインスペクタのDOMツリーを辿って、要素を探してみる。 検索機能を使って、ラベルなどをキーにして要素を探してみる。 ■■XULの要素構造に挿入する内容を定義する ■■■ひな形の準備 closealltabs.xulに以下の内容を加える。 これはブラウザウィンドウのXULファイルに内容を追加するものである。 2行目で、後で作成する外観の定義のファイルを読み込んでいる。 5行目で、後で作成する挙動の定義のファイルを読み込んでいる。 他のリソースを読み込む時には、相対パスも使える。