株式会社クリアコード > ククログ

ククログ

最新
タグ:

設計判断事例の紹介:E4Xの事実上の廃止を受けての、UxUの開発方針の検討

設計判断とは何か?

ソフトウェアの開発においては、仕様を決める時に、並立できない複数の選択肢の中からどれか1つを選ばなければならないことがあります。特に設計に関わる場面での判断のことを、一般的に「設計判断」と言います。「この新機能を加えるのか、加えないのか」「この新形式をサポートするのか、しないのか」などのような、「やるか、やらないか」の判断はその典型です。

設計判断においては、常に「やる」という判断を下せるとは限りません。様々な事情から「やる」という選択肢を選べないこともありますし、あるいは何らかの明確な理由があって積極的に「やらない」という判断を下すこともあります。

つい最近も、Firefox用アドオン開発における自動テストを支援するテスティングフレームワーク「UxU」において、「Firefox 20以降ではE4Xを使ったテストケースをサポートしないことにする」という設計判断を下しました。これを例にとって、このエントリでは具体的な設計判断のプロセスを解説します。

発端

UxUには、テストケースの記述を容易にするためのユーティリティが多数実装されており、その中には、文字列として与えられたCSVを解釈したり加工したりするといった機能も含まれています*1。これらの機能を使う際には、長い文字列をテストケース中に埋め込む形で記述する方法として、E4XのCDATAマーク区間をヒアドキュメント記法の代わりとして使用することを想定していました。

JavaScriptには現在の所、複数行にわたるテキストを素直に文字列リテラルとしてソースコード中に記述する方法がありません。この問題を解決するためにE4Xを使うことができます。E4Xは本来はJavaScript中にXMLの要素をリテラルとして記述するためのものですが、CDATAマーク区間の記法を利用すると複数行にわたるテキストを比較的素直にリテラルとして記述できます。そのため、以下のようにヒアドキュメント代わりに利用するという用法が、一部の開発者の間で知られていました。

1
2
3
4
5
var longText = <><![CDATA[
      aaa bbb ccc
      ddd eee fff
      ggg hhh iii
    ]]></>.toString();

この用法は、配布物を1つのJavaScriptのファイルにまとめる必要があるユーザースクリプトなどにおいても重宝されているようです。

ところが、2013年にリリースされる予定のFirefox 20開発版において、E4Xを既定の状態で無効化するという決定が下りました(参照:788290 - Turn javascript.options.xml.chrome off by default)。このままFirefox 20がリリースされると仮定すると、UxU用に書かれたテストケースのうちE4Xを利用したテストケースはFirefox 20上では動作しません。

そこで、これを受けて、E4Xを使用して書かれたテストケースについてUxU側で何らかの対策を取ることができないかを検討することにしました。

テスティングフレームワークとして重視したい点

UxU用に書かれたテストケースを「UxUというアプリケーション専用のデータファイル」と捉えた場合、UxUが取るべき姿勢は「可能な限り互換性を保つ」「過去に作成されたデータファイルも、新しいバージョンで問題なく利用できるようにする」ということになります。この点について、疑問を差し挟む余地はないでしょう。

その方針に沿って考えると、今回のFirefoxの仕様変更に対して取り得るUxU側でのアプローチは、以下のようなものがあります。

  1. Firefox(Thunderbird)のE4X機能を自動的に有効化するようにする。
  2. E4Xを自前で実装する。
  3. テストケースの読み込み時に、E4Xをヒアドキュメント代わりに使用している箇所を検出して、文字列リテラルに自動的に変換する。

それぞれのアプローチについて、メリットとデメリットを考えてみます。

Firefoxの隠し設定を変更するというアプローチの検討

前述のbugおよびコミットされたパッチを見るとわかりますが、今回の変更は、E4Xの有効無効を切り替える設定を、既定の状態でオフにしておくというだけのものです。この設定は隠し設定ではありますが、Firefox上で動作するアドオンから見た場合、通常の設定項目のひとつと何ら変わりありません。よって、UxU自身が自動的にこの隠し設定を変更して、E4Xを再度有効化するということは普通にできてしまいます。

このアプローチのメリットは、単にFirefox自体の設定を変更するだけなのでUxU側の負担が非常に小さく済む、という点です。

それに対して、この方法のデメリットは最低でも2つあります。1つは、テスト実行時の環境が「テスト対象のコードが実際のユーザーの環境」からかけ離れた環境になってしまうことです。そもそもUxUがFirefox上で動作するアドオンとして開発されているのは、実際の環境になるべく近い環境でテストを走らせたいという理由からです。テスト実行時に環境そのものを変更してしまうと、「実際の環境でどんな問題が起こりうるか」ということを確かめられなくなってしまいます。

もう1つのデメリットは、この方法が明日にでも利用できなくなってしまうリスクがあるという点です。今回はE4Xの有効無効を切り替える設定の初期値が変わっただけでしたが、E4Xのパーサー自体が削除されてしまった場合、この選択肢はとれなくなります。

E4Xを自前で実装するというアプローチの検討

次に、E4XそのものをUxU側で実装する場合を検討します。

現在、E4Xに対応したJavaScriptの処理系は、C++製のSpiderMonkeyとJava製のRhinoがあり、どちらもオープンソースのプロダクトとして公開されています。よって、極端なことを言えば、これらのJavaScript処理系を丸ごと抱え込んでしまうということも技術的には可能です。

ただ、抱え込むコードの量が増えるという事でもあるため、メンテナンスコストは明らかに増大します。Rhinoのソースコードは全体で20MB、SpiderMonkeyのソースコードは40MBにも及ぶ巨大なプロダクトですし、それらを組み込んで動作させるためのコードも相当な規模になることが予想されます。UxUはクリアコード内製の製品ですが、開発に大きなコストをかける余裕は残念ながらありません*2。開発コストの増大は、プロジェクトそのものの存続を危うくしてしまいます。

また、この場合、必然的に、テスト対象のコードも「UxUが動作しているFirefoxのJavaScript実行エンジン」ではなく「テスト環境用にUxUが内蔵しているJavaScript実行エンジン」で走ることになります。これでは、テスト環境と実環境が大きくかけ離れてしまうという問題が残ってしまいます。

E4X(のCDATAマーク区間)の箇所を文字列リテラルに自動的に変換するというアプローチの検討

E4X全体に対応することは無理でも、E4Xをヒアドキュメント代わりに使っている箇所についてだけ対応することはできないか、という考え方もあります。100%完全にあらゆるケースに対応することは難しくとも、最小のコストで全体の8割程度の場合に対応できるようなやり方があるのであれば、それは有力な選択肢になり得ます。

このアプローチで鍵となるのは、E4Xがヒアドキュメントとして使われている箇所をどのようにして見つけるかです。その方法が十分に簡単なのであれば、この方法は検討に値すると言えます。

この方向でまず思いつくのは、スクリプト全体を文字列として扱った上で、E4Xの部分を正規表現で探して一括置換するという方法です。しかし、この方法には以下のようなスクリプトで「誤爆」が発生してしまうという問題があります。

