Chromium浏览器基本原理

670 阅读20分钟

Webkit、Chrome、Chromium的关系

  1. 苹果公司参与了由KDE开源社区发起的网页渲染引擎KHTML的开源项目开发

  2. 后来产生冲突,苹果宣布从KHTML的源代码中复制代码出来,成立封闭的项目WebKit

  3. 2005年,苹果决定将WebKit项目开源

  4. 2008年,Google公司以苹果开源项目WebKit作为内核,创建了一个新的项目Chromium

  5. 在Chromium项目的基础上,Google发布了自己的浏览器产品Chrome

    Chromium本身就是一个浏览器,而不是Chrome浏览器的内核,Chrome浏览器一般选择Chromium的稳定版本作为它的基础

  6. 2013年4月,Google和苹果公司有了一些分歧,Google宣布了从WebKit复制出来并独立运作的Blink项目。

Webkit渲染引擎

img

图1-1 渲染引擎模块及其依赖的模块

  • HTML解释器:解释HTML文本的解释器,主要作用是将HTML文本解释成DOM(文档对象模型)树。

  • CSS解释器:级联样式表的解释器,它的作用是将样式表解释称CSSOM并为DOM中的各个元素对象计算出样式信息。

  • 布局:在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算它们的大小位置等布局信息,形成一个能够表示这所有信息的内部表示模型。

  • 绘图:使用图形库将布局计算后的各个网页的节点绘制成图像结果。

  • JavaScript引擎:JavaScript引擎能够解释JavaScript代码并通过DOM接口和CSSOM接口来修改网页内容和样式信息,从而改变渲染的结果。

WebKit的渲染过程

img图1-2 渲染引擎的一般渲染过程及各阶段依赖的其他模块

渲染过程分成三个阶段

  • 第一个阶段是从网页的URL到构建完DOM树

  • 第二个阶段是从DOM树到构建完WebKit的绘图上下文

  • 第三个阶段是从绘图上下文到生成最终的图像

阶段一:构建 DOM 树

图1-3 从网页URL到DOM树

具体的过程如下:

  1. 当用户输入网页URL的时候,WebKit调用其资源加载器加载该URL对应的网页。
  2. 加载器依赖网络模块建立连接,发送请求并接收答复。
  3. WebKit接收到各种网页或者资源的数据,其中某些资源可能是同步或异步获取的。
  4. 网页被交给HTML解释器转变成一系列的词语(Token)。
  5. 解释器根据词语构建节点(Node),形成DOM树。
  6. 如果节点是JavaScript代码的话,调用JavaScript引擎解释并执行。
  7. JavaScript代码可能会修改DOM树的结构。

阶段二:绘制上下文

图1-4 从CSS和DOM树到绘图上下文

具体的过程如下:

  1. CSS文件被CSS解释器解释成内部表示结构(CSSOM)。
  2. DOM树上附加解释后的样式信息,这就是RenderObject树。
  3. RenderObject节点在创建的同时,WebKit会根据网页的层次结构创建RenderLayer树,同时构建一个虚拟的绘图上下文。

上述图中的四个内部表示结构(DOM、CSSDOM、RenderObject、RenderLayer)一直存在,直到网页被销毁,因为它们对于网页的渲染起了非常大的作用。

阶段三:绘制最终图像

3

图1-5 从绘图上下文到最终的图像

具体的过程如下:

  1. 绘图上下文是一个与平台无关的抽象类,它将每个绘图操作桥接到不同的绘图具体实现类。
  2. 绘图实现类也可能有简单的实现,也可能有复杂的实现。在Chromium中,它的实现相当复杂,涉及到软件渲染和GPU加速机制,这在后面会涉及。
  3. 绘图实现类将2D图形库或者3D图形库绘制的结果保存下来,交给浏览器来同浏览器界面一起显示。

Chromium浏览器

主要是将页面资源转换成一个可视化、可听化的图像

Chromium浏览器的架构

4

Content模块:是渲染网页内容的模块,结合了渲染功能和沙箱模型、跨进程的GPU硬件加速机制等。

Content API:将下面的渲染机制、安全机制和插件机制等隐藏起来,提供一个接口层。

Chromium浏览器:具有浏览器完整的功能。

Content Shell:使用Content API来包装的一层简单的“壳”,是一个简单的“浏览器”,一般用来用来测试Content模块很多功能的正确性,例如渲染、硬件加速等。

