» プラットフォーム開発

Firefox 3.7でのjs-ctypes

現在js-ctypesは開発中のため、今後仕様が変わる可能性があります。

去年行われたFirefox Developers Conference 2009でjs-ctypesについて説明しましたが、いくつかの制限があり、Firefox 3.6で使える状況はあまりなかったかと思います。現在開発を行っているFirefox 3.7では、js-ctypesが大幅に改良される予定で、現在のビルドでは、構造体のサポートやコールバック関数のサポートが含まれます。今回は構造体の使用について説明します。

この説明では、Windowsに含まれるカラーピッカー(色の選択をするダイアログ)を表示する例を挙げます。

Windowsでカラーピッカーを表示するAPIは、ChooseColorです。この関数は引数として以下の構造体のポインタを持ちます。

typedef struct {
 DWORD        lStructSize;
 HWND         hwndOwner;
 HWND         hInstance;
 COLORREF     rgbResult;
 COLORREF     *lpCustColors;
 DWORD        Flags;
 LPARAM       lCustData;
 LPCCHOOKPROC lpfnHook;
 LPCTSTR      lpTemplateName;
} CHOOSECOLOR, *LPCHOOSECOLOR;

これをctypes.StructTypeを利用して、js-ctypesで定義します。この例では、使わないメンバはuint32_tにしていますが、lpTemplateNameは文字列のポインタとして定義するのが正しいです。

var custColors_type = ctypes.ArrayType(ctypes.int32_t, 16);
const CHOOSECOLOR = new ctypes.StructType(
 'CHOOSECOLOR',
 [
  {'lStructSize': ctypes.uint32_t},
  {'hwndOwner' : ctypes.uint32_t},
  {'hInstance' : ctypes.uint32_t},
  {'rgbResult' : ctypes.uint32_t},
  {'lpCustColors' : custColors_type.ptr},
  {'Flags' : ctypes.uint32_t},
  {'lCustData' : ctypes.uint32_t},
  {'lpfnHook' : ctypes.uint32_t},
  {'lpTemplateName' : ctypes.uint32_t}
 ]);

構造体の定義が終わったら、関数を定義しましょう。定義方法は変わりませんが、引数でポインタ型を渡しています。

var comdlg32 = ctypes.open("comdlg32");
var ChooseColor = comdlg32.declare("ChooseColorW", ctypes.stdcall_abi, ctypes.int32_t, CHOOSECOLOR.ptr);

関数を呼び出すために、値をセットします。構造体のメンバにポインタを渡す必要があるため、addressを利用します。

var custColors = new custColors_type();
var col = new CHOOSECOLOR (CHOOSECOLOR.size, 0, 0, 0, custColors.address(), 0, 0, 0, 0);

値を作成したので、関数を呼び出しましょう。

ChooseColor(col.address());

そうすると、col.rgbResultにユーザーが選択した色の値がセットされますので、それを参照するとどの色を選択したかがわかります。

alert(col.rgbResult);

このように、構造体を引数に持つ関数の呼び出し方法が追加されています。これでjs-ctypesが利用できる範囲も広くなったと思います。次は、コールバック関数の利用について説明したいと思います。

なお、今回サンプルで作成したすべてのコードは以下になります。

Components.utils.import("resource://gre/modules/ctypes.jsm");

var custColors_type = ctypes.ArrayType(ctypes.int32_t, 16);
const CHOOSECOLOR = new ctypes.StructType(
 'CHOOSECOLOR',
 [
  {'lStructSize': ctypes.uint32_t},
  {'hwndOwner' : ctypes.uint32_t},
  {'hInstance' : ctypes.uint32_t},
  {'rgbResult' : ctypes.uint32_t},
  {'lpCustColors' : custColors_type.ptr},
  {'Flags' : ctypes.uint32_t},
  {'lCustData' : ctypes.uint32_t},
  {'lpfnHook' : ctypes.uint32_t},
  {'lpTemplateName' : ctypes.uint32_t}
 ]);

var comdlg32 = ctypes.open("comdlg32");
var ChooseColor = comdlg32.declare("ChooseColorW", ctypes.stdcall_abi, ctypes.int32_t, CHOOSECOLOR.ptr);

var custColors = new custColors_type();
var col = new CHOOSECOLOR (CHOOSECOLOR.size, 0, 0, 0, custColors.address(), 0, 0, 0, 0);

ChooseColor(col.address());
alert(col.rgbResult);

Windowsのマウスドライバもしくはユーティリティ開発者への参考情報

MozillaWikiにWindowsのマウスのドライバやユーティリティ開発者向けの参考情報のドキュメントを投稿しました。これの和訳をこちらに書いておきます。英語版が常に最新版となりますので、実際の開発時には原文の方を参照してください。

Gecko:Notes for mouse driver developers on Windows

