Hatena::ブログ(Diary)

koumiyaの日記

2008-09-16

ブラウザの戻るボタンで戻ったときに呼ばれるイベントとかキャッシュとかそこらへんのこと

00:49

IEでは戻るボタンで戻ったときにonloadイベントが呼び出されるが、Firefoxでは呼び出されないなどブラウザによって動きが違うようです。


よくある二度押し防止対策として、送信ボタンをクリックしたときにボタンを無効(disable = false)にして次の画面に遷移させるということをやりますが、その後ブラウザの戻るボタンで戻られるとボタンが無効のままで操作できないままになってしまいます。


その対策として、画面を表示したときに無効を解除するという処理が考えられますが、IEではonloadイベントが呼ばれるがFFでは呼ばれないという上記の問題が発生します。


調べてみたところ、戻るボタンとonloadイベント - a geekで紹介されている方法で解決できるみたいです。IEではonloadを使い、FFではpageshowを使い、Safariではonloadとonunloadの合わせ技で戻るボタンで戻ったときにもイベントを発火させられます。


とりあえずの対応はできたんですが、ページ表示時のイベントの発火やキャッシュについて不明瞭なところが多いのでそれについてのまとめとprototype.jsを使ったときの解決法について考えてみます。


以下、WinXP上のIE7, FF3.0.1, Safari3.1.2, Opera9.52でチェックしてます。

まずonloadの挙動について

全てのブラウザでonloadイベントは画面のレンダリングが完了した時点で呼び出されます。画面Aにonloadを仕込んだとき、白丸でイベントが呼ばれます。

IEの場合

○画面Aを表示

●画面Bに遷移

○画面Aに戻る

FFの場合

○画面Aを表示

●画面Bに遷移

●画面Aに戻る

Safariの場合

○画面Aを表示

●画面Bに遷移

●画面Aに戻る

Operaの場合

○画面Aを表示

●画面Bに遷移

○画面Aに戻る

IEOperaFFSafariが同じ動きをします。

キャッシュについて

戻るボタンで戻ったときにサーバーに問い合わせを行うかをチェックしました。

IEの場合

問い合わせを行う→キャッシュされない

FFの場合

問い合わせを行わない→キャッシュされる

Safariの場合

問い合わせを行わない→キャッシュされる

Operaの場合

問い合わせを行わない→キャッシュされる

IEだけ戻るボタンで戻ったときにサーバーに再度問い合わせています。戻るボタンで戻ったときにIEではonloadが呼ばれるのは、画面を再レンダリングしているからだと考えると納得いきますね。逆にOperaキャッシュを見ているのにonloadが呼ばれるという中途半端な動きです。

onunloadを付けたときのキャッシュはどうか

最後に、Safariですが、こいつも戻るボタンで遷移した場合はonloadハンドラを呼びません*2。ので、先ほどのFirefoxのドキュメントに乗っていた、「unloadイベントがあるとキャッシュしない」の性質を利用します。この性質は当然Firefoxのものではありますが、「unloadがある → スクリプトの終了処理をしている → この状態を保持しても、スクリプトはもう使えない → キャッシュしない」と考えれば、

Safariでも同じ事情になっている可能性が高い、との推論です。

http://d.hatena.ne.jp/hiratara/20080308/1204955060

unloadがあるとキャッシュされないみたいです。一応確認します。IEはそもそもキャッシュしてないので対象外。

FFの場合

問い合わせを行わない→キャッシュされる

Safariの場合

問い合わせを行う→キャッシュされない

Operaの場合

問い合わせを行わない→キャッシュされる

あれ、予想外。Safariはunloadを付けると問い合わせをするようになりましたが、FFOperaは問い合わせていませんでした。キャッシュというのは問い合わせするかどうかということじゃなくてJSによって変更された内容を保持するかってことなんでしょうか。

インラインスクリプトは呼ばれるか。

↑のunloadのチェックから、Safariは画面を再描画、FFOperaスクリプトの評価のみ?を行っているようなのでチェックします。以下、戻るボタンで戻ったときにインラインスクリプトが呼ばれているかをチェックしています。白丸のところでインラインスクリプトが実行されます。

IEOpera

unloadあるなしに関係なく

○画面Aを表示

●画面Bに遷移

○画面Aに戻る

FFSafari

unloadなし

○画面Aを表示

●画面Bに遷移

●画面Aに戻る

unloadあり

○画面Aを表示

●画面Bに遷移

○画面Aに戻る

まとめ

とりあえず、空のunloadを付けておけば、IEFFSafariOperaで戻るボタンで画面を表示したときにもloadイベントが呼び出されるようになります。実際prototype.jsの1.4の頃はunloadイベントをバインドしてブラウザ間の差異を吸収していたようです。同期的なアプリケーションに部分的にJSを組みくむ程度ならこれで問題ないでしょう。サーバーへの負荷もSafariでページのキャッシュが効かなくなるだけです。


ただ、最近のリッチで非同期的なアプリケーションだと話は別だと思います。unloadを付けてしまうとFFSafariDOMキャッシュが効かなくなってしまうからです。最近のリッチなアプリケーションではloadイベントやcontent loadedイベントでAjaxで通信を行いDOMの一部を置き換えることを良くやると思います。こういった場合、画面表示時のリクエストで全てを処理すると重くユーザーへのストレスになるからという理由が付いてくると思います。unloadを付けると、Ajaxで更新した内容がキャッシュされなくなってしまいます。画面表示時のリクエストの処理よりAjaxのリクエストの処理のほうが重いというケースは多いはずなのでサーバーへの負荷が大きくなります。


というのを踏まえると、unloadは戻るボタンで画面を表示したときにイベントを着火したい部分でだけ使ったほうが良さそうです。今回の戻るボタンで戻られたときに二度押し防止をクリアしたいというのは登録・変更画面の話で、Ajaxで画面を更新するのは主にトップや検索画面が多いと思うので十分に対応できるのではないかと思います。


それ以外のケースや戻るボタンで戻ったときのイベント着火とAjax更新を混在させたい場合には、FFならpageshowとloadを使い分ける方法が使えます。但し、ブラウザ間で挙動に違いが出るので注意する必要があります。その上で単純に戻るボタンで画面を表示したときに着火するイベントをクロスブラウザで確保するなら以下のようなコードを追加してあげればいいと思います。要prototype.js


// === ここから ===
Event._observe = Event.observe;
Event.observe = function(element, eventName, handler) {
	if( element == window && eventName == "unload") {
		this._unloadObserved = true;
	}
	if( element == window && eventName == "pageshow" ) {
		if( !Prototype.Browser.Gecko ) {
			eventName = "load"
		}
		if( Prototype.Browser.WebKit && !this._unloadObserved ) {
			Event._observe(window, "unload", function(){});
			this._unloadObserved = true;
		}
	}
	Event._observe(element, eventName, handler);
}
// === ここまで ===

// こんな感じで使う
Event.observe(window, 'pageshow', function(){alert(1);});

IEOperaのときにはloadイベントにバインド、FFのときにはそのままpageshowにバインド、Safariのときにはloadにバインドしunloadに空のコールバックを登録しています。




FFSafariキャッシュを生かしてかつシンプルに今回の問題を解決できれば一番いいんですが、何かいい方法はないもんでしょうか。

AX