阅读 1331

浏览器幕后之渲染引擎

前言

一探浏览器幕后的《三俩事》上一篇的介绍让大家对浏览器的组成有了个模糊的认识:

  1. 浏览器是什么?
  2. 浏览器(chromium)基本架构
  3. 浏览器主要组件
  4. 浏览器内核是什么?
  5. JavaScript(js)引擎
  6. 渲染引擎

今天呢做渲染模块(WebKit)展开学习讨论。

首先作为顺序介绍来说。我应该具体介绍实现一个浏览器应该包含哪些内容(介绍一下chromium实现包含了哪些内容)。但是考虑在前端话题的介绍。只做简单的列举不做具体展开讨论;有兴趣可以自行下去学习。

其次按大家耳熟能详的程度来说的话我应该着重讨论一下JS引擎(V8 event-loop相关);但是本节内容还是想先介绍讨论一下渲染引擎(WebKit)。不为别的我就是喜欢玩~ 因为我写这个系列的受众还是偏向前端,所以涉及到需要代码介入讲解的部分这边采取JavaScript来实现。

渲染引擎

1621765871(1).jpg

画架构图画习惯了,不要在意丑陋的细节 - -.

由上图可以得到渲染引擎内部解析执行大概过程的信息:

  1. 当访问页面的时候会进行网络请求(network)去获取页面的内容;当如果命中缓存(cache)就会从存储(memory)上直接读取。
  2. HTMLParser 进行html解析通过特定标识进行分块。可解析为CSS DOM JS几个模块。
  3. CSSParser 进行css也是解析。
  4. DOMParser 进行DOM结构解析。(构建过程中发现是JS部分执行5,如果是资源进行异步请求.不会阻碍解析)。
  5. JavaScript 部分丢给JS引擎进行解析。(如果有操作DOM/CSS部分会影响之前的解析,同时会阻碍解析)。
  6. 通过上文解析内容构建RenderTree。
  7. 解析完成通过renderTree进行布局和绘制。
  8. 将最终图像显示在屏幕上。

下面对于上文所提部分进行展开讨论一下


Parser 解析

上面Parser的字眼是不是太多了。我们得到一个信息解析是渲染引擎中非常重要的一个环节,所以首先需要介绍一下解析.

解析文档是指将文档转化成为有意义的结构,也就是可让代码理解和使用的结构。解析得到的结果通常是代表了文档结构的节点树,它称作解析树或者语法树。

解析是以文档所遵循的语法规则(编写文档所用的语言或格式)为基础的。比方说HTML解析那么是在HTML4/5的规范上进行的。所有可以解析的格式都必须对应确定的语法(由词汇和语法规则构成)。

image011.png

解析过程通常是分成两个子过程:词法分析和语法分析词法分析是将输入内容分割成大量标记的过程。(进行切分可识别大量标记的词段)语法分析是应用语言的语法规则的过程(到底这段语言是要做什么工作)。

HTMLParser

HTML 解析器的任务是将 HTML 标记解析成解析树。

HTML 语法定义: HTML 的词汇和语法在 W3C 组织创建的规范中进行了定义。

DOM 解析器的输出“解析树”是由 DOM 元素和属性节点构成的树结构。DOM 是文档对象模型 (Document Object Model) 的缩写。它是 HTML 文档的对象表示,同时也是外部内容(例如 JavaScript)与 HTML 元素之间的接口。 解析树的根节点是Document对象。

image.png

DOM解析code示意(不能运行的) 首先要了解解析过程一定是迭代过程:

