最近參與的一個ASP.NET MVC行動網站專案,其中有項操作是由清單頁面按連結跳往編輯網頁,編輯完成後,透過jQuery Mobile內建的回上頁功能回到清單頁面。而清單頁面中透過$(function() { ajaxLoadListData() });的做法,預期每次網頁載入後[jQuery.ready()事件]都會以AJAX方式重新取得最新清單資料,因此回上頁到清單,使用者就可立即看到剛才儲存的變更。

使用IE及Chrome測試功能正常,但進一步使用iPhone/iPad Safari瀏覽時,卻發現編輯完回上頁時不會自動載入更新後的資料,需待重新整理才現身。研究後才理解了一件先前忽略的事實:

部分瀏覽器在回上頁時不會觸發jQuery .ready()/.load()事件

進一步發現,原來這是HTML5世代瀏覽器新增的特性之一--Back-Forward Cache(簡稱bfcache)。傳統瀏覽器在回上頁時,除非是透過指定Cache-Control、Expires等方式強制停用Cache的網頁,否則會直接讀取保存在本機的HTML、CS、JS快取檔案,減少網路往返傳輸,增進瀏覽順暢度。但Cache的層次限於檔案,瀏覽器還是得載入HTML,重新執行JavaScript、產生DOM。而bfcache則更進一步,直接引用在記憶體中先前做好的網頁內容,省去重新產生網頁的時間;既然要使用記憶體中的現成內容,執行頁面的onload事件進行初始化,會弄亂原本因使用者操作而改變的狀態,瀏覽器在回上頁時不觸發onload事件也是合理的。

但程式開發上,還是需要有個事件察覺使用者從其他網頁跳回這個頁面,於是HTML5規格中有兩個新事件onpageshow及onpagehide,會分別在使用者進入及離開網頁時被觸發--即使透過回上頁/到下頁巡覽進出該網頁也會觸發。

我寫了一個網頁來檢測各版本瀏覽器的行為: (線上展示)

<!DOCTYPE html>
 
<html>
<head>
    <title>Page Events</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.js"></script>
    <script>
        function dispLog(msg) {
            var d = new Date();
            $("<li />").text(d.toISOString().substr(14, 9) + " " + msg)
            .appendTo("#dvDisp");
 
        }
        $(window).load(function () {
            dispLog("Load Event");
        }).ready(function () {
            dispLog("Ready Event");
            $("#btnSetColor").click(function () {
                $("a").css("color", "red");
            });
        }).bind("pageshow", function () {
            dispLog("PageShow Event");
        }).bind("pagehide", function () {
            dispLog("PageHide Event");
        });
    </script>
</head>
<body>
<a href="Another.htm">前往其他網頁</a>
<input type="button" id="btnSetColor" value="變色" />
<ul id="dvDisp"></ul>
</body>
</html>

網頁很簡單,透過jQuery掛載了load(), ready(), onpageshow,onpagehide四個事件,事件被觸發時會顯示訊息在網頁上。另外有個超連結可跳至其他網頁,方便測試回上頁跳回的情境,有個按鈕按下後會將超連結變成紅色,藉此在回上頁時觀察超連結是否仍保持紅色,便可判定有無bfcache。

測試步驟為先連上PageEvent.htm,按下【變色】按鈕使超連結變色,再點選【前往其他網頁】跳至Another.htm;接著在Another.htm按【回上頁】鈕回到PageEvent.htm。在不更動瀏覽器預設設定的情況,測試結果如下:

  • IE9
    新載入或回上頁時都會觸發Ready/Load事件,紅色未保留,無bfcache
  • IE10 (Windows 8 Release Preview)
    新載入或回上頁時都會觸發Ready/Load事件,紅色未保留,無bfcache
  • Chrome 21.0.1180.6
    新載入或回上頁時都會觸發Ready/Load/PageShow事件,紅色未保留,無bfcache
  • Firefox 15.0
    新載入時觸發Ready/Load/PageShow,點【前往其他網頁】時觸發PageHide,【回上頁】時觸發PageShow,紅色被保留,有bfcache
  • Safari 5.1.5
    新載入時觸發Ready/Load/PageShow,點【前往其他網頁】時觸發PageHide,【回上頁】時觸發PageShow,紅色被保留,有bfcache
  • Safari on iPad (iOS 5.1.1)
    新載入時觸發Ready/Load/PageShow,點【前往其他網頁】時觸發PageHide,【回上頁】時觸發PageShow,紅色被保留,有bfcache
  • Opera 12.00
    新載入會觸發Ready/Load事件,回上頁時不觸發任何事件且紅色被保留,有bfcache但沒有onpageshow事件

