博客源地址: 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. }