2014年6月10日
KeyboardEvent.code
は、Windowsでは、スキャンコードから求めていますが、::SendInput()
APIから参照できる、KEYBDINPUT
構造体のドキュメントを見る限り、各種ツール類が生成する疑似キーイベントには、適切なスキャンコードが設定されていない可能性が高いです。
幸い、Windowsでは、仮想キーコードから、現在選択されているキーボードレイアウトからスキャンコードを算出するAPIとして、::MapVirtualKeyEx()
が存在していますので、スキャンコードがゼロの場合に限り、仮想キーコードから、スキャンコードを推測するようにしています。
ただし、この手法にも若干問題があり、0x00から0xFFで表現できない拡張キーと、拡張キーではないキーの両方で、その仮想キーコードが生成できる場合、上記のAPIは、拡張キーではない方のスキャンコードを返して来ます。ただ、これがやや期待とは異なる動作を行います。
拡張キーとは何か、ということがまずは重要ですが、一般的なキーボードで、Windowsが拡張キーとしているのは、矢印キー(ArrowDown
、ArrowLeft
、ArrowRight
、ArrowUp
)や、カーソル移動、編集に用いられるコントロールパッドにある、Insert
、Delete
、Home
、End
、PageDown
、PageUp
等があります。これらのキーは、テンキーのNumLockを解除している時に、同じ働きをしますが、テンキーの方は、非拡張キーにあたります。
このため、例えば、VK_DOWN
の場合、算出されるスキャンコードは、テンキーの↓になり、その結果、.code
値は、Numpad2
になってしまいます。おそらく、多くのWeb開発者はArrowDown
を期待すると思います。私もパッチをテストしている時に、そう期待しました。
現在のところ、この問題は、キーイベントを擬似的に生成するアプリのバグに起因する問題ですので、素直にAPIの算出結果に従うようにしています。
今後、DOMイベントがより、原始的な情報を提供できるようになればなるほど、Webアプリ開発時に、様々なブラウザ外の環境のバグも開発中のアプリに影響がでるようになっていく、ということに注意を払わなくてはいけなくなっていくことでしょう。
Webアプリが、Ctrl+PageDown等、キーボードによるタブナビゲーションのキーをkeydown
イベントで、preventDefault()
を呼び出していると、続くkeypress
イベントが発生しなくなっているため、一部のサイトではキーボードでタブを操作できなくなっているというバグです。
よくよく調査してみると、そもそも、keypress
イベントの.stopPropagation()
を呼び出していると、やはり、操作不能になることが分かりましたので、<tabbox>
要素や、<tabbrowser>
要素のキーイベントのハンドリングがまずいことにすぐに気付きました。
このバグでは、keypress
イベントを待たなくてはいけない場合を除き、keydown
イベントでハンドリングするように変更することで、Webアプリがタブのナビゲーションは阻害できないように改善しています。また、.stopPropagation()
対策として、システムイベントグループにリスナを登録するように変更したので、Webアプリが、通常イベントグループでstopPropagation()
を呼び出しても、影響を受けなくなっています。
なお、この修正により、以下のキーイベントは、Firefoxではkeypress
イベントが常に発生しなくなりました。これは、keydown
イベントのpreventDefault()
を呼び出す必要がある、その副作用です。ただし、Gecko以外のブラウザエンジンでは、keypress
イベントが、これらの非文字入力キーで発生することがそもそも無いので、Firefoxに特化したWebアプリを作ったりしていない限りは、問題は無いと思います。
- Ctrl+Tab
- Ctrl+Shift+Tab
- Ctrl+PageDown
- Ctrl+PageUp
- Ctrl+Shift+PageDown
- Ctrl+Shift+PageUp
- Ctrl+ArrowUp (Mac以外)
- Ctrl+ArrowDown (Mac以外)
- Ctrl+ArrowLeft (Mac以外)
- Ctrl+ArrowRight (Mac以外)
- Ctrl+Home (Mac以外)
- Ctrl+End (Mac以外)
- Command+ArrowUp (Mac)
- Command+ArrowDown (Mac)
- Command+ArrowLeft (Mac)
- Command+ArrowRight (Mac)
- Command+Home (Mac)
- Command+End (Mac)
- Command+Option+ArrowLeft (Mac)
- Command+Option+ArrowRight (Mac)
加えて、Mac以外では、Ctrl+F4のkeypress
が原則、発生しなくなりますが、ピン止めされたタブで開かれている場合には、発生します。どちらにしろ、このショートカットキーをWebアプリはハンドリングすべきではありませんが。
Bug-org 777832の修正後に、指摘のあった、より単純化されたコードへの変更です。
特に動作等が変わるわけではないので、詳しくはパッチをご覧ください。
Geckoでは、Element.focus()
を呼び出しても、そのJavascriptのコンテキストがアクセスできないノードがフォーカスを持っている場合、例えば、ブラウザのUIや、フレームに読み込まれた別ドメインのサイト等がフォーカスを持っている場合には、フォーカスを奪えないようになっています。
ただし、mousedown
イベントでクリックされたドキュメントからアクセス可能な要素にフォーカスを移そうとしている場合は、例外的にこれを認めるようにしています。例えば、<label>
要素のような、フォーカスのリダイレクトを実現したいという要求を実現するためです。
ちなみに、mousedown
イベントがpreventDefault()
で消費されない限り、mousedown
イベントのデフォルトアクションとして、マウスのボタンが押された時のウインドウがフォーカスを持つようになっています。
今回見つかった問題は、有名サイトである、JSFIDDLEでは、親ドキュメントが、mousedown
イベントのpreventDefault()
を常に呼び出し、mouseup
イベントでフォーカスをリダイレクトしようとしていたものの、サンプルを表示している<iframe>
は、別ドメインなので、うまく機能していなかった、というバグです。
リスクの大きさを考えると、mousedown
のdefaultPrevented
属性値を無視して、マウスボタンの押されたウインドウに常にフォーカスを移動させるような修正はできませんので、mousedown
イベントと同様、mouseup
イベントの発火中も、そのドキュメントからアクセスできるノードには、どこからでもフォーカスを奪えるように、例外措置を拡大して対応しています。
非常に昔から存在しているバグですが、Spaceキーか、Shift+Spaceキーで、ASCIIの半角スペース以外を入力する場合、スクロールのショートカットキーとして機能しない、というバグです。
過去に、色々とハッキーなパッチを作っていましたが、レビュアのkarltがよりシンプルなものを望んでいたため、修正ができずにいました。しかし、物理キーを表現する、KeyboardEvent.code
のサポートで、これが大きく前進することができるようになりました。
今回の修正では、.code
値が、Spaceの場合かつ、.charCode
が、半角スペースを意味する32
ではない場合、ショートカットキーや、アクセスキーの候補リストを作成する際に、最後に、ASCIIの半角スペースを追加するようにしました。これにより、安全・確実・シンプルな修正が実現しました。
2014年6月11日
当初、D3Eの仕様書では、KeyboardEvent.key
の値をひとつのテーブルで定義していましたが、項目が増えるに従い、キーの種類ごとにテーブルを分割し、さらに、仕様書自体も、D3E本体から分離され、DOM Level 3 KeyboardEvent key Valuesに分離されています。
このバグでは、dom/events/KeyNameList.h
と、widget/shared/NativeKeyToDOMKeyName.h
での、.key
値の定義順を仕様書にあわせて、管理しやすくしました。
この修正に伴い、どのプラットフォームでも定義されていないkey
値はコメントアウトしたので、インデックスから文字列値を取得するための配列のサイズが小さくなっているため、若干ですが、フットプリントを改善しています。
KeyboardEvent.code
の初期実装時に、どのプラットフォームでも利用されないcode
値も定義し、インデックスから文字列に変換可能にしていました。
しかし、若干とはいえ、フットプリントに無駄が生じているのももったいないので、コメントアウトして改善しています。
何故か、Mac OS X 10.8の最適化ビルドで、このテストを走らせた時にだけ、ランダムにタイムアウトが発生するようになったというバグです。
タイムアウトは、テスト終了時のwindow.close()
が動作せず、unload
イベントも発生しないことが、ログをとってみて、すぐに分かりました。
その原因が当初は全く理解できなかったんですが、以下のログを見つけたので、原因が分かりました。
> 00:14:32 INFO - 5076 INFO TEST-START | chrome://mochitests/content/chrome/widget/tests/test_imestate.html
> 00:14:35 INFO - JavaScript error: chrome://browser/content/tabbrowser.xml, line 3036: Cc is not defined
> 00:14:35 INFO - JavaScript error: chrome://browser/content/tabbrowser.xml, line 357: this.browsers[i] is undefined
> 00:14:35 INFO - JavaScript error: chrome://browser/content/tabbrowser.xml, line 357: this.browsers[i] is undefined
> 00:14:35 INFO - JavaScript error: chrome://browser/content/browser.js, line 13823: browser is undefined
Bug-org 1008772の修正で、<tabbrowser>
要素のコンストラクタを修正した際に、Cc
を利用していたのですが、それがランダムに初期化されていないことがあるということのようでした。
とりあえず、コンストラクタとデストラクタという、リスキーなところでは、Cc
とCi
を利用しないようにし、正攻法でアクセスするように修正しています。
2014年6月24日
Bug-org 1015028の修正で、Windowsでは、スキャンコードがきちんと設定されていないキーイベントであっても、仮想キーコードからスキャンコードを算出して、KeyboardEvent.code
を求めるようになりましたが、どのような環境で、これが問題になるか分かりません。
そこで、リリースビルドでもマッピングのログがとれるようにするコードを仕込ました。
環境変数で、NSPR_LOG_MODULES
を、KeyboardLayoutWidgets:5
とし、NSPR_LOG_FILE
を指定しておけば、起動時と、キーボードレイアウト変更時にログをとることができます。
Win8.1上のいくつかのキーボードレイアウトでログをとって、表にしてみると以下のようになりました。
これを見ると、VK_PRINT
等、現在ではどのキーボードレイアウトでもマッピングされていない仮想キーコードがあるのが分かります。
Windowsでは、0キーから、9キーのKeyboardEvent.code
値が、Digit1
から、Digit0
になっている、つまり、ひとつずつ数字がズレているというバグです。
単に、マッピングのテーブルがイージーミスで間違っていただけでした。Auroraでも修正しています。
<input>
要素のmaxlength
属性によって、入力中のサロゲートペアの後半だけが削除されて入力されることがある、というバグです。
Bug-org 670837の報告によると、少なくとも、maxlength
はUTF-16ではなく、Unicodeのコードポイント単位で数えられるべきっぽいんですが、ひとまず、コンセンサスがとれている、サロゲートペアの分断だけは避けよう、というのがこのバグの目的です。
この修正により、サロゲートペアの片側を捨てる際には、もう一方も捨てるように修正しています。
Qtビルドをテストのためにビルドしようとすると、Bug-org 968196のregressionでコンパイルエラーが発生するようになっていたというバグです。
Bug-org 968196では、GTK3ビルドでは、InstallX11ErrorHandler()
を利用しないように修正されていたのですが、その際に、#if (MOZ_WIDGET_GTK == 2)
で囲ってたのが、Qtビルドでエラーになる原因でした。
結果的には、#if (MOZ_WIDGET_GTK != 3)
に変更することで修正しています。GTK固有のコードを#ifdef
で有効化する際には気をつけてください。