この日記はMozillaのプロダクトへの貢献者としての私の成果を中心に、気になったバグやWeb界隈の話題について書いていますが、 断り書きがある場合を除き、いかなる団体のオフィシャルな見解ではありません。あくまでも個人的なものです。 Mozilla Foundation、Mozilla Corporation、及び関連企業の公式情報ではないことに注意してください。

現在、XHTML 1.0 (もどき)から、HTML5なコンテンツに修正中です。古い日記は修正が完了していませんので表示が崩れます。 順次、修正していく予定ですのでしばらくお待ちください。

もずはっく日記(2010年6月)

2010年6月14日

Bug-org 550434 Clicking in an empty contenteditable element that has focus causes the caret to disappear
初回投稿日時: 2010年06月14日16時11分55秒
カテゴリ: Mozilla Core バグ修正
SNS: (list)

大きさを指定した(?)、空のブロックレベルの要素をcontenteditableのホストにすると、二回目以降にクリックした際にキャレットが消えてしまうというバグです。

元々はEhsanがエディタ側で修正したのですが、どう見ても場当たり的で良い方法をとっておらず、またこのあたりを別バグのためにリファクタリングしてるこちらの設計を大きく潰してしまうので根本的な原因を調べて、元のパッチをバックアウトし、根本的な修正を入れました。

問題なのはエディタではなく、選択範囲のコードで、nsIFrame::GetContentOffsetsFromPoint()から返ってきた値をそのまま引数に受け取ると、矛盾を感じるようになっていました。

理由はよく分からないのですが、空のブロック内の座標をnsIFrame::GetContentOffsetsFromPoint()に渡すと、特殊なケースとしてこのブロック全体を選択した状態の範囲が返されるようになっていました。エディタはフォーカスを受け取ったときに選択範囲の限界をそのエディタのルートに設定します。contenteditableの場合、フォーカスを受け取ったホスト要素、つまりこのケースの場合空のブロック要素になります。ところが、次にこの空のブロックをクリックした時にnsIFrame::GetContentOffsetsFromPoint()は、ブロック全体の範囲を返してしまうため、この範囲の示す要素はホストの親になってしまいます(つまり、限界を超えた要素)。このため、選択範囲は矛盾を発見し、選択範囲の限界をリセット、選択範囲を全て削除、という処理に逃げていました。このため、閉じられた選択範囲を視覚化しているキャレットが消えてしまう、ということになっていたのです。

選択範囲のあり方として、ブロック全体を選択することもあるので、nsIFrame::GetContentOffsetsFromPoint()の戻り値を受け取る側を修正するのは不可能です。このため、nsIFrame::GetContentOffsetsFromPoint()自体を修正するしかないと思いました。しかし、nsIFrame::GetContentOffsetsFromPoint()は非常に多くの場所で利用されているのでどのような処理にまで流用されているのか想像もできません。そこで、最小限の修正に抑えることにしました。

もし、指定された座標が空のボックスだった場合、それが編集可能な場合には新しいフラグと共に、その内部の範囲を返すようにしました。編集可能な場合、やはり空のブロック内にも新しくテキストを挿入することが十分に考えられますので、この動作で問題はないはずです。また、この新しいフラグのおかげで、必要なら受けとった側が修正前と同じ値を取得することもできるようになっていますのでregressionがあったとしても簡単に対応できるようにはなっています。

関連するかもしれないエントリ

bug-org 550434を含むエントリ