zoukankan      html  css  js  c++  java
  • 浏览器工作原理和实践(一)——浏览器

      《浏览器工作原理与实践》是极客时间上的一个浏览器学习系列,在学习之后特在此做记录和总结。

    一、Chrome架构

    1)线程和进程

      Chrome打开一个页面会启动4个进程:网络进程、GPU进程、浏览器主进程和渲染进程。

      一个进程就是一个程序的运行实例,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,这样一个运行环境叫进程。

      线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率。

      进程和线程之间的关系有以下 4 个特点:

      (1)进程中的任意一线程执行出错,都会导致整个进程的崩溃。

      (2)线程之间共享进程中的数据。

      (3)当一个进程关闭之后,操作系统会回收进程所占用的内存。

      (4)进程之间的内容相互隔离。

    2)Chrome进程架构

      最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程,如下图所示。

      

      (1)浏览器主进程:负责界面显示、用户交互、子进程管理,同时提供存储等功能。

      (2)渲染进程:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。

      (3)GPU进程:GPU 的使用初衷是为了实现 3D CSS 的效果,随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。

      (4)网络进程:负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的。

      (5)插件进程:负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

    二、网络协议

    1)TCP协议

      在衡量 Web 页面性能时有一个重要的指标FP(First Paint),它是指从页面加载到首次开始绘制的时长。而影响 FP 指标的一个重要因素就是网络加载速度。

      互联网,实际上是一套理念和协议组成的体系架构。互联网中的数据是通过数据包来传输的。

      (1)IP:把数据包送达目的主机。

      (2)UDP:把数据包送达应用程序。

      (3)TCP:把数据完整地送达应用程序。

    2)HTTP协议

      HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础。

      浏览器端发起 HTTP 请求流程:

      (1)构建请求,构建好后,浏览器准备发起网络请求。

      (2)查找缓存,当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求。

      (3)准备 IP 地址和端口,HTTP 的内容是通过 TCP 来传输的,第一步就是先建立TCP连接,而请求 DNS(DNS数据缓存服务) 可返回域名对应的 IP。

      (4)等待 TCP 队列,同一个域名同时最多只能建立 6 个 TCP 连接,如果同时有 10 个请求发生,那么其中 4 个会进入等待状态。

      (5)建立 TCP 连接,快乐地和服务器握手。

      (6)发送 HTTP 请求,和服务器进行通信,向服务器发送请求行,包括请求方法、请求 URI(Uniform Resource Identifier)和 HTTP 版本协议。

      服务器端处理 HTTP 请求流程:

      (1)返回请求,一旦服务器处理结束,便可以返回响应行,包括协议版本和状态码。

      (2)断开连接,但浏览器或者服务器在其头信息中加入了:Connection:Keep-Alive,那么 TCP 连接在发送后将仍然保持打开状态。

      (3)重定向,当两个 URL 不一样时,会涉及一个重定向操作。

      为什么很多站点第二次打开速度会很快?因为 DNS 缓存和页面资源缓存这两块数据是会被浏览器缓存的。

      登录状态是如何保持的?浏览器页面状态是通过使用 Cookie 来实现的。如果服务器端发送的响应头内有 Set-Cookie 的字段,那么浏览器就会将该字段的内容保存到本地。

      下面是张详细的“HTTP 请求示意图”,用来展现浏览器中的 HTTP 请求所经历的各个阶段。

      

      浏览器中的 HTTP 请求从发起到结束一共经历了八个阶段:构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接。

    三、导航流程

      在浏览器里,从输入 URL 到页面展示,这中间发生了什么?其中涉及到了网络、操作系统、Web 等一系列的知识,如下图所示。

      

      整个流程大致描述如下:

      (1)首先,浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。

      (2)然后,在网络进程中发起真正的 URL 请求。

      (3)接着,网络进程接收到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程。

      (4)浏览器进程接收到网络进程的响应头数据之后,发送“提交导航 (Commit Navigation)”消息到渲染进程;

      (5)渲染进程接收到“提交导航”的消息之后,便开始准备接收 HTML 数据,接收数据的方式是直接和网络进程建立数据管道;

      (6)最后,渲染进程会向浏览器进程“确认提交”,这是告诉浏览器进程:“已经准备好接受和解析页面数据了”。

      (7)浏览器进程接收到渲染进程“提交文档”的消息之后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。

      这其中,用户发出 URL 请求到页面开始解析的这个过程,就叫做导航。

    1)用户输入

      当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的 URL。

      当用户输入关键字并键入回车之后,这意味着当前页面即将要被替换成新的页面,不过在这个流程继续之前,浏览器还给了当前页面一次执行 beforeunload 事件的机会。

      beforeunload 事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页面,比如当前页面可能有未提交完成的表单等情况,因此用户可以通过 beforeunload 事件来取消导航,让浏览器不再执行任何后续工作。

    2)URL 请求过程

      浏览器进程会通过进程间通信(IPC)把 URL 请求发送至网络进程,网络进程接收到 URL 请求后,会在这里发起真正的 URL 请求流程(参考上面的HTTP请求流程)。

    3)准备渲染进程

      Chrome 会为每个页面分配一个渲染进程,也就是说,每打开一个新页面就会配套创建一个新的渲染进程。

      但如果属于同一站点,即根域名(例如geekbang.org)和协议(例如https:// 或者 http://)都相同,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫 process-per-site-instance。

    4)提交文档

      提交文档,就是指浏览器进程将网络进程接收到的 HTML 数据提交给渲染进程,具体流程是这样的:

      (1)首先当浏览器进程接收到网络进程的响应头数据之后,便向渲染进程发起“提交文档”的消息;

      (2)渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”;

      (3)等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程;

      (4)浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。

    5)渲染阶段

      一旦文档被提交,渲染进程便开始页面解析和子资源加载。

      一旦页面生成完成,渲染进程会发送一个消息给浏览器进程,浏览器接收到消息后,会停止标签图标上的加载动画。

    四、渲染流程

      HTML 的内容是由标记和文本组成。标记也称为标签,每个标签都有它自己的语义,浏览器会根据标签的语义来正确展示 HTML 内容。

      如果需要改变 HTML 的字体颜色、大小等信息,就需要用到 CSS。CSS 又称为层叠样式表,是由选择器和属性组成。

      至于 JavaScript(简称为 JS),使用它可以使网页的内容“动”起来。

      由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素,这样的一个处理流程叫做渲染流水线。

      按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

      

    1)构建 DOM 树

      浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树,如下所示。

      

    2)样式计算

      样式计算(Recalculate Style)地目的是为了计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步来完成。

      (1)把 CSS 转换为浏览器能够理解的结构——styleSheets。

      

      (2)转换样式表中的属性值,使其标准化,如 2em、blue、bold,需要将它们转换为渲染引擎容易理解的、标准化的计算值。

      

      (3)计算出 DOM 树中每个节点的具体样式,涉及到 CSS 的继承规则和层叠规则。此阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。

    3)布局阶段

      布局就是计算 DOM 树中可见元素几何位置的过程。Chrome 在布局阶段需要完成两个任务:创建布局树和布局计算。

      (1)创建只包含可见元素的布局树。

      (2)计算布局树节点的坐标位置。

      

    4)分层

      渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。

      打开 Chrome 的“开发者工具”,选择“Layers”标签,就可以可视化页面的分层情况,如下图所示。

      

      下面再来看看这些图层和布局树节点之间的关系。通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。

      

      满足下面两点中任意一点的元素就可以被提升为单独的一个图层。

      (1)拥有层叠上下文属性的元素,例如明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等。

      (2)需要剪裁(clip)的地方也会被创建为图层,如果出现滚动条,滚动条也会被提升为单独的层。

    5)图层绘制

      渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:

      

      绘制列表中的指令其实非常简单,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。

    6)栅格化操作

      绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。

      当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。

      合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512,如下图所示:

      

      然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。

      所谓栅格化(raster),是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。

      通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

      

      从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。

    7)合成和显示

      一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

      将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

      一个完整的渲染流程大致可总结为如下:

      (1)渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。

      (2)渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。

      (3)创建布局树,并计算元素的布局信息。

      (4)对布局树进行分层,并生成分层树。

      (5)为每个图层生成绘制列表,并将其提交到合成线程。

      (6)合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

      (7)合成线程发送绘制图块命令 DrawQuad 给浏览器进程。

      (8)浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

      渲染引擎会通过合成线程直接去处理变换,这些变换并没有涉及到主线程,这样就大大提升了渲染的效率。这也是 CSS 动画比 JavaScript 动画高效的原因。

    8)重排

      更新了元素的几何属性。

      

      从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。

      无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

    9)重绘

      更新元素的绘制属性。

      

      从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。

      相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

    10)直接合成

      染引擎将跳过布局和绘制,只执行后续的合成操作,把这个过程叫做合成。

      

      在上图中,使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。

      这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。

  • 相关阅读:
    又见博弈
    两道来自CF的题
    温习及回顾
    笔试面试总结
    Python Cha4
    初学ObjectiveC
    设计模式汇总(三)
    转贴XML的写法建议
    让从Objec中继承的类也拥有鼠标事件
    关于异常处理的一些看法
  • 原文地址:https://www.cnblogs.com/strick/p/13474540.html
Copyright © 2011-2022 走看看