歸納結果,Firefox與Safari已實做bfcache,回上頁時不會觸發load(), ready(),只會觸發onpageshow,而Chrome雖然支援onpageshow,但回上頁時跟IE一樣都會觸發load()、ready()。Opera最麻煩,回上頁時會啟用bfcache,卻不提供onpageshow事件。

回到我遇到的案例上,要怎麼克服bfcache模式下.ready()在回上頁時不會執行的問題呢?

最初的想法是新增一個$.pageshow()方法,若瀏覽器支援,將程式掛在onpageshow事件,否則改用傳統.ready()觸發,如下例:

        $.pageshow = function (fn) {
            if (typeof window.onpageshow == "undefined")
                $(document).ready(fn);
            else
                $(window).bind("pageshow", fn);
        };
        $.pageshow(function () {
            alert("Page Show");
            alert(typeof window.onpageshow == "undefined")
        });

不過很遺憾,這個做法只在Firefox、Safaer上策效,對回上頁時不呼叫.ready()也不支援onpageshow的Opera沒用。

最後,MDC文件的一段說明帶給我靈感 -- Firefox在某些條件下不會啟用bfcache:

There are instances in which Firefox doesn’t cache pages. Below are some common programmatic reasons that a page is not cached:

  • the page uses an unload or beforeunload handler;
  • the page sets "cache-control: no-store".
  • the site is HTTPS and page sets at least one of:
    • "Cache-Control: no-cache"
    • "Pragma: no-cache"
    • with "Expires: 0" or "Expires" with a date value in the past relative to the value of the "Date" header (unless "Cache-Control: max-age=" is also specified);
  • the page is not completely loaded when the user navigates away from it or has pending network requests for other reasons (e.g. XMLHttpRequest));
  • the page has running IndexedDB transactions;
  • the top-level page contains frames (e.g. <iframe> ) that are not cacheable for any of the reasons listed here;
  • the page is in a frame and the user loads a new page within that frame (in this case, when the user navigates away from the page, the content that was last loaded into the frames is what is cached).

猜想若Firefox如此,Safari與Chrome應該也會依循類似的原則。於是我找到一個出奇簡單,且Firefox、Safari、Opera都適用的做法,只要在網頁中多加一行jQuery程式: (線上展示)

$(window).unload(function () { });

經實地測試,網頁一旦掛上任何onunload事件,Firefox/Safari/Opera等瀏覽器便會判定該網頁不適用bfcache,回歸傳統Cache模式,就能避開回上頁不觸發.load()或.ready()事件的困擾囉~


Comments

# by Pinky

我遇到的問題是:我連結到下一頁時,有些被隱藏的按鈕會出現,有的設計清除的按鈕也沒辦法清除。這個是什麼原因呢?

# by Eric3917

Pinky 試試在連結中,使用 rel="external" <a href="@Url.Content("~/")" data-icon="home" rel="external" data-iconpos="notext" data-direction="reverse" class="ui-btn-left jqm-home">Home</a> 我也是初學者,不曉得能不能幫到你.....

# by Paul

Hello, 有照所說加入 <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.js "></script> <script>$(window).unload(function () { });</script> 測試之後目前只有Safari瀏覽器回到前一頁仍不行,其他如Firefox測試結果都可以!? 有解決辦法嗎?

Post a comment