Android WebView:利用Chromium的实现来替换原来Android系统默认的WebView。

Chromium的模型

Chromium的多进程模型

5

图3-5 Chromium的多进程模型

  • Browser进程:浏览器的主进程,负责浏览器界面的显示、各个页面的管理,是所有其他类型进程的祖先,负责它们的创建和销毁等工作,它有且仅有一个。
  • 网页的渲染进程:负责页面的渲染工作,Blink/WebKit的渲染工作主要在这个进程中完成,可能有多个。
  • NPAPI插件进程:用来拓展浏览器功能,该进程是为NPAPI类型的插件而创建的。
    • 插件:表示一些动态库根据定义的一些标准接口可以跟浏览器进行交互,NPAPI接口标准只是其中的一种。
    • 当有多个网页需要使用同一种类型的插件的时候,例如很多网页需要使用Flash插件,Flash插件的进程会为每个使用者创建一个实例,所以插件进程是被共享的。
  • GPU进程:最多只有一个,当且仅当GPU硬件加速打开的时候才会被创建,主要用于对3D图形加速调用的实现。
  • Pepper插件进程:同NPAPI插件进程,不同的是为Pepper插件而创建的进程。
  • 其他类型的进程:图中还有一些其他类型的进程没有描述出来,比如名为“Sandbox”的准备进程。

多进程的好处

  1. Browser进程和页面的渲染是分开的,保证了页面的渲染导致的崩溃不会导致浏览器主界面的崩溃。
  2. 每个网页是独立的进程,这保证了页面之间相互不影响。
  3. 插件进程也是独立的,插件本身的问题不会影响浏览器主界面和网页。
  4. 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

Chromium的多线程模型

6

网页的加载和渲染过程:

  1. Browser进程收到用户的请求,首先由UI线程处理,而且将相应的任务转给IO线程,它随即将该任务传递给Renderer进程。

    Browser进程除了 主线程、IO线程(进程间通信),中间还有很多其他的线程,用来处理视频、存储、网络、文件、音频、浏览历史等。

  2. Renderer进程的IO线程经过简单解释后交给渲染线程。渲染线程接受请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程来帮助渲染。最后Renderer进程将结果由IO线程传递给Browser进程。

    Render进程除了 主线程、IO线程(进程间通信),中间还有很多其他的线程,比如GUI渲染线程、JavaScript引擎线程、事件触发线程、定时器线程、异步http请求线程等。

  3. 最后,Browser进程接收到结果并将结果绘制出来。

Chromium渲染过程

image-20230703080958048

资源加载机制

DNS预取和TCP预连接

  • DNS预取: 当用户正在浏览当前网页的时候,Chromium提取网页中的超链接,将域名抽取出来,利用比较少的CPU和网络带宽来解析这些域名或IP地址
  • TCP预连接: 当用户在地址栏中输入地址,如候选项同输入的地址很匹配,则在用户敲下回车键获取该网页之前,Chromium就已经开始尝试建立TCP连接了

资源缓存机制

8

构建DOM树

HTML网页和它的DOM树表示

在渲染引擎内部,有一个叫 HTML 解析器(HTMLParser) 的模块,它的职责就是负责将 HTML 字节流转换为 DOM 结构。所以这里我们需要先要搞清楚 HTML 解析器是怎么工作的。

10

从资源的字节流到DOM树

DOM树的构建流程

构建CSSOM树

样式的来源:

  1. 网页开发者编写的内部样式信息或者外部样式文

  2. 网页的读者设置的样式信息

    比如缩放网页、开发者工具修改样式等

  3. 浏览器的内在默认样式

img

1. 解释过程

CSS解释器是指从CSS字符串经过CSS解释器处理后变成渲染引擎的内部规则表示的过程。当Webkit需要解释CSS内容的时候,调用CSSParser来负责,最后Webkit将创建好的结构设置到StyleSheetContents对象中。

2. 样式匹配

img

  • StyleResolver类负责获取样式信息,并返回RenderStyle对象,RenderStyle对象包含了匹配完的结果样式信息
  • StyleResolver类首先全网为每个元素匹配不同来源的规则,比如浏览器(用户代理)规则集合、用户规则集合和HTML网页中包含的自定义规则集合
  • 对于自定义规则集合,它先查找ID规则,检查有无匹配的规则,之后依次检查类型规则、标签规则等。如果某个规则匹配上该元素,WebKit把这些规则保存到匹配结果,并进行排序。
  • 对于元素需要的样式属性,WebKit选择从高优先级规则中选取,并将样式属性值返回。

