フリーのプログラマーとしてやっているので、仕事内容はどんどん変わる。
ここ数カ月は、ある企業の WEB サイトデザインの大規模変更を手伝っている。
すでに長年運用されているサイトで、Wordpress で構築されている。
プログラムは拡張を繰り返してスパゲッティ状態だが、今回は「デザイン改修」が趣旨なので、プログラムは出来るだけそのまま活かして…
という指示。
ほぼ完成していたのだが、今月の中ごろに、一部の Android 端末でスクロールがおかしい、というバグが報告された。
Chrome のアップデートで、スクロールがおかしくなるバグが混入したらしい
…と報じているサイトがあった。9月28日の記事で、アップデートは9月25日…ということになっているが、実際には19日。
#Android アプリを配布する Google Play ストアでは、バグやウィルス入りのアプリが一気に広まるのを防止するため、複数のサーバーで徐々に配信が開始される。
アプリの登録・公開は19日だったが、実際にアップデートが入るのは端末ごとに接続するサーバーにより異なり、25日が嘘だということではない。
あぁ、Chrome のバグならしょうがない。そのうちアップデートされるだろうから、この件は一時保留…となったのだが、大きなバグに思える割になかなか修正されない。
クライアントからの強い修正依頼もあり、たとえ Chrome に原因があろうとも、多少別の機能に影響が出ても回避・修正せよ、となったのが昨日。
徹底的に原因の解明が始まった。
改めて確認すると、自分の端末でもバグが出た。
先日新しい端末にしたばかりで、以前の端末では問題が出なかった…と思う。
普段は PC の Chrome で、スマホエミュレートして確認をしているが、やはり問題は出ていない。
ともかく、手元でバグを再現できるようになった。こんなに心強いことはない。
しばらくいじって、バグの出る状況を確認。
スマホのブラウザの場合、スクロールによってアドレスバー…URL を表示したり、入力したりするところが、現れたり消えたりする。
この状態が切り替わった後、最初の Idle 状態にブラウザがなった時に、スクロール位置がトップに戻ってしまう、というバグが出現した。
Idle 状態って表現は説明がいるだろう。
アドレスバーはスクロールに合わせて出入りするが、スクロールが「止まって」も、指が放されるまではスクロールが続いている、と考える。
また、指を放す際にフリックすると、慣性スクロールが始まる。この慣性スクロールが終わるまでは、スクロールが続いている。
そして、スクロールが終わった時が「Idle 状態になった時」だ。
この瞬間にバグが出る。
多少の心当たりはあった。
作製しているサイトでは、画面上にある「ナビゲーションボタン」を、スクロールに合わせて表示を切り替えていた。
ページ表示時には、ページのヘッダ部分があり、その下にナビゲーションがついている。
文章を読み進め、下に向かってスクロールすると、当然ヘッダとナビゲーションは上に消えていく。
…はずなのだが、ナビゲーションがちょうど画面の上に貼りついたタイミングから、ナビゲーションだけがその位置に固定される。
CSS を position:fixed に変えているわけだが、これは画面スクロールに合わせて行っている。
スクロールのタイミングに合わせてバグが出る、というのは、この一連のプログラムと関係があるのかもしれない。
そこで、Javascript の該当プログラムを動かないようにしてみる。
…それでもバグは出た。
サイトはすでに古いものだし、Wordpress で構築されている。
他にも無数の Javascript プログラムが動いている。
このどこかでおかしくなっているのかもしれないし、全く違うバグかもしれない。
いろいろとプログラムや CSS など、外部から読み込むプログラムを外しては、リロードしてみる。
この最中、不思議なことに気が付いた。
ある程度スクロールを進めた状態でリロードを行うと、まず画面のトップ位置が表示され、続いてリロード前の位置にジャンプし、さらにまたトップ位置に戻される。
…普通は、直前まで読み進めた位置にジャンプして終わりのはずだ。最後の「トップ位置に戻る」が余計だ。
何度かリロードして観察して、このタイミングは、画面上の全要素を配置し終わったタイミング、いわゆる「window.load」だ、と見きわめた。
Javascript で何か処理が行われている可能性が高い。
無数の Javascript ファイルのどこで処理が行われているのかわからないが、ステップ実行を延々と…10分以上も繰り返して、やっと最後の「おかしな動作」をするタイミングを見極めた。
ウィンドウのロード、リサイズ時に、document.body.scrollTop を調べ 0 ならば window.scrollTo(0,1) する、というプログラムがあった。
このプログラム、5年くらい WEB サイト構築をしている人なら何かわかるだろう。
5年ほど前に、「ページを開いた瞬間から、アドレスバーが消えて画面が広く読みやすい」という WEB デザインが流行したことがある。
アドレスバーは、下にスクロールすれば消える。これを Javascript で実現するのが、上のプログラムだ。
ロード・リサイズ時に画面の一番上を表示していたら…つまり、ページ読み込み直後なら、1ドットだけ下にずらしてやる。
すると、アドレスバーが消えた。
しかし、アドレスバーが消えると表示中のサイトの URL もわからなくなるわけで、フィッシング詐欺には好都合だった。
他のサイトと同じような動作なら疑問に思う人もいない。そういう詐欺サイトが横行した。
これをうけて、スマホのブラウザでは、人が操作しない、Javascript でのスクロールはアドレスバーに影響を与えないようになった。
つまり、先のプログラムは今となっては無意味だ。
無意味だけど、やっぱり読み込み直後には1ドットスクロールする。
それ以外は何もせず、無害なプログラム、のはずだった。
スクロール位置は、古くは document.body.scrollTop で調べられた。
しかし、body 要素は「ドキュメントの表示部分」を意味する。それに対し、スクロール位置というのは、表示ウィンドウの問題とか、もっと別の次元のものだ。
この論理的なおかしさをただすため、document.documentElement.scrollTop でスクロール位置を調べる、という方法が標準化された。
Chrome の元となっていた webkit にもこの変更は反映された。
だけど、コンパイル時のオプション設定により、新しい「標準モード」と、古い「互換モード」を、切り替えられるようになっていた。
Chrome では過去との互換性のため、body.scrollTop が使い続けられていた。
しかし、9月頭にアップデートされた Chrome で、body.scrollTop から documentElement.scrollTop に切り替えられたらしい。
…が、超巨大なページの上、技術者でないと意味が分からないので、特に興味が無ければ読まないのがお勧め。
で、このログで scrollTopLeftInterop を検索すると、default が Enable され、WebView が Disable されているのがわかる)
これは、「いつか行う、と予定されていた変更」が実行されたに過ぎない。
すでに documentElement を使用していたブラウザもあるし、「ブラウザを調べて」ではなく、「環境を調べて」動作を変えるプログラムを作っておかなくてはならなかった。
そのための期間も十分に与えられていたにもかかわらず、動作がおかしくなったサイトがあったとすれば、そのサイトに潜在バグがあったと言わざるを得ないだろう。
これは Chrome のバグなどではなく、もちろん今後のアップデートで修正されるものでもない。
上に書いたのはデスクトップ版の話で、同じ変更が9月下旬に Android 版 Chrome に入った。
今後、body.scrollTop は、実際のスクロール位置を反映せず、常に 0 となる。
これが、2012 年ごろに流行った「アドレスバーを消す」ための Javascript と、非常に相性が悪い。
画面ロード時・リサイズ時には、本来「現在のスクロール位置が 0 なら」、scrollTo(0,1) でアドレスバーを消していた。
これが、現在のスクロール位置に関わらず、いつでも scrollTo(0,1) で、ページトップに戻ることになる。
いや、これでもまだ、PC 版で同じプログラムが動いていたとしてもあまり害をなさない。
ロード時に動くのは問題ないし、ページ中でのウィンドウリサイズも、それほど行わないからだ。
スマホだと、スクロールしただけで、アドレスバーが表示・消去される。
アドレスバーを消す理由は、コンテンツの表示領域を広げるためだ。
このため、Chrome ではスクロールしただけでリサイズイベントが発行されることがある。
どうも、このイベントの発行は、Scroll イベントが置き続けている時は避けて、Idle 状態になった時に行われるようだ。
これで、最初に書いたバグの条件がすべて整った。
スクロールに伴い、アドレスバーの表示・非表示が切り替わると、その後最初に Idle 状態になったタイミングで、現在のスクロール位置に関わらず、画面トップに戻される。
原因が理解できれば、修正も簡単。
幸いなことに、「1ドットスクロールさせる」というプログラムは、2012年頃には意味のあるものだったが、今では全く意味をなさない。
このプログラム自体を消してしまおう。
これで正常な動作に戻った。
今回の仕事ではこれで終わりだったが、「スクロール位置を変更したい」場合の動作も変わっているらしい。
jquery 的表現だと、今まで Chrome は $('body').scrollTop(y) などでスクロール位置を指定できた。
これを、$('html').scrollTop(y) にする必要がある。
…FireFox では、html に指定しなくてはならなかったので、$('html,body').scrollTop(y) としているサイトも多いかもしれない。
この場合、特に問題は出ず、今まで通り動き続ける。
機種判別して使い分けていた場合は、修正が必要だ。
同じテーマの日記(最近の一覧)
別年同日の日記
申し訳ありませんが、現在意見投稿をできない状態にしています。 |