このドキュメントは参考情報です。

WM_MOUSEWHEELWM_MOUSEHWHEELを送信してください

GeckoはWM_MOUSEWHEELWM_MOUSEHWHEELの両方をサポートしています。全てのマウスドライバ、ユーティリティはGeckoのウインドウに対してはこれらのメッセージを送信すべきです。

Gecko 1.9.3以降(Firefox 3.7以降)はWM_VSCROLLWM_HSCROLLメッセージもサポートされますが、マウスドライバ、ユーティリティはこれらのメッセージをマウスホイールの操作時に利用してはいけません。Geckoはこれらのメッセージと、WM_MOUSEWHEEL/WM_MOUSEHWHEELは異なる意味をもつメッセージだと考え、異なる処理をしているからです。具体的には、WM_MOUSEWHEELWM_MOUSEHWHEELDOMMouseScrollイベントをWebページ上で生成します。つまり、Webアプリケーションの作者はマウスホイールの回転イベントとして、これをハンドリングすることができ、また、コンテンツのスクロールを抑制することもできます。これに対して、WM_VSCROLLWM_HSCROLLはこのDOMイベントを生成せずに、単にスクロール可能な領域をスクロールするのみです。つまり、Webアプリケーションの作者にはスクロールバーの操作と見分けがつきません。

フォーカスをもつGeckoのウインドウにメッセージを送信してください

カーソルの下にあるウインドウではなく、フォーカスを持ったGeckoのウインドウに対してメッセージを送信してください。Geckoはスクロール対象を決定するときにマウスカーソルの位置を利用します(詳しいルールはGecko:Mouse_Wheel_Scrollingを参照してください)。Geckoはカーソルの下にあるウインドウに対して自動的にメッセージを再送信します。

Geckoはプラグインのウインドウがカーソルの下にあっても、これに対してメッセージを直接再送信しません。その理由はGeckoはマウスホイールの操作にはトランザクションが成立していると考えているからです(詳細はGecko:Mouse_Wheel_Scrolling)。ユーザはプラグインの親のスクロール可能な領域をスクロールしたいのかもしれません。たとえば、プラグインウインドウが親のスクロールによってカーソルの下へ移動してきた時、Geckoはその直後のホイールイベントも親をスクロールし続けるように処理します。もし、マウスドライバやユーティリティがメッセージを直接プラグインのウインドウに送信した場合、この処理ができなくなってしまいます。

マウスホイールのメッセージを不明なウインドウに対して利用してください

Gecko 1.9.2以降(Firefox 3.6以降)は、次のクラス名を持つウインドウを生成します。これらは将来のバージョンで変更される可能性があることに注意してください。

  • MozillaHiddenWindowClass
  • MozillaUIWindowClass
  • MozillaContentWindowClass
  • MozillaContentFrameWindowClass
  • MozillaWindowClass
  • MozillaDialogClass
  • MozillaDropShadowWindowClass

フォーカスをもったウインドウがこれらのクラス名であった場合、WM_MOUSEWHEELWM_MOUSEHWHEELを細工なく送信できると考えて問題ありません。

Firefox 3.6ではGeckoのウインドウの構造が変更になりました。この時、私たちはいくつかのマウスドライバ、ユーティリティがこれらのメッセージを送信してこなくなったことを確認しています。これらのメッセージはWindowsアプリケーションにとってマウスホイールのアクションを意味する標準のイベントです。歴史的な問題から、古いアプリケーションや一部のアプリケーションがこれらのイベントを期待通りに処理してくれないことはあるでしょう。マウスドライバやユーティリティはそういったアプリケーションのウインドウに対しては小細工を行うべきかもしれません。ですが、もしあなたが把握していないウインドウ全てがそういったアプリケーションのウインドウであると決めつけているのであればそれは良くないことです。

nsIPrefはFirefox 3.7以降では利用できません

加藤です。

現在のFirefoxでは、設定を読み込んだり書き込んだりする際には、nsIPrefまたはnsIPrefBranchが利用できました。nsIPrefは以前より廃止予定のインターフェイスでしたが、Firefox 3.7 (Gecko 1.9.3)でこのインターフェイスは削除されました。そのため拡張などでnsIPrefを未だに使用している場合は、nsIPrefBranchを利用するようにしてください。

なお、以前から利用可能だった”@mozilla.org/preferences;1″は拡張の互換性のために残していますが、nsIPrefを取得することはできません。

Linux版のIMのログをとる方法

Linux版(GTK2版)は先日、コードの分離が完了し、Windows版と同様にNSPRのログ機能を利用してログを記録できるようになりました。なお、製品版ではFirefox 4以降で可能になります。

Linuxでは環境変数を以下のように設定しておきます。

<code>export NSPR_LOG_MODULES=nsGtkIMModuleWidgets:1
export NSPR_LOG_FILE=/home/&lt;user name&gt;/fx.log
</code>

