Webkit 内核初探

  • 做者: 阿吉
  • 校对&整理: lucifer

当下浏览器内核主要有 Webkit、Blink 等。本文分析注意是自 2001 年 Webkit 从 KHTML 分离出去并开源后,各大浏览器厂商魔改 Webkit 的时期,这些魔改的内核最终以 Chromium 受众最多而脱颖而出。本文就以 Chromium 浏览器架构为基础,逐层探入进行剖析。前端

引子

这里以一个面试中最多见的题目从 URL 输入到浏览器渲染页面发生了什么?开始。面试

这个很常见的题目,涉及的知识很是普遍。你们可先从浏览器监听用户输入开始,浏览器解析 url 的部分,分析出应用层协议 是 HTTPS 仍是 HTTP 来决定是否通过会话层 TLS 套接字,而后到 DNS 解析获取 IP,创建 TCP 套接字池 以及 TCP 三次握手,数据封装切片的过程,浏览器发送请求获取对应数据,如何解析 HTML,四次挥手等等等等。 这个回答理论上能够很是详细,远比我提到的多得多。算法

本文试图从浏览器获取资源开始探究 Webkit。如浏览器如何获取资源,获取资源时 Webkit 调用了哪些资源加载器(不一样的资源使用不一样的加载器),Webkit 如何解析 HTML 等入手。想要从前端工程师的角度弄明白这些问题,能够先暂时抛开 C++源码,从浏览器架构出发,作到大体了解。以后学有余力的同窗再去深刻研究各个底层细节。浏览器

本文的路线按部就班,从 Chromium 浏览器架构出发,到 Webkit 资源下载时对应的浏览器获取对应资源如 HTML、CSS 等,再到 HTML 的解析,再到 JS 阻塞 DOM 解析而产生的 Webkit 优化 引出浏览器多线程架构,继而出于安全性和稳定性的考虑引出浏览器多进程架构。缓存

一. Chromium 浏览器架构

Chromium浏览器架构

(Chromium 浏览器架构)安全

咱们一般说的浏览器内核,指的是渲染引擎。网络

WebCore 基本是共享的,只是在不一样浏览器中使用 Webkit 的实现方式不一样。它包含解析 HTML 生成 DOM、解析 CSS、渲染布局、资源加载器等等,用于加载和渲染网页。前端工程师

JS 解析可使用 JSCore 或 V8 等 JS 引擎。咱们熟悉的谷歌浏览器就是使用 V8。好比比较常见的有内置属性 [[scope]] 就仅在 V8 内部使用,用于对象根据其向上索引自身不存在的属性。而对外暴露的 API,如 __proto__ 也可用于更改原型链。实际上 __proto__ 并非 ES 标准提供的,它是浏览器提供的(浏览器能够不提供,所以若是有浏览器不提供的话这也并非 b ug)。多线程

Webkit Ports 是不共享的部分。它包含视频、音频、图片解码、硬件加速、网络栈等等,经常使用于移植。架构

同时,浏览器是多进程多线程架构,稍后也会细入。

在解析 HTML 文档以前,须要先获取资源,那么资源的获取在 Webkit 中应该如何进行呢?

二.Webkit 资源加载

HTTP 是超文本传输协议,超文本的含义即包含了文本、图片、视频、音频等等。其对应的不一样文件格式,在 Webkit 中 须要调用不一样的资源加载器,即 特定资源加载器。

而浏览器有四级缓存,Disk Cache 是咱们最常说的经过 HTTP Header 去控制的,好比强缓存、协商缓存。同时也有浏览器自带的启发式缓存。而 Webkit 对应使用的加载器是资源缓存机制的资源加载器 CachedResoureLoader 类。

若是每一个资源加载器都实现本身的加载方法,则浪费内存空间,同时违背了单一职责的原则,所以能够抽象出一个共享类,即通用资源加载器 ResoureLoader 类。 Webkit 资源加载是使用了三类加载器:特定资源加载器,资源缓存机制的资源加载器 CachedResoureLoader 和 通用资源加载器 ResoureLoader

既然说到了缓存,那不妨多谈一点。

资源既然缓存了,那是如何命中的呢?答案是根据资源惟一性的特征 URL。资源存储是有必定有效期的,而这个有效期在 Webkit 中采用的就是 LRU 算法。那何时更新缓存呢?答案是不一样的缓存类型对应不一样的缓存策略。咱们知道缓存多数是利用 HTTP 协议减小网络负载的,即强缓存、协商缓存。可是若是关闭缓存了呢? 好比 HTTP/1.0 Pragma:no-cache 和 HTTP/1.1 Cache-Control: no-cache。此时,对于 Webkit 来讲,它会清空全局惟一的对象 MemoryCache 中的全部资源。