元素匹配结果

img

3. JavaScript获取和设置样式

  • CSSOM(CSS对象模型)提供了接口让JavaScript获得和修改CSS代码设置的样式信息。在Webkit中,这需要JavaScript引擎和渲染引擎共同来完成。

  • CSSOM不会阻塞DOM的生成,但会阻塞JS的执行,JS又影响DOM的生成,从而影响页面渲染进度。

    当在JS中访问了CSSDOM中某个元素的样式,需要等待这个样式被下载完成才能继续往下执行JS脚本。

大致的过程是,JavaScript引擎调用设置属性值的公共处理函数,然后该函数调用属性值解析函数。而后Webkit将解析后的信息设置到元素的style属性的样式webkitTransform中,然后设置标记表明该元素需要重新计算样式,并触发重新布局。最后就是Webkit的重新绘制。

4. CSSOM树生成过程

  1. 解析CSS文件(如果是link的)

  2. 将字节转换成字符。

  3. 确定tokens(标签)

    • 词法分析:逐句的读取每个字符,根据构词规则生成对应的单词或符号
    • 语法分析:在词法分析的基础上,根据生成的单词或符号,使用逻辑处理,得到对应的语句,函数,表达式等,这一步会判断我们写的代码在结构上是否正确。如我们使用if语句没有写括号呀等
    • 语义分析:语义分析就是判断我们写的代码是否符合逻辑,如我们的变量是const声明的,结果我们要修改变量值,在这一步就会报错
  4. 将tokens转换成节点

  5. 最后根据节点构建CSSOM树。

源代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            font-size: 16px;
        }
        p {
            font-weight: bold;
        }
        span {
            color: red;
        }
        p span {
            display: none;
        }
        img {
            float: right;
        }
    </style>
</head>
<body>
    <p>
        <span>xx</span>
    </p>
    <span>xx</span>
    <img src="" />
</body>
</html>

CSSOM

RenderObject树

WebKit会为可视节点建立相应的RenderObject对象,后面将它们的内容绘制到最终的网页结果中。

head、script、meta标签和display:none等非可视节点不会生成RenderObject对象

img

DOM树节点和RenderObject树的对应关系

  • WebKit检查该DOM节点是否需要创建RenderObject对象。
  • 如果需要,WebKit建立或者获取一个创建RenderObject对象的NodeRenderingContext对象,NodeRenderingContext对象会分析需要创建的RenderObject对象的父亲节点、兄弟节点等,设置这些信息后完成插入树的动作。

DOM、CSSOM、Render树的关系

RenderLayer树

img

RenderObject树和RenderLayer树的关系

  • RenderLayer树是基于RenderObject树建立起来的一棵新树。
  • RenderLayer节点和RenderObject节点是一对多的关系。

以下的RenderObject节点需要建立新的RenderLayer节点:

  1. DOM树的Document节点对应的RenderView节点。
  2. DOM树中的Document的子女节点,也就是HTML节点对应的RenderBlock节点。
  3. 显式的指定CSS位置的RenderObject节点。(position为absolute/fixed或者transform不为none)
  4. 有透明效果的RenderObject节点(opacity不为1,filter不为none)
  5. 节点有溢出(Overflow)、alpha或者反射等效果的RenderObject节点。
  6. 使用Canvas 2D和3D(WebGL)技术的RenderObject节点。
  7. Video节点对应的RenderObject节点。

WebKit中的实际内部表示和布局信息

绘图操作

WebKit的机制操作将内部模型转换成可视的结果分为两个阶段:1.每层的内容进行绘图工作 2. 之后将这些绘图的结果合成为一个图像。