// 分析标记<>和属性的正则表达式
    var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[\w-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
        endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/,
        attr = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
    function ParserHTML(html){
         // 接收参数html
        while(html){

            // 匹配注释内容
            if(html.indexOf("<!--") == 0){
                //
            }
            // 匹配开始标签
            if (html.indexOf("<") == 0) {
                match = html.match(startTag);

                if (match) {
                  html = html.substring(match[0].length); //匹配截取
                  //继续迭代
                }
           }
           if(html.indexOf("</") == 0){
               //匹配结束标签操作
           }

        }
    }
    // 输出示意结构
    {
        element:"",
        parentNode:{},
        childrenNode:{},
        content:"",
        ....
    }
    // 我之前写的找不到了--, 大家可以试着去实现一下。有问题随时沟通 本示例正则可用。
    
复制代码

CSSParser

CSS解析同HTML不同地方是上下文无关的语法;可通过很多解析器做解析。 词法语法(词汇)是针对各个标记用正则表达式定义的:

  • comment /*[^]*+([^/][^]*+)/
  • num [0-9]+|[0-9]*"."[0-9]+
  • nonascii [\200-\377]
  • nmstart [_a-z]|{nonascii}|{escape}
  • nmchar [_a-z0-9-]|{nonascii}|{escape}
  • name {nmchar}+
  • ident {nmstart}{nmchar}*

通过 CSS 语法文件自动创建解析器, 会创建自下而上的移位归约解析器。会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。如下:

image023.png

RenderTree

在 DOM 树构建的同时,浏览器还会构建另一个树结构:渲染树。这是由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。它的作用是让您按照正确的顺序绘制内容。

WebKits RenderObject 类是所有渲染树的基类,其定义如下:

class RenderObject{
  virtual void layout();  //布局
  virtual void paint(PaintInfo);  //绘制
  virtual void rect repaintRect(); //重新绘制Rect
  Node* node;  // DOM节点
  RenderStyle* style;  // 计算render style
  RenderLayer* containgLayer; //render layer 
}
复制代码
渲染树和 DOM 树的关系

渲染树是和 DOM 元素相对应的,但并非一一对应。非可视化的 DOM 元素不会插入渲染树中,例如“head”元素。如果元素的 display 属性值为“none”,那么也不会显示在渲染树中(但是 visibility 属性值为“hidden”的元素仍会显示)。

关于多渲染树的例子是格式无效的 HTML。根据 CSS 规范,inline 元素只能包含 block 元素或 inline 元素中的一种。如果出现了混合内容,则应创建匿名的 block 渲染树,以包裹 inline 元素。

有一些渲染对象对应于 DOM 节点,但在树中所在的位置与 DOM 节点不同。浮动定位和绝对定位的元素就是这样,它们处于正常的流程之外,放置在树中的其他地方,并映射到真正的框架,而放在原位的是占位框架。

布局

创建完成渲染树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。

HTML 采用基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历 (3.5)。

坐标系是相对于根框架而建立的,使用的是上坐标和左坐标。

布局是一个递归的过程。它从根渲染树(对应于 HTML 文档的 元素)开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的渲染树计算几何信息。

根渲染树的位置左上角 是 0,0,(跟canvas2D坐标规则一致)其尺寸为视口(也就是浏览器窗口的可见区域)。 所有的渲染树都有一个layout或者reflow方法,每一个渲染树将会调用其需要进行布局的子代的 layout 方法。

绘制

在绘制阶段,系统会遍历渲染树,并调用渲染树的paint方法,将渲染树的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。

绘制的顺序其实就是元素进入堆栈样式上下文的顺序;从后向前。块渲染树的堆栈顺序如下:

  1. 背景颜色
  2. 背景图片
  3. 边框
  4. 子代
  5. 轮廓

绘制实现可参考canvas(canvas一定要学的啊 时刻关注一些前沿的动作),了解基础图元绘制方法与过程。例如绘制line(需要坐标 基础图元结构等等):

image.png

最后

距离上一篇文章的生活日记: 20210521那天呢照样开心(朋友圈依旧不活跃)。20210522呢不开心。20210523也就是今天总的来说过得去 并没有学习。(劳逸结合 哈哈)

然后呢因为最近有人总微信我编程范式到底选择哪种好一点呢? 我现在用的更多是什么编程范式呢? 自己写代码维护起来好困难怎么办呢?... (做了很久的解答,应该记录下来提供给大家)下期JS引擎之前 我会把编程范式做一个简单介绍供大家参考 小小插曲(预计明天)。

加油!有问题请大家随时留言,看到一定会回复的

相关参考

  1. CSS 的词法和语法
  2. 处理模型
  3. WebCore Rendering
文章分类
前端
文章标签