2013年9月30日
GeckoのDOM KeyboardEvent
と、TextEvent
にはisChar
というプロパティがありますが、TextEvent.isChar
の取得時に、間違えたキャストを行って、正しい値を返していなかったというバグです。
isChar
の値は、nsKeyEvent
と、nsTextEvent
のメンバに保存されているので、それぞれに正しくキャストしてアクセスしなくてはいけないのですが、nsTextEvent
のインスタンスであっても、nsKeyEvent
にキャストしてアクセスしていました。
見つけた時は焦ったのですが、調べたところ、32bit版でも、64bit版でも、クラスのインスタンス内を参照しているので、クラッシュすることはありません。返ってくる値がデタラメなだけでした。
このバグ自体は修正しましたが、そもそも、nsTextEvent::isChar
は正しく初期化されていないプラットフォームの方が多い上に、他のブラウザには存在しない非標準イベントですので、参照しないようにしてください。
全ての内部イベントクラスにメンバをコピーするメソッドを実装しようというバグです。
もともとは、nsDOMEvent
で、変数に保存されたDOMイベントのディスパッチが終わった後にも、各プロパティにアクセスできるようにするために、コピーしていただけだったので、nsDOMEvent
内で直接行われていました。このため、nsGUIEvent.h
内でイベントクラスにメンバを追加しても、ここの修正を忘れていたり、Bug-org 910156のようなバグの温床になるので、クラスの定義部分で確実に管理できるようにしようというのが、このバグ修正の理由となっています。
コピーコンストラクタを作れ、という意見もあるかと思いますが、イベント全体を丸ごと単純にコピーする、という用途は皆無なので、あえて、通常のコンストラクタでインスタンス生成後に、内容を条件に応じてコピーするメソッドを利用するという形になっています(strong referenceなメンバを含んでいるので、単純なコピーを許可すると、メモリリークバグを産みやすくなってしまうため)。
nsIDOMWindowUtils::SendTextEvent()
は、trustedなnsTextEvent
イベントをJavascriptから生成し、IMEの未確定文字列や、確定文字列を擬似的に生成するAPIです。ただし、これは自動テストで、XPレベルのIMEハンドリングを検査するために設計したものなので、未確定文字列内を最大で3つの文節にしか区切れませんでした。ところが、Firefox OSでは、このAPIをIMEの未確定文字列生成に使うことになってしまったので、日本語のIMEの開発には大きな障害となっていました。その制限を無くそうというのがこのバグです。
このバグでは、nsIDOMWindowUtils::SendTextEvent()
を廃止し、nsIDOMWindowUtils::CreateCompositionStringSynthesizer()
というAPIを追加しました。これを呼び出すと、nsICompositionStringSynthesizer
というインターフェースを持った、XPCOMオブジェクトのインスタンスが生成され、返されます。
nsICompositionStringSynthesizer
のインスタンスは、そのDOM window
へのweak referenceを保持していて、このwindow
が生存している限りは再利用可能になっています(実際に再利用しているコードはありませんが)。
nsICompositionStringSynthesizer::SetString()
で未確定文字列、もしくは確定文字列を指定します。
nsICompositionStringSynthesizer::AppendClause()
で、文節の長さと、属性を指定し、文節を追加します。これはメモリ容量が許す限り、何度でも呼び出すことができますので、最大、未確定文字列の文字数分の文節を作り出すことができます。確定文字列生成の場合は呼び出してはいけません。
nsICompositionStringSynthesizer::SetCaret()
はキャレットの位置と長さを指定できますが、Gecko自体は、1文字以上に渡る、ワイドキャレットはまだサポートしていませんが、一応指定自体は可能です。これは、不要なら呼び出す必要はありません。
最後に、nsICompositionStringSynthesizer::DispatchEvent()
を呼び出すと、イベントをディスパッチし、全ての情報がクリアされ、再利用可能な状態になります。
ちなみに、nsICompositionStringSynthesizer::DispatchEvent()
が呼び出された際に、文節情報や、キャレット情報が、文字列の長さに対して矛盾している場合、例外が発生し、イベントのディスパッチには失敗しますが、全ての情報はクリアされ、再利用可能な状態になります。
以前にも紹介しましたが、LinuxのGTK (GDK)では、キーイベントが発生した段階では、モディファイアのフラグがそのキーが押された状態を反映した値に変化していません。例えば、Shiftキーを押したイベントの、モディファイアフラグの値は、Shiftキーが押された状態にはなっていません。これは、他のプラットフォームの動作とは異なっているため、Geckoでは、キーのハードウェアキーコードから、アクティブになるフラグを予想して、わざわざセットする、ということをやっていました。
しかし、その処理にバグがあり、JISキーボードで、CapsLockキーをShift+英数に割り当てている場合、Shiftキー無しで、英数キーだけを押した場合に、KeyboardEvent.getModifierKey("CapsLock")
がtrue
を返していました。
今回の修正では、ハッキーな推測をやめ、XKB Extentionのモディファイアフラグの変更イベントをキーイベントを受け取ったときに、キューに来ていないか調査し、存在する場合には補完するというコードになりました。これで同種のバグがあったとしても一掃されているはずです。
長年の悲願だった、Geckoの内部イベントリファクタリングを実行することを決意しました。まず、このバグでは、肥大化したnsGUIEvent.h
を複数のヘッダファイルに分割し、イベント関連のコードを修正した場合に、リビルドする範囲を軽減しようというのがまずは第一目標としました。
まず、イベントクラスのリネームもこの機会に行うため、まずは、nsEvent.h
と、nsGUIEvent.h
で定義されていた、mozilla::widget
名前空間のものをmozilla
名前空間に移しました。これだけは、Mozilla 26で修正されています。
これ以降は、Mozilla 27での修正になります。
各イベントクラスや、関連する構造体の前方宣言を行っている、nsEvent.h
は、mozilla/EventForwards.h
にリネームしました(ソースコードは、widget/EventForwards.h
です)。
次に、イベントの種類ごとに、以下のようにヘッダファイルを分割しました。
mozilla/BasicEvents.h
-
enum nsEventStructType
- イベントメッセージ
mozilla::BaseEventFlags
mozilla::EventFlags
mozilla::WidgetEvent
(旧nsEvent
)
mozilla::WidgetGUIEvent
(旧nsGUIEvent
)
mozilla::WidgetInputEvent
(旧nsInputEvent
)
mozilla::InternalUIEvent
(旧nsUIEvent
)
mozilla/ContentEvents.h
-
mozilla::InternalScriptErrorEvent
(旧nsScriptErrorEvent
)
mozilla::InternalScrollPortEvent
(旧nsScrollPortEvent
)
mozilla::InternalScrollAreaEvent
(旧nsScrollAreaEvent
)
mozilla::InternalFormEvent
(旧nsFormEvent
)
mozilla::InternalClipboardEvent
(旧nsClipboardEvent
)
mozilla::InternalFocusEvent
(旧nsFocusEvent
)
mozilla::InternalTransitionEvent
(旧nsTransitionEvent
)
mozilla::InternalAnimationEvent
(旧nsAnimationEvent
)
mozilla/MiscEvents.h
-
mozilla::WidgetContentCommandEvent
(旧nsContentCommandEvent
)
mozilla::WidgetCommandEvent
(旧nsCommandEvent
)
mozilla::WidgetPluginEvent
(旧nsPluginEvent
)
mozilla/MouseEvents.h
-
enum nsDragDropEventStatus
mozilla::WidgetMouseEventBase
(旧nsMouseEvent_base
)
mozilla::WidgetMouseEvent
(旧nsMouseEvent
)
mozilla::WidgetDragEvent
(旧nsDragEvent
)
mozilla::WidgetMouseScrollEvent
(旧nsMouseScrollEvent
)
mozilla::WidgetWheelEvent
(旧mozilla::WheelEvent
)
mozilla/TextEvents.h
-
NS_VK_*
mozilla::AlternativeCharCode
(旧nsAlternativeCharCode
)
mozilla::WidgetKeyboardEvent
(旧nsKeyEvent
)
mozilla::TextRangeStyle
(旧nsTextRangeStyle
)
mozilla::TextRange
(旧nsTextRange
)
mozilla::TextRangeArray
(旧nsTextRangeArray
)
mozilla::WidgetTextEvent
(旧nsTextEvent
)
mozilla::WidgetCompositionEvent
(旧nsCompositionEvent
)
mozilla::WidgetQueryContentEvent
(旧nsQueryContentEvent
)
mozilla::WidgetSelectionEvent
(旧nsSelectionEvent
)
mozilla/TouchEvents.h
-
mozilla::WidgetGestureNotifyEvent
(旧nsGestureNotifyEvent
)
mozilla::WidgetSimpleGestureEvent
(旧nsSimpleGestureEvent
)
mozilla::WidgetTouchEvent
(旧nsTouchEvent
)
mozilla/MutationEvent.h
-
mozilla::InternalMutationEvent
(旧nsMutationEvent
)
NS_EVENT_BITS_MUTATION_*
ネイティブイベントをGeckoに伝達するために、widgetが使用するイベントは、Widget
prefix付きで、それ以外、DOMイベントの実データの保存領域として使われるものは、Internal
prefix付きのクラス名になっています。
このバグの修正時には、全ての旧クラス名や、旧構造体名はそのまま利用できるようにtypedef
で別名を宣言してありますが、Bug-org 920377で順次、この別名を削除して、新しい名前を使用するように置換作業を行っています。
また、大量にあったマクロやインライン関数のイベントに関するユーティリティ類は、全て、WidgetEvent
のメソッドとして実装し、widget/shared/WidgetEventImpl.cpp
で実装しています。今後、さらに有用なメソッドを追加しても、その実装によって依存関係が増えない形になりましたので、より、イベント周りのコードは整理されていくと思います。
また、最後にnsGUIEvent.h
は削除され、新しいヘッダファイルは必要なものだけを直接、インクルードするように修正しています。BasicEvents.h
を変更した場合のリビルド対象はまだまだ多いですが、この辺も、別のバグで対応していく予定です。