この例だと、ユーザのホームディレクトリにfx.logというファイルにログを記録します。

Windows版と同様、非常に細かいログを吐きますので、bugzillaへの提出時には最低限の操作のみを行ったログを提出してください。また、テスト終了時には必ずこれらの環境変数をリセットし、ログを取らないように修正するのを忘れないようにしてください

ところで、Linuxで更新され続けるログファイルをリアルタイムで表示してくれるソフトウェアを知らないので、教えていただけると助かります。

Windows版のIMEのコードのログをとる方法

Windows版のIMEのコードは、Firefox 3.6以降 (Gecko 1.9.2以降)、nsWindowから分離され、nsIMM32Handlerクラスで処理されるようになりました。この修正時に、このクラスのログをリリースビルドでも記録することができるようにしています。これにより、開発者が所有していないサードパーティ製のIMEでのみ発生するバグがあったとしても、バグを再現できる人が開発者に対してログを提出することで原因を調査を助けることができるようになりました。

ログをとるには、環境変数を二つ、追加する必要があります。

まず、NSPR_LOG_FILEという環境変数を追加してください。この環境変数にはログファイルのパスを設定します。c:\fx_imm32.logのように、Windowsのパス形式で書き込み可能なパスを記述してください。

次に、NSPR_LOG_MODULESという環境変数を追加してください。この環境変数にはnsIMM32HandlerWidgets:1を設定してください。

これらの環境変数を追加した後に、Firefox 3.6以降を起動すれば(すでに起動している場合はFirefoxを再起動してください)、NSPR_LOG_FILEに指定したファイルが作成され、ログが逐一記録されていきます(ログファイルはUTF-8です)。

nsIMM32Handlerは非常に多くのログを記録します。ですので、なんらかのバグのためにbugzillaにログを提出する場合は、起動させた直後にバグを再現させ、そのまますぐに終了してください。余計な動作はログを読みにくくしますし、また、入力された文字は全てログに残るので、提出する際にプライバシーの問題が出てきます。

テスト後には環境変数を削除するのを忘れないようにしてください。ログを出力している分、動作速度は落ちてしまいますし、ログの出力先がSSDの場合はSSDの寿命を縮めてしまうかもしれません。

ログをリアルタイムで表示させたい場合、私はSSLogMonというソフトを使っています。もし、他にもこのようなソフトをご存じの方が居たら、紹介してもらえるとありがたいです。

<code>gtk_im_context_reset()</code>が動かない

LinuxのIME処理には重要なAPIであるgtk_im_context_reset()が一部の環境下、特に新しいプラットフォームで動かなくて困っています。

今のところ分かっている情報から推測すると、uimと、iBusを利用していることが原因なのかもしれません。私の手元ではUbuntu 9.04 (SCIM)では問題ありませんが、Ubuntu 9.10 (iBus)では機能しませんし、早くから、Debianのsidでuimを利用するとマウスクリック時にうまく確定されないという報告がありました。

Geckoは一般的なGTK2アプリとは違って、IMのコンテキストをウインドウ単位で生成し、これをウインドウ内のネイティブウィジット間で共有しています。また、一つのネイティブウィジット内でも、<input><textarea>が複数存在するため、どちらにしても複数のエディタ間でIMのコンテキストを共有しないといけない、特殊なアプリケーションです(Webブラウザで馬鹿正直にエディタごとに用意すると、とんでもないことになりかねません)。

このため、Geckoではフォーカスが移動する時や、マウスのクリック時に処理を単純にするためにXPレベルで未確定文字列を強制的に確定しようとします(これが好ましいかどうかは別にして、現在の設計ではこれ以外の解決策をとるのは短期的には現実的ではありません)。ですが、現状、Linuxではこれがうまく機能しなくなっています。

実際にこの問題を確認するには、未確定文字列がある状態で、そのエディタ内をクリックしてみてください。

Ubuntu 9.10でテストしてみると、見た目では確定されているのですが、そのままスペースキーを叩くと確定したはずの文字列で変換が始まります。もちろん、そのまま確定すると文字がダブることになります。

この問題に関して、原因を究明する情報が不足していて困っています。この環境なら再現しない、この環境、このIMだと再現する、といった情報や、IMの開発コミュニティの情報をお持ちでしたら、是非それを教えてください。お願いします。

追記(2010/01/8/14:30):

uim-jaに投稿してみたところ、やはりIM側の実装に大きく依存するようです。

シンプルなテストケースを作ろう

実際にコードを書けない人でも、開発に大きく貢献しようと思えば、テストケースを作り、bugzillaの該当のバグに提出する、という作業で貢献することができます。役に立つテストケースは実際に、パッチを作成するエンジニアにとって非常に大きな助けとなります(私も昔、パッチが書けなかった時はよくやっていました)。

