渲染进程是浏览器的一个核心部分。在渲染页面的同时,渲染进程还负责很多其他工作,这篇就简单讲讲 资源加载机制 这块。

Chromium 浏览器的渲染部分可以简单分为三层:第一层 Blink 引擎,负责渲染页面;第二层渲染进程,每一个都包含一个 Blink 实例;最底层是浏览器主进程,负责管理和协调系统资源和提供 API 供上层调用。

渲染进程要开启渲染一个页面,首先需要一个 Url,比如 github.com,浏览器会根据这个 Url 向服务器请求资源。假设每一个 tab 都是一个单独的渲染进程,具体的请求过程是:

  1. 渲染进程实现了一个 WebURLLoaderImpl 类,当渲染进程解析到资源请求时,这个类使用 ResourceDispatcher 创建一个唯一ID,并通过 IPC 将请求转发到浏览器进程
  2. 浏览器进程有一个 RenderProcessHost 类,负责接受各个渲染进程的信号,然后将请求转发到 ResourceDispatcherHostResourceDispatcherHost 发送实际的请求,并接收、处理响应,然后将结果发送回渲染进程
  3. 渲染进程根据第一步生成的唯一ID处理响应,完成一次请求。

部分解释

WebURLLoaderImpl 类实现了 WebURLLoader 接口,WebURLLoaderBlink 提供的一个定义文件,这个没什么好说的。

ResourceDispatcher 是渲染进程里的一个核心对象,使用了单例模式,负责管理进程内请求和与浏览器进程进行交互,有意思的是,这个类的实现里还有记录时间的代码(代理、dns 解析、ssl 握手等),可能还会跟开发者工具或者 performance 有关。ResourceDispatcherHost 是浏览器进程的一个对象,负责统一管理请求的发送、接受与处理,可能也使用了单例模式。

渲染进程的 RenderProcess 与浏览器进程的 RenderProcessHost 类是一一对应的,是 IPC 部分的关键。

所以可以看到请求并不由渲染进程发送,而是渲染进程通过 IPC 向浏览器进程发送一个信号,浏览器进程再向外部发送请求,至于如何发送、处理,完全是浏览器自己实现的,对渲染进程来说是个黑盒,这么做可能有很多考虑,比如提升兼容性,Blink 只负责提供一个接口,毕竟宿主环境总归需要实现这部分。

资源加载优化

上面说到所有渲染进程的请求都由浏览器进程统一管理,这样做有什么好处呢?

HTTP 规范里有一个状态码 429,含义是 Too Many Request,表示一个用户代理不应该在一段时间内向同一个 Host 发起太多请求,如果交由渲染进程主动管理请求,由于 Site Isolation,渲染进程是独立的,当同时打开 10 个 github.com 时,浏览器会同时向 github.com 发起 10 个请求,再加上附加的资源请求,那么很有可能会导致超出限制而被拒绝,而浏览器统一管理,就可以在浏览器层避免这个限制。

说到这个就还要说一个前端面试经常问到的一个资源加载优化,如果开发中有注意过的话,就会发现,主流站点都采用了资源分离的模式,比如 js、css、图片资源与主站点分离。比如打开 github.com,我能看到的主机就有:

  • github.com
  • github.githubassets.com
  • avatars[1-n].githubusercontent.com
  • spotlights-feed.github.com
  • api.github.com

这样就可以规避浏览器层对单个 Host 采取的限制,从而加快页面打开速度。

前面说到渲染进程通过 IPC 与浏览器进程通信进行请求,如果不考虑其它因素,看似多了一步,降低了请求效率,但如果请求由渲染进程发送,cookie 部分就需要在每次请求时,渲染进程从浏览器进程存取,反而效率更低。

Cookie 管理的另一个关键地方在于所有 tab 下必须保持一致,比如我在两个 tab 下打开了 github.com,其中一个登录后,另一个也需要变成登录状态。在请求交给浏览器进程后,这种需求做起来就非常简单了。

DOM 提供了一个 document.cookie 方法,可以访问页面的 cookie,这个方法其实是一个阻塞性方法,会阻塞渲染线程。至于考虑和实现,暂时没有查到。

参考