资源加载器内容先到这里。浏览器架构是多进程多线程的,其实多线程能够直接体如今资源加载的过程当中,在 JS 阻塞 DOM 解析中发挥做用,下面咱们详细讲解一下。

三.浏览器架构

浏览器是多进程多线程架构。

对于浏览器来说,从网络获取资源是很是耗时的。从资源是否阻塞渲染的角度,对浏览器而言资源仅分为两类:阻塞渲染如 JS 和 不阻塞渲染如图片。

咱们都知道 JS 阻塞 DOM 解析,反之亦然。然而对于阻塞,Webkit 不会傻傻等着浪费时间,它在内部作了优化:启动另外一个线程,去遍历后续的 HTML 文档,收集须要的资源 URL,并发下载资源。最多见的好比<script async><script defer>,其 JS 资源下载和 DOM 解析是并行的,JS 下载并不会阻塞 DOM 解析。这就是浏览器的多线程架构。

JS async defer

总结一下,多线程的好处就是,高响应度,UI 线程不会被耗时操做阻塞而彻底阻塞浏览器进程。

关于多线程,有 GUI 渲染线程,负责解析 HTML、CSS、渲染和布局等等,调用 WebCore 的功能。JS 引擎线程,负责解析 JS 脚本,调用 JSCore 或 V8。咱们都知道 JS 阻塞 DOM 解析,这是由于 Webkit 设计上 GUI 渲染线程和 JS 引擎线程的执行是互斥的。若是两者不互斥,假设 JS 引擎线程清空了 DOM 树,在 JS 引擎线程清空的过程当中 GUI 渲染线程仍继续渲染页面,这就形成了资源的浪费。更严重的,还可能发生各类多线程问题,好比脏数据等。

另外咱们常说的 JS 操做 DOM 消耗性能,其实有一部分指的就是 JS 引擎线程和 GUI 渲染线程之间的通讯,线程之间比较消耗性能。

除此以外还有别的线程,好比事件触发线程,负责当一个事件被触发时将其添加到待处理队列的队尾。

值得注意的是,多启动的线程,仅仅是收集后续资源的 URL,线程并不会去下载资源。该线程会把下载的资源 URL 送给 Browser 进程,Browser 进程调用网络栈去下载对应的资源,返回资源交由 Renderer 进程进行渲染,Renderer 进程将最终的渲染结果返回 Browser 进程,由 Browser 进程进行最终呈现。这就是浏览器的多进程架构。

多进程加载资源的过程是如何的呢?咱们上面说到的 HTML 文档在浏览器的渲染,是交由 Renderer 进程的。Renderer 进程在解析 HTML 的过程当中,已搜集到全部的资源 URL,如 link CSS、Img src 等等。但出于安全性和效率的角度考虑,Renderer 进程并不能直接下载资源,它须要经过进程间通讯将 URL 交由 Browser 进程,Browser 进程有权限调用 URLRequest 类从网络或本地获取资源。

近年来,对于有的浏览器,网络栈由 Browser 进程中的一个模块,变成一个单独的进程。

同时,多进程的好处远远不止安全这一项,即沙箱模型。还有单个网页或者第三方插件的崩溃,并不会影响到浏览器的稳定性。资源加载完成,对于 Webkit 而言,它须要调用 WebCore 对资源进行解析。那么咱们先看下 HTML 的解析。以后咱们再谈一下,对于浏览器来讲,它拥有哪些进程呢?

四.HTML 解析

对于 Webkit 而言,将解析半结构化的 HTML 生成 DOM,可是对于 CSS 样式表的解析,严格意义 CSSOM 并非树,而是一个映射表集合。咱们能够经过 document.styleSheets 来获取样式表的有序集合来操做 CSSOM。对于 CSS,Webkit 也有对应的优化策略---ComputedStyle。ComputedStyle 就是若是多个元素的样式能够不通过计算就确认相等,那么就仅会进行一次样式计算,其他元素仅共享该 ComputedStyle。

共享 ComputedStyle 原则:

(1) TagName 和 Class 属性必须同样。

(2)不能有 Style。

(3)不能有 sibling selector。

(4)mappedAttribute 必须相等。

对于 DOM 和 CSSOM,你们说的合成的 render 树在 Webkit 而言是不存在的,在 Webkit 内部生成的是 RenderObject,在它的节点在建立的同时,会根据层次结构建立 RenderLayer 树,同时构建一个虚拟的绘图上下文,生成可视化图像。这四个内部表示结构会一直存在,直到网页被销毁。