1
2
var cdataLikeMatcher = /<![CDATA[]/;
if (array[object['property']]>10) { ... }

この問題を回避するためには、「<![CDATA[」という文字列がどのような文脈で登場しているのかを調べて、地の文として登場している場面だけを検出する必要があります。ですが、これは思ったほど簡単な事ではなく、JavaScriptにマクロ記法を導入するライブラリであるsweet.jsで採用されている字句解析レベルでの効率のよい手法を使ったとしても、それなりの規模のコードが必要となります。

「100%完全にあらゆるケースに対応することは難しくとも……」という観点でいえば、そこまでの事をせずとも、単純な文字列置換でできる場合についてはそれで動けばよしとして、動かない場合はE4Xの使用を諦めてスクリプトを書き直してもらう、というやり方もあるように思えるかもしれません。ですが、その場合に生じる「うまく動かないケース」には、E4Xをヒアドキュメント代わりに使っているケースだけではなく、先の例のような「それ自体は何の変哲もないスクリプトだが、たまたまE4Xに似ている文字列が登場する」ケースも含まれ得ます。本来使えないはずのE4XがE4Xとして使えないのは許容できるとしても、本来であれば何の問題も起こらないはずのスクリプトまでもが「E4X対策の影響」で動作しなくなってしまうのでは、本末転倒もいいところです。

前提を見直す

ここまでいくつかのアプローチを検討してきましたが、残念ながらどのアプローチにも問題があり、選ぶに選べない結果となってしまいました。

このように行き詰まってしまった時は、行き詰まりの原因になっている前提を疑ってみることも必要です。いくつかの選択肢を前にして、必ずしもその中からどれかを選ばなくてはならないということはありません。ある観点で見た時に解決策が見えなくても、視野を広げて別の観点から問題を捉え直すことで、解決策が見えてきたり、あるいは、問題が問題ではなくなってくることがあります。ここでは、「UxUでこの問題に対策を行わないといけない」という前提自体を疑ってみます。

そもそも今回の件の発端を考えると、問題提起の仕方は「E4Xをヒアドキュメント代わりに使っている部分がFirefox 20以降で動かなくなる。どうしよう。」というものでした。「E4Xの仕様がサポートされない」「E4Xで書かれた部分が動かない」という点は元々は問題視していなかったのです。

そして、ここでもう1つ大事なのが、ヒアドキュメントの記法があるか無いか・使えるか使えないかというのは、UxUに求められる機能というよりも、JavaScript一般で求められる機能であるという事です。

設計上の判断材料の1つとして、プロジェクトの責任範囲は常に頭に入れておく必要があります。いくら利便性が高まるといっても、本来の責任範囲を逸脱した部分に手を出し始めてしまうと、そのうち収拾がつかなくなってしまいます。今回はUxUのテストケースでのニーズが発端ではありましたが、「FirefoxやThunderbirdのアドオンの自動テストを支援する」というUxUプロジェクト本来の目的を考えると、これはいささか外れたトピックと言わざるを得ません。「Firefoxで動作するスクリプトはテストケース中でも全部利用できること」「UxUが提供するユーティリティやアサーションが正常に機能すること」といった事柄は本来の目的から演繹できる責任範囲ですが、「Firefoxが対応しなくなったJavaScriptの文法上の拡張仕様に対応すること」を演繹することは難しく、UxUの責任の範囲外にあると言えます。

結論

以上を踏まえて、最終的に、UxUプロジェクトはE4Xの事実上の廃止に対しては「特に何もしない」という判断を下すことにしました*3

この結論に至るまでに、以下の要素が判断基準として登場しました。

  • 手間(コスト)はどれだけ必要か。
  • プロジェクト本来の目的を疎外しないか。
  • どれだけの場合に対応できるか。(100%すべての場合に対応できるのか、一部のエッジケースには対応できないのか。)
  • 追加・変更が必要なコードの量はどの程度か*4
  • プロジェクトの持続性にどれだけ影響するか。
  • プロジェクトの責任範囲に含まれるか。

ある点を突き詰めると別の点で問題が増大するという風に、判断基準同士の間にトレードオフの関係がある場合、すべての基準を満足させる選択肢というものは無いことになります。その時に重要になるのが、判断基準同士の中での優先度です。そのプロジェクトが何を大事にしているのか・何を大事にしていくのかという軸がはっきりしていればいるほど、判断のぶれは小さくなりますし、素早い判断が可能になります。

また、目先の問題や、最初のよく調べていない時点で仮に設定した目標に囚われてしまうと、そのまま泥沼に突っ込んで戻って来られなくなってしまうことがあります。今回の事例で言えば、「E4Xの仕様を完全に満たさないといけない」という意識に囚われたばかりに、字句解析処理の実装に着手したものの、いつまで経っても完成に辿り着けず、そのうちプロジェクト自体が頓挫してしまう、という「最悪のケース」に陥ってしまう可能性も十分にありました。

自分が今進もうとしている道の先にあるのが底なし沼なのかどうなのかを早めに見極めて、これは駄目だとなったら深手を負う前に引き返すことが大切です。そのためにも、何を大事にするのかという判断基準が必要になります。「そこは大事にしなくてもよい」ということが分かっていれば、引き返す判断を早めに下しやすいですし、埋没費用の発生もなるべく小さく抑えられます。

最良の選択を最初の時点ですぐに行い、それに向かって無駄なく一直線で進むのが、理想的といえば理想的です。しかし、最初の時点ですべてを知り尽くせていない事はままあります。今回も、調べ始めてみるまで「ソースコードの中で、どこがE4XのCDATAマーク区間なのかを判別する」ということがそこまでコストの高い事であるとは分かっていませんでした。最初にすべての方針を決めてまっすぐ邁進することに比べると、このような進め方は一見、無駄が多く不格好かもしれません。しかし、一度に投入できるリソースに厳しい制限がある場合には、その時々の目標設定を随時見直してスピーディーに方針を転換していく方が、リスクやコストを平滑化できるのではないでしょうか。

もちろんその時には、ぶれない大目的が存在していることが前提にあります。大目的無しに方針転換を繰り返すのは、ただの迷走です。目標は、目的に向かって進むための途中の道しるべに過ぎません。今回の例で言えば、「アドオンのコードを実際の環境に近い環境で簡単にテストできるテスティングフレームワークを提供すること」が目的で、「E4XのCDATAマーク区間を使ったヒアドキュメント的な機能を利用できること」は(仮の)目標です。目的のためには目標は動かしてもいいものです。その逆に、目標のために目的が犠牲になって(目的がぶれて)はいけません。

皆さんもプロジェクトを運営される際には、目的をはっきりと持ち、それに則った設計判断を下すよう心がけましょう。

付録:ユーザ側での、あるいは別プロジェクトとしての解決の方向性

なお、UxUプロジェクトとしては「責任の範囲外なので、この件についてはなにもしない」という態度を取るとしても、実際にUxUを利用してテストケースを書くユーザー開発者としては、何らかの対策がないと困ります。そこで、UxUとは別のレイヤーでこの問題を解決する方法についても検討しました。

ヒアドキュメント風の機能を実現する別のアプローチとして、文法を拡張するのではなく、JavaScriptの文法の範囲内で実現するという考え方があります。その実例が、cho45さんがNode.js用に開発されているnode-hereというライブラリです。

このライブラリは「ライブラリが提供するhere()という関数が呼び出された時のスタックトレースを参照して、ソース中の何文字目にあたる箇所で関数が呼ばれたのかを特定し、ソースを文字列として読み込んだ上でその箇所に書かれたコメントの内容を抽出して、それを文字列として返す」という処理を行うものです。スクリプトが実行される時点では純粋に単なる関数呼び出しでしかないため、これのせいで普通のスクリプトが動作しなくなるといった副作用もありません。Node.js(V8)用に開発されていますが、動作だけを見ればFirefox上でも同じようなことができるはずです*5

調査の結果、このアプローチにも限界があることは分かっています*6が、実用上はほぼ問題ないと考えられます*7

今回はUxUプロジェクトとしての対応は見送りましたが、このような機能が十分に小さなライブラリとして実装されるならば、標準添付のライブラリの1つとして含めるという選択もあり得るかもしれません*8

何かの機能を廃止したり、今まではできたことをできなくしたりすると、ユーザーからは必ず反発があります。有効な代替案がないままにただ「できなくなりました」とだけ言い放ってしまうと、ユーザーの不興を買ってしまい、ユーザー離れを引き起こしたり、古いバージョンを使い続けられてしまったりする事もあります*9。「なぜできなくなったのか」という事を説明したとしても、この点は変わりません*10。ですのでこのような場合には、ユーザーの負担を減らすためにも、何らかの形で有効な代替案を用意しておくか、案内をしておくのが望ましいでしょう。

*1  主に、データ駆動テストで利用することを想定しています。

*2  UxUの開発そのものは会社の収入に直結しない上に、その間、収入に繋がる他の業務もストップしてしまうため。

*3  もちろん、UxU自身のコードでE4Xを使っている箇所があれば、そのままではUxU自体が動作しなくなってしまうため、その点の修正は厭わない方針です。

*4  変更の量は少なくても、その後に致命的な悪影響を与える変更、というものもあるので、量だけが重要というわけではない。

*5  実際に、再起動不要なアドオンの開発用の簡易フレームワークRestartlessの一部としてhere.jsを実装しています。

*6  Node.jsではスタックトレースから行内のカラム位置を取得できるため、正確に関数呼び出しが行われた位置を把握できるのですが、Firefoxではスタックトレースから行番号までしか特定できないため、同じ行に複数のhere()が存在していた場合に、どのhere()が呼ばれたのかがわかりません。

*7  元々の目的が「複数行にわたる文字列をすっきり記述したい」という事であると考えれば、1行に複数のhere()が登場するようなケースは相当イレギュラーであると言えるため。最小の実装でほとんどの場合に対応できるのであればよしとする、という考え方に基づけば、この点は目をつぶることができる。

*8  例えば、非同期処理のテストを行う際の利便性向上のため、UxUにはJSDeferredを標準添付している。

*9  実際に、愛用しているアドオンが対応しているという理由からセキュリティ上の脆弱性がある古いバージョンのFirefoxを使い続けている人もいる。このような事態が発生すると、結果的にユーザーを危険に晒してしまうことになるため、非常によろしくない。

*10  背景事情など、ユーザーにとってはどうでもいいことなので。

タグ: Mozilla | UxU
2013-01-16

Fx Meta Installerを使った、カスタマイズ済みのFirefoxのインストーラーの作り方

クリアコードでは、Firefoxサポート事業やThunderbirdサポート事業の一環として、ユーザー企業さまの社内で使うためにFirefoxやThunderbirdを一括導入するお手伝いを承っております。その際、クリアコードでは「Fx Meta Installer」というソフトウェアを利用しています。Fx Meta InstallerはGitHubにて公開していますので、誰でも自由に利用することができます。

この記事では、Fx Meta Installerの概要と、簡単な利用手順を解説します。

  1. はじめに
    1. Fx Meta Installerとは何か?
    2. Fx Meta Installerの実態
    3. 使用・利用の条件について
  2. 必要なもの
    1. Windowsでのビルド環境の構築
    2. Linuxでのビルド環境の構築(Debian、Ubuntu)
  3. ビルドしてみよう
    1. ビルド用設定ファイルの作成
      1. インストールするアプリケーションの設定
      2. Firefoxのバージョンの指定
      3. Firefoxのインストーラーの取得方法に関する設定
      4. サイレントインストールやウィザードの挙動に関する設定
      5. 既定のブラウザの設定
    2. 同梱するファイルの準備
      1. Firefoxのインストーラー
      2. アドオン
      3. 集中管理用の設定ファイル
    3. ビルドする
      1. ビルドスクリプトの実行
      2. メタインストーラーのテスト実行
      3. 配布用メタインストーラーの作成
  4. おわりに

はじめに

Fx Meta Installerとは何か?

Firefoxのインストーラーにはサイレントインストール*1機能があります。しかしながら、この機能ではFirefoxのインストール先やショートカットの作成の有無などは制御できるものの、Firefoxの細かい設定までは変更することができません。設定や挙動を変えた状態にするためには、Firefoxをインストールした後で変更を行うか、もしくは、ソースコードを変更した上で独自ビルドを作成するかのどちらかの方法を選ぶ必要があります。

Fx Meta Installerは、前者の方法でのカスタマイズを支援するソフトウェアです。Fx Meta Installerの最終生成物はWindows用の実行ファイル(インストーラー)となり、これを実行すると、Firefoxのインストール、設定の変更(同梱の設定ファイルの設置)、および同梱したアドオンのインストールを自動的に完了できます。Firefoxのインストーラーは同梱することもできますし、ネットワーク経由で自動的に取得してくることもできます。「Firefoxのインストーラーをキックするインストーラー」ということで、「Fx Meta Installer」という名前にしました。

Fx Meta Installerの実態

Fx Meta Installerは、2つの実行ファイルを作成します。1つは、実際のインストール処理を行うモジュール「fainstall」。もう1つは、実際に頒布することになる最終生成物の実行ファイル「メタインストーラー」です。メタインストーラーの実態は、fainstallとその他の頒布物を併せて7−Zip形式で圧縮した自己解凍書庫ファイルとなります。

Fx Meta Installerは元々、以下のような意図の元で開発されていました。

  • Firefoxを「Firefox用のアドオン形式で開発されたアプリケーションを実行するための依存ライブラリ」と見なして、アドオンのminVersion/maxVersionの条件を満たすバージョンのFirefoxがまだインストールされていなければ(依存関係が満たされていなければ)Firefoxをインストールする。依存関係が満たされていれば、何もしない。
  • その上で、Firefoxの組み込みのモジュール*2としてアドオンをインストールする。

その後、カスタマイズ内容をアドオンの形式で提供することの利点に着目し、FirefoxやThunderbirdの導入案件において利用しやすいよう機能の拡張を行ってきました。その結果、現在ではFirefoxとアドオンの同時インストールだけでなく、集中管理用の設定ファイルの設置や、Firefoxに任意のオプションを指定した状態で起動するためのショートカットの作成なども行えるようになっています。

使用・利用の条件について

fainstallについては、使用および利用にあたっての条件は特にありません。個人的利用、法人での利用、商用利用など、自由に使っていただいて問題ありません。

それに対して、最終的な頒布物となるメタインストーラーの使用および利用の許諾条件は、そのメタインストーラーによってインストールされるソフトウェア自体の使用および利用の許諾条件に依存しますので、くれぐれもご注意下さい。例えば、以下のような制限があり得ます。

  • 同梱したアドオンやプラグインの利用許諾条件として、非営利での利用が必須条件となっている場合、メタインストーラーを有償で販売することはできません。
  • Firefoxを自動的にインストールするように設定した場合、メタインストーラーを不特定多数に向けて無断で一般公開することはできません。一般公開のためにはMozillaの許諾を得る必要があります。詳しくは法人向けFAQの「製品をカスタマイズして配布しても構わないのですか?」の項を参照して下さい*3

必要なもの

Fx Meta Installerは、FirefoxのWindows版インストーラーと同じく、Nullsoft Scriptable Install System(NSIS)によって開発されています。よって、fainstallおよびメタインストーラーをビルドするためには、NSISスクリプトのビルド環境を用意する必要があります。各プラットフォームでのビルド環境構築の手順は以下の通りです。

Windowsでのビルド環境の構築
  1. NSIS v2.46をダウンロードし、インストールします*4
  2. 以下に挙げる、必要なプラグインをインストールします。DLLファイルはC:\Program Files\NSIS\Plugins以下に、ヘッダーファイル*5はC:\Program Files\NSIS\Include以下にインストールして下さい*6
  3. 「config.bat.sample」をコピーし、「config.bat」という名前にします。必要に応じてNSISのインストール先のパスを修正します。例えば、64bit版のWindows 7でNSISを既定の設定でインストールした場合は、「C:\Program Files (x86)\NSIS」と書き換えます。
Linuxでのビルド環境の構築(Debian、Ubuntu)
  1. 必要なパッケージをインストールします。

    $ sudo apt-get install nsis
  2. 必要なプラグインをインストールします。プラグインの一覧は「Windowsでのビルド環境の構築」と同様です。DLLファイルは/usr/share/nsis/Plugins以下に、ヘッダーファイルは/usr/share/nsis/Include以下にインストールして下さい。

ビルドしてみよう

それでは、以下のような仕様のメタインストーラーを作成するという想定で、ビルド手順を順番に解説していきましょう。

  • 必ずFirefox 16.0.2をインストールする。インストーラーの実行ファイルは同梱する。
  • ウィザードを使わず、実行したら所定の設定でインストールが自動的に完了するものとする。
  • インストール後、Firefoxを既定のブラウザとする。
  • インストール中、何が起こっているか分かるように、進行状況のプログレスバーは表示する。
  • システムモニターを同梱する。
  • 初期設定として、Firefoxとアドオンの自動更新を無効化する。また、この設定をロックして、ユーザーが自分で有効化できないようにする。
ビルド用設定ファイルの作成

Fx Meta Installerの設定は、ビルド時と、ビルド後の2つのタイミングで行うことができます。基本的にはビルド時の設定だけで問題ありませんが、NSISのビルド環境が整っていない環境でも細かい挙動を調整できるように、一部の設定はビルド後も「fainstall.ini」というファイルを使って変更できるようになっています。また、機能の中にはfainstall.iniに設定を書き加えることでしか利用できないものもあります。

ビルド時の設定は、config.nshで行います。サンプルファイルが「config.nsh.sample」という名前で含まれていますので、最初はこれをコピーして使ってください。

このファイルでは、以下の書式で設定を記述します。

!define 設定名 "設定値"

サンプルファイルの内容のままビルドした場合、できあがるメタインストーラーは以下のような仕様になります。

  • Firefoxをインストールする。
  • Firefoxのバージョンは10から99までの間を想定する。
  • Firefoxのダウングレードは行わない。
  • Firefoxのインストーラーが同梱されていない場合は、インストールを中断する。
  • ウィザードを表示する*7
  • Firefoxのインストーラーのウィザードは表示しない。
  • Firefoxを既定のブラウザとしない(現在の状態を維持する)。

では、それぞれの設定を必要に応じて変更していくことにします。

インストールするアプリケーションの設定

Fx Meta Installerは、FirefoxとThunderbirdのどちらかを選択してインストールできるようになっています。Thunderbird用のメタインストーラーを作成する場合には、以下の箇所を変更します。

!define APP_NAME "Thunderbird" ; "Firefox" から変更する

今回はFirefox用のメタインストーラーを作成しますので、この部分の設定は変更しません。

Firefoxのバージョンの指定

Fx Meta Installerでは、Firefoxのバージョンについて細かい指定ができるようになっています。サンプルの設定は、以下のようになっています。

  • Firefoxが全くインストールされていない場合は、Firefoxを新たにインストールする。
  • 10.0から99.99までの間のいずれかのバージョンのFirefoxがインストールされている場合は、何もしない*8
  • 10.0よりも古いバージョンのFirefoxがインストールされている場合は、上記の範囲のバージョンのFirefoxにアップグレードする。
  • 99.99よりも新しいバージョンのFirefoxがインストールされている場合は、インストール処理全体を中止する。

今回はFirefox 16.0.2のインストールを行うようにしたいところですが、サンプルのままだと、Firefox 10やFirefox 11のように古いバージョンがインストールされている環境では、Firefoxのインストール処理がスキップされてしまいます*9。そこで、以下の通り変更します。

!define APP_MIN_VERSION "16.0.2" ; "10.0" から変更する

また、Firefox 17やFirefox 18のように新しいバージョンがインストールされている環境でも、やはりFirefoxのインストール処理はスキップされてしまいます。企業向けの導入においては、システム管理部門で検証済みのバージョンのみ許可し、検証されていないバージョンは、たとえセキュリティアップデートであっても導入を許可しない、という風にFirefoxのバージョンを厳密に固定する場合があります。今回は新しいバージョンのFirefoxが導入済みの場合でも必ずFirefox 16.0.2をインストールするようにしたいので、以下の通り変更します。

!define APP_MAX_VERSION "16.0.2" ; "99.99" から変更する
!define APP_ALLOW_DOWNGRADE      ; コメントアウトを外す
Firefoxのインストーラーの取得方法に関する設定

Fx Meta Installerは、FirefoxやThunderbirdのインストーラーが同梱されている場合にはそれを使い、同梱されていない場合にはNASに置かれたファイルを利用し、それもなければWebサーバーからインストーラーを自動的にダウンロードしてくる、という風に、Firefoxのインストーラーの取得元を順番にフォールバックしていく機能を含んでいます。これは、以下の箇所のコメントアウトを外して適切な設定を行うことで利用できます。

;!define APP_DOWNLOAD_PATH "\\fileserver\shared\Firefox Setup 15.0.1.exe"
;!define APP_DOWNLOAD_URL  "http://download.mozilla.org/?product=firefox-15.0.1&os=win&lang=ja"
;!define APP_HASH          "8d017402de51a144d0e0fe4d3e2132cb"

サンプルからわかる通り、取得したインストーラーが確かに想定しているファイルと同一かどうかを確かめられるように、MD5チェックサムを指定することもできます*10

なお、いずれの方法でもインストーラーを取得できなかった場合には、メタインストーラーはインストール処理を中止します。

今回はネットワーク経由での取得は行わないため、上記の箇所は変更しません。

サイレントインストールやウィザードの挙動に関する設定

Fx Meta Installerはサンプルの状態では、一般的な「インストールの可否を訊ねる」「EULA(エンドユーザー向けの利用許諾書)への同意を求める」といったウィザードを表示するようになっています。

企業向けの導入においては、ログイン時に特定のファイルを自動的に実行するなどの方法で一括導入を行う場合があり、そのようなケースではウィザードの操作を省略する必要があります。そのようなニーズに対応するため、Fx Meta Installerでは2段階のサイレントインストールが可能となっています。

ウィザードやダイアログの類を一切表示しない完全なサイレントインストールを行うようにしたい場合は、以下のように変更します。

!define PRODUCT_INSTALL_MODE "QUIET" ; "NORMAL" から変更する

ウィザードの操作はさせたくないけれどもインストールの進行状況だけは表示させたいという場合*11は、以下のようにします。

!define PRODUCT_INSTALL_MODE "PASSIVE" ; "NORMAL" から変更する

今回は、ウィザードの操作は省略しつつインストールの進行状況を表示したいので、「PASSIVE」に設定します。

既定のブラウザの設定

Fx Meta Installerでは、インストールしたFirefoxをシステムの既定のブラウザにするかどうか*12も設定できます。今回はFirefoxを既定のブラウザにしたいので、以下の通り変更します。

!define DEFAULT_CLIENT "StartMenuInternet\FIREFOX.EXE" ; コメントアウトを外す。

なお、Thunderbirdをインストールする場合で、Thunderbirdを既定のメールクライアントにしたい場合は、以下のように設定します。

!define DEFAULT_CLIENT "Mail\Mozilla Thunderbird"

以上で、メタインストーラの設定は終了です。これ以外にも色々な設定項目がありますが、今回は解説を省略します*13

同梱するファイルの準備

メタインストーラーの挙動を設定できたら、次は、メタインストーラーに含めるファイルを用意します。

メタインストーラーに含めるファイルは、「resources」という名前のフォルダの中に配置します。resourcesフォルダは初期状態では用意されていないので、新しく作成して下さい。

Firefoxのインストーラー

メタインストーラーにFirefoxのインストーラーを同梱するには、インストーラーを「Firefox-setup.exe」*14という名前でresourcesフォルダに置きます。

今回はFirefox 16.0.2を同梱したいので、まずはMozillaの公式サイトからFirefox 16.0.2のリリース版のWindows用インストーラーをダウンロードします。ダウンロードしたファイルは「Firefox Setup 16.0.2.exe」という名前になっていますので、ファイル名を「Firefox-setup.exe」に変更し、resourcesフォルダに置いて下さい。

なお、ウィザードを表示するよう設定してメタインストーラーを作成する場合は、ウィザードの途中でFirefoxのEULA参考訳)に同意する画面が表示されるので、そのためのEULAのテキストファイルを含めておく必要があります。この場合は、EULAの文章をテキストファイルとして保存し、resourcesフォルダに「Firefox-EULA.txt」という名前で置いて下さい。

Firefoxのインストーラーを同梱しなかった場合、メタインストーラーはビルド時の設定に従ってNASおよびWebサーバーからインストーラーの取得を試みます*15

アドオン

メタインストーラーにアドオンを同梱するには、そのアドオンのXPIパッケージをresourcesフォルダに置きます。Firefoxにはインストール先の distribution/bundles/ 以下に置かれたアドオンをモジュールとして組み込んだ状態で起動する仕組みがあり、メタインストーラーはこの仕組みを利用して、アドオンをFirefoxにインストールします。

今回はシステムモニターを同梱したいので、XPIパッケージ「system_monitor-0.6.3-fx+tb-win32.xpi」をダウンロードし、resourcesフォルダに置いて下さい。

なお、この方法でFirefoxのモジュールとして組み込まれたアドオンは、完全にFirefoxの一部として扱われるため、アドオンマネージャーには表示されず、セーフモードでも無効化されることはありません。ユーザが任意に設定を変更できるようにするためには、アドオンマネージャー以外の場所から設定画面にアクセスできるようにしておくか、アドオンのインストール先を distribution/bundles/ 以下ではなく extensions/ 以下に変更する必要があります。アドオンのインストール先を変更する方法については、Fx Meta Installerの構成ファイルの中のdoc/usage.txt.jaを参照して下さい。

また、アドオンは何個でもインストールできます。インストールしたいアドオンが複数ある場合は、すべてのアドオンのXPIファイルをresourcesフォルダに置いて下さい。

集中管理用の設定ファイル

メタインストーラーには、集中管理用の設定ファイルを適切な場所にインストールする機能も含まれています。具体的には、拡張子が「.cfg」であるファイルをresourcesフォルダに置くとFirefoxのインストール先のフォルダに、拡張子が「.js」であるファイルをresourcesフォルダに置くとFirefoxのインストール先の defaults/pref/ 以下にインストールされます。

今回はFirefoxとアドオンの自動更新を無効化し、同時に、その設定をロックしたいので、まずは自動更新を無効化する設定をロックする集中管理用の設定ファイルを作成します。以下の内容のファイルを作成し、resourcesフォルダに「autoconfig.cfg」という名前で保存して下さい。

// 最初の行は無視されるため、必ずコメント行とする。
lockPref("app.update.enabled", false);
lockPref("extensions.update.enabled", false);

次に、集中管理用の設定ファイルを読み込ませるための設定ファイルを作成します。以下の内容のファイルを作成し、resourcesフォルダに「autoconfig.js」という名前で保存して下さい。

pref("general.config.filename", "autoconfig.cfg");
pref("general.config.vendor", "autoconfig");
pref("general.config.obscure_value", 0);
ビルドする

以上で、すべての必要なファイルが準備できました。いよいよビルドしてみましょう。

ビルドスクリプトの実行

設定が完了し、必要なファイルを準備できたら、Windows環境であれば「make.bat」を、Linux環境であれば「make.sh」を実行して下さい。ビルドに成功すると、メッセージの最後に「Success」と出力され、「FxMetaInstaller-source」という名前のフォルダが作成されます。このフォルダには、配布用メタインストーラーの構成ファイル一式と、配布用メタインストーラーの実行ファイルを作成するためのバッチファイルが収められています。

ビルドに失敗した場合、メッセージの最後に「Failed to build fainstall.exe!」と表示されます。ビルド時の設定に間違いがないか、必要なプラグインがすべてインストールされているかなどを再確認して下さい。

メタインストーラーのテスト実行

ビルドに成功した場合であっても、resourcesフォルダの内容などにミスがあった場合、期待通りの実行結果を得られません。そのため、この時点でテスト実行しておくことをお薦めします。

テスト用のWindows環境のローカルディスク上に「FxMetaInstaller-source」フォルダを置き、フォルダ内の実行ファイル「fainstall.exe」を実行して下さい。メタインストーラーを実行した場合と同じ処理が行われ、Firefoxのインストールやアドオンのインストールなどが行われます。インストールされたFirefoxの設定が期待通りになっているか、同梱したアドオンが認識されているかなどを確認して下さい。

配布用メタインストーラーの生成

テスト実行で問題がなければ、配布用のメタインストーラーの実行ファイルを作成します。Windows環境のローカルディスク上に「FxMetaInstaller-source」フォルダを置き、その中にあるバッチファイル「FxMetaInstaller.bat」を実行して下さい。この操作により、配布用のメタインストーラーの実行ファイル「FxMetaInstaller.exe」が作成されます。

なお、「FxMetaInstaller-source」フォルダに出力される設定ファイル「fainstall.ini」を編集したり、resourcesフォルダの内容を変更すると、再ビルドが必要ない範囲でメタインストーラの挙動やインストール対象のファイルを変更することができます*16。設定の変更後は、バッチファイル「FxMetaInstaller.bat」を実行して配布用のメタインストーラーの実行ファイルを再作成して下さい。

おわりに

Fx Meta Installerは、上記の例で行った以外にも様々な設定が可能です。詳しい使い方はdocフォルダの中のドキュメントに解説がありますので、参照してみて下さい。

*1  ウィザードを使わずに、あらかじめ決められた通りの設定でインストールを行うこと

*2  ユーザが自分でインストールするアドオンとは異なり、どのユーザでFirefoxを起動してもそのアドオンが組み込まれた状態で起動することになるアドオン。Nortonツールバーなどがこの形式をとっている

*3  なお、公式のFirefoxのバイナリではなく、Firefoxのロゴなどを含まない独自ビルドのバイナリであれば、この限りではありません。

*4  デバッグ時には、デバッグメッセージの出力に対応したバージョンを使うと、詳細なログを参照することができます。

*5  拡張子が「.nsh」であるファイル

*6  64bit版Windowsでは、NSISはC:\Program Files(x86)\NSIS以下にインストールされます。この場合、プラグインのインストール先もC:\Program Files(x86)\NSIS\PluginsおよびC:\Program Files(x86)\NSIS\Include以下となりますので注意して下さい。

*7  サイレントインストールではない。

*8  そのまま処理を継続する

*9  依存関係が満たされていると判断するため

*10  現状では、SHA1などでのハッシュ値を計算するためのNSIS用プラグインにはバグがあり利用できないため、MD5を使用しています。

*11  ウィザードではないので実行を止めることはできません。

*12  Thunderbirdであれば、既定のメールクライアントにするかどうか

*13  詳しくはFx Meta Installerの構成ファイルの中のdoc/usage.txt.jaを参照して下さい。

*14  Thunderbirdの場合は、「Thunderbird-setup.exe」

*15  取得できなければ、エラーとなりインストール処理を中止します。

*16  fainstall.iniでどのような設定が可能かや、どのようなファイルをインストール対象にできるかについては、doc/usage.txt.jaを参照して下さい。

つづき: 2013-01-08
タグ: Mozilla
2012-11-07

Firefox/Thunderbird用アドオン開発者向けテスティングフレームワーク UxU 1.0.0をリリースしました

長らく更新がない状態が続いてしまっておりましたが、Firefox/Thunderbird用アドオンの開発者向けテスティングフレームワークUxU(UnitTest.XUL)の新バージョンとなるバージョン1.0.0を、本日付けでリリースしました。動作対象は、現在Mozillaによって公式にサポートが継続されているFirefox 10 ESR、Thunderbird 10 ESR、および最新のリリース版までです。Firefox 3.6、Thunderbird 3.1などの旧バージョンでの動作は保証されていませんので、ご注意下さい。

テストの並列実行

バージョン1.0.0では影響の大きな変更点として、複数のテストの並列実行に関する動作が大幅に改善され、実用的な機能になりました。

設定画面から最大の並列実行数を変更するか、コマンドライン引数を通じて並列実行数を明示的に指定することで、これまですべてシーケンシャルに実行されていたテストが、テストケース(ファイル)単位で並列に実行されるようになります。

並列実行が有効な場面

JavaScriptを使ったテストでは、Webページの読み込みなど、同期的には結果を得られない・処理待ちが必要となる場面が多くあります。UxUでは、ユーティリティメソッドなどを通じて簡単に処理待ちを行える事が特長の1つとなっています。

しかしながら、処理待ちが発生するという事は、必然的に、全体のテスト実行時間が長くなってしまう事にも繋がります。

複数のテストを並列実行すると、1つのテストで処理待ちしている間に、別のテストの処理を進める事ができます。処理待ちしている時間を無駄にせずに済むようになるため、処理待ちが多い場合においては、全体的なテスト実行時間の短縮が期待できます。

並列実行が有効でない場面

UxUは、プロファイルを指定して別プロセスを起動する場合を除き、すべてのテストが同一プロセスの同一スレッド上で動作する設計となっています。そのため、単純なforループやwhileループなど、同期的な処理により時間がかかってしまっている場面については、他のテストを並列実行する事ができません。

このような理由でテストの実行時間が長くなっているケースについては、残念ながら、並列実行では実行時間の短縮は望めません。

並列実行できない場面

テストの設計によっては、他のテストと並列に実行するとランダムに失敗するようになってしまう場合があります*1。並列実行機能は、個々のテスト同士が衝突しないようになっている事を確認した上で利用して下さい。

なお、テストケース中で「var parallel = false;」と明示的に指定する事により、そのテストケースを並列実行の対象外にする事ができます。並列実行の対象外になっているテストケースは、同時に2つ以上実行される事はありません*2。並列実行すると失敗するテストについてはこの方法で問題を回避し、できる所から並列実行の恩恵を得るという事もできます。

まとめ

UxU バージョン1.0.0で実用的な機能となった複数テストの並列実行機能について、有効な場面と有効でない場面、問題が起こる場面などの情報を簡単にご紹介しました。テスト実行時間の長さに悩まされていた方は、是非一度お試し下さい。

*1  例えば、単一のテスト用フレームを2つのテストで同時に使用してしまっている場合や、複数のテストでそれぞれウィンドウを開いていて「最前面にあるウィンドウ」を取り合ってしまう場合など。

*2  並列実行の対象になっているテストが他にある場合は、それとは並行して実行されます。

タグ: Mozilla | テスト | UxU
2012-07-24

Netscape Communicator 4.5以降のプロファイル情報の解析

クリアコードではMozillaサポート事業を行っていますが、その一環としてNetscape Communicator 4からFirefoxやThunderbirdへの移行のお手伝いもしています。今回は、NC4.5からThunderbirdへの移行の際に特に問題となった、NC4.5のユーザプロファイル一覧の取得方法の調査の解説を通じて、クリアコードでのオープンソース製品のサポート業務の一例をご紹介したいと思います。

NC4.5のユーザプロファイルの困った事情

本題の前に、まずは背景事情を説明しましょう。

Thunderbirdは、設計的にはNC4(Netscape Messenger)の子孫と言えますが、製品としてはNC4の後継ではありません。そのため、ThunderbirdのNC4からの移行支援の対象は非常に限られており、実際に「設定とデータのインポート」機能ではNC4のユーザ情報からはローカルに保存されたメールしかインポートできません。それ以上の情報をインポートしようと思うと、現状ではそのための仕組みを独自に用意する必要があります。

この時に問題になる事の1つとして、NC4のプロファイルの一覧やそれぞれのプロファイルの内容の保存場所をどのように取得すればよいのか?という事が挙げられます。

今のWindowsはマルチユーザ前提の設計になっており、"C:\Users\<ユーザ名のフォルダ>\AppData\Roaming" (Windows XP以前では "C:\Documents and Settings\<ユーザ名>\Application Data")内にユーザごとの設定を保存するのが当たり前になっています。しかし、昔のWindowsは「1台のPCを使うのは1人だけ」という前提で設計されていたため、複数人で1台のPCを共用しているなどの理由で複数のユーザ設定を使い分けたい場合には、アプリケーションの側で独自にマルチユーザ対応のための仕組みを持つ必要がありました。

NC4もそういった時代に作られた古いアプリケーションの1つで、既定の状態ではプロファイルは "C:\Program Files\Netscape\Users" 以下に保存されるようになっています。

ただ、プロファイルの実体は任意の場所に作成できるため、単純に "C:\Program Files\Netscape\Users" のサブフォルダを辿るだけでは見落としが発生してしまいます。すべてのプロファイルをもれなく把握するためには、そのPC上にあるプロファイルの一覧を取得するための正しい手順を踏まなくてはなりません。実は、Thunderbird 2までのバージョンにはnsreg.datの内容を解釈した結果を取得するためのAPIが含まれていたのですが、Thunderbird 3までの間でそのAPIは削除されてしまったので、今回は代わりの方法を考える必要がありました。

そのプロファイルの一覧の保存先なのですが、NC4の初期のバージョンではWindowsのレジストリに情報が保存されていたのに対し、Netscape 4.5以降では "C:\Windows\nsreg.dat" というバイナリファイルに情報が保存されるように仕様が変更されました。よって、NC4.5以降からThunderbirdへの移行を支援するためには、このバイナリファイルに立ち向かう必要があります。

前置きが長くなりましたが、このバイナリファイル(nsreg.dat)はどのような形式になっていて、どのようにすれば内容を読み取る事ができるのか、というのがこの記事での主題となります。

nsreg.datの内容を読み解くヒントを探す

バイナリファイルの内容自体は、以下のようにしてバイト列として読み込む事ができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
// nsIFileでnsreg.datを渡すと仮定
function readBinaryFrom(aFile) {
  var fileStream = Cc['@mozilla.org/network/file-input-stream;1']
                     .createInstance(Ci.nsIFileInputStream);
  fileStream.init(aFile, 1, 0, false);
  var binaryStream = Cc['@mozilla.org/binaryinputstream;1']
                       .createInstance(Ci.nsIBinaryInputStream);
  binaryStream.setInputStream(fileStream);
  var bytes = binaryStream.readByteArray(fileStream.available());
  binaryStream.close();
  fileStream.close();
  return bytes;
}

この関数によって読み込まれた内容は、1バイト(8ビット)単位の内容がNumberとして格納されたArrayとして返されます*1。ですので、後はこの単純なバイト列の中に埋め込まれた「意味」を読み取ればよいという事になります。

nsreg.datの内容について解説したドキュメントは無いものかと思いまずは簡単に検索してみましたが、断片的な情報はいくつか見つかるものの、ちゃんとした技術情報には辿り着けませんでした。なのでとりあえず、ファイルの中身を見てみる事にしました。

このファイルをバイナリエディタ*2で開くと、「ProfileLocation」やプロファイルの位置と思われる文字列がNULL文字(終端文字)を伴う形で、一定の規則に則って埋め込まれている事が見て取れます。そこで、最初はこれらを単純にパターンマッチングで取り出してプロファイルの一覧として列挙するという事を試みました。

しかしながら、実際に使い込まれた環境のnsreg.datで検証すると、この方法では同じ名前のプロファイルが何度も列挙されるといった結果になってしまいました。どうやら、nsreg.datの内容は新しい情報が追記されていく一方で古い情報には削除フラグが立てられるだけという作りになっている模様で、単純なパターンマッチングでは余計な情報が大量にヒットしてしまう事があるようです。

どこが削除フラグなのか、そもそもどこからどこまでが正確に1つの情報の単位なのか、という事をここから探り当てるのは非常に困難です。そこで根本的解決を見るために、Mozilla Seamonkeyのソースツリーの中からnsreg.datのパースに関する処理を探し出す事にしました*3

探索にあたって、まず単純にMXRで「nsreg.dat」を文字列検索してみたところ、nsDogbertProfileMigratorというファイルが見つかりました。MozillaプロジェクトではNetscape製品の事は何故かDogbertと呼ばれていたようなのですが、このモジュールはその名の通り、NC4のプロファイルを移行するための諸々の処理を実装しているようです。

この中でさらにプロファイルの一覧を取得しているらしい関数を探して中身を見てみた所、今度はreg.h(ヘッダファイル)とreg.c(実装)というさらに低レベルのAPIに辿り着きました。

nsDogbertProfileMigrator、もしくはさらに低層のAPIだけをコンパイルしてバイナリ形式のコンポーネントにするという手も無くはないのですが、

  • ビルドのための環境を整えるのには手間がかかりすぎる。
  • バイナリコンポーネントをアドオンに含めるのは今(Gecko 2.0以降)では色々と面倒が多い。
  • そして何より、インターフェース的にもあまり使いやすい物では無さそうである。

という事から、今後*4のためにも、これに相当する使いやすいモジュールをJavaScriptで実装し直してみる事にしました。

ただ、nsDogbertProfileMigrator.cppは2200行程、reg.cは4100行程あって、全部を読んで把握するというのはちょっと面倒です。なので、実際のnsreg.datとソースコードの両面から、重要そうと思われるポイントに絞って調査を進めていきました。

nsreg.datのレジストリエントリ

先のソースコードにおけるnsreg.datの読み取り処理と、実際のnsreg.datの内容とを比較しながら見ていったところ、ファイルにはdescriptionと呼ばれる単位でレジストリのエントリが保存されている模様である事が分かりました。また、descriptionの中身は以下のようになっているという事も分かりました。

  • description全体の長さ:32バイト
    • 1バイト目〜4バイト目(location):そのdescriptionの先頭の位置(符号無し32ビット整数)
    • 5バイト目〜8バイト目(name):「名前」の先頭の位置(符号無し32ビット整数)
    • 9バイト目〜10バイト目(nameLength):nameの長さ(符号無し16ビット整数)
    • 11バイト目〜12バイト目(type):ノードの種類のフラグ(符号無し16ビット整数)
    • 13バイト目〜16バイト目(left):同じ階層にある次のノード(兄弟ノード)にあたるdescriptionの先頭の位置(符号無し32ビット整数)
    • 17バイト目〜20バイト目(down):最初の子ノードにあたるdescriptionの先頭の位置(符号無し32ビット整数)
    • 21バイト目〜24バイト目(value):「値」の先頭の位置(符号無し32ビット整数)
    • 25バイト目〜28バイト目(valueLength):valueの長さ(符号無し32ビット整数)
    • 29バイト目〜32バイト目(parent):親ノードにあたるdescriptionの先頭の位置(符号無し32ビット整数)

それぞれの値は8ビット毎のリトルエンディアン形式*5で格納されており、下位の8ビットから順に並べ替える事で実際の値を得られました。

スクリーンショット:実際のnsreg.datの内容をバイナリエディタで表示した様子

実際にnsreg.datの中をもう一度見てみた所、プロファイルの位置と思われる文字列の近くに32ビットの長さのバイナリ部があり、先頭の4バイトがそのバイナリ部の位置そのものを示している事を確認できました。また、nameやvalueが指している位置にはUTF-8バイト列として文字列が格納されており、終端文字まで含めた長さがnameLengthおよびvalueLengthの長さとして格納されていました*6

前述の「削除フラグ」は、この中のtypeという部分に含まれていました。この部分で特定のビットが立っていると、そのdescriptionは削除済みであるということになるようです。また、nsreg.datの中にはツリーの形で情報が保存されており、left、down、parentの各フィールドからツリー構造を辿っていける模様である事も分かりました。

DOM的にnsreg.datの内容を見るための実装

と、ここまで分かった所で、このdescriptionをDOM風のオブジェクトとして扱うための実装を試験的に作成してみる事にしました。

まず、バイト列をJavaScriptの数値や文字列(UCS2)に変換するユーティリティを定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// バイト列→数値
function bytesToNumber(aBytes) {
  var converted = 0;
  aBytes.forEach(function(aValue, aIndex) {
    // リトルエンディアンの符号無し整数なので、
    // 単純に8桁ずつビットシフトした結果を合計すれば
    // 表現されている数値を得られる。
    converted += (aValue << (aIndex * 8));
  });
  return converted;
}

// バイト列→文字列
function bytesToString(aBytes) {
  var converted = '';
  aBytes.some(function(aValue, aIndex) {
    if (!aValue)
      return true;
    // 数値の配列から、1文字が1バイトの値を表す
    // UTF-8バイト列としての文字列に一旦変換する。
    converted += String.fromCharCode(aValue);
    return false;
  });
  return UTF8toUCS2(converted);
}

function UTF8toUCS2(aUTF8Octets) {
  return decodeURIComponent(escape(aUTF8Octets));
}

次に、これらのユーティリティを使って、「値の内容」「次のノード」などを自ら見つけ出すようなDescriptionクラスを試験的に作成しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
function Description(aBytes, aOffset) {
  // 他のノードを探すためにはnsreg.dat全体のバイト列を
  // 保持しておかないといけない。
  this.allBytes = aBytes;
  this.bytes = aBytes.slice(aOffset, aOffset + this.DESCRIPTION_SIZE);

  this.location = bytesToNumber(this.bytes.slice(0, 3));
  if (this.location != aOffset)
    throw new Error('invalid description at '+aOffset);

  this.type = bytesToNumber(this.bytes.slice(10, 11));

  var nameOffset = bytesToNumber(this.bytes.slice(4, 7));
  var nameLength = bytesToNumber(this.bytes.slice(8, 9));
  this.name = bytesToString(this.allBytes.slice(nameOffset,
                                                nameOffset + nameLength));

  // 他のノードはlazy getterでその都度インスタンス化するため、
  // ここでは位置の情報だけを保持しておく。
  this._left   = bytesToNumber(this.bytes.slice(12, 15));
  this._down   = bytesToNumber(this.bytes.slice(16, 19));
  this._parent = bytesToNumber(this.bytes.slice(28, 31));

  // valueは他のノードを指している事があるので、これも
  // 位置と長さの情報だけを保持しておく。
  this._valueOffset = bytesToNumber(this.bytes.slice(20, 23));
  this._valueLength = bytesToNumber(this.bytes.slice(24, 27));
}
Description.prototype = {
  DESCRIPTION_SIZE : 32,
  // typeフィールドにおける、削除済のレジストリエントリかどうかを
  // 示すフラグ。
  TYPE_DELETED     : 0x80,

  get deleted() {
    return !!(this.type & this.TYPE_DELETED);
  },

  // valueはノードかもしれないし文字列かもしれないので、
  // 両方の可能性を考慮する。
  get value() {
    return this.nodeValue || this.stringValue;
  },
  get stringValue() {
    if (typeof this._stringValue == 'undefined')
      this._stringValue = bytesToString(
        this.allBytes.slice(this._valueOffset,
                            this._valueOffset + this._valueLength));
    return this._stringValue;
  },
  get nodeValue() {
    if (typeof this._nodeValue == 'undefined') {
      try {
        this._nodeValue = new Description(this.allBytes,
                                          this._valueOffset);
      }
      catch(e) {
        this._nodeValue = null;
      }
    }
    return this._nodeValue;
  },

  // left、down、parentに対応するlazy getter。
  // left/downではなくnext/firstChildなのは、その方が
  // オブジェクト的な表現での実態に即しているから。
  get nextDescription() {
    if (this._left && !this._nextDescription)
      this._nextDescription = new Description(this.allBytes, this._left);
    return this._nextDescription;
  },
  get firstChildDescription() {
    if (this._down && !this._firstChildDescription)
      this._firstChildDescription = new Description(this.allBytes, this._down);
    return this._firstChildDescription;
  },
  get parentDescription() {
    if (this._parent && !this._parentDescription)
      this._parentDescription = new Description(this.allBytes, this._parent);
    return this._parentDescription;
  },

  // deletedなdescriptionは実質的には無い物として扱うバージョン。
  // (実際にdeletedなdescriptionが参照されたままになっている事が
  // あるのかどうかはまだ分からないが、念のため。)
  get next() {
    return this.nextDescription && !this.nextDescription.deleted ?
             this.nextDescription : null ;
  },
  get firstChild() {
    return this.firstChildDescription && !this.firstChildDescription.deleted ?
             this.firstChildDescription : null ;
  },
  get parent() {
    return this.parentDescription && !this.parentDescription.deleted ?
             this.parentDescription : null ;
  },

  // deletedでない子ノードを収集する。
  // (実際にdeletedなdescriptionが参照されたままになっている事が
  // あるのかどうかはまだ分からないが、念のため。)
  get children() {
    if (!this._children) {
      this._children = [];
      let child = this.firstChildDescription;
      let found = {}; // 無限ループに陥ってしまわないよう、念のため。
      while (child) {
        if (!child.deleted && !found.hasOwnProperty(child.location))) {
          this._children.push(child);
          found[child.location] = child;
        }
        child = child.nextDescription;
      }
    }
    return this._children;
  },

  // 子ノードが名前を持っている場合に
  // それを簡単に取得するためのユーティリティ。
  getNamedChild : function(aName) {
    var found = null;
    this.children.some(function(aChild) {
      if (aChild.name == aName)
        found = aChild;
      return found;
    }, this);
    return found;
  },
  getNamedChildren : function(aName) {
    return this.children.filter(function(aChild) {
        return aChild.name == aName;
      });
  }
};

これを先のreadBinaryFrom()と組み合わせれば、description単位での調査はずっと容易になります。

nsreg.datの全容

ここまでの調査の過程で、nsreg.datの中には全てのユーザ情報のルートとなるエントリが存在しており、nsDogbertProfileMigratorのGetSourceProfiles()というメソッドの中でそれを起点としてその子ノードを走査しているらしいという事が分かっていました。という事は、その「ユーザ情報のルート」から芋蔓的にプロファイルの一覧を見つけられる(しかも削除済みのレジストリエントリを除外した形で)と考えられます。

残る問題は、そのレジストリエントリがどこにあるのかという事です。

ファイルの先頭から32バイトが全体のルートになるdescriptionなのでは?とも一瞬考えましたが、実際のファイルの先頭には先頭のアドレス(0000)とは似ても似つかない値が入っていたので、この可能性は無いようです。この調子で2バイト目から順番に見ていってもきりが無いですし、そもそもそれで見つかったとしても常にその位置に最初のdescriptionがあるとは限らないので、ここはやはり既存の実装を見るのが一番確実そうです。

という事でソースコードを見ていた所、reg.hの中に「must equal MAGIC_NUMBER*7」や「major version number」といったコメントが付いた、REGHDRといういかにも怪しい名前の構造体の定義がある事に気がつきました*8MAGIC_NUMBERという定数の定義を見ると、nsreg.datの先頭4バイトがまさにこのMAGIC_NUMBERと一致しています。また、REGHDRの中でrootというフィールドにあたる位置(先頭から13バイト目~16バイト目)に書き込まれていた値をdescriptionの位置として参照してみた所、全体のルートとなるdescriptionらしき内容に行き当たりました(このdescriptionの子ノードの中には先の「ユーザ情報のルート」となるdescriptionが含まれている事も確認できました)。さらに駄目押しで既存の実装から「REGHDR」を参照している箇所を探してみた所、レジストリファイルをオープンする処理の中で固定のオフセットとしてファイルの先頭からREGHDR分の長さだけ内容を読み込んでくるという処理も見つかりました。やはりこれがルートと見て間違いなさそうです。

残りの実装

これでようやく、nsreg.datの中を機械的に辿ってユーザプロファイルの一覧を取得する目処が立ちました。後は不足している部分を実装するだけです。

まず、nsreg.datの内容全体をバイト列として渡すとルートにあたるレジストリエントリを返す関数を定義します。

1
2
3
4
5
6
7
function getRootDescription(aBytes) {
  const ROOT_LOCATION        = 0xC;
  const ROOT_LOCATION_LENGTH = 4;
  var root = aBytes.slice(ROOT_LOCATION,
                          ROOT_LOCATION + ROOT_LOCATION_LENGTH - 1);
  return new Description(aBytes, bytesToNumber(root));
}

次に、それを使ってユーザのプロファイル一覧(名前とパスの組)を取得して返す関数を定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getProfilesFromBinary(aBytes) {
  var root = getRootDescription(aBytes);
  var users = root.getNamedChild('Users').children;
  var profiles = users.map(function(aUserNode) {
      return {
        name : aUserNode.name,
        path : aUserNode.nodeValue.stringValue
      };
    });
  profiles.sort(function(aA, aB) {
    return aA.name > aB.name;
  });
  return profiles;
}

最後に、システムのnsreg.datを自動検出してgetProfilesFromBinary()に渡し、その結果を返す関数を定義します*9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getProfiles() {
  try {
    const DirectoryService = Cc['@mozilla.org/file/directory_service;1']
                               .getService(Ci.nsIProperties);
    let file = DirectoryService.get('WinD', Ci.nsIFile);
    file.append('nsreg.dat');
    if (file.exists()) {
      let bytes = readBinaryFrom(file);
      return getProfilesFromBinary(bytes);
    }
  }
  catch(e) {
  }
  return [];
}

以上で必要な実装は揃いました。後はこれらを1つのファイルにまとめてJavaScriptコードモジュールにしておけば、今後またNC4からの移行案件があっても安心して対応できますね。

まとめ

NC4.5以降の古いNetscape製品ではバイナリ形式の独自のレジストリファイルが使われているという事、そのレジストリファイルに関する情報とパーサの実装はMozilla Seamonkeyのソースツリーから見つけられる事、そしてDOM的なアプローチでのパーサの再実装の流れを紹介しました。これ自体は非常に限定的な局面でしか役に立たない情報ですが、クリアコードのMozillaサポート事業やオープンソースソフトウェアのサポート事業では実際にどのような事をやっているのか?という事例紹介*10も兼ねて記事にしてみた次第です。

古い製品であってもソースコードさえあれば、後の時代からでもこのように目的を達成できる可能性が高くなります。ソースが無いと、ここまですんなりとはいかなかったでしょう(本格的なリバースエンジニアリングをしないといけないとなると、どれだけの手間がかかるか考えたくもないですね)。「クローズドソースのパッケージ製品を重宝していたにも関わらず、提供元が倒産等で存在しなくなってしまい、もはやサポートを受けられなくなってしまった」という状況に陥ってしまう将来的なリスクを非常に重く見る場合、オープンソースな製品を導入するというのも現実的な選択肢の1つなのではないでしょうか。

*1  今ならTyped Array を使った方が良いかも知れませんね。

*2  Ubuntu上で「Hexエディタ」を使いました。

*3  NC4の後継ソフトウェアであるNetscape 6〜Netscape 7は、Mozilla Application Suiteというソフトウェアにいくつかの機能を足した物でしたが、差異を最小限にする(Netscape側でないとできないという作業を最小化する)ためにほとんどの機能はApplication Suiteに含める方針だったようで、nsreg.datのパース処理もそういった機能の1つとしてApplication Suiteに含まれていました。そのMozilla Application Suiteは、Netscape亡き後も有志の手によりMozilla Seamonkeyとして開発が継続されています。実はThunderbird 2にもこの処理は含まれていたので、Thunderbird 2のソースコードを調査してもよかったのですが、今回探索を行った時にはその事を失念していました……

*4  あるんでしょうか?

*5  8ビットを超える長さのデータを表現する際の表現形式で、8ビットを1桁と考えた時に上の桁から順に格納する方式をビッグエンディアン、下の桁から順に格納する方式をリトルエンディアンと言います。ビッグエンディアンは人間にとって分かりやすく、リトルエンディアンはコンピュータにとって分かりやすいという利点がそれぞれあります。

*6  ただし、valueLengthは0になっている場合があり、その場合はvalueの位置から32バイトの内容がまた別のdescriptionになっている、という構造になっていました。

*7  定数値の中でも、それ単体を見ても意味が全く分からない上に、他に何の説明も無い物。一意な識別子として使われる事がある。

*8  「hdr」はThunderbirdのソース中では「header」または「handler」の意味で使われている事が多いです。

*9  ソースコードを見た限りではWindows版だけでなくMac OS版やUNIX版のNC4.5もnsreg.datを使用している様子でしたので、この箇所だけ各プラットフォーム向けに実装を分ければWindows/Mac OS/UNIXの3プラットフォームに対応できそうです。

*10  純粋に調査のみの依頼の場合もあれば、今回のように移行案件の中で必要に迫られて行う場合もあります。

タグ: Mozilla
2011-12-19

Firefoxの技術書「Firefox Hacks Rebooted」

オライリーより、Firefoxの高度な使い方からアドオン開発のノウハウ、新しいWeb技術まで手広く解説・紹介する書籍「Firefox Hacks Rebooted」が、2011年10月26日に発売されました。弊社でMozillaサポート事業に従事している下田も執筆者の一人として名を連ねています。

Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
浅井 智也/池田 譲治/小山田 昌史/五味渕 大賀/下田 洋志/寺田 真/松澤 太郎
オライリージャパン
¥ 3,570

内容が多岐に渡るため、「こういう人に読んでもらいたい!」という想定読者が章ごとにそれぞれ異なるのですが、全体を見た時の内容の充実度からは、Firefox用アドオンの開発に関心がある方に特にお薦めと言えるでしょう。そこでこの記事では、開発者視点から本書の見所をいくつか紹介します。

Vimperator vs KeySnail

目次を順番に眺めて目を引くのは、2章におけるVimperatorとKeySnailの解説でしょう。VimとEmacsといえば開発者が使う開発環境の二大巨頭で、よくエディタ戦争のネタにもされますが、本書の2章でも、Vimを模したVimperatorとEmacsを模したKeySnailの戦争が繰り広げられています。

……というのは冗談なのですが、Vimperator開発メンバーの一人であるteramakoさんがVimperatorを、KeySnailの作者であるmoozさんがKeySnailをそれぞれ解説し、最後に二人がそれぞれの設計思想の違いを解説するという構成になっていて、こと両アドオンの解説記事としてはこれ以上無い豪華な内容と言えるでしょう。開発者自らによる解説という事で、内容の信頼性の高さも折紙付です。

快適な開発生活を送る上では、効率の良い情報収集や情報発信の方法を知る事も大切です。また、情報は発信する人の所に集まるとも言います。最新の技術へのフォローを欠かす事ができない開発者の人達にとっても、この章の内容は役立つのではないでしょうか。

Add-on SDKでのアドオン開発

3章は、FireGesturesなどの作者としても知られるGomitaさんによるAdd-on SDKの解説です。基礎概念の解説に始まり、少しずつ機能を付け足しながら実際に1つのアドオンを完成させるまでの過程をチュートリアル形式で紹介する事により、Add-on SDKを使ったアドオン開発の一通りの流れを理解する事ができます。

Firefox 4以降のバージョンでは高速リリースが採用された事により、従来の形式のアドオンの開発スタイルだとFirefoxの更新に追従するだけでも大変という状況になっています。Add-on SDKはFirefoxのバージョン間の違いを吸収する層としての働きも備えているため、これから新たにアドオンを開発する場合はSDKを利用した方が良いと言えますが、本章はそのファーストステップとして最適でしょう。

また、後の章にはHTML5関連技術などの新しいWeb標準技術の解説もあります。それらを組み合わせる事によって、SDKが提供している機能を超えた様々な事が実現できるようになります。本書は話題が多岐に渡っていますので、全体を通読する中でそういった発展に繋がるヒントを得やすいのではないでしょうか。

Add-on SDKの枠を超えたアドオン開発

弊社所属の下田は、4章と6章の一部として、SDKに依らない・より低層の部分に関する技術情報を寄稿しています。

4章では、再起動のいらないアドオンを、これまでの一般的なアドオン開発の延長線上にある物として捉えた上で、これまで通りにできる事とそうでない事とを整理して、それらに対する対策となる様々なテクニックを紹介しています。

また、プロセス分離型の設計に移行していくにあたって、そもそもFirefoxではどのようにプロセスの分離が実現されているのかを理解し、プロセスが分離された環境ではどのような事に気をつけなくてはならないのかについても解説しています。

SDKの枠を超えた開発を行いたい時や、SDKに含まれている標準ライブラリの中で行われている事を理解したい時などには、各要素技術への理解が必要になってきます。そういった場合も、この章の情報が手がかりとなるかも知れません。

その他にも、非同期処理の記述を支援する軽量ライブラリ「JSDeferred」のFirefox上での活用事例や、CPUの使用率とメモリの使用量を表示するFirefoxアドオン「システムモニター」でも利用している、C言語で開発されたプラットフォームネイティブのライブラリをJavaScriptから透過的に利用する技術「js-ctypes」の解説(6章に収録)など、SDKベースでの開発にも応用可能な情報もあります。

Webでは断片的にしか得られない情報のまとめ&発展として

本書には、Web上にあるFirefoxの断片的な情報を取っ掛かりとして、それらをさらにもう1段階・2段階と掘り下げた情報が多数収録されています。自分で情報を探そうとして挫折した方、見つけた情報が中途半端で途方に暮れてしまった方など、表面的な情報よりももっと本質的な情報を求めている方にお薦めと言えるでしょう。

なお、本書の目次各章のサンプルがWebで公開されています。Firefoxのヘビーユーザーの方やアドオン開発者の方は、役立つトピックが含まれているかもしれませんので、興味を持たれた際には是非一度目を通してみて下さい。

つづき: 2011-12-26
タグ: Mozilla
2011-11-02

CPUの使用率とメモリの使用量を表示するFirefoxアドオン「システムモニター」をFirefox 4用に更新しました

システムの情報をFirefoxのツールバー上に表示するアドオン「システムモニター」のバージョン0.6バージョン0.6.1*1をリリースしました。以下のリンク先からダウンロードできます。

前のバージョンからの主な変更点は以下の通りです。

  • Firefox 4に対応しました。
  • Firefox自身のメモリ使用量の表示に対応しました。
  • 背景を半透明で描画するようにしました。

機能的な改善点

このバージョンから、Firefox(Thunderbird)自身のプロセスが使用しているメモリの量*2を取得できるようになりました。全体の消費メモリのうちのどれだけをFirefoxが使用しているのかを、グラフによって視覚的に確認する事ができます。 Firefoxのメモリ使用率の表示

また、グラフの背景自体の透明度を変更できるようになったため、Personas(軽量テーマ)と組み合わせて利用しやすくなりました。 Personasと併用した様子

技術的な改善点

このバージョンではFirefox 4への対応にあたって、バイナリ形式のコンポーネントの代わりにJavaScriptベースのモジュールを使用するように実装を改めました*3

この新しい実装は、Firefox 3.6以降で導入されたjs-ctypesという技術に基づいています。Firefox 4では構造体による情報の受け渡しが可能になった事により、バイナリ形式のコンポーネントを開発せずにこのようなアドオンを簡単に実現できるようになりました。これまではビルド環境を整えるのが難しかったために対応できていなかったような環境でも、低レベルな処理が必要なアドオンを開発しやすくなっています。

まとめ

Firefox 4に対応したシステムモニターの新バージョンをリリースしました。Firefox 4ではこのような、低レベルの層と連携して動作するアドオンも開発しやすくなっています。

関連情報:システムモニターのこれまでのリリース情報

*1  バージョン0.6.0にはLinux環境で動作しない問題があったため、修正版としてリリースしました。

*2  各環境において、メモリ使用量の表示は以下の基準に則ります。Windowsの場合:タスクマネージャの「メモリ(プライベート ワーキング セット)」に対応する値。Mac OS Xの場合:アクティビティモニタで表示されるメモリ使用量に対応する値。Linuxの場合:psコマンドで表示されるメモリ使用量に対応する値。

*3  Firefox 3.6以前のバージョンでは今まで通りバイナリ形式のコンポーネントを使用します

つづき: 2011-11-02
タグ: Mozilla
2011-03-23

CPUの使用率とメモリの使用量を表示するFirefoxアドオン「システムモニター」を更新しました

システムの情報をFirefoxのツールバー上に表示するアドオン「システムモニター」のバージョン0.5をリリースしました。以下のリンク先からダウンロードできます。

前のバージョンからの主な変更点は以下の通りです。

  • マルチCPUの環境で、個々のCPUの使用率の表示に対応しました。
  • グラフの表示形式として、折れ線グラフを選択できるようにしました。

個々のCPUの使用率のグラフ

マルチCPUに対応するにあたって、グラフの表示形式を追加しました。これらの表示様式は設定ダイアログで切り替える事ができます。

CPUごとの使用率の棒グラフ(重ね合わせ表示) 重ね合わせ表示(既定の表示スタイル)は、それぞれのCPUについて下端が0%・上端が100%となるグラフを表示し、それらを全て重ね合わせるモードです。

CPUごとの使用率の棒グラフ(積み上げ表示) 積み上げ表示は、グラフの高さをCPUの個数分で割って、それぞれを個々のCPUの使用率の0〜100%に割り当てるモードです。

全CPUの使用率の合計の棒グラフ 全CPUの使用率の合計を表示するよう指定した場合、旧バージョンと同じ表示になります。

また、棒グラフの代わりに折れ線グラフでも表示できるようになりました。

CPUごとの使用率の折れ線グラフ(重ね合わせ表示) CPUごとの使用率の折れ線グラフ(積み上げ表示) 全CPUの使用率の合計の折れ線グラフ

ただ、ツールバーに表示するUIでは表示領域が限られますので、CPUの数が増えてくると見にくくなるかもしれません。次のバージョンではこのあたりの問題についてうまい解決策を考えたい所です。

クアッドコアの環境での動作

Web APIで個々のCPUの使用率を取得する方法

Web APIの利用形式は前のバージョンと同様ですが、このバージョンでは新たに、system.addMonitorの第1引数として「cpu-usages」と「cpu-times」を受け付けるようになりました。「cpu-usage」および「cpu-time」を使用した場合は今まで通り全CPUの使用率の合計値がリスナに渡されますが、「cpu-usages」および「cpu-times」を使用した場合は、個々のCPUごとの値が格納された配列が渡されます。

以下は、CPUごとの使用率を監視する例です。マルチCPUの環境では、グラフが重ねて描画される事をご確認いただけると思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
var container = document.getElementById("system-monitor-multi-cpu-demo");
if (!window.system || !window.system.addMonitor) {
  container.innerHTML = "システムモニターがインストールされていません";
  container.style.color = "red";
} else {
  container.innerHTML = "<div><canvas id='system-monitor-multi-cpu' width='300' height='60'></canvas>"+
                        "<br /><span id='system-monitor-multi-cpu-console'></span></div>";

  var width = 300;
  var interval = 1000;

  var CPUArray = [];
  var arrayLength = width / 2;
  while (CPUArray.length < arrayLength) {
    CPUArray.push(undefined);
  }

  function onMonitor(aUsages) {
    var console = document.getElementById("system-monitor-multi-cpu-console");
    console.textContent = aUsages.map(function(aUsage) { return aUsage+'%'; }).join(' / ');

    CPUArray.shift();
    CPUArray.push(aUsages);

    var canvasElement = document.getElementById("system-monitor-multi-cpu");
    var context = canvasElement.getContext("2d")
    var y = canvasElement.height;
    var x = 0;

    context.fillStyle = "black";
    context.fillRect(0, 0, canvasElement.width, canvasElement.height);

    context.save();
    context.globalAlpha = 1 / aUsages.length;
    CPUArray.forEach(function(aUsages) {
      if (aUsages == undefined) {
        drawLine(context, "black", x, y, 0);
      } else {
        aUsages.forEach(function(aUsage) {
          drawLine(context, "lime", x, y, y - (y * aUsage));
        });
      }
      x = x + 2;
    }, this);
    context.globalAlpha = 1;
    context.restore();
  }

  function drawLine(aContext, aColor, aX, aBeginY, aEndY) {
    aContext.beginPath();
    aContext.strokeStyle = aColor;
    aContext.lineWidth = 1.0;
    aContext.lineCap = "square";
    aContext.moveTo(aX, aBeginY);
    aContext.lineTo(aX, aEndY);
    aContext.closePath();
    aContext.stroke();
  }

  // リスナを登録する。
  window.system.addMonitor("cpu-usages", onMonitor, interval);
}
["
\n", "\n"]
タグ: Mozilla
2010-10-19

CPUの使用率とメモリの使用量を表示するFirefoxアドオン「システムモニター」を更新しました

システムの情報をFirefoxのツールバー上に表示するアドオン「システムモニター」のバージョン0.4.1をリリースしました。以下のリンク先からダウンロードできます。

前のバージョンからの主な変更点は以下の通りです。

  • CPUの使用率に加えて、メモリの使用量を表示できるようになりました。
  • 64bit版Linux向けのバイナリを同梱するようにしました。
  • Firefox 3.6およびThunderbird 3.1で動作します。Firefox 3.5およびThunderbird 3.0では動作しません。

Web APIの利用

Web APIの利用形式は前のバージョンと同様ですが、このバージョンでは以下の点が改良されました。

  • メモリの使用量を表示および監視できるようになりました。
  • リスナを登録したページがアンロードされた際に、リスナの登録を自動的に解除するようになりました。

メモリの使用量を監視する場合は、system.addMonitorの第1引数に「memory-usage」を指定します。この時、リスナには以下のプロパティを持つオブジェクトが渡されます。

  • used(使用済みのメモリ領域のバイト数:64bit整数)
  • free(未使用のメモリ領域のバイト数:64bit整数)
  • total(全体のメモリ領域のバイト数:64bit整数)

また、以前のバージョンでは登録したリスナは明示的にsystem.removeMonitorで登録を解除するまで情報を受け取り続ける使用でしたが、バージョン0.4.1からは、ページがアンロードされると自動的にリスナの登録も解除されるようになりました。

以下は、メモリの使用量を監視する例です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
var container = document.getElementById("system-monitor-demo");
if (!window.system || !window.system.addMonitor) {
  container.innerHTML = "システムモニターがインストールされていません";
  container.style.color = "red";
} else {
  container.innerHTML = "<div><canvas id='system-monitor' width='300' height='60'></canvas>"+
                        "<br /><span id='system-monitor-console'></span></div>";

  var width = 300;
  var interval = 1000;

  var MemoryArray = [];
  var arrayLength = width / 2;
  while (MemoryArray.length < arrayLength) {
    MemoryArray.push(undefined);
  }

  // リスナとして登録する関数には、メモリの情報を保持したオブジェクトが渡される。
  function onMonitor(aUsage) {
    var console = document.getElementById("system-monitor-console");
    console.textContent = parseInt(aUsage.used/1024/1024)+'MiB / '+parseInt(aUsage.total/1024/1024)+'MiB';

    MemoryArray.shift();
    MemoryArray.push(aUsage.used / aUsage.total);

    var canvasElement = document.getElementById("system-monitor");
    var context = canvasElement.getContext("2d")
    var y = canvasElement.height;
    var x = 0;

    context.fillStyle = "black";
    context.fillRect(0, 0, canvasElement.width, canvasElement.height);

    context.save();
    MemoryArray.forEach(function(aUsage) {
      var y_from = canvasElement.height;
      var y_to = y_from;
      if (aUsage == undefined) {
        drawLine(context, "black", x, y_from, 0);
      } else {
        y_to = y_to - (y * aUsage);
        y_from = drawLine(context, "aqua", x, y_from, y_to);
        drawLine(context, "black", x, y_from, y_to);
      }
      x = x + 2;
    }, this);
    context.restore();
  }

  function drawLine(aContext, aColor, aX, aBeginY, aEndY) {
    aContext.beginPath();
    aContext.strokeStyle = aColor;
    aContext.lineWidth = 1.0;
    aContext.lineCap = "square";
    aContext.moveTo(aX, aBeginY);
    aContext.lineTo(aX, aEndY);
    aContext.closePath();
    aContext.stroke();
    return aEndY - 1;
  }

  // リスナを登録する。
  window.system.addMonitor("memory-usage", onMonitor, interval);
}
["
\n", "\n"]

Firefox 3.5およびThunderbird 3.0での動作について

システムモニター0.4.1では、前述したリスナ登録の自動的な解除を実現するためにいくつかの実装が加わりました。しかしながら、この機能が依存しているGecko側の機能についてGecko 1.9.1とGecko 1.9.2の間で互換性が無いため、Gecko 1.9.2のSDKを用いてビルドされたシステムモニターのバイナリはGecko 1.9.1では動作しなくなりました。よって、システムモニター0.4.1からはGecko 1.9.1ベースのFirefox 3.5(Thunderbird 3.0)はサポート対象外となっています。

どうしてもFirefox 3.5(Thunderbird 3.0)でシステムモニター0.4.1を利用したい場合は、Gecko 1.9.1のSDKを使ってソースからバイナリをビルドし直す必要があります。また、独自に変更を加える場合もバイナリをビルドし直さなくてはなりません。以下に、Windows、LinuxおよびMac OS Xの各環境でのビルド手順を解説します。

Ubuntu 10.04LTS Lucid Lynxの場合(Firefox 3.6)

リリース版のシステムモニター0.4.1のLinux用バイナリは、32bit版、64bit版のいずれもUbuntu 10.04LTS上でビルドしています。Ubuntu 10.04LTSでのビルド手順は以下の通りです。

  1. 必要なパッケージをインストールします。

    $ sudo aptitude install automake libtool xulrunner-1.9.2-dev libgtop2-dev zip
  2. システムモニターのソースをダウンロードして展開します。

    $ wget http://git.clear-code.com/xul/extensions/system-monitor/snapshot/system-monitor-0.4.1.tar.bz2
    $ tar xvf system-monitor-0.4.1.tar.bz2
  3. 作業ディレクトリに入ってビルドします。

    $ cd system-monitor-0.4.1
    $ ./automake.sh
    $ ./configure
    $ make

これで、作業ディレクトリ直下にインストール用のパッケージ「system-monitor-0.4.1.xpi」が生成されます。

Ubuntu 9.04 Jaunty JackalopeおよびUbuntu 9.10 Karmic Koalaの場合(Firefox 3.5)

基本的にはUbuntu 10.04LTSの場合と同じですが、Gecko SDK(XULRunner SDK)のバージョンを1.9.1にする点が異なります。必要なパッケージの準備の際に、「xulrunner-1.9.2-dev」ではなく「xulrunner-1.9.1-dev」をインストールして下さい。

$ sudo aptitude install automake libtool xulrunner-1.9.1-dev libgtop2-dev zip

それ以外の手順はUbuntu 10.04LTSの場合と同一です。

Windowsの場合

リリース版のシステムモニター0.4.1のWindows(Win32)用バイナリは、64bit版Windows 7でビルドしています。Windowsでのビルド手順は以下の通りです。

  1. 必要なソフトウェアをインストールします。
  2. ソースコードのZIPアーカイブをダウンロードし、展開します。展開すると「system-monitor-0.4.1」というフォルダが展開されますので、Cygwinのホーム直下などの位置に移動しておきます。
  3. Firefox 3.5/Thunderbird 3.0用にビルドする場合はGecko 1.9.1のSDKを、Firefox 3.6/Thunderbird 3.1用にビルドする場合はGecko 1.9.2のSDKをダウンロードし、展開します。展開するとどちらも「xulrunner-sdk」というフォルダが展開されますので、c:\xulrunner に移動しておきます。(c:\xulrunner\xulrunner-sdk に置くのではなく、c:\xulrunner-sdk の位置に移動した上でフォルダ名をxulrunnerに変更します。)
  4. Cygwin Bash Shellを起動し、作業ディレクトリに入ってビルドします。例えば作業ディレクトリが~/system-monitor-0.4.1であれば、以下の通りです。

    $ cd ~/system-monitor-0.4.1
    $ ./autogen.sh
    $ ./configure --with-libxul-sdk=/cygdrive/c/xulrunner
    $ make

    このビルド操作はエラーで停止しますので、続けて、VC++でバイナリをビルドします。(Cygwin Bash Shellはそのまま置いておいてください。)

  5. system-monitor-0.4.1\components\SystemMonitor\SystemMonitor.vcproj をダブルクリックします。すると、VC++が起動してプロジェクトファイルが読み込まれますので、「ビルド」メニューから「SystemMonitorのビルド」を選択してください。出力の最後の行が ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップとなっていればビルド成功です。
  6. XPIファイルを作成します。Cygwin Bash Shellに戻り、以下のコマンドを実行します。

    $ make xpi

これで、作業ディレクトリ直下にインストール用のパッケージ「system-monitor-0.4.1.xpi」が生成されます。

c:\xulrunner 以外の位置にGeckoのSDKを置く場合は、SystemMonitor.vcproj の内容を修正する必要があります。VC++で SystemMonitor.vcproj を開いてプロジェクトのプロパティを編集するか、SystemMonitor.vcproj をテキストエディタで開いて c:\xulrunner を指している箇所を置換して下さい。

Mac OS Xの場合

リリース版のシステムモニター0.4.1のMac OS X(Intelプロセッサ・32bit)用バイナリは、Mac OS X 10.6.4 Snow Leopardでビルドしています。

  1. 必要なソフトウェアをインストールします。
    • Xcode:WebサイトまたはOSのインストールディスクからインストールしておいて下さい。
    • MacPorts :以下のパッケージをインストールしておいて下さい。
      • pkg-config
  2. ソースコードのアーカイブをダウンロードし、展開します。展開すると「system-monitor-0.4.1」というフォルダが展開されますので、ホーム直下などの位置に移動しておきます。
  3. Firefox 3.5/Thunderbird 3.0用にビルドする場合はGecko 1.9.1のSDKを、Firefox 3.6/Thunderbird 3.1用にビルドする場合はGecko 1.9.2のSDKをダウンロードし、展開します。展開するとどちらも「xulrunner-sdk」というフォルダが展開されますので、/opt/xulrunner-sdk に移動しておきます(別の場所に置いても構いません)。
  4. アプリケーション→ユーティリティ→ターミナルを起動し、作業ディレクトリに入ってビルドします。例えば作業ディレクトリが~/system-monitor-0.4.1であれば、以下の通りです。

    $ cd ~/system-monitor-0.4.1
    $ ./autogen.sh
    $ ./configure --with-libxul-sdk=/opt/xulrunner-sdk CXXFLAGS="-arch i386"
    $ make

これで、作業ディレクトリ直下にインストール用のパッケージ「system-monitor-0.4.1.xpi」が生成されます。

Snow Leopardでは初期状態では64bit用のバイナリが作成されてしまいます。./configureの際に明示的に CXXFLAGS="-arch i386" と指定することで、32bit用のバイナリを作成できます。

ビルドに使用するGecko SDKは ./configure の --with-libxul-sdk オプションで指定したパスにある物が使われます。パスをその都度明示的に指定すれば、Gecko 1.9.1用とGecko 1.9.2用のそれぞれのバイナリを同一環境上でビルドすることもできます。

まとめ

今回、システムの情報をFirefoxのツールバー上に表示するアドオン「システムモニター」のバージョン0.4.1をリリースしました。また、Firefox 3.5向けにソースコードからバイナリをビルドする手順を紹介しました。

タグ: Mozilla
2010-10-05

クリアコードの公開gitリポジトリ

すでにお気づきの方もいるかもしれませんが、先日から、クリアコードで開発したフリーソフトウェアが入ったgitリポジトリの公開を始めました。

リポジトリ内にはgit用のコミットメール送信スクリプトを含むgit関連ユーティリティ集「git-utils」CPUの使用率を表示するFirefoxアドオン「システムモニター」も含まれています。中には試し作りしただけのものなども含まれています。それぞれのソフトウェアはリポジトリ内に同梱されているライセンスにしたがって自由に利用できます*1

クリアコードは既存のフリーソフトウェアプロジェクトの開発に参加するだけではなく、新たにフリーソフトウェアプロジェクトを立ち上げたりもしてきました。中にはプロジェクトを立ち上げるほどでもないような小さなソフトウェアもあり、それらのソフトウェアはこのようにひっそりと開発していたりします。これらはフリーソフトウェアなので、有用だと思うものがあったのなら、ソースコードにアクセスし、自由に利用してください。

もっと自由にソフトウェアを利用できる世界になるとよいですね。

関連: クリアコードの公開Subversionリポジトリ

*1  設定しているライセンスはGPL/LGPL/MPLあたりです。

つづき: 2010-12-29
タグ: Ruby | Mozilla
2010-06-08

処理待ちをより簡単に行えるようになったUxU 0.7.6をリリースしました

2010年1月29日付で、テスティングフレームワークUxUのバージョン0.7.6をリリースしました。

新機能のutils.wait()について

今回のアップデートでの目玉となる新機能は、非同期な機能のテストをより簡単に記述できるようにするヘルパーメソッドであるutils.wait()です。これは、以下のように利用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function testSendRequest() {
  myFeature.sendRequest();
  utils.wait(1000); // 1000ミリ秒=1秒待つ
  assert.equals('OK', myFeature.response);
}

function testLoad() {
  var loaded = { value : false };
  content.addEventListener('load', function() {
    content.removeEventListener('load', arguments.callee, false);
    loaded.valeu = true;
  }, false);
  myFeature.load();
  utils.wait(loaded); // valueがtrueになるまで待つ
  assert.equals('OK', content.document.body.textContent);
}

また、utils.wait()は関数の中でも利用できます。

1
2
3
4
5
6
7
8
9
10
function testSendRequest() {
  function assertSend(aExpected, aURI) {
    myFeature.sendRequest(aURI);
    utils.wait(1000);
    assert.equals(aExpected, myFeature.response);
  }
  assertSend('OK', '...');
  assertSend('NG', '...');
  assertSend('?', '...');
}

utils.wait()が受け取れる値は、これまでの処理待ち機能で yieldに渡されていた値と同じです。詳しくは処理待ち機能の利用方法をご覧下さい。大抵の場合、yield Do(...);と書かれていた箇所は、utils.wait(...);へ書き換えることができます。

ただし、このヘルパーメソッドはFirefox 3以降やThunderbird 3以降など、Gecko 1.9系の環境でしか利用できません。Thunderbird 2などのGecko 1.8系の環境ではエラーとなりますのでご注意下さい。(それらの環境でもテストの中で処理待ちを行いたい場合は、従来通りyieldを使用して下さい。)

これまでの処理待ち機能の特徴と欠点

これまでUxUでは、「機能を実行した後、N秒間待ってから、機能が期待通りに働いたかどうかを検証する」「初期化処理で、ページの読み込みの完了を待ってから次に進む」といった処理待ちを実現する際は、JavaScript 1.7以降で導入されたyieldを使う仕様となっていました。

yieldを含む関数はジェネレータとなり、関数の戻り値をイテレータとして利用できるようになります。この時ジェネレータの内側から見ると、「yieldが出現する度に処理が一時停止する」という風に考えることができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function gen() {
  alert('step1');
  yield 'step1 done';
  alert('step2');
  yield 'step2 done';
  alert('step3');
}
var iterator = gen(); // この時点ではまだ関数の内容は評価されない
var state;
state = iterator.next(); // 'step1' が表示される
alert(state);            // 'step1 done' が表示される
state = iterator.next(); // 'step2' が表示される
alert(state);            // 'step2 done' が表示される
try {
  state = iterator.next(); // alert('step3'); が実行される
} catch(e if e instanceof StopIteration) {
  // 次のyieldが見つからないので、StopIteration例外が投げられる
}

UxUに従来からある処理待ち機能は、この考え方を推し進めて作られています。テスト関数の中にyieldがある場合(つまり、関数の戻り値がイテレータとなる場合)は、フレームワーク側で自動的にイテレーションを行い、yieldに渡された値をその都度受け取って、次にイテレーションを行うまでの待ち条件として利用しています。例えば、数値が渡された場合はその値の分の時間だけ待った後で次にイテレーションを行う、といった具合です。

このやり方の欠点は、yieldを含む関数から任意の戻り値を返すことができないという点です。

1
2
3
4
5
6
7
8
9
10
11
function gen() {
  alert('step1');
  yield 'step1 done';
  alert('step2');
  return 'complete';
}
try {
  var iterator = gen();
} catch(e) {
  alert(e); // TypeError: generator function gen returns a value
}

returnを書くと、関数の実行時にエラーになってしまいます。どうしても何らかの値を取り出したい場合は、値を取り出すためのスロットとなるオブジェクトを引数として渡すなどの工夫が必要になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function gen(aResult) {
  alert('step1');
  yield 'step1 done';
  alert('step2');
  aResult.value = 'complete';
}
var result = {};
var iterator = gen(result);
var state;
state = iterator.next(); // 'step1' が表示される
alert(state);            // 'step1 done' が表示される
try {
  state = iterator.next(); // 'step2' が表示される
} catch(e if e instanceof StopIteration) {
  alert(result.value); // 'complete' が表示される
}

また、ジェネレータは実行してもその段階では関数の内容が評価されないという点にも注意が必要です。例えば以下のようなテストは、期待通りには動いてくれません。

1
2
3
4
5
6
7
8
9
10
function testSendRequest() {
  function assertSend(aExpected, aURI) {
    myFeature.sendRequest(aURI);
    yield 1000;
    assert.equals(aExpected, myFeature.response);
  }
  assertSend('OK', '...');
  assertSend('NG', '...');
  assertSend('?', '...');
}

この例では、assertSend()を実行したことで戻り値としてイテレータが返されているものの、そのイテレータに対するイテレーションが一切行われていないため、リクエストも行われなければアサーションも行われないということになってしまっています。これは以下のように、返されたイテレータをそのままフレームワークに引き渡して、フレームワーク側でイテレーションを行わせる必要があります。

1
2
3
4
5
6
7
8
9
10
function testSendRequest() {
  function assertSend(aExpected, aURI) {
    myFeature.sendRequest(aURI);
    yield 1000;
    assert.equals(aExpected, myFeature.response);
  }
  yield assertSend('OK', '...');
  yield assertSend('NG', '...');
  yield assertSend('?', '...');
}

また、このままではジェネレータの中で発生した例外のスタックトレースを辿れないという問題もあります。スタックを繋げるためには、ヘルパーメソッドのDo()を使ってyield Do( assertSend('OK', '...') )のように書かなければなりません。

utils.wait()を使う場合、これらのことを考慮する必要はありません。冒頭のサンプルコードのように、素直に書けば素直に動作してくれます。

スレッド機能を使った処理待ち

utils.wait()がどのように実装されているかについても解説しておきます。

このメソッドの内部では、Gecko 1.9から実装されたスレッド関連の機能を利用しています。

1
2
3
4
window.setTimeout(function() {
  alert('before');
}, 0);
alert('after');

JavaScriptは基本的にシングルスレッドで動作するため、このようにタイマーを設定すると、その処理はキューに溜められた状態となります。その上で、メインの処理が最後まで終わった後でやっとキューの内容が処理され始めるため、この例であれば「after」「before」の順でメッセージが表示されることになります。

1
2
3
4
5
6
7
8
9
10
11
12
var finished = false;
window.setTimeout(function() {
  alert('before');
  finished = true;
}, 0);
var thread = Cc['@mozilla.org/thread-manager;1']
              .getService()
              .mainThread;
while (!finished) {
  thread.processNextEvent(true);
}
alert('after');

Gecko 1.9以降のスレッド機能を使うと、メインの処理を一旦中断して先にキューに溜められた処理の方を実行し、その後改めてメインの処理に戻るということができます。実際に、こちらの例では「before」「after」の順でメッセージが表示されます。UxU 0.7.6ではこれを応用して、任意の条件が満たされるまでthread.processNextEvent(true);でメインの処理を停止し続けることによって、処理待ちを実現しています。

なお、HTML5にもWeb Workersというスレッド関係の機能がありますが、こちらは別スレッドでスクリプトを動作させる機能しか持っていないため、上記のようなことは残念ながらできません。

まとめ

UxU 0.7.6からは、utils.wait()を使ってより簡単に処理待ちを行えるようになりました。Firefox 3以降やThunderbird 3以降専用にアドオンを開発する際には、是非利用してみて下さい。

つづき: 2010-12-29
タグ: UxU | テスト | Mozilla
2010-01-29

Firefox Developers Conference 2009にて発表を行いました

こんにちは。下田(Piro)です。

昨日2009年11月8日に開催されたFirefox Developers Conference 2009にて、トークセッション「Aza Raskin に一問一答!」にパネリストとして参加しました。また、懇親会でのライトニングトーク第2部にて発表を行いました。参加された皆様、お疲れ様でした。また、セッションにお越しいただいた皆様、誠にありがとうございます。

発表資料および映像は以下よりご覧いただけます。

また、ライトニングトーク中で紹介しているアドオン「システムモニター」の最新版であるVer.0.3は、以下からダウンロードできます。

以下、イベントの感想と、イベント中に語ることができなかった点について書いていきたいと思います。

Aza Raskinさん、天野(amachang)さんとのトークセッション「Aza Raskin に一問一答!」について

このセッションでは、次期Firefoxでの標準搭載も視野に入れて開発が進められているJetpackや、新しいUIの可能性を模索するUbiquityの開発者として知られるAza Raskinさんに対して、現在Firefoxアドオンを開発している・開発した経験がある人の立場からの質問を私が、主にWebアプリケーションの開発を行っておりこれからJetpack用のスクリプト(Jetpack feature)の開発に携わる可能性がある人の立場からの質問を天野さんが行う形で、Firefoxの今後とWebアプリケーションの将来像について様々な話を聞かせていただきました。

Firefoxはアドオン(拡張機能)によるカスタマイズ性の高さが最大の特長ですが、自由度の高さゆえに、アドオンの開発には様々な準備と知識が必要になるという欠点もあります。Jetpackは、この欠点をカバーしてFirefoxの機能を拡張するスクリプトをより容易に配布・利用できるようにする物として開発されている、新しいアドオンのメカニズムです。「拡張機能」がテーマである今回のFirefox Developers Conference 2009においては、避けて通れない話題ですね。

今回のトークセッションにあたって事前にいくつかのJetpack featureを開発したのですが、その際に最も強く感じた事は、APIについての不満でした。現状ではAPIに関するドキュメントが圧倒的に不足しており、また、APIがドラスティックに変更されている最中であるため、安定したJetpack featureの開発および利用はまだまだ難しいのが実情です。セッション中でも質問を行いましたが、これについては、むしろ今が開発に口出ししてAPIをより良い物にしていくチャンスと捉えてほしいという回答をいただきました。とはいえ、APIドキュメントの重要性は彼らの間でも認識されており、そのための体制を整えているところであるとの話も聞けました。

Azaさんは、セカンドシステム症候群に陥ってはならないという事を再三述べていました。これまでのアドオンの仕組みに多くの不満があるからといって、その代替として新たに登場してきたJetpackに過剰な要求をして完璧な物を最初から求めると、議論が紛糾するばかりで実装が停滞し、Jetpackという仕組み自体がいつまで経っても完成しなくなってしまいます。ほどほどの所でまずは皆に使ってもらい、その上で実際の利用状況を見ながら少しずつ不足箇所を補っていく、という姿勢が重要ですね。

Jetpack featureで利用可能なライブラリとしては、現在はjQueryが標準添付されていますが、ゆくゆくはPerlにおけるCPANのように、様々なライブラリを簡単に利用できるようにする計画もあるそうです。また、現状のJetpackも将来のバージョンも、jQueryを使うことは必須というわけではなく、ライブラリの機能を使わずにJetpack featureを開発しても問題は無いとのことです。なお、私も天野さんも既存のライブラリをほとんど利用していない理由について、Azaさんより逆に質問をされるという場面もありました。(天野さんの場合は主に既存のライブラリのオーバーヘッドが気になるためで、私の場合はFirefox用アドオンの開発のみを行う都合上、ライブラリの最大のメリットである「ブラウザ間の差異の吸収」の恩恵を受けにくいというのが理由です。)

天野さんがWebアプリの開発者の観点から投げかけられた質問として、WebのUIを良くするにはどのようにすればよいか?という物もありました。これについてAzaさんは、作り手側が絶対に手出し・口出ししないようにして、目の前でエンドユーザに実際に使ってもらいその様子を観察すれば、WebのUIはもっと良くなるだろうとおっしゃっていました。ユーザビリティ・テストはUI開発の基本と言えますが、DTPなど視覚的デザインの文脈で捉えられることがまだまだ多いWebの世界においても、きちんと「インターフェース」としての作り方を意識する必要があるということですね。

国ごとの文化の違いがUIに与える影響について、Azaさんは欧米と中国のWebブラウズの仕方の違いを例に挙げてお答え下さいました。曰く、欧米や日本では1つの画面にかじりついて操作するのが一般的であるのに対し、中国では画面から少し離れて、画面内に非常に多くの機能・要素を詰め込んでそれらを平行して利用する場合が多いそうです(そのため、Googleのかつてのシンプルなトップページは不評だったそうです)。そういった文化ごとの利用形態の違いに合わせるために、末端にいる人達にUIのデザインを行う権限を持たせる必要があるのではないか、という意見も示されていました。

他のブラウザの隆盛、特にWebKitが多数のプロジェクトで採用されている事についての質問もありました。これについては、かつては止まったWebブラウザの進化をFirefoxが刺激して活性化させたように、今はWebKitがWebを活性化させているとして、その結果Webがより便利になるのであれば最終的な勝者は我々エンドユーザだ、との考えをお答えいただきました。

というように、Jetpackの話題のみにとどまらず、WebのUI一般の話にまで言及したトークセッションとなりましたが、皆様お楽しみいただけましたら幸いです。

ライトニングトーク「Webアプリとハードウェアを繋げたい!」について

ライトニングトークへの参加希望者が多かったため、過去に発表経験のある数人については懇親会の場で発表を行うこととなりました。今回はシステムモニターで行った、アドオンによるWebアプリケーション向けAPIの提供方法について発表しました。

「scriptable hardware」というキーワードで、基調講演でChris BlizzardさんがFirefoxの加速度センサー対応やカメラ用APIの実験について語っていらっしゃいました。クリアコードでも独自に同様の研究を行っており、システムモニター以外にも、指紋認証アドオン「Fingerprint-Auth」の実装を進めております。また、その開発の過程で浮上してきたアイデアとして、USB機器全般をJavaScriptから制御できるようにしてみてはどうかという話もあり、それについても「進行中のプロジェクト」としてお話しさせていただきました。Fingerprint-Authについては実際にお試しいただける形の物を近日中に公開する予定ですので、どうぞお楽しみに。

セッション「js-ctypes ~ネイティブコードを呼び出す新しいカタチ」では、JavaScriptからプラットフォームネイティブなバイナリの機能を呼び出すより平易な方法を提供するFirefox 3.6からの新機能js-ctypesを紹介されていましたが、システムモニターやFingerprint-AuthのようにJavaScriptのグローバルな名前空間にAPIを提供しようと思うと、現状ではXPCOMコンポーネントとして実装を行う必要があります。また、XPCOMコンポーネントはC++での開発となるため、各プラットフォーム向けのビルドやnsISecurityCheckedComponentインターフェースの実装などの作業が必要となります。残念ですね。

なお、発表ではすべての実装がJavaScriptによって行われているXUL/MigemoでのAPIの提供方法を例として紹介しました。JavaScript製XPCOMコンポーネントの場合は、比較的簡単に機能をWebアプリケーション向けのAPIとして公開することができます。作成手順についてはFirefox 3 Hacks等の解説と発表資料中の説明を併せてご覧下さい。

セッション「はてなブックマーク Firefox 拡張の裏側」について

株式会社はてなさんはてなブックマークFirefox拡張についての発表では、開発に使用したツールとして弊社開発のテスティングフレームワークUxUをご紹介いただけました。主にデータベース関連の機能等のユニットテストに利用されているとのことで、非同期処理が絡むテストの開発に役立ったとのお言葉をいただけたのは嬉しかったです。また、私が個人的に開発している各種のアドオンについても、ソースコードをアドオン開発の参考にしていただけたそうで、ありがたい限りです

なお、UxUでは最近のバージョンアップでより使いやすいユーザ操作エミュレーション用のAPIが追加されました。基本部分のユニットテスト以外にも、GUIが関係する機能テストやインテグレーションテストにもUxUを活用していただけたらと思っております。

イベント全体について

UbiquityにもJetpackにも共通している1つの狙いとして、「より多くの人が開発を行えるようにする」という目標があるそうです。より簡単に開発できるようになり、より多くの人が開発できるようになれば、より多くのイノベーションが生まれる、というのは前回のFirefox Developers Conferenceの際にもAzaさんが語られていた話ですが、AutoPagerize、ひいてはその原点であるGoogleAutoPagerが、どちらもGreasemonkeyスクリプトという世界から登場したことは、まさにその象徴的な出来事だと思います。

これはFirefoxのアドオン開発のみに限られた話ではなく、Chris Blizzardさんが語ったキーワード「scriptable hardware」もそれに連なる話であると言えるでしょう。折しも、先日GoogleがClosure Toolsという、JavaScriptでのアプリケーション開発のためのツール集を公開しました。APIやツールなど様々な側面から、Webクライアント上で動作する高機能なアプリケーションを開発しやすくするための土壌が整いつつあります。Webアプリケーションがエンドユーザからただの「アプリケーション」として扱われるようになる、Web OSという未来が本当にすぐそこまで来ているのだということを、改めて意識させられたイベントでした。

感想・イベントレポートリンク集

発表資料リンク集

つづき: 2009-12-21
タグ: Mozilla
2009-11-09

UxUを用いたデータ駆動テストの記述

2009年10月30日付で、テスティングフレームワークUxUのバージョン0.7.5をリリースしました。

UxUはこれまで「Firefoxアドオン開発用テスティングフレームワーク」と銘打っていましたが、Thunderbird用アドオンの開発にも利用されていることと、バージョン0.7.0以降からXULRunnerベースのアプリケーション一般に対してインストール可能なようになったことから、現在のプロジェクトページ上では「Firefox/Thunderbird用アドオン・XULRunnerアプリケーション開発用テスティングフレームワーク」と表記しています。

バージョン0.7.0以降で、UxUはデータ駆動テストの記述に対応しました。今回はUxUでのデータ駆動テストの記述方法の解説を通じて、データ駆動テストの利便性についてご紹介したいと思います。

このエントリ内の目次:

  1. データ駆動テストとは?
    1. べた書きしたテスト
    2. 関数を使ったテスト
    3. ループを使ったテスト
    4. データ駆動テスト
  2. UxUでのデータ駆動テストの書き方
  3. データ駆動テスト用の便利な機能

データ駆動テストとは?

データ駆動テストとは、簡単に言えば、「テストのロジックとデータを分離した自動テスト」の事です。データ駆動テストの利点を理解していただくために、データ駆動テストではないテストの例もいくつか挙げながら、それぞれの利点と欠点を見ていきましょう。

べた書きしたテスト

以下は、XUL/Migemoという「ローマ字入力で普通の日本語の検索を行う」アドオンの中の、半角英数字によるローマ字入力をひらがなに変換するモジュールのテストの一部です。

1
2
3
4
5
6
7
8
9
10
11
12
13
function test_roman2kana() {
  assert.equals('あいうえお',      transform.roman2kana('aiueo'));
  assert.equals('aiueo',      transform.roman2kana('aiueo'));
  assert.equals('がぎぐげご',      transform.roman2kana('gagigugego'));
  assert.equals('にほんご',        transform.roman2kana('nihongo'));
  assert.equals('ぽーと',          transform.roman2kana('po-to'));
  assert.equals('きゃっきゃ',      transform.roman2kana('kyakkya'));
  assert.equals('うっうー',        transform.roman2kana('uwwu-'));
  assert.equals('\\(\\)\\[\\]\\|', transform.roman2kana('\\(\\)\\[\\]\\|'));
  assert.equals('\\(\\[',          transform.roman2kana('\\(\\['));
  assert.equals('\\)\\]',          transform.roman2kana('\\)\\]'));
  assert.equals('\\|',             transform.roman2kana('\\|'));
}

このモジュールのroman2kana()メソッドは、半角英数字のローマ字入力をひらがなに変換し、それ以外の入力はそのまま返すという仕様になっています。この仕様通りに動作するかどうかを検証するため、ここでは11種類の引数を渡し、戻り値をassert.equals()で検証しています。検証しないといけないパターンが増えた時には、行をコピーして引数と期待値の部分を書き換えることになります。

パッと見て分かるかと思いますが、このテスト用コードには以下のような問題があります。

  • 同じコードの繰り返しが多く冗長である
  • テスト対象のメソッドを呼び出す記述が直接書かれているため、メソッド名や引数の取り方が変わった時などには、11行すべてを書き換える必要がある

「テストのロジック」と「テストしなければいけないデータ」が一緒に記述されているため、メンテナンス性が低いコードになってしまっていると言えます。

関数を使ったテスト

このようなテストのメンテナンス性を高めるための方法の1つとしては、アサーションを行う部分を関数としてまとめておくというやり方が考えられます。例えば以下の要領です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function test_roman2kana() {
  function oneTest(aExpected, aInput) {
    assert.equals(aExpected,
                  transform.roman2kana(aInput));
  }
  oneTest('あいうえお',      'aiueo');
  oneTest('aiueo',      'aiueo');
  oneTest('がぎぐげご',      'gagigugego');
  oneTest('にほんご',        'nihongo');
  oneTest('ぽーと',          'po-to');
  oneTest('きゃっきゃ',      'kyakkya');
  oneTest('うっうー',        'uwwu-');
  oneTest('\\(\\)\\[\\]\\|', '\\(\\)\\[\\]\\|');
  oneTest('\\(\\[',          '\\(\\[');
  oneTest('\\)\\]',          '\\)\\]');
  oneTest('\\|',             '\\|');
}

コードの冗長さが減りました。また、メソッド名や引数の取り方が変わった時も、変更が必要な箇所は1箇所だけになりました。

これでも悪くはないのですが、実際にテストを繰り返し走らせていると、以下のような問題が浮き彫りになってきます。

  • テストする入力パターンのどれか1つでアサーションに失敗したら、そこから後の記述がスキップされてしまう

例えば以下のようなシナリオが考えられます。

  1. テストを実行した。
  2. 2番目のパターンでfailした。
  3. 2番目のパターンでfailする原因となっていたバグを直した。
  4. 再度テストを実行した。
  5. 5番目のパターンでfailした。
  6. 5番目のパターンでfailする原因となっていたバグを直した。
  7. 再度テストを実行した。
  8. 6番目のパターンでfailした。
  9. …(以下続く)

「3の段階で行った修正で5や8のバグが発生した」という可能性もありますが、最初から2や5や8のバグがあったのであれば、まとめて直せていたかもしれません。これでは、バグを直しても直してもきりがないという、モグラ叩きのような感覚に陥ってしまいます。

ループを使ったテスト

メンテナンス性を高める別の手法として、アサーションに使う期待値とメソッドに渡す引数だけを配列で別途定義しておくというやり方も考えられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function test_roman2kana() {
  var patterns = [
        ['あいうえお',      'aiueo'],
        ['aiueo',      'aiueo'],
        ['がぎぐげご',      'gagigugego'],
        ['にほんご',        'nihongo'],
        ['ぽーと',          'po-to'],
        ['きゃっきゃ',      'kyakkya'],
        ['うっうー',        'uwwu-'],
        ['\\(\\)\\[\\]\\|', '\\(\\)\\[\\]\\|'],
        ['\\(\\[',          '\\(\\['],
        ['\\)\\]',          '\\)\\]'],
        ['\\|',             '\\|']
      ];
  for each (var pattern in patterns) {
    assert.equals(pattern[0],
                  transform.roman2kana(pattern[1]));
  }
}

これにもやはり問題があります。

  1. テストする入力パターンのどれか1つでアサーションに失敗したら、そこから後に記述された入力パターンに対するアサーションが行われない。(関数を使った書き方の場合と同じ問題)
  2. 配列の内容の順番を覚えておかないといけない
  3. failした時に、どの入力パターンでfailしたのかが分からない
  4. 1、2、3の問題を回避するための対処を毎回しなければならないのが面倒

1つ目の問題点については前述したとおりです。

2つ目の問題は、この例のように2次元配列を使った場合に現れます。上記の例では配列の0番目の要素が期待値、1番目の要素が入力となっていますが、これではどっちがどっちなのかを常に意識する必要があります。

3つ目は、ループに特有の問題です。UxUではテストにfailした時にスタックトレースが表示され、べた書きした場合や関数を使った書き方の場合であれば、スタックトレースを辿れば「どのパターンで失敗したのか」の情報に辿り着くことができます。しかし、ループを使っていると、スタックトレースの行き着く先はループの中になってしまうため、どのパターンに対して失敗したのかが一目では分からなくなってしまいます

この対策として、UxUのアサーションでは最後の引数として任意のメッセージを渡せるので、以下のようにして「どのパターンで失敗したのか」を表示させることは可能です。

1
2
3
assert.equals(pattern.expected,
              transform.roman2kana(pattern.input),
              pattern.input+'に対するテスト');

しかし、実際にたくさんテストを書くようになってくると、これが地味に面倒です。これが4つ目の問題点です。

べた書きした場合や関数を使った書き方の場合であれば、このような配慮なしに淡々とテストを書いていても、テスト実行時にはスタックトレースを辿ればデバッグに必要な情報を得られます。それなのに、ループに対してはこのような配慮をしなければいけないわけです。この面倒さによって、テストを新しく書いたり過去に書いたテストをメンテナンスしたりする意欲がじわじわと削がれてしまう、というのが一番の問題点だと言えます。

データ駆動テスト

UxU 0.7.0以降で導入されたデータ駆動テストの仕組みを使うと、上記のテストはこのように書くことができます。

1
2
3
4
5
test_roman2kana.parameters = utils.readParametersFromCSV('patterns.csv');
function test_roman2kana(aParameter) {
  assert.equals(aParameter.expected,
                transform.roman2kana(aParameter.input));
}

テスト用のコードにはロジックだけを書き、データは外部ファイルで定義します(後述しますが、テストケース内にデータを埋め込むこともできます)。データを定義しているファイルの形式はCSVです。

patterns.csvの内容
 inputexpected
半角英数aiueoあいうえお
全角英数aiueoaiueo
濁音のみgagigugegoがぎぐげご
濁音混じりnihongoにほんご
音引きpo-toぽーと
拗音kyakkyaきゃっきゃ
撥音uwwu-うっうー
paren\\(\\)\\[\\]\\|\\(\\)\\[\\]\\|
parenOpen\\(\\[\\(\\[
parenClose\\)\\]\\)\\]
pipe\\|\\|

この時、UxUは「test_roman2kana」という1つのテストではなく、「test_roman2kana (半角英数)」「test_roman2kana (全角英数)」……という名前の11個のテストを実行するようになります。

  • コードの繰り返しが無く、簡潔に書ける
  • すべてが独立したテストとして扱われるので、入力パターンのどれか1つでアサーションに失敗しても、他のテストはスキップされない
  • (CSVの場合)カラム名が引数として渡ってくるハッシュのキーとなるので、引数の順番を覚えておかなくていい
  • failした時は、実行されたテストの名前の中に実行時の入力パターンの名前が含まれるので、どのパターンで失敗したのかがすぐ分かる
  • これらをフレームワークの機能として提供しているので、テストを記述する時に面倒なことを考えなくてもよい

このように、データ駆動テストには多くのメリットがあります。単純な入出力のパターンを数多く検証しなければいけない場面で、データ駆動テストは威力を発揮します。

UxUでのデータ駆動テストの書き方

UxUでは、テスト関数のparametersプロパティに配列またはハッシュを代入すると、そのテストをデータ駆動テストとして実行するようになります。この時テスト関数には引数として、parametersプロパティの配列またはハッシュの要素が1つずつ渡されます。

以下は、配列を指定した場合の例です。

1
2
3
4
5
6
7
8
9
10
11
12
13
test_someFunc.parameters = [
  { expected : '29',   input : 'niku' },
  { expected : '2929', input : 'nikuniku' },
  { expected : '029',  input : 'oniku' }
];
function test_someFunc(aParameter) {
  /*
    1回目: aParameter = { expected : '29',   input : 'niku' }
    2回目: aParameter = { expected : '2929', input : 'nikuniku' }
    3回目: aParameter = { expected : '029',  input : 'oniku' }
  */
  ...
}

ハッシュを指定した場合は、以下のようになります。ハッシュのキーはテスト関数には渡されず、結果を表示する時のテスト名として表示されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
test_someFunc.parameters = {
  simgle: { expected : '29',   input : 'niku' },
  double: { expected : '2929', input : 'nikuniku' },
  o:      { expected : '029',  input : 'oniku' }
};
function test_someFunc(aParameter) {
  /*
    1回目: aParameter = { expected : '29',   input : 'niku' }
    2回目: aParameter = { expected : '2929', input : 'nikuniku' }
    3回目: aParameter = { expected : '029',  input : 'oniku' }
  */
  ...
}

データ駆動テスト用の便利な機能

データ駆動テストのサポートに併せて、いくつかの新しいヘルパーメソッドが追加されています。これらを使うことで、データ駆動テストをより簡単に作成・メンテナンスすることができます。

前述の例で使用しているutils.readParametersFromCSV()は、CSVファイルの内容を読み込み、最初の行のカラム名をキーとしたハッシュとして返します。例えば前述の例のCSVは、以下のようなハッシュとして解釈されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// test_roman2kana.parameters = utils.readParametersFromCSV('patterns.csv');
// これは、以下のように書くのと同じ
test_roman2kana.parameters = {
  '半角英数':   { input: 'aiueo',           expected: 'あいうえお' },
  '全角英数':   { input: 'aiueo',      expected: 'aiueo' },
  '濁音のみ':   { input: 'gagigugego',      expected: 'がぎぐげご' },
  '濁音混じり': { input: 'nihongo',         expected: 'にほんご' },
  '音引き':     { input: 'po-to',           expected: 'ぽーと' },
  '拗音':       { input: 'kyakkya',         expected: 'きゃっきゃ' },
  '撥音':       { input: 'uwwu-',           expected: 'うっうー' },
  paren:        { input: '\\(\\)\\[\\]\\|', expected: '\\(\\)\\[\\]\\|' },
  parenOpen:    { input: '\\(\\[',          expected: '\\(\\[' },
  parenClose:   { input: '\\)\\]',          expected: '\\)\\]' },
  pipe:         { input: '\\|',             expected: '\\|' }
};

CSVファイルはRFC4180準拠の形式の読み込みに対応しています。カンマ区切りではなくタブ区切りのファイルを使用したい場合は、utils.readParametersFromTSV()を使用して下さい。どちらも、読み込みたいファイルのパス(相対パスも利用できます)を第1引数に、ファイルのエンコーディングを第2引数に指定します。エンコーディング指定を省略した場合はUTF-8として読み込みます。

以下のリンク先に、各メソッドの詳しい説明があります。

また、JSON形式で保存した外部ファイルを読み込むためのutils.readJSON()というメソッドもあります。

1
test_roman2kana.parameters = utils.readJSON('patterns.json');

こちらも、読み込みたいファイルのパス(相対パスも利用できます)を第1引数に、ファイルのエンコーディングを第2引数に指定します。エンコーディング指定を省略した場合はUTF-8として読み込みます。詳しい説明は以下のリンク先をご覧下さい。

まとめ

データ駆動テストの仕組みを利用すると、テストのロジックとデータを分離できるため、メンテナンス性が高まることが期待できます。様々なパターンの入力を受け付ける機能を開発する時は、データ駆動テストをぜひ一度試してみて下さい。

タグ: Mozilla | UxU | テスト
2009-10-30

CPUの使用率を表示するFirefoxアドオン「システムモニター」を更新しました

CPUの使用率をFirefoxのツールバー上に表示するアドオン「システムモニター」のバージョン0.2をリリースしました。以下のリンク先からダウンロードできます。

前のバージョンからの変更点は以下の通りです。

  • グラフの左右端をドラッグすることで任意の幅に変更できるようになりました。
  • ツールバーのカスタマイズでグラフを任意の位置に移動できるようになりました。(初回起動時に、ツールバーに項目を追加するかどうかの確認を求めるようになりました。)

導入手順

インストールすると、初回起動時に以下のイメージのようなダイアログが表示されます。

初回起動時の確認ダイアログ

「はい」を選択すると、メニューバーの右端(Mac OS Xではナビゲーションツールバーの右端)に、以下のイメージのようにCPU使用率のグラフが表示されます。

CPUモニター

プラットフォームは、WindowsXP SP1以降、Mac OS X、Linuxをサポートしています。Linuxではlibgtop2を使用していますので、別途インストールしてください。

Web APIの利用

このアドオンを導入した環境では、システムモニターが提供するAPIを通じて、Webページ上のスクリプトからシステムモニターと同じ情報を取得できるようになります。以下は、Webページ内にCPU使用率のグラフを埋め込む例です。実際に上記リンク先からアドオンをインストールした状態で、このページを表示してみて下さい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var container = document.getElementById("system-monitor-demo");
if (!window.system || !window.system.addMonitor) {
  container.innerHTML = "システムモニターがインストールされていません";
  container.style.color = "red";
} else {
  container.innerHTML = "<div><canvas id='system-monitor' width='300' height='60'></canvas>"+
                        "<br /><span id='system-monitor-console'></span></div>";

  var width = 300;
  var interval = 1000;

  var CPUTimeArray = [];
  var arrayLength = width / 2;
  while (CPUTimeArray.length < arrayLength) {
    CPUTimeArray.push(undefined);
  }

  // リスナとして登録する関数には、CPUの使用率が割合として渡される。
  function onMonitor(aUsage) {
    var console = document.getElementById("system-monitor-console");
    console.textContent = aUsage;

    CPUTimeArray.shift();
    CPUTimeArray.push(aUsage);

    var canvasElement = document.getElementById("system-monitor");
    var context = canvasElement.getContext("2d")
    var y = canvasElement.height;
    var x = 0;

    context.fillStyle = "black";
    context.fillRect(0, 0, canvasElement.width, canvasElement.height);

    context.save();
    CPUTimeArray.forEach(function(aUsage) {
      var y_from = canvasElement.height;
      var y_to = y_from;
      if (aUsage == undefined) {
        drawLine(context, "black", x, y_from, 0);
      } else {
        y_to = y_to - (y * aUsage);
        y_from = drawLine(context, "green", x, y_from, y_to);
        drawLine(context, "black", x, y_from, y_to);
      }
      x = x + 2;
    }, this);
    context.restore();
  }

  function drawLine(aContext, aColor, aX, aBeginY, aEndY) {
    aContext.beginPath();
    aContext.strokeStyle = aColor;
    aContext.lineWidth = 1.0;
    aContext.lineCap = "square";
    aContext.moveTo(aX, aBeginY);
    aContext.lineTo(aX, aEndY);
    aContext.closePath();
    aContext.stroke();
    return aEndY - 1;
  }

  // リスナを登録する。
  window.system.addMonitor("cpu-usage", onMonitor, interval);

  window.addEventListener("unload", function() {
    window.removeEventListener("unload", arguments.callee, false);
    // ページのアンロード時にリスナの登録を解除する必要がある。
    window.system.removeMonitor("cpu-usage", onMonitor);
  }, false);
}
["
\n", "\n"]

次世代のWebアプリケーションに向けて

上の例をご覧いただいても分かるとおり、このアドオンは、Webアプリケーション(Webページ上のスクリプト)から各種ハードウェアデバイスへアクセスする技術のデモンストレーションとして開発されています。

Webカメラとの連動によるAR(拡張現実)や、指紋認証、Felica認証といった特殊なハードウェアを必要とする認証技術など、この技術を応用することにより今までのWebアプリケーションではできなかった様々なことが可能となります。

クリアコードでは、このようにWebとハードウェアを直結する技術の開発に取り組んでおります。特殊なデバイスを搭載したハードウェアへのWebブラウザエンジンの組み込みや、特殊なデバイスの利用を前提としたWebベースのシステム開発など、ご用命がございましたらぜひinfo@clear-code.comまでお問い合わせください

タグ: Mozilla
2009-10-20

CPUの使用率を表示するFirefoxアドオンをリリース

CPUの使用率をFirefoxのウインドウ右上に表示するアドオンをリリースしました。ダウンロードはこちらからどうぞ。新しいバージョンがすでに公開されていますので、そちらを参照して下さい。

インストールすると、以下のイメージのようにウインドウ右上に直近のCPU使用率がグラフで表示されます。

CPUモニター

プラットフォームは、WindowsXP SP1以降、Mac OS X、Linuxをサポートしています。Linuxではlibgtop2を使用していますので、別途インストールしてください。

また、このアドオンではCPU使用率をWebアプリケーションからも使えるようにしてあります。JavaScriptで以下のように書くと、intervalTime間隔ごとにfunctionが呼び出され、aUsageにその時のCPU使用率が渡ってきます。

system.addMonitor("cpu-usage", function(aUsage){}, intervalTime);

クリアコードでは、このようにWebアプリケーションから各種ハードウェアデバイスにアクセスする機能の開発を行っております。ご用命はinfo@clear-code.comまでお問い合わせください

タグ: Mozilla
2009-10-19

Windows Mobile用Fennecのビルド方法

2009年6月1日時点でのWindows Vista上でWindows Mobile用Fennecをビルドする方法を紹介します。時間が経つとビルド方法が変わると思うので、注意してください。

情報源

Fennecのビルド方法に関する情報はMobile/Build/Fennec - MozillaWiki(英語)にあります。最新情報が必要な場合はこのWikiを見てください。

ただし、Wiki上の情報が古いことがあるので注意が必要です。

下準備

Fennecをビルドするために必要なツールをインストールします。 (参考: Mobile/Build/Windows Mobile PrepForBuild - MozillaWiki

まず、Microsoftが提供しているツールをインストールします。

次に、Mozillaが提供しているビルドツールをインストールします。

このビルドツールの中にはMSYSやMercurialなどのツールが入っています。

「c:\mozilla-build\start-msvc9.bat」というバッチファイルがインストールされます。このバッチファイルを実行することにより、bashが起動し、Fennecビルド用環境に入ることができます。

ビルド

ビルドに必要なツールが揃ったので、それらのツールを使ってFennecをビルドする方法を説明します。(参考: Mobile/Build/Windows Mobile BuildingIt - MozillaWiki

FennecのソースコードはMercurialで管理されています。まず、Mercurialでソースコードを取得します。ここからはstart-msvc9.batで起動した環境の中で作業します。

ソースコードは「c:\hg\」以下に置くことにします。

$ mkdir /c/hg/
$ cd /c/hg/
$ hg clone http://hg.mozilla.org/mozilla-central
$ cd mozilla-central
$ hg clone http://hg.mozilla.org/mobile-browser mobile

ソースコードを取得したらビルド用の設定ファイル「.mozconfig」を作成します。.mozconfigは雛形となるファイルを少しずつ変更して作成するとよいでしょう。ここでは、クリアコードが公開している雛形を利用します。

$ wget -O .mozconfig http://www.clear-code.com/repos/svn/fennec/wince.mozconfig

現在、リポジトリ内にデフォルトの.mozconfigを入れてはどうか、という話がでている(Bug 479515 – push default mozconfig to mobile-browser repo)ので、将来的には取得したソースコード中から雛形を利用できるようになることでしょう。

.mozconfigを作成したら、以下のコマンドでビルドできます。

$ time make -f client.mk build

マシンの速度にもよりますが、1時間以上かかるでしょう。timeはビルド時間を計測するためにつけているだけなので、以下のようにtime無しでビルドすることもできます。

$ make -f client.mk build

ビルドの完了を待つ間に、最新版に追従する方法を説明します。

ソースコードのアップデート

Mozillaのソースコードは毎日アップデートされています。最新版のソースコードに追従するためには、ソースコードがあるディレクトリで以下のコマンドを実行します。

$ hg pull && hg update && (cd mobile && hg pull && hg update)

このコマンドを実行することにより、手元のソースコードが最新版になります。

アップデートされたソースコードでビルドしなおす場合も最初のビルドのときと同じコマンドを使います。

$ time make -f client.mk build

マシンの速度やソースコードがどの程度アップデートされたかにもよりますが、30分以上かかるでしょう。ビルドの完了を待つ間に、Fennecのデバッグ方法を説明します。

デバッグ

Fennecのデバッグ方法を説明します。(参考: Mobile/Build/Windows Mobile DebuggingIt - MozillaWiki

まず、Visual StudioでFennecデバッグ用のプロジェクトを作成します。プロジェクトの種類は「Visual C++→スマートデバイス→Win32スマートデバイスプロジェクト」を選んでください。このプロジェクトはデバッガを使うためだけのプロジェクトなので設定はデフォルト値で問題ありません。

プロジェクトを作成したらプロジェクトのプロパティで以下を設定します。

  • 構成プロパティ→デバッグ→リモート実行ファイル: 「\Storage Card\fennec.exe」
  • 構成プロパティ→配置→配置デバイス: 「USA Windows Mobile 6.1.4 Professional VGA Emulator」

設定したらF5でデバッグを開始します。エミュレータが起動するので、Fennecのビルド結果を出力したディレクトリをエミュレータの「ストレージカード」としてアクセスするための設定をします。この設定は1回行えば今後はその設定を利用できます。

「ストレージカード」としてアクセスするためには、エミュレータのウィンドウメニューから「ファイル→構成→全般→共有フォルダ」とアクセスします。「共有フォルダ」に「c:\hg\fennec-debug\mobile\dist\bin」を指定してください。

これで、エミュレータ上で「\Storage Card\fennec.exe」としてFennecを実行できるようになります。もう一度、Visual Studio上でF5を押してデバッグを開始してください。デバッガ上でFennecが起動します。

注: 現在のFennecは初回起動時(プロファイルがない状態で起動した場合)のみ内部で再起動しています。そのため、再起動した段階でデバッガからデタッチしてしまいます。2回目以降の起動ではそのままデバッガ上で動くので、一度タスクマネージャからFennecを終了してF5でデバッグを開始しなおしてください。

エミュレータでのネットワーク接続

エミュレータでネットワークに接続するためにはVirtual PCをインストールする必要があります。

Virtual PCをインストールするとエミュレータでネットワークを利用できるようになります。エミュレータのウィンドウメニューから「ファイル→構成→NE2000 PCMCIAネットワークアダプタを有効にし、次の項目にバインドする」とアクセスします。この項目にチェックを入れるとエミュレータからネットワークにアクセスできるようになるので、エミュレータ上のFennecからWebを閲覧することができるようになります。

Windows Mobile 6.1.4用のエミュレータは英語用のため日本語フォントが入っていません。日本語を表示するためには、Fennecでの日本語表示設定のように別途日本語フォントを入れる必要があります。

リリース用ビルドの作成

エミュレータ上でのFennecのデバッグについて説明したので、次に、実機へFennecをインストールする方法を説明します。

雛形.mozconfigではデバッグ用ビルドの設定が有効になっています。しかし、実機へインストールする場合は最適化されたリリース用ビルドの方がよいでしょう。インストール方法を説明する前に、まず、リリース用ビルドの設定を説明します。

雛形.mozconfigの中に以下のような記述があります。

ac_add_options --enable-debug
ac_add_options --disable-optimize
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-debug
#ac_add_options --enable-optimize
#mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-release

この部分のコメントアウトされていない部分がデバッグ用ビルドの設定で、コメントアウトされている部分がリリース用ビルドの設定です。つまり、リリース用ビルドにする場合は以下のように変更します。

#ac_add_options --enable-debug
#ac_add_options --disable-optimize
#mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-debug
ac_add_options --enable-optimize
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-release

この.mozconfigを使ってビルドすることにより最適化されたリリース用ビルドを作成することができます。

それでは、リリース用ビルドを使ってWindows Mobile用のインストーラを作成する方法を説明します。

Windows Mobile用のインストーラの作成

Windows Mobile用のインストーラである.cabファイルを作成するには、「$(MOZ_OBJDIR)/mobile/」ディレクトリ($(MOZ_OBJDIR)は.mozconfigで指定したビルド結果出力ディレクト リ)で以下のコマンドを実行します。

$ make installer

雛形.mozconfigのように

mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-release

としていた場合は、以下のようになります。

$ cd ../fennec-release/mobile
$ make installer

コマンドが完了すると「$(MOZ_OBJDIR)/mobile/dist/」以下にfennec.1.0a1.en-US.wince-arm.cabファイルができます。(ファイル名の中の「1.0a1」はバージョン番号でバージョン毎に異なります。)このファイルをActiveSyncなどで実機へコピーしてインストールすることができます。

まとめ

Windows Vista上でFennecのビルド・デバッグ・インストーラ作成を行う方法を紹介しました。

これで環境環境は用意できると思うので、あとは「開発合宿後も、Fennec の開発活動に関わる意思を有する方」という条件を満たせば、Mozilla Japan主催のMobile Firefox開発合宿に参加できます。興味のある方は参加してはいかがでしょうか。

タグ: Mozilla
2009-06-01

Fennecでの日本語表示設定

Nightlyビルドでの日本語対応状況

2月26日のエントリのエントリでお伝えした通り、以前のWindows Mobile版Fennecには日本語表示に致命的なバグが存在していましたが、弊社エンジニアがこれを修正し、本家にも既にこの修正が取り込まれております。このため、現在mozilla.orgで公開されているNightlyビルドでも、バイナリを修正することなく日本語を表示することが可能です。 ただし、現在のバージョンではまだフォントの自動選択処理に不具合があるため、設定を正しく行っていない場合、インストールしたフォントや訪れたサイトによっては、文書の一部あるいは全てが文字化けする可能性があります。 そこで今回は、4/28現在のNigtlyビルドにおいて日本語を正しく表示する設定を紹介致します。

Nightlyビルドのインストール

mozilla.orgのFTPサイトから最新のcabファイルを取得し、インストールします。その後、一度Fennecを起動して、プロファイルを作成します。プロファイルは \Application Data\Mozilla\Fennec\Profiles\xxxxxxxx.default フォルダ以下に作成されます。この後、設定ファイルを手動で変更しますので、一旦Fennecを終了します。

日本語フォントのインストール

現在のFennecは、Windows Mobile日本語版に標準で搭載されているAC3形式のフォントに対応していません。このため、別途日本語TTFフォントをインストールする必要があります。ここでは例として、VLゴシックをインストールします。

  • VLゴシックのサイトからzipアーカイブを取得
  • 上記zipアーカイブからVL-PGothic-Regular.ttfを抽出
  • 上記TTFファイルを \windows フォルダにコピー

prefs.jsの設定

プロファイルフォルダ以下のprefs.jsファイルを開いて、以下ような記述を追加します。

user_pref("font.language.group", "ja");
user_pref("font.name.sans-serif.ja", "VL PGothic");
user_pref("font.name.sans-serif.x-unicode", "VL PGothic");
user_pref("font.name.sans-serif.x-western", "VL PGothic");
user_pref("font.name.serif.ja", "VL PGothic");
user_pref("font.name.serif.x-unicode", "VL PGothic");
user_pref("font.name.serif.x-western", "VL PGothic");
user_pref("intl.accept_languages", "ja");
user_pref("intl.charset.default", "UTF-8");
user_pref("intl.charset.detector", "ja_parallel_state_machine");

TrueTypeフォントであれば、VLゴシックに限らずどのフォントでも使用可能と思われます。例えばIPA Pゴシックを使用する場合は、VL PGothicという記述をIPAPGothicに変更します。

userContent.cssの設定

以上でほぼ日本語の表示は可能なのですが、これでもなおフォーム内の文字が化ける場合があります。今回はユーザーCSSでフォントを指定して、この問題を回避します。 プロファイルフォルダ以下に chrome というフォルダを作成し、userContent.css という名前で以下のような内容のファイルを作成します。

input[name], input[value], select[name], option, textarea, button, fieldset, label, legend, optgroup[label] {
  font-family: 'VL PGothic', sans-serif;
  font-size: 12px;
}

以上で、フォームでも日本語が表示されるようになります。

まとめ

Fennecで日本語を表示することはできたでしょうか? クリアコードでは、上記のような煩わしい設定を行わなくともFennecで日本語を表示できるよう、引き続き改善作業を行っています。また、現在のNightlyビルドにはまだ含まれていないものの、日本語入力時の未確定文字列が表示されない問題等についても修正を行い、本家にフィードバックしています。

タグ: Mozilla
2009-04-28

2009年4月のFennec

何度かここにも書いてあるように、クリアコードはWindows Mobile上で動作するブラウザFennecの改善に力をいれています。

改善した結果は積極的に本家にフィードバックしており、次のFennecのリリースには取り込まれる予定です。具体的には、以下の点が改善されます。

  • フォントまわり:
    • 480249 gfxWindowsPlatform::ResolveFontName() almost fails since direct access to mName member.
    • 484083 Should load TruType Collection file too
    • 486624 AppendFacesFromFontFile is called twice for the same font file.
    • 489511 gfxFontCache will never hit on Freetype2 backend.
  • 起動まわり:
    • 485031 WinCE Crash on First Startup (or command line environment passing)
    • 487174 Modify nsXULStub to launch from GRE folder on WinCE
    • 485465 wince fennec ignores "command line" options

フォントまわりの改善で、日本語の表示問題が修正されたり、表示速度が高速化されたりします。起動まわりの改善で、使い勝手がよくなったりします。

MozillaサポートやMozilla本体・拡張機能の開発に関心のある方はinfo@clear-code.comまでお問い合わせ下さい。

タグ: Mozilla
2009-04-24

クリアコードの公開リポジトリ

すでにお気づきの方もいるかもしれませんが、先日から、クリアコードで開発したプログラムが入ったSubversionリポジトリリポジトリの更新状況のRSS)の公開を始めました。

クリアコードでは既存のフリーソフトウェアの開発に参加したり、新しくmilter managerなどのフリーソフトウェアを開発したりしていますが、それらの開発成果の公開場所はケースバイケースとなっています。

  • 既存のフリーソフトウェアの開発に参加する場合は、基本的に、開発成果はアップストリームに還元しています。
  • 新しくフリーソフトウェアを開発する場合は、基本的には関連コミュニティで標準的なホスティングサイトを利用しています。例えば、Ruby関連のソフトウェアであればRubyForge、GNOME関連のソフトウェアであればgnome.orgといった具合です。
  • 関連するソフトウェアがGitHubGoogle Codeを利用している場合は、それらのサイトを利用することもあります。
  • 特に標準的なホスティングサイトが無い場合はSourceForgeを利用しています。

このように、クリアコードの開発成果のソースコードは様々なホスティングサイトのリポジトリにて管理、および公開されています。

まもなくクリアコードは設立から3年が経とうとしていますが、その間、プロジェクトを作るまでもないような小規模なソースコードがいくつかたまってきました。この度、そのようなソースコードをSubversionリポジトリで公開することにしました。

このリポジトリには現在、ページの一部を折りたたむfolding.jsや、このククログを生成するためのtDiary関連のスクリプト(日記のデータをSubversionで管理するIOバックエンド日記を静的なHTMLに変換するスクリプトなどの記事で述べた物)、Thunderbird用の各種アドオンのソースコードが入っています。誰でも自由にチェックアウトできますので、注意事項をご了承の上でどうぞご利用ください。

以下、現在入っているプログラムを簡単に紹介します。

注意事項

  • これらのプログラムはすべて無保証です。
  • プログラムは予告なく追加・削除・変更されることがあります。

JavaScript関連

tDiary関連

ククログだけではなく、milter managerのブログでも使っています。使い方はmilter managerのtdiary.confが参考になると思います。

  • /tdiary/subversionio.rb: tDiaryのSubversionバックエンド
  • /tdiary/gitio.rb: ↑のSubverionバックエンドのgitバージョンです。
  • /tdiary/html-archiver.rb: tDiaryのデータをHTML化するで紹介した物ですが、その後、カテゴリに対応するなどいくつかの点で改良されています。
  • /tdiary/patches/customizable-style-path.rb: スタイルファイルのパスをカスタマイズできるようにします。RDスタイルなど、標準では有効になっていないスタイルを使うときに便利です。本家に提案したのですが、rejectされたのでここに入っています。
  • /tdiary/plugin/classed-category-list.rb: class付きでカテゴリリストを生成します。ククログのトップに並んでいる「タグ: ...」の部分です。
  • /tdiary/plugin/date-to-tag.rb: 日付を本文の下に生成します。今思えば名前が悪いですね。後で変えるかもしれません。
  • /tdiary/plugin/link-subtitle.rb: サブタイトルをその記事のリンクにします。通常は日付がリンクになるのですが、このククログでは日付は本文の下に置いてあるので、代わりにサブタイトルをリンクにしています。
  • /tdiary/plugin/multi-icon.rb: ページアイコンとして、favicon.ico(ICO形式の画像)とfavicon.png(PNG形式の画像)を両方指定できるようにします。
  • /tdiary/plugin/title-navi-label.rb: 「前の日記」「次の日記」リンクのラベルをリンク先の日記のタイトルにします。
  • /tdiary/plugin/zz-permalink-without-section-id.rb: section_footerプラグインが生成するpermalinkから「#pXX」を削除します。ククログでは、1記事を1セクションにして同じ日には複数の記事を書かないという方針で運営しており、セクションIDが必要ないため、このプラグインを作成しました。ファイル名の「zz-」は、このプラグインの読み込み順序を最後の方にさせるためのものです。

Thunderbird関連

Thunderbird Add-ons - クリアコードで公開している、Thunderbirdのバグを回避するパッチや挙動の変更を行う拡張機能です。公開ページにも書いてありますが、これらの拡張機能は無保証です。業務上の必要性からの導入をお考えの場合は、Mozilla Firefox & Mozilla Thunderbird保守・サポートサービスのご利用もご検討ください。

おまけ

リポジトリの更新状況を配信しているRSSは、Subversionに標準添付のcommit-email.rbで生成しています。今回、RSSのタイトルや説明を日本語にしたかったので、Subversionのtrunkに--rss-titleと--rss-descriptionオプションを追加しました。また、--repository-uriで指定されたリポジトリのURIをコミットメールのX-SVN-Repositoryヘッダに設定するようにもしています。

Subversionリポジトリの整形表示にはRepos Styleを利用しています。mod_dav_svnにはSVNIndexXSLTというオプションがあって、それを利用しています。

タグ: Ruby | JavaScript | Mozilla
2009-04-06

UxUで外部テキストエディタを使う時のおすすめ設定

みなさん、テストしてますか?(挨拶)

UxUでは、テスト失敗時に表示されるスタックトレースからテキストエディタを起動することができます。この時、利用するテキストエディタがコマンドライン引数による行指定に対応していれば、エラーが発生した行を直接開いて編集できます。きちんと設定しておけば、テストを実行して、編集して、またテストして、といったサイクルで開発を進められるので非常に便利です。

以下に、有名なテキストエディタ向けの設定の例をいくつか挙げてみました。UxUの設定ダイアログの「MozUnitテストランナー」タブでエディタ起動用のコマンドとして入力してください。(エディタの実行ファイルのパスは必要に応じて読み替えてください)

秀丸エディタ
"C:\Program Files\Hidemaru\Hidemaru.exe" /j%L,%C "%F"
TeraPad
"C:\Program Files\TeraPad\TeraPad.exe" /j=%L "%F"
サクラエディタ
"C:\Program Files\sakura\sakura.exe" "%F" -X=%C -Y=%L
EmEditor
"C:\Program Files\EmEditor\EmEditor.exe" /l %L /cl %C "%F"
xyzzy
"C:\Program Files\xyzzy\xyzzycli.exe" -l "%F" -g %L -c %C
萌エディタ
"C:\Program Files\moeditor\moe.exe" "%F" -m %L,%C
gedit
/usr/bin/gedit +%L "%F"
Vim(vi)
/usr/bin/vim +%L "%F"
Emacs
/usr/bin/gnuclient +%L "%F"

なお、%Lは行番号、%Cは列番号、%Fはファイルのパスへと、それぞれ自動的に置換されます。

タグ: Mozilla | テスト | UxU
2009-04-01

Fennecでの日本語表示

1月7日のエントリでもお伝えしたとおり、クリアコードでは独自にWindows Mobile版Mozillaの改善に取り組んでいます。

先日リリースされたFennecのプレアルファ版には、日本語が全く表示されないという問題がありました。オープンソースカンファレンス2009 Tokyo/Springにおいて、弊社ブースにてこの問題の修正を目指したライブ開発を行っておりましたが、それから約1週間かかって、ようやく日本語が表示できる所までこぎつけました。

Fennecでクリアコードのサイトを開いたところ

ただし、現在のFennecはAC3形式のフォントには対応しておりません。Windows Mobile日本語版のデフォルトのフォントはこのAC3形式であるため、日本語を表示するためには、別途TrueTypeフォントをインストールする必要があります。

今回のバグは、Fennecがインストールされているフォントを認識できず、結局システムフォントしか利用されないことが原因でした。このため、日本語環境のみならず、他のどの環境においても、システムフォント以外のフォントに変更することができない状態となっておりました。

タグ: Mozilla
2009-02-26

Windows Mobile版Mozilla

モバイルFirefox「Fennec」

現在Mozillaの開発コミュニティにおいて、Fennecと呼ばれるモバイルデバイス向けのウェブブラウザが開発されていることをご存じの方も多いかと思います。Fennecでは、PC版のFirefoxと同じGeckoエンジンを使用することによってPC上と同じレンダリング結果が得られるほか、拡張機能もサポートされることがアナウンスされており、モバイルデバイス用ウェブブラウザに新たな変革をもたらす事が期待されています。

Windows Mobile版の開発状況

Fennecのターゲットプラットフォームとしては、Linuxの他に、Windows Mobileも挙げられています。MozillaのMercurialリポジトリで開発履歴を確認すると、Windows Mobile版Mozillaは、主にBrad Lassey氏とDoug Turner氏の2人によって活発に開発が進められているようです。

しかしながら、現在のところ一般向けにモバイルデバイス用実行ファイルが 提供されているのはNokia N810用のみであり、Windows Mobile用はアルファ版すら提供されていない状況です。

N810が日本では未発売ということも相まって、Windows Mobile版のMozillaがどの程度動作するものなのか、やきもきされている方もおられるのではないでしょうか?

Windows Mobile版Mozillaの動作デモ

現在、クリアコード社内においても組み込み機器向けMozillaの研究を進めており、Windows Mobile用XULRunner のビルド及び動作も確認しておりますので、そのような方々に向けて、開発版の動作状況を動画で紹介したいと思います。

ただし、この動画で動作しているウェブブラウザはFennecではなく、MyBrowserという動作確認用の簡素なサンプルブラウザです。また、動画で使用しているバージョンは2008年12月中旬頃のものを元にしており、さらに、最低限の動作を確認できるよう弊社内で独自の修正を加えているため、現在リポジトリから取得できるバージョンとは少し状況が異なります。

本動画は、リモートの開発環境からデバッグ版MyBrowserを起動した直後から開始されています。ブラウザ画面が表示されるまで20秒以上と、起動がやや遅い印象はありますが、一度起動してしまうと、思いのほかスムースにレンダリングされているように見受けられます。

まだまだモバイルデバイス向けの最適化が進んでいないため、単純に他のモバイルデバイス用ウェブブラウザと比較してしまうと苦しい面もありますが、現時点でも十分実用に成り得るポテンシャルがあることは確認していただけるかと思います。

Fennecの動作状況

弊社では、Fennecについても同様にWindows Mobile上での動作を確認しています。

Windows Mobile上で動作するFennec

しかし、こちらはまだWindows Mobileデバイスに合ったレイアウト調整やパフォーマンス調整が行われておらず、動画で紹介するには時期尚早であるため、今回はデモを見送りました。FennecについてはPCで動作するバージョンがリリースされているため、こちらを試用して夢を膨らませておくのが良いでしょう。

今後の予定

ククログでは、今後とも随時モバイル版Mozillaの研究・開発状況をレポートしていく予定です。

タグ: Mozilla
2009-01-07

UxU(UnitTest.XUL)を利用したFirefoxアドオンのデバッグの例

Firefoxアドオン開発者向け自動テストツールのUxUは、新たに発見したバグの修正にも活用することができます。本日リリースされたXUL/Migemo バージョン0.11.7で行われた修正の場合を例に、実際のデバッグ作業の流れを解説します。

状況

XUL/Migemoは、Firefoxで表示しているページ内のテキストを検索する機能を提供するアドオンですが、検索を開始する際に、「現在のスクロール位置から検索を開始する」という処理を含んでいます。0.11.6以前では、この機能を使用している時に、ページ先頭から検索が始まるべき場面で、先頭以外の場所から検索が始まってしまうことがあるという問題が起こっていました。

再現条件の特定

いくつか条件を変えて調査した結果、スクロールが発生しているページでは期待通りの結果になっているのに対して、スクロールが全く発生していないページ(ページ全体がウィンドウの現在の大きさの中に収まっているページ)では期待と異なる結果になっていることが判明しました。

原因箇所の特定

前述の処理の肝となっているのは、pXMigemoFindクラスfindFirstVisibleNode()というメソッドです。このメソッドは、渡されたフレーム(DOMWindow)において現在のスクロール位置で見えている最初の要素を検索して返す物です。このメソッドの戻り値を確認した所、前述の条件下では戻り値が期待と異なっている事が判明しました。

このことから、今回主な修正対象になるのはこのfindFirstVisibleNode()というメソッドであるということになります。

テストケースの作成

上記メソッドの実装を見直す前に、UxU用のテストケースを作成します。これにより、これから行う修正で目指すべきゴールが明確になります。つまり、このテストが成功する状況まで持って行くことが、今回の修正のゴールとなります。

テストケースはJavaScriptのコードだけで完結する場合もありますが、今回のような場合は実際のWebページを使ってテストを行う必要があります。そこで、問題が発生する条件と発生しない条件の両方の事例としてHTMLドキュメントを用意します。

これらのドキュメントを使い、shortPage.htmlではスクロールが発生せずlongPage.htmlではスクロールが発生する、という条件の下でテストを行うテストケースをこれから作成することになります。

ところで、現在の実装で問題が起こっている場合だけでなく、すでに正常に動いている場合の事例も同時に作成していることに気がついたでしょうか? 両方の場合を常にテストすることで、「ある問題を修正したら、今度は、今までは正常に動いていた物が動かなくなった」という状況、いわゆる後退バグを未然に防ぐことができます。 *1

それではテストケースを作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
utils.include('pXMigemoClasses.inc.js');

var findModule;

function setUp()
{
  yield utils.setUpTestWindow();
  findModule = new pXMigemoFind();
  findModule.target = utils.getTestWindow().gBrowser;
}

function tearDown()
{
  findModule.destroy();
  findModule = null;
  utils.tearDownTestWindow();
}

function testFindFirstVisibleNode()
{
  // ここに実際のテスト内容を記述する
}

pXMigemoFindクラスの単体テストはまだ作成されていなかったので、今回はsetUpとtearDownから作成します。すでに作成済みのテストケースがあり、それにテスト項目を追加する場合、この作業は不要となります。

pXMigemoFindクラスはtabbrowser要素を用いて初期化する必要があるため、setUpでテスト用のFirefoxウィンドウを開き、そのウィンドウのtabbrowser要素で初期化します。また、tearDownではsetUpで開いたテスト用のFirefoxウィンドウを閉じて、pXMigemoFindクラスのインスタンスを破棄します。UxUは関数名を見て自動的にその種類を判別するため、これだけで、これらの関数はテストの初期化処理と終了処理として認識されるようになります。

なお、インクルードしているpXMigemoClasses.inc.jsというファイルは、pXMigemoFindクラスやそのクラスが依存しているすべての関連クラスの定義を読み込む物です。

次に、テストの内容を作成していきます。

1
2
3
4
5
6
7
8
function testFindFirstVisibleNode()
{
  var win = utils.getTestWindow();
  win.resizeTo(500, 500);
  assert.compare(200, '<', utils.contentWindow.innerHeight);

  // ここに実際のテスト内容を記述する
}

関数名を「test」で始めると、その関数はテストの内容として自動的に認識されます。

最初に、ウィンドウの大きさを調整して、「shortPage.htmlではスクロールが発生せずlongPage.htmlではスクロールが発生する」という条件を整えておきます。ここでは、テスト自体が期待通りの条件下で実行されていることを確認するために、assert.compare()でテスト用フレームの大きさを調べています。

1
2
3
4
5
6
7
8
9
  yield utils.addTab(baseURL+'../res/shortPage.html', { selected : true });

  var frame = utils.contentWindow;
  var node = findModule.findFirstVisibleNode(findModule.FIND_DEFAULT, frame);
  assert.equals(utils.contentDocument.documentElement, node);

  item = frame.document.getElementById('p3');
  node = findModule.findFirstVisibleNode(findModule.FIND_BACK, frame);
  assert.equals(item, node);

テスト用のドキュメントを新しいタブで開き、findFirstVisibleNode()メソッドの返り値が期待通りかどうかを検証します。1つ目の検証は前方検索、2つ目は後方検索です。

同様にして、スクロールが発生する場合のテストも作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  yield utils.addTab(baseURL+'../res/longPage.html', { selected : true });

  frame = utils.contentWindow;
  node = findModule.findFirstVisibleNode(findModule.FIND_DEFAULT, frame);
  assert.equals(utils.contentDocument.documentElement, node);

  item = frame.document.getElementById('p10');
  frame.scrollTo(0, item.offsetTop);
  node = findModule.findFirstVisibleNode(findModule.FIND_DEFAULT, frame);
  assert.equals(item, node);

  frame.scrollTo(0, item.offsetTop - frame.innerHeight + item.offsetHeight);
  node = findModule.findFirstVisibleNode(findModule.FIND_BACK, frame);
  assert.equals(item, node);

  item = frame.document.getElementById('p21');
  frame.scrollTo(0, item.offsetTop - frame.innerHeight + item.offsetHeight);
  node = findModule.findFirstVisibleNode(findModule.FIND_BACK, frame);
  assert.equals(item, node);

スクロールされていない時、ページ途中までスクロールされている時、ページの最後までスクロールされている時の各ケースで前方検索と後方検索を行い、結果を検証します。

ここで、かなりの部分のコードが重複していることに気がついたでしょうか。このような場合、それぞれの検証の前で重複しているコードと検証とをひとまとめにして実行する関数(カスタムアサーション)を定義しておくと、テスト項目の追加が簡単になります。以下は、カスタムアサーションを使ってここまでのテスト内容を書き直した物です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function testFindFirstVisibleNode()
{
  var win = utils.getTestWindow();
  win.resizeTo(500, 500);
  assert.compare(200, '<', utils.contentWindow.innerHeight);

  function assertScrollAndFind(aIdOrNode, aFindFlag)
  {
    var frame = utils.contentWindow;
    var item = typeof aIdOrNode == 'string' ? frame.document.getElementById(aIdOrNode) : aIdOrNode ;
    frame.scrollTo(
      0,
      (aFindFlag & findModule.FIND_BACK ?
        item.offsetTop - frame.innerHeight + item.offsetHeight :
        item.offsetTop
      )
    );
    var node = findModule.findFirstVisibleNode(aFindFlag, frame);
    assert.equals(item, node);
  }

  yield utils.addTab(baseURL+'../res/shortPage.html', { selected : true });
  assertScrollAndFind(utils.contentDocument.documentElement, findModule.FIND_DEFAULT);
  assertScrollAndFind('p3', findModule.FIND_BACK);

  yield utils.addTab(baseURL+'../res/longPage.html', { selected : true });
  assertScrollAndFind(utils.contentDocument.documentElement, findModule.FIND_DEFAULT);
  assertScrollAndFind('p10', findModule.FIND_DEFAULT);
  assertScrollAndFind('p10', findModule.FIND_BACK);
  assertScrollAndFind('p21', findModule.FIND_BACK);
}

テストの実行

テストケースが完成したら、テストを実行してみましょう。実装の修正前なので、当然、このテストは「失敗」という結果が出ます。ですが、この段階では問題ありません。これから、このテストの結果が「成功」になることを目指して実装を修正していきます。

実装の修正

実装の修正内容については省略します。良いアイディアを思いついたら、それを実装に反映して、再度テストを実行してみましょう。テストに成功しないようであれば、まだ修正が必要です。

何度テストを実行しても結果が「成功」になるようになれば、実装の修正はひとまず完了です。修正内容をリポジトリにコミットするなり、修正済みの新しいバージョンとしてリリースするなりしましょう。

新たな問題が発覚した時や、仕様が変わった時は

以上で、今回発見された問題の修正は完了しました。

しかし、上記のテストだけではテストしきれないような、より複雑な条件でだけ発生するバグが新たに見つかるかもしれません。そのような場合は、テストを新たに追加して、それらがすべて「成功」するようになるまで修正してやりましょう。その時はもちろん、他のテストも同時に実行することを忘れないようにしましょう。

また、開発を進める中で、他の部分に加えた変更の影響を受けて上記のテストが失敗するようになることがあるかもしれません。そのような場合、再びテストが通るようになるように実装を修正する必要があります。

実装の仕様を変更した時にも、ここで作成したテストケースは「成功」しなくなる場合があります。このような場合は「ゴール」自体が変わったということになりますので、実装ではなくテストケースの側を修正しなくてはなりません。

まとめ

自動テストを使った開発では、メンテナンスする必要があるコードが「実装」と「テストケース」の2つになるため、一見すると、手間だけが倍増するように思えるかもしれません。

しかし、一連のテスト手順を自動化しておくことで、人の手によるテストでは見落としてしまいかねない思わぬ後退バグの発生に迅速に気づけるようになります。後退バグの発生に日々頭を悩ませている人は、是非、自動テストを開発に取り入れてみてください。

*1  もちろん、後退バグの発生自体は未然には防ぎきれません。しかし、後退バグの発生にすぐ気がつくことができれば、コミットやリリースの前にその後退バグを修正できるため、他の共同開発者やユーザには後退バグの影響を与えずに済むようになります。

タグ: Mozilla | テスト | UxU
2008-11-17

Windows XPでThunderbirdのLDAP関連機能をテストする

多数のユーザを抱える企業や大学などでは、ユーザの情報をディレクトリサーバに保存していることが多いと思われますが、Thunderbirdには、そのようなディレクトリサーバにLDAP(Lightweight Directory Access Protocol)で接続して必要な情報を取得する機能があります。具体的には、Thunderbirdでは以下の場面でLDAPが利用できます。

  • アドレス帳の共有。
  • 宛先などのメールアドレス入力時の補完候補。
  • AutoConfigを利用した設定の自動適用。

これらの機能をテストするためには、当然ながらディレクトリサーバを用意する必要があります。ここでは、インストールが簡単な「ApacheDS」を利用して、Windows XP環境下でThunderbirdでのLDAP関連機能をテストする手順を解説します。

ApacheDSのセットアップ

ApacheDS(Apache Directory Server)は、Webサーバ「Apache」で有名なApache Foundationで開発されているオープンソースのディレクトリサーバです。Windows用にはインストーラが用意されており、特別な知識が無くてもインストールしてすぐに利用を始められます。

  1. ダウンロードしたインストーラからApacheDSをインストールする。
  2. 「コントロールパネル」→「管理ツール」→「サービス」で、サービス管理ツールを起動する。
  3. サービス一覧の中で「Apache Directory Service - default」を右クリックして、「開始」を選択する。

Apache Directory Studioのセットアップとダミーのユーザ情報の登録

Apache Directoryプロジェクトではディレクトリサーバの内容をGUIで管理する「Apache Directory Studio」というEclipseベースのツールも開発されています。これを使って、テストのためのダミーのユーザ情報をディレクトリサーバに登録します。

  1. ダウンロードしたインストーラからApache Directory Studioをインストールする。
  2. 「すべてのプログラム」→「Apache Directory Suite」→「Studio」→「Studio」でApache Directory Studioを起動する。
  3. 「Window」→「Open Perspective」→「LDAP」を選択する。
  4. 左下の「Connections」パネルにある「Example」をダブルクリックする。これで、ApacheDSに最初から用意されているサンプル設定のディレクトリサーバに接続される。
  5. 左上に表示されている「LDAP Browser」のパネル内にツリーが表示されるので、「Root DSE」→「ou=system」→「ou=users」と辿る。
  6. 「ou=users」を右クリックして「New Entry」を選択。
  7. 「Create entry from scratch」を選択。
  8. 「Object Classes」で左のリストの「inetOrgPerson」と「uidObject」をダブルクリックする。これで右側のリストに「inetOrgPerson」「organizationalPerson」「person」「top」「uid」が加わるので、そのまま「Next」をクリック。
  9. 「RDN:」欄で左のボックスに「uid」、右のボックスにそのユーザを識別するための一意な文字列(例えば「user001」など)と入力して「Next」をクリックする。
  10. 「cn」欄にユーザの表示名(例:Yamada Taro)、「sn」欄にユーザの名字(例:Yamada)を入力する。また、表の上で右クリックして「New Attribute」を選択し、「Attribute type:」欄に「mail」と入力して「Finish」をクリックすると表に新しく「mail」欄の行が増えるので、「mail」欄にE-mailアドレス(例:yamada@example.com)を入力する。すべて入力し終えたら「Finish」をクリックする。

6から10の操作を繰り返して、さらに何人分かユーザ情報を登録します。「uid」はユーザの識別子となりますので、他と重複しない値を入力してください。

なお、9のステップで「RDN:」欄の右にある「+」ボタンをクリックして入力欄を増やすと以下の操作が期待通りには動かなくなる場合がありますので、ここでは必ず「uid」のみを設定して下さい。

Thunderbirdにディレクトリサーバの設定を追加する

ここまでで入力した情報を、Thunderbirdで実際に利用してみましょう。

  1. Thunderbirdを起動し、「ツール」→「アドレス帳」でアドレス帳を起動する。
  2. 「ファイル」→「新規作成」→「LDAPディレクトリ」を選択する。
  3. LDAPサーバの設定を入力するダイアログが開かれるので、「名前」に「ApacheDS」、「ホスト名」に「localhost」、「ベース識別名」に「ou=users,ou=system」、「ポート番号」に「10389」、「バインド識別名」に「uid=admin,ou=system」と入力して「OK」をクリックする。

これで、Thunderbirdでディレクトリサーバに接続する準備が整いました。

試しに、ディレクトリサーバの内容をアドレス帳として表示してみましょう。アドレス帳ウィンドウの左のアドレス帳一覧から「ApacheDS」を選んで、ウィンドウ右上の検索欄に先ほど登録したダミーのユーザの名前やメールアドレスの一部を入力します。ディレクトリサーバに接続するためのパスワードの入力を求められますので、「secret」(ApacheDSに最初から用意されているサンプル設定のディレクトリサーバのパスワードです)と入力すると、ダミーのユーザ情報のうち今入力した文字列にヒットした物がリストアップされます。また、新規にメール作成ウィンドウを開き、宛先の入力欄に先ほど登録したユーザの名前の先頭数文字を入力すると、該当するユーザのメールアドレスが補完候補として表示されます。

以上で、基本的な手順の解説は終わりです。LDAP関連の各種の隠し設定をテストする場合などに利用してみると良いでしょう。

タグ: Mozilla
2008-09-10

UxUで始めるFirefoxアドオンの自動テスト

Firefox用アドオンやXULRunnerアプリケーションなどのいわゆるXULアプリケーションは、ロジック部を主にJavaScriptで記述するため、script.aculo.usのテスト関連機能などJavaScript用のテストツールを使って自動テストを行えます。しかし、一般的なJavaScript用のテストツールはWebアプリケーションをテストすることを主眼において開発されているため、利用できる機能に制限があったり、HTMLではなくXULを使用するXULアプリケーションのテストでは不具合が生じたりする場合があります。

UxU(UnitTest.XUL)は、著名なXULアプリケーション開発支援ツールであるMozLabをベースにクリアコードで開発を行っている自動テスト実行ツールです。FirefoxやThunderbirdなどのXULアプリケーション上での利用を前提としているため、前述のような制限や問題を気にすることなく自動テストを記述できる、便利なヘルパーメソッドを利用できる、などの特長があります。

テストの記述方法やヘルパーメソッドの一覧はUxUの紹介ページに情報がありますが、ここではFirefox用アドオンのXUL/Migemoのテストを実例として示しながら、UxUによる自動テストの方法について簡単にご紹介をしたいと思います。Subversionのリポジトリ内にテストのサンプル用に用意されたタグがありますので、まずはこちらから必要なファイル一式をチェックアウトしておいてください。

クラスのユニットテスト

まずは最も簡単な例として、「tests」→「unit」とフォルダを辿った中にあるdocShellIterator.test.jsを見てみましょう。

XUL/MigemoはFirefoxのページ内検索を拡張するアドオンですので、フレーム内に検索語句が見つからなかったときは、子フレームや親フレームを検索対象として自動的に再検索を行うといった処理が必要になります。docShellIterator.test.jsでは、このための処理を担当するクラス「DocShellIterator」が正しく機能するかどうかをテストしています。

include
1
utils.include('../../components/pXMigemoFind.js', null, 'Shift_JIS');

冒頭では、ヘルパーメソッドのutils.includeを使って、DocShellIteratorクラスが定義されているファイルpXMigemoFind.jsの内容を取り込んでいます。第3引数で読み込むファイルのエンコーディングを指定していますが、これはファイルの中に含まれる日本語のコメントがShift_JISになっているためです。

なお、何らかの事情でそのままファイルをincludeできない(includeするとまずい)場合には、ファイルの内容をテキストとして読み込んで加工した後に評価するという方法もあります。上の例は、以下のように書き換えても同様に動作します。

1
2
3
4
5
var extract;
eval('extract = function() { '+
     utils.readFrom('../../components/pXMigemoFind.js') +
     '; return DocShellIterator }');
var DocShellIterator = extract();

utils.readFromはファイルの内容を文字列として返しますので、replaceなどを使って邪魔な部分を消してやれば、そのままではincludeできないファイルから必要な部分だけを取り出して評価できます。

テストケースの定義

このファイルにはテストケースが一つだけ定義されています。テストの前処理(setUp)と後処理(tearDown)は以下の通りです。

1
2
3
4
5
6
7
8
9
10
11
12
var DocShellIteratorTest = new TestCase('DocShellIteratorのユニットテスト');

DocShellIteratorTest.tests = {

  setUp : function() {
    yield Do(utils.loadURI('../res/frameTest.html'));
  },

  tearDown : function() {
    iterator.destroy();
    yield Do(utils.loadURI());
  },

setUpとtearDownは、それぞれのテストを実行する前と後に毎回実行されます。このテストケースでは、テスト実行前に「テストに使用するフレームにHTMLファイルを読み込む」という処理を行い、テスト終了後に「フレームに空のページを読み込んで内容を破棄する」という処理を行うことで、毎回必ずクリーンなテスト環境を準備するようにしています。

setUpの中では、「フレームに指定したページを読み込み、読み込みが完了するのを待って次に進む」といった処理待ちを行うために、ヘルパーメソッドとyield式を使用しています。yieldは本来はJavaScript 1.7で導入されたジェネレータのための物ですが、UxUではこれを応用して処理待ちを実現しています。JavaScriptで処理待ちというと、タイマーやonloadのようなイベントハンドラを使う方法が真っ先に思い浮かぶと思いますが、yield式を使用すれば、それらの場合に比べて処理の流れをフラットに記述することができます。

テストの内容

個々のテストの定義を見てみましょう。以下のテストでは、DocShellIteratorクラスのインスタンスを生成して、検索対象のフレームを移動する処理を実際に行い、処理結果が期待されたものと等しいかどうかをテストしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'前方検索': function() {
  // 1番目のフレームを初期状態としてインスタンス生成。
  iterator = new DocShellIterator(content.frames[0], false);
  // 初期化は成功したか?
  assert.initialized(iterator, content.frames[0]);

  // 次のフレームに移動するメソッドを実行。
  assert.isTrue(iterator.iterateNext());
  // フォーカスは正常に2番目のフレームに移動したか?
  assert.focus(iterator, content.frames[1]);
  assert.isFalse(iterator.isInitial);
  // もう一度フレームを移動。
  // 3番目のフレームは無いので、一巡して最上位のフレームにフォーカスする。
  assert.isTrue(iterator.iterateNext());
  // フォーカスは正常に最上位のフレームに移動したか?
  assert.focus(iterator, content);
  assert.isFalse(iterator.isInitial);
  // もう一度フレームを移動。1番目のフレームに戻る。
  assert.isTrue(iterator.iterateNext());
  // フォーカスは正常に1番目のフレームに移動したか?
  assert.focus(iterator, content.frames[0]);
},

処理が成功したかどうかを確認する手続きを、アサーション(宣言)と呼びます。assert.isTrue、assert.isFalse、assert.equalsなどのアサーション用ヘルパーメソッドは、実行されると渡された値を評価します。実際に渡された値が期待された値と等しければそのまま処理を続行しますが、値が異なっていた場合は「アサーションに失敗した」という内容の例外を発生させてテストの実行が中断されます。これらの例外やメソッド実行時に発生した未知の例外はUxUのインターフェース上に逐次表示され、例外が発生した行のスタックトレースを後で辿ることができるため、どの時点で問題が起こったのか、どこまでは予想通りに正常に動いたのかを詳しく調べてデバッグに役立てられます。

UxUのページのアサーション一覧にないassert.initializedやassert.focusはこのテスト専用に定義したカスタムアサーションです。その実体は、このファイルの冒頭でassertオブジェクトに追加されている新しいメソッドで、以下のように、内部でより単純なアサーションを複数行っています。

1
2
3
4
5
6
7
8
9
assert.focus = function(aIterator, aFrame) {
  assert.equals(aFrame.location.href, aIterator.view.location.href);
  assert.equals(aFrame, aIterator.view);
  assert.equals(aFrame.document, aIterator.document);
  assert.equals(aFrame.document.body, aIterator.body);
  var docShell = getDocShellFromFrame(aFrame);
  assert.docShellEquals(docShell, aIterator.current);
  assert.isTrue(aIterator.isFindable);
}

複数の条件を満たしているかどうかを確認する必要がある場合、そのままテストを記述すると、同じコードがテストケースの中に大量に並んでしまいます。そういった一連の処理はカスタムアサーションとしてまとめておけば、テストケースの内容を綺麗に見やすくできます。

テストの実行

テストの実行は、MozRepl互換のUxUサーバを起動してコンソールから接続して行うか、MozUnit互換のテストランナーを起動して行います。「ツール」メニューから「UnitTest.XUL」→「MozUnitテストランナー」と辿り、テスト実行用のGUIを起動します。

UxUのテスト実行用GUIは、テストケースのファイルのドラッグ&ドロップを受け付けます。実行したいテストのファイル(docShellIterator.test.js)をウィンドウにドラッグ&ドロップすると「作業中のファイル」欄のファイルのパスがドロップされたファイルのものになりますので、後は「実行」ボタンを押すだけで自動テストを実行できます。

テストの現在の実行状況はプログレスメーターで表示されます。アサーションに失敗したり予期しないエラーが起こったりするとプログレスメーターが赤くなりますが、正常にテストが成功すれば緑色のままです。すべてのテストケースの実行結果が緑となることを目指して開発や修正を進めていきましょう。

まとめ

このように、一連の処理と期待される結果をまとめておき、自動的にテストできるようにしておくと、開発した機能がきちんと動作しているかどうかを人手を煩わさず機械的に確かめられます。一通りのテストケースを作成するにはそれなりの手間と時間を要しますが、一度作成しておけばテストは何度でも簡単に実行できますので、うっかりミスによるエンバグを未然に防ぐ*1ことができます。

Firefox用のアドオンはFirefox自身が持っている機能と連携して動作するように作られることが多く、自動テストを作るのはなかなか大変です。UxUには処理待ち機能をはじめとした多くの便利な機能があり、「このような操作を行った時に、こういう結果になる」といった人間の操作をシミュレートする形の複雑なテストでも、処理の流れを追いやすい形で記述できます。Firefox用アドオンの自動テスト作成にはまさにうってつけと言えるでしょう。

*1  エンバグしてしまってもすぐにそれに気がつくことができる

タグ: Mozilla | テスト | UxU
2008-06-11

最新
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|
タグ:
RubyKaigi2008Speaker
RubyKaigi2009Sponsor
RubyKaigi2009Speaker
SapporoRubyKaigi02Sponsor
SapporoRubyKaigi02Speaker
RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer badge_speaker.gif RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer SapporoRubyKaigi 2012 OfficialSponsor SapporoRubyKaigi 2012 Speaker