about:home
启动缓存¶
默认情况下,用户的浏览器会话以一个窗口和一个指向 about:home 的标签页开始。这意味着确保 about:home
尽可能快地加载以提供快速的整体启动体验非常重要。
about:home
(在功能上与 about:newtab
相同)是通过在父进程中计算适当的状态对象并将其传递到内容进程中的 React 库以渲染最终的交互式页面来动态生成的。这在启动序列中存在问题,因为计算初始状态在计算上可能代价高昂,并且需要多次读取磁盘。
about:home
启动缓存旨在解决此开销。它的工作原理是假设在浏览器会话之间,about:home
*通常* 不需要更改。
about:home
启动缓存机制的组件¶
缓存机制有三个主要组件
HTTP 缓存¶
HTTP 缓存通常用于缓存通过网络检索的网页,但似乎也适合存储 about:home
缓存。
在浏览网页时,网络栈通常会查询 HTTP 缓存。但是,在访问 chrome://
或 resource://
URL 时,通常不会查询 HTTP 缓存,因此我们必须手动为 about:home
的情况手动执行此操作。这意味着为 about:home
提供填充和读取 HTTP 缓存的特殊功能。为了避免潜在的安全问题,这要求我们将 about:home
/ about:newtab
隔离到它们自己的特殊内容进程中。“特权 about 内容进程”为此目的而存在,也用于 about:logins
和 about:certificate
。
HTTP 缓存位于父进程中,因此任何读写操作都需要在父进程中启动。幸运的是,HTTP 缓存使用 nsIOutputStream
接受数据并使用 nsIInputStream
提供数据。我们可以通过消息管理器发送 nsIInputStream
,并将 nsIInputStream
转换为 nsIOutputStream
,因此我们拥有所有必要的工具来有效地与“特权 about 内容进程”通信以保存和检索页面数据。
HTTP 缓存的官方文档可以在此处找到。
AboutHomeStartupCache
¶
此单例组件位于 BrowserGlue
内部,以避免在启动期间在父进程中从 omni.ja
文件加载另一个 JSM。
AboutHomeStartupCache
负责向“特权 about 内容进程”提供它需要呈现初始 about:home
文档的 nsIInputStream
。它还负责使用“特权 about 内容进程”发送的更新版本填充 about:home
缓存。
由于访问 HTTP 缓存是异步的,因此存在竞争条件的可能性,缓存可以在请求初始 about:home
之前或之后被访问并可用。为了适应这两种情况,AboutHomeStartupCache
构造 nsIPipe
实例,并在启动一个实例后立即将其发送到“特权 about 内容进程”。
如果 HTTP 缓存条目在进程启动时已可用,并且缓存数据可用,我们将缓存连接到 nsIPipe
以将数据流式传输到“特权 about 内容进程”。
如果 HTTP 缓存尚不可用,我们将保存对这些 nsIPipe
实例的引用,并等待直到缓存条目可用。只有在那时,我们才会将缓存条目连接到 nsIPipe
实例以将数据发送到“特权 about 内容进程”。
AboutNewTabService
¶
AboutNewTabService
用于父进程和内容进程中的 AboutRedirector
,以确定如何处理加载 about:home
和 about:newtab
的尝试。
AboutNewTabService
有不同的版本 - 一个用于父进程(BaseAboutNewTabService
),一个用于内容进程(AboutNewTabChildService
,它继承自 BaseAboutNewTabService
)。
在“特权 about 内容进程”中运行的 AboutRedirector
知道将加载 about:home
的尝试重定向到 AboutNewTabChildService
的 aboutHomeCacheChannel
方法。然后,此方法负责选择是返回缓存文档的 nsIChannel
还是返回 about:home
的动态生成版本。
AboutHomeStartupCacheChild
¶
此单例组件位于“特权 about 内容进程”内部,并在从父进程接收到包含将用于从缓存潜在加载的 nsIInputStream
的消息后立即初始化。
当“特权 about 内容进程”中的 AboutRedirector
注意到已发出对 about:home
的请求时,它会要求 nsIAboutNewTabService
为该文档返回一个新的 nsIChannel
。AboutNewTabChildService
然后检查 AboutHomeStartupCacheChild
是否可以返回任何缓存内容的 nsIChannel
。
如果此时父进程尚未流式传输任何内容,我们将回退到加载动态 about:home
文档。如果缓存尚不存在,或者我们加载速度太慢而无法从磁盘获取,则可能会发生这种情况。随后加载 about:home
的尝试将绕过缓存并改为加载动态文档。即使特权 about 内容进程崩溃并创建了一个新进程,情况也是如此。
AboutHomeStartupCacheChild
还将负责定期生成缓存。定期,AboutNewTabService
将从父进程发送 about:home
的最新状态,然后 AboutHomeStartupCacheChild
将在 ChromeWorker
内使用 ReactDOMServer 生成文档标记。生成后,“特权 about 内容进程”将发送初始页面状态的标记和脚本的 nsIInputStream
实例。BrowserGlue
内部的 AboutHomeStartupCache
单例负责接收这些 nsIInputStream
并将其持久化到 HTTP 缓存中,以便下次启动时使用。
什么是缓存?¶
缓存了两样东西
about:home
的原始 HTML 标记。一小段 JavaScript 代码,通过 React 库“水化”标记,使页面在绘制后变得交互式。
由于 about:home
的 CSP 不允许内联脚本,因此无法将缓存的 JavaScript 代码直接作为内联脚本放入 HTML 标记中。相反,我们从 about:home?jscache
加载一个脚本。这将通过与从缓存中检索 HTML 文档相同的机制进行,但会下载缓存的脚本。
如果 HTML 标记被缓存,那么我们假设脚本也被缓存。我们不能缓存一个而不缓存另一个。如果只有一个缓存存在,或者在请求 about:home
文档时只有一个缓存被发送到“特权的 about 内容进程”,那么我们将回退到加载动态的 about:home
文档。
刷新缓存¶
缓存会定期刷新,方法是让 ActivityStreamMessageChannel
在它向预加载的 about:newtab
发送任何消息时告诉 AboutHomeStartupCache
。一般来说,此类消息是一个很好的提示,表明下一个 about:newtab
的某些视觉内容已更新,并且可能应该刷新缓存。
AboutHomeStartupCache
会对有关此类消息的通知进行去抖动,因为这些通知往往会集中爆发。
使缓存失效¶
随着版本的更新,about:home
的组成或布局可能会发生变化。发生这种情况时,可能需要使用户可能存在的任何预先存在的缓存失效,以避免他们在启动时看到过时的 about:home
。
为此,我们在缓存条目上设置一个版本号,并确保版本号在启动时符合我们的预期。如果版本号与我们的预期不符,则缓存将被丢弃,并且 about:home
文档将被动态渲染。
版本号当前设置为应用程序的构建 ID。这意味着当应用程序更新时,在应用浏览器更新后的第一次重启时,缓存将失效。
处理错误¶
about:home
通常是用户启动浏览器时看到的第一件事。它必须快速且正确地运行至关重要。如果在检索或保存到缓存时发生任何错误,我们应该回退到动态生成文档。
例如,理论上浏览器在保存到缓存的过程中崩溃是可能的。在这种情况下,我们可能保存了一个部分文档或一个部分脚本——这两种情况都是不可接受的。
值得庆幸的是,HTTP 缓存的设计考虑了弹性,因此部分写入的条目会自动丢弃,这使我们能够回退到动态页面生成模式。
作为该弹性的额外冗余,我们还确保每次填充缓存时都创建一个新的 nsICacheEntry,并将版本元数据作为最后一步写入。由于版本元数据是最后写入的,因此我们知道,如果我们在尝试加载缓存时发现它丢失,则表示页面和脚本的写入未完成,并且我们应该回退到动态渲染页面。