はじめに
レスポンシブWebデザインのWebサービスを開発していて引っかかった問題について書いてみようと思います。検索してもこの問題についてまだヒットしません。
ブラウザのhistory apiで、pushState()、popStateイベントに関する問題です。
結論から申しますと、この問題を直接回避する手段はなさそうです。
経緯
当該サイトはページングもするし、それぞれのページではBootstrap(+改造)を使ってポップアップも多重に行います(中身はHTML+CSS+JavaScriptでのウインドウ作成)。( 以後「独自ウインドウ」といいます)
動作確認環境
デバイス | ブラウザ |
---|---|
PC(Windows) | Chrome、Firefox、IE |
Androidスマホ | Chrome、Firefox、Opera |
iPad mini | Safari、Chrome |
今年(2016年)5月くらいまでは問題は無かったのですが、それ以後、iPadのChromeだけ挙動が変ってしまいました。
遭遇した問題
問題の動作
問題の動作の箇所ですが・・・
このWebサービスにユーザがログインするとマイページに移動させます。その直後に独自ウインドウを開いて「ログインしました」というメッセージを表示しています。
このウインドウを閉じた直後に問題が発生します。
JavaScriptでは
その独自ウインドウを表示する際に pushState() でダミーの履歴を追加し、ウインドウを通常の方法で閉じたときは back() で履歴を取り除き、その際発生するpopStateイベントは無視するようにしています。
ユーザがブラウザのback機能を使った場合に発生するpopStateイベントハンドラでは独自ウインドウを閉じるようにしています。
余談ですが、Androidではブラウザではなく、システムのbackボタンがブラウザのbackに割り当てられますから、Android端末ではbackによりウインドウを閉じるという動作は頻繁に使用するものと思われ、今回history apiでの実装はAndroidのためだけに行ったと言っても過言ではありません。
問題の詳細
問題の挙動は、iPadのCromeのみで起こり、独自ウインドウを閉じた直後にbackしてログイン画面に戻ってしまう、という動作です。本来ならマイページ画面に留まるはずです。
実は、他にも独自ウインドウはAjaxと組み合わせていて多用しています。他の大部分の箇所では問題が発生せず、一部のみこの問題が発生します。
違いはUIイベント以外でpushState()をコールしているという差異しかありません。しかし、pushState()前後のhistory.lengthを調べてみても、ちゃんと増えています(増えなければまだきれいな解決方法があったはず)。その後、back()をコールした際に、なんとpopStateイベントが発生せず、同時にpushState()した履歴を飛ばして更に1つ追加でback動作をしてしまう仕様のようなのです。
まとめると、
正常の場合、
1.独自ウインドウを開くと同時に pushState()コール
2.独自ウインドウを画面から閉じられたときに back()コール
3.back()によりpopStateイベントが発生、ハンドラでの処理はなし
異常の場合、
1.独自ウインドウを開くと同時に pushState()コール
2.独自ウインドウを画面から閉じられたときに back()コール
3.back()によるpopStateイベントが発生しない上、pushStateした履歴を飛ばして更にback動作をしてしまい、画面が変ってしまう。
そして、異常の動作になる条件としては、手順1.でのpushState()コールがUIイベントではない場合(この場合はreadyイベント)です。
Windows版やAndroid版のChromeを含め、他のブラウザでこの現象は確認されていません。
問題の対処は?
おそらく、これは蟻地獄サイト(backして抜けられないようにする)対策の仕様だと思われますが、なぜiPad(おそらくiOS版全般)版のChromeだけそのようにしているのかは腑に落ちませんし、情報が得られていません。( なにか情報があったら教えてください!)
現状、iOS版のChromeは非推奨とし、iOS版Chromeだけを切り分けて(UserAgentからOS/ブラウザなどの調べかたのまとめ)ブラウザのback機能で独自ウインドウを閉じる処理自体をやめています。そのままだとあまりにもバギーな動作なので
というわけで、本質的な問題回避には至っておりませんが、もし、この仕様が主力となって多くのブラウザの標準になってしまうようだと困ってしまいます。
完全な1ページ設計にすることが求められそうです。現在は、Ajax+独自ウインドウを多用はしていますが、ページ移動も併用している中途半端ハイブリッドな設計になっています。
ただ、完全な1ページ設計にしたからといって、最初にサイトにアクセスした直後に(UIイベントによらず)ポップアップウインドウを表示する、という場面が完全に回避できるとは限りません。と、考えるとけっこう困った仕様に思えてきます。( 標準化されませんように!)
readyイベントをUIイベントに偽装する方法があればいいのですが、それは多分無理だと思って調べてはいません。それができたら蟻地獄サイトもできてしまうことになるでしょうから。UIイベント、ということに思い当たる前はsetTimeout()ハンドラを使ってスレッドを分割してみるという事もして見ましたが無駄でした(他の問題が発生する場所ではけっこうこれで解決できることもあります)。
余談
実は、iPadのChromeは現在の仕様に至る前に、ちょっと違う仕様に変化したことがあります。上記×の3でpopStateイベントは発生するのですが、それでもブラウザがbackしてしまうようになっていたのです。そのときは、そのハンドラの戻り値にfalseを指定することで実際にブラウザがbackしてしまうことを回避できていました。これは偶然発見した回避策ですが、その後それさえも通用しなくなってしまい、現在に至っています。
2016.7