宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
先に要点だけ書いておくと、文字入力中にXULのmenupopupで補助的な情報や機能を表示したい時は、openPopupAtScreen()
やopenPopup()
の引数で明示的にコンテキストメニューであると指定してポップアップを開くようにすると、キャレットが消えなくなります(あくまでworkaround。根本的な解決策とは言えない)。それか、menupopupの代わりにtooltipやpanelを使うようにするというのも、キャレットを消えなくする方法として有効のようです。という話。以下は、そこに辿り着くまでの間に何を調べたかという事の記録です。
セカンドサーチは、FirefoxのWeb検索バーに文字を入力した時に他の検索エンジンの一覧をポップアップ表示して、上下キーでそれを選択すると既定の検索エンジン以外で検索を実行できる、というアドオンなのだけれども、ポップアップを開いている間はキャレット(文字の入力位置を示す、点滅するカーソル)が消えてしまうという問題が起こっていた。まあ我慢できない事も無いし……と思って放置してたんだけど、人から指摘されてしまったので、じゃあってんで調べてみることにした。
まず、選択範囲の問題(Firefox内部ではキャレットは長さ0の選択範囲として扱われているので)かと思ってnsISelectionControllerに何かそれっぽい機能が無いかなーと思って見てみた。そしたら、setCaretEnabled()
などといういかにもそれっぽいメソッドがあったので、試しにポップアップメニューを開く前後や開いた後でdocument.getElementById('searchbar')._textbox.editor.selectionController.setCaretEnabled(true)
とするようにしてみたけど、残念ながら効果が無かった。
次に、ちょっと角度を変えて、フォーカスが外れてるとかそういう理由で駄目なのかな?と思って、focus()
やblur()
で強制的にフォーカスを与えてみたんだけど、これも無駄だった。
ポップアップがあると全面的に駄目なのか?という可能性も考えたけど、オートコンプリートのポップアップの場合はキャレットが消えるということは起こっていなかったので、何か抜け道があるはず。という事でsetCaretEnabled()
を呼んでいる箇所を見てヒントが無いか調べていたら、C++のレイヤにはSetCaretVisible()
というメソッドが呼ばれている箇所がいくつかあるという事に気がついた。
それでもう1回nsISelectionControllerの機能の一覧をmxrで見てみたら、caretVisible
というプロパティが定義されていて、どうもこれに関係しているメソッドっぽい様子がうかがえた。しかし、メソッドの引数にnsPresShellというC++のレイヤでしか登場しない型が書かれているし、XPIDLでもreadonlyなプロパティという定義になっているので、これをJavaScriptから操作するのは無理っぽかった。
とはいえ、このプロパティの値を見てみてホントに内部的にキャレットを非表示にするように判定されているのかどうかを調べてみることには意味があると思ったので、ポップアップを開く処理の前後でdump(document.getElementById('searchbar')._textbox.editor.selectionController.caretVisible+'\n')
して値の変化を確認してみた。すると、ポップアップが開かれている時は値がfalse
になっていて、確かにキャレットを表示しないように判定されているのだという事が分かった。という事は、その判定の基準を調べて判定結果が変わるようにしてやれば、キャレットを表示させることは不可能ではないと言える。一歩前進だ。
そこでまたnsISelectionControllerの機能を見返していると、今度はsetCaretVisibilityDuringSelection()
というこれまた怪しい名前のメソッドがある事に気がついた。そこでこれを使ってdocument.getElementById('searchbar')._textbox.editor.selectionController.setCaretVisibilityDuringSelection(true)
としてみた所、caretVisible
の値がtrue
になるようになって、実際にキャレットも表示されるようになった。
これで解決したか?と思ったけど、でもこのメソッドは範囲選択中にキャレットを表示させるようにするという別の目的のための物なので、それがそのまま副作用になってしまう。後で元に戻すという手もあるだろうけど、元に戻すタイミングを間違えたり見落としてしまったりすると、状態が元に戻らなくてまたそれが問題になりそうだという事も思った。なので、別の方法は無いかまた調べてみた。
setCaretVisibilityDuringSelection()
のC++での実装の中身から辿っていくと、nsCaretというクラスのMustDrawCaret()
というメソッドに辿り着いた。名前からして相当怪しい。これがキャレットの表示・非表示を制御している鍵っぽいと思って中身を見てみたら、実際にいくつかの判断基準が書かれていて、その中の1つにIsMenuPopupHidingCaret()
というメソッドの戻り値も使われていた。
名前からして、ポップアップメニューがある時はキャレットは隠される物だという前提がひしひしと感じられたのだけれども、そっちの実装も見てみたところ条件次第ではポップアップメニューが開かれているがキャレットは隠さないという判定結果になる場合があるようだったので、どういう流れを辿ったらこのメソッドがfalse
を返すのかという方向で考えてみた。
まずポップアップメニューが1つも開かれていなければすぐfalse
を返す(よく考えてみたら、オートコンプリートのポップアップはmenupopupではなくpanelなので、それでここでfalse
が返されてるんだなあ……とこのエントリを書いてて改めて気がついた)けど、これは今回の要件からは採れない選択肢なので除外した。
次に、キャレットがポップアップの中にある時もfalse
を返すようだったけれども、これもセカンドサーチの設計上あり得ない話だ。後はメソッドの最後まで到達する場合でないとfalse
は返されないようだったので、true
を返す条件分岐に引っかからないようにするしか無い。
そのような条件の1つに、ポップアップメニューがコンテキストメニューとして開かれているかどうかという判断が行われていた。そういえばopenPopupAtScreen()
やopenPopup()
にはコンテキストメニューかどうかを指定する引数があったなと思ってセカンドサーチの当該箇所を見てみると、false
(コンテキストメニューではないという指定)になっていたので、試しにtrue
に書き換えてみた。すると、やっとキャレットが消えなくなった。caretVisible
の値もtrue
になっていたし、LinuxだけでなくMac OS Xでも問題が解消されたことを確認できた。
コンテキストメニューかどうかという指定を偽るのは、さっきのsetCaretVisibilityDuringSelection()
に比べればまだ無害な方と言えるんじゃないか? と僕には思えるので、menupopupを使った実装における今の所のworkaroundとしてはこれが一番無難な気がしてる。ほんとはオートコンプリートのポップアップみたいにpanelで実装するべきなんだろうけど、サブメニューの展開のこととか考えると面倒だからやっぱりmenupopupを使いたいし、弊害が表面化しない限りはこのままでいこうと思う。
の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2012-03-10_caret-with-popup.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。
writeback message: Ready to post a comment.