博客源地址: https://github.com/LeuisKen/l...
相關討論還請到源 issue 下。
往返緩存(Back/Forward cache
,下文中簡稱bfcache
)是瀏覽器爲了在用戶頁面間執行前進後退操做時擁有更加流暢體驗的一種策略。該策略具體表現爲,當用戶前往新頁面時,將當前頁面的瀏覽器DOM狀態保存到bfcache
中;當用戶點擊後退按鈕的時候,將頁面直接從bfcache
中加載,節省了網絡請求的時間。javascript
可是bfcache
的引入,致使了不少問題。下面,舉一個咱們遇到的場景:html
頁面A是一個任務列表,用戶從A頁面選擇了「任務1:看新聞」,點擊「去完成」跳轉到B頁面。當用戶進入B頁面後,任務完成。此時用戶點擊回退按鈕,會回退到A頁面。此時的A頁面「任務1:看新聞」的按鈕,應該須要標記爲「已完成」,因爲bfcache
的存在,當存入bfcache
時,「任務1」的按鈕是「去完成」,因此此時回來,按鈕也是「去完成」,而不會標記爲「已完成」。java
既然bug產生了,咱們該如何去解決它?不少文章都會提到unload
事件,可是咱們實際進行了測試發現並很差用。因而,爲了解決問題,咱們的bfcache
探祕之旅開始了。git
在檢索page cache in chromium
的時候,咱們發現了這個issue:https://bugs.chromium.org/p/c... 。裏面提到 chromium(chrome的開源版本)在好久之前就已經將PageCache
(即bfcache
)這部分代碼移除了。也就是說如今的chrome應該是沒有這個東西的。能夠肯定的是,chrome之前的版本中,bfcache
的實現是從webkit
中拿來的,加上咱們項目目前面向的用戶主體就是 iOS + Android,iOS下是基於Webkit,Android基於chrome(且這部分功能也是源於webkit)。所以追溯這個問題,咱們只要專一於研究webkit
裏bfcache
的邏輯便可。github
一樣經過上文中描述的commit記錄,咱們也很快定位到了PageCache
相關邏輯在Webkit中的位置:webkit/Source/WebCore/history/PageCache.cpp。web
該文件中包含的兩個方法引發了咱們的注意:canCachePage
和canCacheFrame
。這裏的Page
便是咱們一般理解中的「網頁」,而咱們也知道網頁中能夠嵌套<frame>
、<iframe>
等標籤來置入其餘頁面。因此,Page
和Frame
的概念就很明確了。而在canCachePage
方法中,是調用了canCacheFrame
的,以下:chrome
// 給定 page 的 mainFrame 被傳入了 canCacheFrame bool isCacheable = canCacheFrame(page.mainFrame(), diagnosticLoggingClient, indentLevel + 1);
源代碼連接:webkit/Source/WebCore/history/PageCache.cpp瀏覽器
所以,重頭戲就在canCacheFrame
了。緩存
canCacheFrame
方法返回的是一個布爾值,也就是其中變量isCacheable
的值。那麼,isCacheable
的判斷策略是什麼?更重要的,這裏面的策略,有哪些是咱們可以利用到的。網絡
注意到這裏的代碼:
Vector<ActiveDOMObject*> unsuspendableObjects; if (frame.document() && !frame.document()->canSuspendActiveDOMObjectsForDocumentSuspension(&unsuspendableObjects)) { // do something... isCacheable = false; }
源代碼連接:webkit/Source/WebCore/history/PageCache.cpp
很明顯canSuspendActiveDOMObjectsForDocumentSuspension
是一個很是重要的方法,該方法中的重要信息見以下代碼:
bool ScriptExecutionContext::canSuspendActiveDOMObjectsForDocumentSuspension(Vector<ActiveDOMObject*>* unsuspendableObjects) { // something here... bool canSuspend = true; // something here... // We assume that m_activeDOMObjects will not change during iteration: canSuspend // functions should not add new active DOM objects, nor execute arbitrary JavaScript. // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code // canSuspend functions so it will not happen! ScriptDisallowedScope::InMainThread scriptDisallowedScope; for (auto* activeDOMObject : m_activeDOMObjects) { if (!activeDOMObject->canSuspendForDocumentSuspension()) { canSuspend = false; // someting here } } // something here... return canSuspend; }
源代碼連接:webkit/Source/WebCore/dom/ScriptExecutionContext.cpp
在這一部分,能夠看到他調用每個 ActiveDOMObject
的 canSuspendForDocumentSuspension
方法,只要有一個返回了false
,canSuspend
就會是false
(Suspend這個單詞是掛起的意思,也就是說存入bfcache
對於瀏覽器來講就是把頁面上的frame
掛起了)。
接下來,關鍵的ActiveDOMObject
定義在:webkit/Source/WebCore/dom/ActiveDOMObject.h ,該文件這部分註釋,已經告訴了咱們最想要的信息。
The canSuspendForDocumentSuspension() function is used by the caller if there is a choice between suspending and stopping. For example, a page won't be suspended and placed in the back/forward cache if it contains any objects that cannot be suspended.
canSuspendForDocumentSuspension
用於幫助函數調用者在「掛起(suspending)」與「中止」間作出選擇。例如,一個頁面若是包含任何不能被掛起的對象的話,那麼它就不會被掛起並放到PageCache
中。
接下來,咱們要找的就是,哪些對象是不能被掛起的?在WebCore
目錄下,搜索包含canSuspendForDocumentSuspension() const
關鍵字的.cpp
文件,能找到48個結果。大概看了一下,最好用的objects that cannot be suspended
應該就是Worker
對象了,見代碼:
bool Worker::canSuspendForDocumentSuspension() const { // 這裏實際上是有一個 FIXME 的,看來 webkit 團隊也以爲直接 return false 有點簡單粗暴。 // 不過仍是等哪天他們真的修了再說吧 // FIXME: It is not currently possible to suspend a worker, so pages with workers can not go into page cache. return false; }
源代碼連接:webkit/Source/WebCore/workers/Worker.cpp
業務上添加以下代碼:
// disable bfcache try { var bfWorker = new Worker(window.URL.createObjectURL(new Blob(['1']))); window.addEventListener('unload', function () { // 這裏綁個事件,構造一個閉包,以避免 worker 被垃圾回收致使邏輯失效 bfWorker.terminate(); }); } catch (e) { // if you want to do something here. }