実際にパッチを書いている立場から、どのようなテストケースが有用で、助かるのかと問われれば、シンプルで、かつ、分かりやすいという(時には相反する)二つの重要なポイントを守っておいてください、と答えます。

例えば、キー入力やマウスの操作である等、挙動に関するバグのテストケースである場合、余計なスタイルシート等は指定せず、必要最小限のもののみを指定するようにします。なぜなら、エンジニアは検証時に、様々なスタイルが適用されているテストケースを見ると、それら全てがバグに関係があるものなのかと考えてしまうかもしれませんし、また、少なくとも疑ってかかる必要が出てくるためです。さらに、本当に重要なポイントが分かりにくくなる、という点も見逃せません。

これらの事から、HTML自体の記述では、ブロックレベルの要素ならdiv要素、インラインレベルの要素が必要な場合はspan要素を使うのが好ましいことが理解してもらえると思います。他の要素はこれらの要素にスタイルを追加していたり、特殊な動作を追加したりしているため、それそのものが複雑だからです。

ですが、複数の要素を配置する必要があり、それらの違いが分かる必要がある場合、例えば、HTMLやCSS等のレイアウトやレンダリングに関わるバグのテストケースではボックスごとに異なる色のborderbackground-colorのどちらかのみを指定する必要があることがあります。一般的に、レイアウトに関するバグの場合にborderの利用には注意が必要です。borderはその太さの分、レイアウトに影響を与えてしまうためです。ですが、background-colorを指定した場合はその背後にあるボックスが見えなくなるという、特性があるためz-indexのテスト等では使いにくい、または使えないことがありますので、ケース毎に判断していく必要があります。

また、エンジニアはテストで長時間そのテストケースを見ることが多々ありますので、あまり目に優しくない色の組み合わせや、ビビッドな色の利用には注意しましょう。

HTML自体を記述する場合にも、strictなHTMLにこだわる必要はないという点に気をつけてください。テストケースがアクセシブルである必要や、複数のブラウザで表示できる必要は全くありません。Gecko (or Firefox)のためのテストなので、これでテスト可能な最低限のシンプルなコードを書くのが最も良いのです。

例えば、Standardsモードでレンダリングさせたい場合、<!DOCTYPE html>と、HTML5のDOCTYPE宣言を入れるだけでかまいません。長い、HTML4のDOCTYPEを挿入したりする必要はありませんし、Standardsモード、Quirksモード、どちらでも良い場合であればこの一行そのものが必要ありません。

同様に、html要素、head要素、meta要素、body要素、さらにはtitle要素も必要ありません。文字コード宣言も必要ありません。UTF-8で作成し、bugzillaに添付すれば、bugzillaはデフォルトで、ヘッダにcharset=UTF-8を付与して送信します。UTF-8以外のテストケースが必要な場合であっても、meta要素で文字コード宣言を行わなくても、添付時にContent Typeの項目で、enter manuallyを選択し、text/html;charset=Shift_JISと指定するだけで済みます。

最後に、シンプルなテストケースを作ることに成功しても、シンプル過ぎるが故に他人には分かりにくすぎるテストケースになってしまっている可能性が高いことには注意してください。例えば、CSSのテストではテストケースがどのように表示されるべきなのか分かりにくいことがよくあります。このような場合は別のファイルに、よりシンプルなスタイル指定で、本来表示されるべきリファレンスを作ってしまうと良いでしょう。そうしておけば、開発者はそれらのファイルをそのままreftestに流用可能です。これは、開発者の負担を大きく下げてくれますし、長期間、バグが放置された後も、バグが再現しなくなっているのかどうかの検証が誰にでも簡単に確実に行えるようになります。

それ以外の場合には、テストケースを添付する際のコメントでしっかりと説明しておきましょう。

テストケースを書くというのは地味ですが難しい作業です。つまり、この作業をパッチを作成できる人がやるのはプロジェクト全体としては非常に効率が悪いと言えます。実際にパッチを作成することに比べれば明らかに(技術的にも時間的にも)敷居は低いのでより多くの方がこの分野で活躍されれば、プロジェクト全体の開発速度を上げることに貢献できます。我こそは、という方は是非参加してみてください。

PR_MIN()、PR_MAX()マクロの利用から、NS_MIN()、NS_MAX()インライン関数へ

これまでGeckoの開発ではPR_MIN()PR_MAX()マクロを利用して、最小値、最大値を求めるようにしていましたが、今後はNS_MIN()NS_MAX()インライン関数を利用するようにするようです。

現在、置き換え作業が進行中です

PR_MIN()、PR_MAX()では、パフォーマンスに問題がでるミスが発生しやすい、というのがその原因のようです。

レビュー中のパッチがある方は注意しましょう。