WebKit 页面缓存 I – 基础知识

这是两篇关于现代浏览器引擎中一项通常不太受关注的功能的博文中的第一篇:页面缓存。

今天我将谈谈这项功能是什么,为什么它经常不起作用,以及我们有哪些改进计划。

页面缓存概览

有些人可能更熟悉其他浏览器称之为页面缓存的功能。Firefox 称之为“后退-前进缓存”或“bfcache”。Opera 称之为“快速历史导航”。最近我们开始将 WebKit 的实现称为“页面缓存”,以减少与我们的“后退/前进列表”的混淆。

请注意,页面缓存是一项终端用户功能,可让网页导航更加流畅。它不是“HTTP 意义上”的“缓存”。它不是将原始资源存储在本地磁盘上的“磁盘缓存”意义上的“缓存”。它也不是将 WebKit 在内存中保留解码资源以供多个网页共享的传统“内存缓存”意义上的“缓存”。

那么……它究竟是什么呢?

简单来说,页面缓存的作用是,当你离开一个页面时,我们会“暂停”它,当你回来时,我们按下“播放”。

当用户点击链接导航到新页面时,之前的页面通常会被完全丢弃。DOM 被销毁,Javascript 对象被垃圾回收,插件被卸载,解码的图像数据被丢弃,并发生各种其他清理工作。

当发生这种情况,并且用户稍后点击后退按钮时,这对他们来说会很痛苦。WebKit 可能需要重新通过网络下载资源,重新解析主 HTML 文件,重新运行动态设置页面的脚本,重新解码图像数据,重新布局页面,重新滚动到正确位置,并重新绘制屏幕。所有这些工作都需要时间、CPU 使用和电池电量。

理想情况下,之前的页面可以被放入页面缓存中。即使页面不在屏幕上,整个活动页面也会被保留在内存中。这意味着代表你在屏幕上看到的内容以及你与之交互的所有不同部分都被暂停而不是销毁。如果稍后你点击后退按钮,它们就可以被恢复。

为什么这很重要?

当页面缓存起作用时,点击后退按钮几乎是瞬间完成的。

你可以进行搜索,点击一个搜索结果,然后后退并立即看到完全相同的搜索结果页面。你可能正在浏览像 Reddit 或 Digg 这样的聚合网站,并想在同一选项卡中快速查看许多不同的链接。你可能正在浏览图片库,并决定通过交替快速点击“后退”和“前进”来比较两张图片。或者你可能只是点击了错误的链接,想后退纠正你的错误。

任何时候你可能点击后退或前进按钮时,你都无意中希望页面缓存站在你这边。使用页面缓存时,即使用户不知道幕后的魔法,他们也会很高兴。

反之,当页面缓存被绕过时,用户经常会对浏览器和整个 Web 感到沮丧。

为什么它会不起作用?

那么,如果页面缓存如此出色,为什么当你导航到新页面时,WebKit 不总是使用它呢?

这个问题有几个主要答案。

有些页面不值得缓存

首先,有时缓存页面是没有意义的,因为它在完全相同的状态下返回并不值得关注。例如,页面甚至可能还没加载完成。或者页面加载时出现了错误。或者页面可能是个重定向页面,其唯一作用是自动将用户转移到某个新 URL。

在这些情况下,我们对 WebKit 当前的页面缓存行为感到满意。

有些页面很复杂

其次,页面可能不被考虑用于页面缓存,因为它很难弄清楚如何“暂停”它。这发生在执行复杂操作的更复杂的页面上。

例如,插件包含可以做任何它想做的原生代码,因此 WebKit 无法对它们“按下暂停按钮”。另一个例子是具有多个框架的页面,WebKit 历史上就没有缓存它们。

令人沮丧的是,在这些更高级的页面中导航,页面缓存本应带来最大的好处。

有些页面是安全的

HTTPS 站点的服务器管理员通常有特定的安全担忧,对浏览器的行为非常敏感。例如,金融机构在允许其客户使用特定浏览器之前,通常会非常彻底地验证每个浏览器的行为。

通常关注的一个领域是后退/前进行为。这些机构——可以理解地——对用户导航时浏览器留下的数据类型非常挑剔。因此,为了极度谨慎起见,WebKit 从一开始就禁止所有 HTTPS 站点进入其页面缓存。

一个更细粒度的方法可能极大地改善用户体验。

计划改进

显然,我们没有处理一些重要情况,因此有很大的改进空间。

WebKit 的页面缓存最初是在 2002 年,在第一个 Safari 测试版发布之前编写的。它的能力反映了当时 WebKit 的架构以及 2002 年的 Web 格局。

2009 年的 Web 已大不相同,我们需要将页面缓存提升到同等水平。幸运的是,这项工作正在顺利进行。

例如,截至 修订版 48036,一个主要限制已被解决,带有框架的页面现在可以放入页面缓存中。使用最新的 WebKit nightly 构建浏览网页总是“感觉更快”,快得你无法具体指出是哪里,最近你们中的一些人可能已经体验到了这项增强。

但还有很多工作要做。

插件是我们打击名单上的下一个大目标。正如我之前提到的,插件可以运行任何喜欢的原生代码,所以我们无法可靠地对它们按下“暂停”按钮。

早期版本的 WebKit 处理单框架页面以及某些类型的插件。WebKit 会在离开页面时卸载插件,并在用户返回时恢复它。但随着 WebCore 为使其更快、更容易移植而继续进行工作,这项能力丧失了。

Bug #13634 追踪了使这项功能在所有页面上的所有插件上再次工作。

然后是 HTTPS 页面。我们现在完全禁止它们,但更具选择性的方法应该能够惠及用户,同时也能让注重安全的机构满意。

Bug #26777 追踪了允许 HTTPS 页面被缓存,除非其响应头包含“cache-control: no-store”或“cache-control: no-cache”,这已成为一个谨慎的组织保护其内容的标准方式。

如果你还有其他关于改进点的想法,请随时在相应的 Bug 中评论或提交你自己的新 Bug!

卸载处理程序

我还没提到的一点是带有 unload 事件处理程序的页面。

unload 事件的设计是为了让页面在用户关闭页面时做一些清理工作。

浏览器不能在将页面放入页面缓存之前触发 unload 事件,因为页面会认为自己处于终止状态,并可能销毁自身的关键部分。这完全违背了页面缓存的目的。

但如果浏览器在不运行 unload 处理程序的情况下将页面放入页面缓存,那么页面可能在“暂停”和隐藏状态下被浏览器销毁,而那些清理工作——这可能非常重要——就永远不会发生。

由于 unload 事件的目的是允许“页面关闭时进行重要工作”,所有主流浏览器都拒绝将此类页面放入其页面缓存中,这直接对用户体验产生了负面影响。

在未来的博文中,我将更详细地讨论 unload 事件处理程序,并且对许多 Web 开发者来说实际上会布置作业!敬请关注……