本节简单介绍下Chromium/Blink绘制页面的基本工作流程。用户点击链接或输入网址,到最终显示网页内容,经历了以下几个重要步骤: 1. Chromium的启动和资源加载 用户输入URL(如www.163,com)并回车,Chrome会立即调用资源加载器,加载网页本身(比如index.html)。解析HTML时,如果发现某个节点还指向的其它的HTML文件,以及JS文件,CSS文件,图片,音视频文件等其它资源,也会加载这些资源。解析新加载的HTML,JS文件,CSS文件时,如果还指向其它需要加载的资源,则继续加载。整个过程类似于深度优先算法,伴随着HTML, CSS和JS的解析与执行而不断发现新的资源需要加载,如此往复,直到所有的资源全部加载完成。 2. 解析HTML, CSS, 执行Javascript 从网络接收到的HTML文件(实际上是字符流),会被解析成一个个Token,并构建Dom tree。同时还会解析CSS并构建RenderObject Tree,解析并执行JS。它和资源加载过程有重叠,因为解析HTML/CSS/JS过程可能发现当前结点或文件指向一个或多个新的HTML/CSS/JS文件。那么问题来了,当前HTML/CSS/JS解析过程中发现需要加载新资源,是否会打断当前的解析过程呢?这得看情况,如果新加载的文件是HTML/CSS/JS,这时的新加载是同步的,会暂停当前文件的解析,先加载新的文件。待加载完成后再解析当前文件。而如果新加载的文件是图片或音视频文件,则发起异步加载请求(通常有单独的加载线程),而无需打断当前的解析过程。 3. 构建DOM Tree, RenderObject Tree, RenderLayer Tree以及创建绘图上下文 上文提到,解析HTML时,将字符流解析成一个个Token, 并根据Token构建DOM tree. 而在解析CSS时,会根据CSS提供的Style信息把可视的DOM构建成RenderObject,从而形成RenderObject Tree. 而解析和执行JS时,可能修改DOM tree和RenderObject Tree. 另外,特殊的DOM(Canvas, WebGL, Video, Pepper3D, CSS 3D Transformation Dom)会建立单独的RenderLayer, 以加快后期的布局计算和绘制过程。而所有的其它结点则位于Root Layer。这样,又形成了一颗RenderLayer Tree。这些RenderLayer很可能有自己的绘图存储空间(BackingStore,或者RenderSurface), 而这往往又涉及到3D Context的创建,以及相应资源的申请。比如BackingStore可能只是位于主存(main memory)的二维数组来保存像素信息,也可能是显存上的一块Texture,或者显存上的离屏的FBO(FrameBuffer Object)。 4. 布局(Layout) 负责计算各个DOM的位置,它和CSS解析以及RenderObject的创建密切相关。位置信息往往会添加到RenderObject Tree中。而且JS的执行或用户事件(如拖拽等触摸动作或鼠标事件)可能会导致页面需要重新计算布局。 5. 绘制(Paint)与合成(Composite) 将上述的RenderObject Tree, RenderLayer Tree等提供的信息,调用图形库将各个Layer都绘制出来,转化成像素信息以便显示。由于多个Layer对应着多层像素图,它们需要按照合适的顺序(z-order)合成在一起,并最终与浏览器的UI合成,得到合成后的像素信息写到Framebuffer, 供监视器显示。 以上是Chromium/Blink的绘制页面的基本工作流程。 实际上,JS或用户操作会动态修改页面,从而需要反复计算布局和绘制,甚至重新发起URL导航。而且JS的执行往往是由requestAnimationFrame / setInterval / timeout 等注册的循环事件,所以页面的反复绘制与更新,以及用户的操作请求(比如滑动页面),实际上是处于一个由事件驱动的main loop里。