🧭

WebKit のバグを修正した: text-underline-offset の repaint 条件

2024/12/24に公開

少し前に WebKit のバグを見つけ、 PR を送ったら無事マージされました。
あまり大したバグではないですが、レビュワーの指摘を踏まえて調べたところ「どうしてこのバグが起きたのか」の話が勉強になったのでまとめておきます。

バグの概要と修正

「ホバー時に text-underline-offset を変えようとしてもSafariでは正しく機能しない」というバグです。どうやったら動くか色々と試行錯誤していると、どうやら以下のサンプルの「OK」の例のように他のプロパティーも一緒に変更すれば正常に動作するようでした。


text-underline-offset のみを変更するとホバーしても反映されない

ソースコード

JSFiddle

<a href="https://webkit.org" id="ok">OK</a>
<a href="https://webkit.org" id="ng">NG</a>
a {
  text-decoration: underline;
  text-decoration-skip-ink: none;

  &:hover {
    text-underline-offset: -5px;
  }
}

#ok:hover {
  color: orange;
}

ブラウザがどのように機能しているかはほとんど知らなかったのですが、挙動から、再描画をするかどうかの条件に text-underline-offset が含まれていないのではないかと予想できたので再描画の判定を行っていそうな箇所を探し始めました。

しばらく探すうちに RenderStyle::changeRequiresRepaintIfText というピッタリな関数が見つかり、ここに text-underline-offset を考慮するよう変更を加えたところ正常に動作したため、PRを送りました。

ここからはなぜこのバグが発生したかについて、今の段階での私の理解を紹介します。

(前提) レンダリングの処理の流れ

まず前提として、ブラウザでは以下のような順でWebページを描画します。
今回のバグに関係があるのは layout と paint の部分で、 layout で要素の位置やサイズを定め、その後に paint で実際に結果を描画します。


The full pixel pipeline, illustrated. (web.dev)

Webページの内容に変更があった時、それが layout に関わる場合は再度 layout が実行 (reflow) され、そうでない場合は再度 paint が実行 (repaint) されます。

下線の位置の変化とトリガされる処理

テキストに下線を追加した状態から下線の位置を移動した場合にトリガされる処理は以下のようになっています。


(テキストの周囲に表示されている点線が bounding box をイメージしたもの)

外側から内側、内側から外側や、外側から外側へ移動した際には下線が移動したことで要素からはみ出している部分が変化し、 reflow が実行されます。[1]

それに対し、 内側から内側の例でははみ出している部分に変化はありません。そのため、 reflow はトリガされず、 repaint で処理される必要があります。しかし冒頭で説明したように repaint されるべきかを判定する処理には text-underline-offset が考慮されていなかったため、再描画されずホバーしてもスタイルが変化しなかったようです。

おわりに

下線を要素内から要素内に移動させるような状況は確かに多くないと思うのでこれまで気づかれなかったのも理解できます。小さいですが、ブラウザのレンダリングの流れや変更の反映方法について学ぶきっかけになる面白いバグでした。また、 WebKit への PR ははじめてだったので専用のコマンドを使って git 操作を行うなど WebKit 独自のシステムに触れることができたのもよかったです。

脚注
  1. 下線は ink overflow と呼ばれる、要素からはみ出しているが layout に関係ない装飾要素の一つだと紹介されているのですが、 WebKit では変更があった場合には reflow を行っているようでした。 ↩︎

Discussion