hogashi.*

日記から何から

ブラウザで:has()セレクタが実装されてjQueryの:has()セレクタの挙動が変わったの調べた

 Chromeに実装された疑似クラス「:has()」がjQueryの「:has()」に悪影響、一定の条件下でWebサイトが壊れる可能性 - Publickey を読んだので、調べたものとあわせてまとめる。

三行

原因

  • jQuery には CSS セレクタの拡張がされている
  • jQuery は、セレクタによって要素を取得する際、パフォーマンスのためにブラウザの querySelectorAll() メソッドを使う
    • セレクタが不正だった場合は querySelectorAll() メソッドがエラーになるので、 jQuery の拡張された実装で要素を取得し直す (フォールバックするような感じ)
  • かつては :has() はブラウザの querySelectorAll() メソッドにとって不正なセレクタだったため、 (エラーになるので、) jQuery の実装で要素が取得されていた
    • このほどブラウザで :has() が実装された
      • ここまでは問題なく、 jQuery でやっていたことがブラウザでもできるようになったということ
    • ただし、 :has() の引数に書かれたセレクタは不正でも無視される
  • これにより、 :has() の引数に、ブラウザでは対応していない jQuery の拡張のセレクタを書いた場合、 jQuery で処理されなくなってしまった
    • 今まで jQuery で取得できていたが、取得できなくなった
forgiving-selector-list

 MDN から :has() の仕様をたどるとここで、引数には <forgiving-relative-selector-list> をとると書かれている。

The relational pseudo-class, :has(), is a functional pseudo-class taking a <forgiving-relative-selector-list> as an argument.

https://w3c.github.io/csswg-drafts/selectors-4/#has-pseudo

 <forgiving-relative-selector-list> の項 https://w3c.github.io/csswg-drafts/selectors-4/#typedef-forgiving-relative-selector-list を見ると、 <forgiving-selector-list> でありながら <complex-selector> ではなく <relative-selector> としてパースされるものとある。今回は <forgiving-selector-list> であるという部分だけ気にしたらよい。

 <forgiving-selector-list> は、セレクタそれぞれをひとつずつパースし、パースに失敗したセレクタは無視して、成功したものだけを使うとのこと。今回の (ブラウザの) :has()querySelectorAll() だけを試すとこういう感じで、ブラウザではセレクタとしてパースできない :even が引数にあるが、エラーにならずに単に無視されている *1


ChromeSafari の状況の違い
  • 記事中のツイートでは、 Safari の実装では jQuery の挙動を変えないと書かれているように読めた
    • しかし調べたところでは、 Safari でも部分的に jQuery の挙動を変えてしまっていそうに見える
  • Safari:has() の引数を forgiving-relative-selector-list として完全に実装できていないので、 jQuery の挙動が変わってしまう場合と変わらない場合がある

 実際 SafariquerySelectorAll() だけを試すとそのようになっている *2:even はブラウザでパースできないので、 :even がついたセレクタだけが引数にある場合 (1行目と 3行目) はエラーになっている (この場合は jQuery が要素を取得でき、挙動は変わらない)。対して 2行目は body がパースできるセレクタのため、エラーにならない (この場合は jQuery が処理をしないので、挙動が変わってしまう)。


されている対応と今後

Of course, there's still some breakage for selectors like `div:has(div, span:contains('Item'))` but, as I understand, Chrome is hoping that there's not much usage of it in the wild considering WebKit was able to go away with it.

https://bugs.chromium.org/p/chromium/issues/detail?id=1358953#c40

感想

 jQuery 側を直したい気持ちになるけど、古いバージョンのまま残っているサイトを壊さないという観点ではそれは難しいので、ブラウザ側がなんとかして避けるしかないのが (いつもそうだけどやはり) 大変そう。 forgiving-selector-list は初めて知ったし forgiving/unforgiving という表現が使われるのはへ〜という感じだった。あと Chromium の issue で querySelectorAll()qSA と略されて呼ばれているのが面白い。

*1:Google Chrome 105.0.5195.102

*2:Safari 16.0 (17614.1.25.9.10, 17614)

 枕が枕カバーになかなか入らず、枕の成長に思いを馳せて、中身がうまく乾燥したのだろうと思った(結局ちゃんと入った)。
 最近は皿を洗う頻度が上がらない。もともと自炊はできないタイプなので困りにくいせいもある。気持ちには波があって、洗うか、となるまでの波長が長くなっているとは感じているものの、それがなぜなのかはわかっていない。
 何につけ理由を考えがちだと思う。突き詰めていくとなぜ生きるかみたいになるので、諦める線は必要なのだけど、そういう諦めないといけないところか、あるいはちゃんと納得できるところまでは考えていそう。
 昔からこういう性格で、小さい頃は何かを見つけるごとに「これ何これ?」と親に聞き続け、冷蔵庫の扉を閉めると照明が消えることに気づくと仕組みを調べていた(温まっちゃうでしょうと言われてはいた)。しかし今は小さい頃ほどの熱意がないように思う。知っていることが多くなったことで知りたいことの割合が減ったからなのか、やる必要があることに追われるからなのかわからない(これ自体ここである程度納得して止まっている)。
 周りの人に聞くと、そんなとこ気にしてなかったよ、という人も、もっと色々調べたりしてたよ、という人もいて、改めてこういう心の向きは大事にしたいことだなと思っている。しかし親には考えすぎだとよく言われるので、程度が難しい。

 祖父がテレビのリモコンのことをピポパと呼んでいておもしろかったのを思い出した。葬式の次の日に仕事場の小さな機械が勝手に動き出して、「まだ居るわ」って家族でのどかに話していたのもたまに思い出しておもしろい。畳屋は仕事が減っていったようだったものの、小さい頃にはぎりぎり仕事場で仕事している様子が見れたのはよかった。成人してから会いに行って、多少ぼけつつ燗した酒を楽しそうにめちゃくちゃ注いでくるので飲み続けた覚えがある。
 最近近くの喫茶店にいって店主の方と常連の方と話したところ家族を大事にしたいねという話を経由したのでなんとなくそういう心持ちになっている。ここ数年は神社や寺に行ってはあらゆる人の無病息災を願っている。

 ギリギリ夕方よりは前に起きて、掛け布団を冬→夏に変え、シーツと布団/枕カバーを洗い、ゴザと床を拭き、ご飯を食べ、蚊取り液体を買い、川に来ている。来たる夏が嫌すぎて何も用意できていないまま過ごしていたのをやっとなんとかできた。なんとかできたところで夏は来るので困る。