HTML 性能优化 -Prerender2.0 机制解读
一、前言
日志是一种按照时间顺序 Prerender(预渲染),是用户在打开网页之前提前将网页中资源加载并执行解析渲染工作,使网页真正被打开时以最快的速度呈现出来,理想状态下打开已预渲染的页面几乎是即时展现的。
Chromium 在过去已经支持过 Prerender(Prerender1.0,使用时间在 2011~2018),其 API 使用方法如下,针对单个链接做预渲染,由于此能力的使用需要消耗较高的设备资源,实用性不太大,导致没有得到广泛应用,故逐渐被弃用。而此 API 后续应用在 NoState Prefetch 中,NSP 机制只对目标网页中的关键资源进行预加载,并不会执行后续的渲染工作,也不会执行 JS,即通过减少资源加载耗时以提高页面性能,但做不到预渲染及时展现页面的效果。
Chromium 从 M94 开始将 Prerender 机制(Prerender2.0)重新引入。Prerender2.0 是全新的预渲染技术,此版本着重于提高预渲染页面的准确性并减少执行预渲染所需的设备资源量,缩短页面加载时间、改善用户体验并降低设备功耗。Prerender2.0 比 Prerender1.0 更加灵活、智能和轻便,可以更有效地提高页面加载速度。
丨 1.1 多页面架构(MPArch)
MPArch(Multiple Page Architecture)多页面架构,此架构中采用了单 WebContents 模型,实现一个 WebContent 中管理多个 page(可见/不可见),WebContents 和 Page 之间不是一一对应关系。单 WebContents 模型非常适用 BF Cache 和 Prerender 等需要在同一选项卡中支持多个页面的功能。
Prerender2.0 建立在 MPArch 之上,即使用了单 WebContents 多 page 模型。而 Prerender1.0 时还没有支持此能力,而是每预渲染一个页面均创建新的 WebContent,每个 WebContent 仅对应一个预渲染页面,即 Prerender1.0 采用的是多 WebContents 模型。
图片来源于 chromium
可见 Prerender 机制在使用单 WebContents 模型相对于多 WebContents 模型在内存的使用上有着明显的优势,这应该是 Prerender2.0 比 Prerender1.0 轻便之处。
二、Prerender2.0 使用及效果
丨 2.1 能力使用
使用 Prerender2.0 机制需要使用新增的 Speculation Rules API(https://github.com/WICG/nav-speculation/blob/main/triggers.md#speculation-rules),此 API 可同时支持触发 Prefetch、NSP、Prerender 机制。
查看浏览器是否支持此 API。
API 使用示例如下:
API 中关键
未继续使用<link rel=prerender>的原因如下:
当前已经由 NoState Prefetch 机制使用,若强制改为 Prerender 机制,将不符合已使用此 API 站点的预期(网站并不想对使用此 API 的 URL 进行预渲染),且消耗资源大幅度增加,导致网站崩溃的风险。
规则可塑性较差,部分要求无法通过此规则限制,如 Speculation Rules API 中的 requires 参数含义难以在此规则中体现,且无法同时对多个站点预处理。
由于 Speculation Rules API 的灵活性较好,Chromium 预言此 API 将逐步替代规则,成为开发者使用较多的 API。
使用了以上 API 对页面发起 Prerender 后,如何知道用户打开了已 Prerender 的页面,获取方式如下:
当 activationStart 大于 0 时便表示 prerender 页面复用成功。
监听'prerenderingchange'事件。
丨 2.2 使用效果
使用 Chromium 提供的视频示例,不使用 Prerender 能力可以明显的看到页面中内容从 0 到 1 展示的过程,而使用 Prerender 后页面几乎是即时展示的。效果对比非常明显。
三、实现流程(基于 M97)
丨 3.1 解析 Speculation Rules API
解析到 HTML 中的 Speculation Rules API 后的流程如下:
流程如下:
解析 HTML 中包含 ScriptType 为 kSpeculationRules 的 JS 内容后,开始对原数据进行 JSON 解析,匹配出有效内容,包括 SpeculationAction(kPrefetch、kPrefetchWithSubresources、kPrerender)、SpeculationRule(urls、requires_anonymous_client_ip_when_cross_origin);
若 SpeculationAction 为 kPrerender 时,开始判断是否符合发起 prerender 的条件(判断条件如下),若符合则开始执行 StartPrerendering;
PrerenderHost 是在 Browser 侧管理着单个预渲染页面的类,其对应着 PrerenderHost::PageHolder,在 PageHolder 中创建预渲染的 FrameTree,而 PrerenderHostRegistry 管理所有的 PrerenderHost。可见一个 WebContents 对应多个 FrameTree。
丨 3.2 预渲染流程
每个预渲染页面均以 PrerenderHost::StartPrerendering()开始,然后执行到 NavigationRequset 中 BeginNavigation 方法,后面的逻辑均和正常页面加载流程一致。
在 Browser 线程中判断跳转页面是否需要网络层处理,若需要网络层处理,则走网络请求逻辑,实现资源请求并返回主文档首包(也有跳转不需要网络层处理,比如同 Document 跳转)。无论是否走网络层逻辑,最终都会走到 CommitNavigation 方法,CommitNavigation 就是 Browser 线程向 Render 线程提交跳转的入口。
CommitNavigation 至 Render 线程后将创建 Document 和 DocumentLoader 等对象,而 DocumentLoader 是用于处理主文档内容加载完成后回调通知。待收到主文档数据后开始解析、渲染流程。
预渲染完成后的页面将以 FrameTree 的结构存在,FrameTree 由 PrerenderHost 持有。每个预渲染页面对应一个 PrerenderHost 对象,所有的 PrerenderHost 存储在 Map 中以 frame_tree_node_id 作为 key,Map 以成员变量的形式保存在 PrerenderHostRegistry 中。此时预渲染的 Page 对于用户是不可见的。
丨 3.3 页面加载流程
点击链接进入已预渲染的页面,依旧是执行到 NavigationRequset 中 BeginNavigation 方法。
执行 BeginNavigation 方法时会到 PrerenderHostRegistry 类中存放 PrerenderHost 的 Map 中查找是否有符合要求的 PrerenderHost 对象,匹配规则是查看当前 navigationRequest 中的 URL 与 PrerenderHost 中的 URL 是否一致,一致则匹配成功,同时返回 frame_tree_node_id。找到符合条件的 frame_tree_node_id 后开始操作将 prerenderPage 的状态切换为 active。
后续依旧需要至网络库中请求主文档首包,用于查看页面是否可用,并对请求头进行验证,确保可复用的预渲染页面与需要加载的页面的 RequestHeaders 和 ResponseHeaders 内容一致。
以上验证均通过后,便可执行 CommitNavigation,针对 BFCache 和 Prerender 页面都是先找到 stored_page,并将 stored_page 的状态切换为 active,以展示给用户。
找出符合条件的 stored_page,将此页面展现出来,并将上个页面隐藏存储至 BFCache 中。如下图所示。
四、应用场景
由于预渲染页面对于设备的资源消耗较大,是个需要谨慎使用的机制能力,若肆无忌惮的使用此能力,不仅无法提高用户的浏览体验,还会耗尽用户的宝贵资源,可能带来卡顿、崩溃等负向影响。Chromium 推荐用法中表示当用户加载某些页面的可能性很高时才推荐使用 Prerender 机制,此时可将 Prerender 机制带来的正向收益最大化。
丨 4.1 Chromium
Chromium 将预测技术与 Prerender2.0 机制结合使用,利用预测技术提高预渲染页面的准确性。通过 chrome://predictors 页面可看出 Chromium 对用户行为的预测,如下图。
绿色表示有足够信心触发预渲染。以上示例可看出,当用户在输入框中输入"b"/"ba"/"bai"字符后 chromium 将对"https://www.baidu.com/"站点发起预渲染,因为经过多次统计有足够的信心认为用户将进入目标站点。
Chromium 会根据用户输入和选择不断的更新预测结果。
当置信度>50%(图中黄色),Chromium 会发起预链接
当置信度>80%(图中绿色),Chromium 会发起预渲染
同时 Chromium 会将预测的结果呈现在 Sug 选项栏中,辅助用户进入目标站点。
进入目标站点后通过"performance.getEntriesByType('navigation')[0].activationStart"查看页面是否通过预渲染呈现的,根据 activationStart 大于 0 可确定此页面已预渲染成功。
丨 4.2 开发者
对于搜索类浏览器可参考 Chromium 思路,定制合适的预测器并结合 Prerender2.0 机制使用,用于提升搜索结果页的上屏性能,优化用户体验。
对于网站开发者而言,可以结合自身站点情况使用预渲染技术
对于明确站点中的某些页面是大部分用户会浏览的,便可对这些站点进行预渲染,此场景可通过静态方式将预渲染 API 写入 HTML 中。
也可针对不同用户使用不同策略,根据用户行为通过 JS 动态插入需要预渲染 API。
网站衡量使用预渲染能力是否得当,可增加指标查看预渲染效果。主要看两个指标,一个是发起预渲染页面数量,另一个是用户真实加载已预渲染的页面数量(上文中提及的获取方式),两个指标的对比可估算出命中率。网站可调整预渲染策略以保持高命中率。
五、总结
本文主要介绍了 Prerender 机制的发展过程和现状,并对 Prerender2.0 技术进行介绍。其中重点介绍了 Prerender2.0 技术的使用、实现流程和应用场景。百度 APP 是基于 Chromium M97,此版本中 Prerender2.0 机制还在初级阶段,目前为止 Chromium 还在对 Prerender2.0 技术进行完善,故文档中内容可能和最新方案有些许差异。后期我们将持续关注 Prerender2.0 技术发展,发掘其应用于业务的可行场景,持续优化页面上屏性能,提升用户体验。
——END——
参考资料:
[1] Prerender 开发者文档:
https://developer.chrome.com/blog/prerender-pages/
[2] Prerender 官方文档:
https://docs.google.com/document/d/1P2VKCLpmnNm_cRAjUeE-bqLL0bslL_zKqiNeCzNom_w/edit
[3] 多页面架构文档:
推荐阅读:
评论