Home > Latest topics

Latest topics > 文字入力中に補助的な情報や機能をポップアップとして表示するとキャレットが消えてしまう件について分かったこと

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

文字入力中に補助的な情報や機能をポップアップとして表示するとキャレットが消えてしまう件について分かったこと - Mar 10, 2012

先に要点だけ書いておくと、文字入力中に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を使いたいし、弊害が表面化しない限りはこのままでいこうと思う。

分類:Mozilla > XUL, , , , , 時刻:07:07 | Comments/Trackbacks (0) | Edit

Comments/Trackbacks

TrackBack ping me at


の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2012-03-10_caret-with-popup.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2020年11月30日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき