2017年10月27日の日記です


Android Chromeでスクロールがおかしくなるバグの原因と修正  2017-10-27 11:30:48  コンピュータ

フリーのプログラマーとしてやっているので、仕事内容はどんどん変わる。

ここ数カ月は、ある企業の 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 に切り替えられたらしい


こちらにgitログがある。

 …が、超巨大なページの上、技術者でないと意味が分からないので、特に興味が無ければ読まないのがお勧め。

 で、このログで 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) としているサイトも多いかもしれない。

この場合、特に問題は出ず、今まで通り動き続ける。


機種判別して使い分けていた場合は、修正が必要だ。



同じテーマの日記(最近の一覧)

コンピュータ

別年同日の日記

01年 10/27

02年 妹主演の劇を見る

04年 枝豆

11年 太陽電池

16年 ダイナマイト刑事

20年 getGamepads


申し訳ありませんが、現在意見投稿をできない状態にしています


戻る
トップページへ

-- share --

0003

-- follow --




- Reverse Link -