RenderLayer 在浏览器控制台中 Layers 功能卡中能够看到当前网页的图层分层。图层涉及到显式和隐式,如 scale()、z-index 等。层的优势之一是只重绘当前层而不影响其余层,这也是 Webkit 作的优化之一。同时 V8 引擎也作了一些优化,好比说隐藏类、优化回退、内联缓存等等。

五.浏览器进程

浏览器进程包括 Browser 进程、Renderer 进程、GPU 进程、NPAPI 插件进程、Pepper 进程等等。下面让咱们详细看看各大进程。

  • Browser 进程:浏览器的主进程,有且仅有一个,它是进程祖先。负责页面的显示和管理、其余进程的管理。
  • Renderer 进程:网页的渲染进程,可有多个,和网页数量不必定是一一对应关系。它负责网页的渲染,Webkit 的渲染工做就是在这里完成的。
  • GPU 进程:最多一个。仅当 GPU 硬件加速被打开时建立。它负责 3D 绘制。
  • NPAPI 进程:为 NPAPI 类型的插件而建立。其建立的基本原则是每种类型的插件都只会被建立一次,仅当使用时被建立,可被共享。
  • Pepper 进程:同 NPAPI 进程,不一样的是 它为 Pepper 插件而建立的进程。
注意:若是页面有 iframe,它会造成影子节点,会运行在单独的进程中。

咱们仅仅在围绕 Chromium 浏览器来讲上述进程,由于在移动端,毕竟手机厂商不少,各大厂商对浏览器进程的支持也不同。这其实也是咱们最多见的 H5 兼容性问题,好比 IOS margin-bottom 失效等等。再好比 H5 使用 video 标签作直播,也在不一样手机之间会存在问题。有的手机直播页面跳出主进程再回来,就会黑屏。

以 Chromium 的 Android 版为例子,不存在 GPU 进程,GPU 进程变成了 Browser 进程的线程。同时,Renderer 进程演变为服务进程,同时被限制了最大数量。

为了方便起见,咱们以 PC 端谷歌浏览器为例子,打开任务管理器,查看当前浏览器中打开的网页及其进程。

打开浏览器任务管理器

当前我打开了 14 个网页,不太好容易观察,但能够从下图中看到,只有一个 Browser 进程,即第 1 行。可是打开的网页对应的 Renderer 进程,并不必定是一个网页对应一个 Renderer 进程,这跟 Renderer 进程配置有关系。好比你看第 六、7 行是每一个标签页建立独立 Renderer 进程,可是蓝色光标所在的第 八、九、10 行是共用一个 Renderer 进程,这属于为每一个页面建立一个 Renderer 进程。由于第 九、10 行打开的页面是从第 8 行点击连接打开的。第 2 行的 GPU 进程也清晰可见,以及第 三、四、5 行的插件进程。

浏览器进程

关于,Renderer 进程和打开的网页并不必定是一一对应的关系,下面咱们详细说一下 Renderer 进程。当前只有四种多进程策略:

  1. Process-per-site-instance: 为每一个页面单首创建一个进程,从某个网站打开的一系列网站都属于同一个进程。这是浏览器的默认项。上图中的蓝色光标就是这种状况。
  2. Process-per-site:同一个域的页面共享一个进程。
  3. Process-per-tab:为每一个标签页建立一个独立的进程。好比上图第 六、7 行。
  4. Single process:全部的渲染工做做为多个线程都在 Browser 进程中进行。这个基本不会用到的。

Single process 忽然让我联想到零几年的时候,那会 IE 应该仍是单进程浏览器。单进程就是指全部的功能模块所有运行在一个进程,就相似于 Single process。那会玩 4399 若是一个网页卡死了,没响应,点关闭等一会,整个浏览器就崩溃了,得从新打开。因此多进程架构是有利于浏览器的稳定性的。虽然当下浏览器架构为多进程架构,但若是 Renderer 进程配置为 Process-per-site-instance,也可能会出现因为单个页面卡死而致使全部页面崩溃的状况。

故浏览器多进程架构综上所述,好处有三:

(1)单个网页的崩溃不会影响这个浏览器的稳定性。

(2)第三方插件的崩溃不会影响浏览器的稳定性。

(3)沙箱模型提供了安全保障。

总结

Webkit 使用三类资源加载器去下载对应的资源,并存入缓存池中,对于 HTML 文档的解析,在阻塞时调用另外一个线程去收集后续资源的 URL,将其发送给 Browser 进程,Browser 进程调用网络栈去下载对应的本地或网络资源,返回给 Renderer 进程进行渲染,Renderer 进程将最终渲染结果(一系列的合成帧)发送给 Browser 进程,Browser 进程将这些合成帧发送给 GPU 从而显示在屏幕上。
(文中有部分不严谨的地方,已由 lucifer 指出修改)

你们也能够关注个人公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。