流程

  1. 图层绘制:有了图层树之后我们渲染的准备工作基本上就做完了,接下来我们需要做的就是将我们的渲染工作分成一个个的指令,由这些指令来指导计算机来绘制,按照指令顺序生成绘制列表,绘制列表是用来记录绘制顺序和绘制指令的,绘制列表来决定先绘制背景还是先绘制边框呀这些。

    绘制列表 image.png

  2. 栅格化:绘制操作是由合成线程来完成的,当绘制列表完成主线程会把绘制列表提交到合成线程

    合成线程将图层分为图块(tile),合成线程会按照视口(viewport),也就是用户可以看到的区域优先来生成位图,所以栅格化就是将图块生成位图,图块是栅格化的最小单位,通常栅格化会使用GPU加速,并存储在GPU内存中

    分割图块、生成位图: 在渲染进程生成绘制列表后便会开一个线程来专门处理绘制工作,这个线程叫做合成线程,通过commit消息将之前生成的绘制列表提交给合成线程

    分割图块:我们知道很多情况下包含滚动条的页面是非常大的,如果一次性渲染出来是非常消耗性能的,所以我们合成线程需要做的第一件事便是将图层进行分块处理,分割成的图块后续上传到GPU处理。

    生成位图:在合成线程完成分割图块之后,我们的渲染进程会优先把位于视口附加的图块,发送给自己控制的一个叫栅格化线程池的东西,由其将图块转换为位图 数据,转化完之后再发送给合成线程。 这里还需要知道的是这里将图块转化为位图数据的过程会调用硬件GPU来进行加速。

    在位图数据生成完之后,合成线程给浏览器进程发送命令,浏览器将页面保存到内存,然后再发送到显卡,通过显示器显示出来。

渲染方式:

  1. 软件渲染:对于软件渲染机制,WebKit需要使用CPU来绘制每层的内容。会创建一个位图,每一层的内容都绘制上去,所以没有合成阶段。
  2. 硬件加速渲染(GPU) :对于硬件加速这种合成化渲染方式来说,会为每一层提供后端存储。
  3. 混合模式

如果需要更新某个层的一个区域,因为软件渲染没有为每一层提供后端存储,因而它需要将和这个区域有重叠部分的所有层次的相关区域依次从后向前重新绘制一遍,硬件加速渲染只需要重新绘制更新发生的层次。

Webkit 软件渲染技术

使用场景

  • 用来渲染基本的HTML元素

chromium渲染过程

  1. Browser进程发起的请求如窗口变化、路径改变等 或者页面自身的逻辑变更如果DOM操作等。
  2. Renderer进程的RenderWidget类接收到更新请求时,调用TransportDIB类来创建一个共享内存区域(以下称为共享内存位图)。
  3. Renderer进程遍历RenderObject树,使用Skia Canvas来绘制每个RenderObject节点及其子女,并把内容绘制并存储到共享内存位图。
    1. 首先Webkit计算重绘的区域是否和RenderLayer对象有重叠,如果有,Webkit要求绘制该层中的所有RenderObject对象。
  4. Renderer进程通知Browser进程绘制完成。
  5. Browser进程的RenderWidgetHost类把位图复制到BackingStore对象的相应区域中,释放共享内存位图
  6. Browser进程调用Paint函数来把结果绘制到窗口中。
  7. Browser进程发消息发给Renderer进程,表示已经使用完该共享内存,可以进行回收利用等操作。随后Renderer进程释放共享内存位图。

img

Webkit 硬件加速

使用场景

  • 用来绘制含3D图形、HTML5新功能的元素并且性能特别好

流程

  1. WebKit决定将哪些RenderLayer对象组合在一起,形成一个有后端存储的新层,这一新层不久后会用于之后的合成(Compositing),这里称之为合成层(Compositing Layer)。
  2. 每个新层都有一个或者多个后端存储,这里的后端存储可能是GPU的内存。对于一个RenderLayer对象,如果它没有后端存储的新层,那么就使用它的父亲所使用的合成层。
  3. 将每个合成层包含的这些RenderLayer内容绘制在合成层的后端存储中
  4. 由合成器(Compositor)将多个合成层合成起来,形成网页的最终可视化结果

哪些RenderLayer对象可以是合成层呢?

  1. RenderLayer具有CSS 3D属性或者CSS透视效果。
  2. RenderLayer包含的RenderObject节点表示的是使用硬件加速的视频解码技术的HTML5“video”元素。
  3. RenderLayer包含的RenderObject节点表示的是使用硬件加速的Canvas 2D元素或者WebGL技术。
  4. RenderLayer使用了CSS透明效果的动画或者CSS变换的动画。
  5. RenderLayer使用了硬件加速的CSS Filters技术。
  6. RenderLayer使用了剪裁(Clip)或者反射(Reflection)属性,并且它的后代中包括一个合成层。
  7. RenderLayer有一个Z坐标比自己小的兄弟节点,且该节点是一个合成层。

后端(存储RenderLayerBacking)对象

img

重绘与回流

回流: 我们的页面通常是很多个元素组成的,如果我们修改了页面中一个元素的大小,可能整个页面所有元素的位置都会发生改变,这个时候我们就要从生成DOM树开始重新计算一下页面中所有元素的位置,样式等。就相当于从构建DOM开始包括解析合成所有的流程再来一遍,这个过程就叫做回流(reflow),也叫做重排(当然也包括生成图块栅格化等后续处理)。

    图片

重绘: 重绘就简单多了,如果我们改变了页面中一个元素的样式,并没有修改其几何属性,那我们只需要重新计算一下这个元素的样式,然后直接生成绘制列表,然后分割图块栅格化等便好了,省略了生成DOM树布局树, 建立图层这些过程。

  图片

可见回流是包含重绘的,回流一定引起重绘重绘不一定引起回流。 所以从前端性能优化的角度我们一定要避免造成回流。

V8引擎

img

  1. Parser模块:将js源代码解析成AST抽象语法树

  2. 字节码:在虚拟机上执行的代码,而不是在最终的物理机器上执行的二进制代码。可以理解为机器码的抽象。

  3. lgnition:将AST抽象语法树解释成字节码,并转为机器码执行,同时收集turbofan所需要的优化信息。

  4. turtofan:可以将字节码编译成CPU直接运行的机器码,如果一个函数被多次调用,那么会标记成热点函数,由turbofan直接编译成优化后的机器码来提升性能

  5. 反优化

    function add(a, b) {
      const params1 = a + 42;
      const params2 = b;
      return params1 + params2;
    }
    
    var result1 = add(1, 2);
    var result2 = add(3, 4);
    var result3 = add(5, 6);
    

    该add函数会被标记为热点函数, turbofan 会对其编译优化,并且缓存起来,下次执行add函数时会直接执行优化后的机器码

    function add(a, b) {
      const params1 = a + 42;
      const params2 = b;
      return params1 + params2;
    }
    
    var result1 = add('a', 'b');
    

    执行到add('a', 'b')时,会进行反优化,lgnition解释器重新生成字节码,解释执行。

在早期,V8 团队采取了非常激进的策略,直接将 JavaScript 代码编译成机器代码。但是二进制机器码占用空间大,导致性能不太好,从而转向占用空间较小的字节码。

V8垃圾回收机制

在V8中,将内存分为了新生代(年轻分代)和老生代(年老分代)。它们特点如下:

  • 新生代:对象的存活时间较短。新生对象或只经过一次垃圾回收的对象。
  • 老生代:对象存活时间较长。经历过一次或多次垃圾回收的对象。

新生代

新生代中的对象主要通过 Scavenge 算法进行垃圾回收。Scavenge 的具体实现,主要采用了Cheney算法。

  1. 垃圾回收过程
    1. 将堆内存一分为二,一个处于使用状态(from) 一个处于闲置状态(to)。
    2. 分配对象时,先是在From空间中进行分配,当开始进行垃圾回收时,会From空间中存活的对象复制到To空间中,而非存活对象将被释放。
    3. 完成复制后,From空间和To空间的角色发生对换。
    4. 当一个对象经过多次复制仍然存活时,这种生命周期较长的对象会被移动到老生代中,也称为晋升
  2. 对象晋升的条件
    1. 对象是否经过Scavenge回收。
    2. 当要从From空间复制一个对象到To空间时,将使用超过to空间25%的内存时,则这个对象直接晋升到老生代空间中。

老生代

  1. 垃圾回收过程

    阶段一:标记清楚法

    img

    • 采用标记清楚法(Mark-Sweep),遍历堆中的所有对象,并标记活着的对象
    • 在清理阶段,只清理没有被标记的对象,但会存在一些内存碎片。

    阶段二:标记整理法 当标记清除法处理后的内存不足以分配的时候,才会进行标记整理法 img

    • 将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存。

    阶段三:增量标记 标记的流程会被切片,减少对页面执行的影响 image.png

引用

  1. 《webkit底层原理》
  2. DOM树的构建流程
  3. 带你看看从输入URL到页面显示背后的故事