浏览器架构

爱吃猪头爱吃肉

浏览器架构与渲染原理

浏览器架构

主流浏览器及其内核

主流浏览器

全球份额占比

img

内核

浏览器内核表示浏览器渲染引擎(排版引擎),渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息,从而导致了不同内核展示内容不一致。

浏览器名称 浏览器内核
Chrome Webkit -> Blink
Firefox Gecko
IE(已停止更新) Trident
Opera Presto -> Blink
Safiri Webkit
Edge EdgeHTML(chromium)

chrome浏览器设计

进程与线程

  • 进程:表示具有独立功能的程序关于某个数据集合的一次活动,简单理解就是进行中的程序,当应用被启动时就会创建对应的进程。
  • 线程:是操作系统能够进行调度的最小单位。通常在一个进程中可以包含若干个线程。程序可以利用线程去辅助进程的执行。
  • 协程:协程是一种比线程更加轻量级的存在。你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,async/await就是利用协程实现的。

浏览器的进程发展史

单进程架构

(听说)在2007年之前的浏览器都是采用单进程架构,将所有的功能都放在一个进程下,如 网络、插件、js执行、渲染等去全部功能,这就会导致当导致单进程浏览器不稳定、不流畅和不安全的

  • 不稳定:插件,脚本,页面的内存泄露
  • 不安全:插件、页面脚本

多进程架构

将原本的单进程中的模块,改为多进程。从而保证了浏览器的稳定性,不会由于单一问题导致整个浏览器不能用。

img

借助操作系统对进程安全的控制,浏览器可以将页面放置在沙箱中,站点的代码可以运行在隔离的环境中,保证核心进程的安全。

虽然多进程的架构优于单进程架构,但由于进程独享自己的私有内存,以渲染进程为例,虽然渲染的站点不同,但工作内容大体相似,为了完成渲染工作它们会在自己的内存中包含相同的功能,例如 V8 引擎(用于解析和运行 Javascript),这意味着这部分相同的功能需要占用每个进程的内存空间。为了节省内存,Chrome 限制了最大进程数,最大进程数取决于硬件的能力,同时当使用多个页签访问相同的站点时浏览器不会创建新的渲染进程。采用多进程的好处:

  1. 避免单个页面崩溃影响整个浏览器
  2. 避免第三方插件崩溃时影响整个浏览器
  3. 多进程充分利用多核优势
  4. 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

当我们在地址栏中输入一个地址时,浏览器进程中的 UI 线程最先得知这个动作,并开始处理。

img

浏览器进程(Browser process)

浏览器进程做为 Chrome 中最核心的进程,负责管理 Chrome 应用本身,包括地址栏、书签、前进和后退按钮。同时也负责可不见的功能,比如网络请求、文件按访问等,也负责其他进程的调度。

img

网络进程(Network process)

网络进程主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面,现在独立开来,成为一个单独的进程

img

渲染进程(Renderer process)

渲染进程负责文档的渲染,其中也包括 JavaScript 代码的运行,页面布局绘制,web worker 的管理等。渲染进程是基于站点隔离的,也就是说每个tab页的内容相互隔离,互不影响。但是可以利用 iframe 可以在同一个页面访问不同站点的资源,但从安全的角度考虑,同源策略不允许一个站点在未得到同意的情况下访问其他站点的资源,所以从 Chrome 67 开始默认启用独立的渲染进程处理每个站点。

img

GPU进程(GPU process)

GPU进程最开始的时候chrome仅仅使用GPU实现CSS的3D动画效果,但由于随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制因此chrome增加了GPU进程。主要是使用显卡硬件加速,提高你网页浏览的体验,并且最多只会开启一个,常用于3d绘制,使用chrome://flags/#ignore-gpu-blocklist 开启实验性的加速,chrome://gpu/查看是否加速了

img

插件进程(Plugin process)

插件进程负责为浏览器提供各种插件功能,例如 React Developer Tools

img

从输入url到页面的展示

处理输入内容

主进程的UI线程会根据你的输入,去判断输入的是个url地址还是其他的内容

img

如果输入内容不是域名且不带有浏览器能够标识的信息时,则调用浏览器中设置的默认搜索引擎,获取查询网址。比如百度查询aaa的结果为https://www.baidu.com/s?ie=UTF-8&wd=aaaa

img

浏览器地址栏能够识别的信息

  • 本地文件地址:file:///Users/user/test``/index.html
  • 浏览器内部功能: chrome://xxxx 如浏览器插件管理页面chrome://extensions,以及插件页面等

发起网络请求

当主进程的UI线程识别输入的内容为url 或 经过搜索引擎转的查询网址,则开启网络进程辅助完成网络请求。

  1. DNS解析获取资源地址与端口号
  2. TCP链接尝试链接资源服务器进行3次握手
  3. HTTPS链接获取网络资源

重定向

当服务器返回给浏览器重定向响应时如返回301、302状态码时,网络进程会通知主进程的UI 线程需要重定向,然后会以新的地址重新请求资源。

处理响应数据

网络进程接受到资源数据时,会从响应头中获取资源的content-type,以便于识别资源类型,如果发现响应头中content-type结果为text/html; charset=utf-8,则会将数据传递给渲染进程进行下一步的处理,如果访问资源是其他文件类型时,浏览器则会将文件传给下载管理器,在进行是预览还是下载等操作。

img

在将数据传给渲染进程之前,网络进程会先对数据进行检查,防止数据来自于不安全的网站,如(你懂得),如果发现数据是不安全的,浏览器则会提示访问站点不安全等信息

同时网络进程利用CORB(Cross Origin Read Blocking)预防跨域问题,防止敏感数据传给渲染进程

  • CORB 是一种判断是否要在跨站资源数据到达页面之前阻断其到达当前站点进程中的算法,降低了敏感数据暴露的风险

img

开始渲染

当网络进程请求到数据并对数据检查完毕,并确认数据没毛病后。那么此时网络进程就可以通知浏览器主进程中的UI线程可以开始渲染了,UI线程就会根据当前站点去查找或启动一个渲染进程,完成渲染。

img

值得注意的是,UI线程将请求地址给网络进程之后,网络进程发送请求到接收到响应是需要一段时间的。浏览器为了更高效的进行处理,此时的UI线程会尝试查找或者开启一个渲染进程,这个行为与网络进程处理网络请求是同时进行的。如果网络进程按照预期获取到了资源数据并检测完毕就可以通知渲染进程进行渲染了,如果发生了重定向,则提前初始化好的渲染进程有可能不会被使用。

渲染进行中

浏览器进程做的事情

当网络进程获取到数据,当渲染进程初始化完毕,此时浏览器主进程的UI线程会利用主进程通过IPC通信,将本次访问传递给渲染进程。同时也保证渲染进程在渲染途中,依然可以从网络进程中获取到其他资源数据。

这个时候浏览器的地址栏出现安全图标,同时显示出请求地址。浏览器的历史记录(history)中会添加该条信息。为了快速访问该条信息,会将访问记录存储到硬盘中。

img

img

渲染进程做的事情

查看后续渲染进程执行过程

页面加载完毕

当整个访问提交给渲染进程之后,渲染进程会持续的加载资源并绘制页面。当渲染进程绘制页面完毕后,会通过IPC通信向浏览器主进程发送渲染完毕的信息。这个消息会在所有资源加载完毕后发出,也就是页面的onload事件触发后发出,当浏览器进程接收到这个消息后,ui线程会将tab上的loading图标关闭,以表示本次加载完成

img

注意:

beforeunload

在表单提交的页面中,大多数UED都希望,这个页面打开其他页面时希望出现弹框,告知用户关闭了就没有办法保存你的数据。此时就可以使用beforeunloadAPI,但是内容无法定制。注意:为避免意外弹出窗口,除非页面已与之交互,否则浏览器可能不会显示在beforeunload事件中创建的提示,甚至根本不会显示它们。

由于事件的监听还是在js执行线程中进行,因此这个过程还是在渲染进程中被处理的。

  • 在url中输入跳转
    • 浏览器进程向当前渲染进程发送消息,判断是否需要处理unload事件。如果需要处理,当前渲染进程会执行该事件
  • 在html中点击a标签
    • 渲染进程首先检查beforeunload。然后再执行和浏览器进程初始化访问同样的步骤,只不过区别在于这样的访问请求是由渲染进程向浏览器进程发起的。

在确认访问一个新的地址时,浏览器进程会初始化一个新的渲染进程执行这个页面,为的是处理unload这种事件时,老页面能够保持当前状态。页面的更多生命周期Page lifecycle

img

ServiceWorker

提到Service Worker本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器,是实现PWA的主要功能。当开始请求地址时,网络进程会查看是否有Service Worker去处理这个请求,如果存在,则会执行对应的Service Worker中的代码,Service Workerweb worker不同,安装后会在浏览器后台开启一个worker进程,用于处理缓存与其他问题。webworker是在渲染进程中开启一个js执行线程,用于执行js主线程中会阻塞UI渲染的代码

img

img

浏览器渲染流程

当网络进程拿到数据并对数据安全检测后,就会对浏览器主进程的UI线程发送信号,告知可以进行渲染了,UI线程发送这个请求访问给它刚刚初始化或者找到的渲染进程,渲染进程会从网络进程获取数据(网络进程请求资源与渲染进程渲染页面是同时进行的)

当渲染进程拿到网络进程传输的html字节信息后,就可以开始进行渲染了。总流程如下图:

Gecko(火狐)内核:

img

webkit内核:

img

正常的加载过程

img

解析HTML

HTML不与XML一样,有着严格的标签格式。HTML中会存在自闭合、不闭合以及闭合标签,HTML解析器也是极其大度的,即使你写错了,也能够识别出来。

imgimg

那么HTML的解析过程是:Buffer -> 字符串 -> token -> DOM,在这其中包含了转化token和DOM树构造这两个过程

转化token是词法分析,将字符串解析为token。HTML 标记包括开始token、结束token、属性名称和属性值。分词器可以识别生成token,将其提供给DOM树的构造函数,并使用下一个字符来识别下一个令牌,依此类推,直到输入结束(状态机)。

img

当全部解析完成之后,浏览器会将文档标记为interactive,并开始解析处于“deferred”模式的script:在解析文档后应执行的脚本。然后将文档状态设置为“complete”,并触发“load”事件。此时文档已经全部构建完成。

解析CSS

每个 CSS 文件都被解析为一个样式表对象Style Sheet。每个对象都包含 CSS Rule,CSS 规则对象包含选择器与 CSS 语法对应的其他对象。

img

当我们没有设置样式规则时,此时会用浏览器提供的默认样式。所以有的网站会在样式表中设置

* { margin:0px; padding:0px; }以及其他的清除默认样式的CSS代码

CSS与JavaScript的执行顺序

JavaScript

由于渲染进程中GUI与js执行线程是互斥的,因此当文档解析到script时会暂停解析过程,直到script被执行。如果是引用的外部script,那么文档同样是停止解析,等待从网络进程获取资源、解析、执行等过程直至被执行。

HTML4与5提供了异步加载JavaScript的方法,加载过程并不会阻止文档的解析。

  • async:异步加载立即执行,因此script的执行顺序全凭天意
  • defer:异步加载,并等DOM解析完毕后执行,会按你写的顺序执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script type="text/javascript">
// undefined
console.log('head 中:',document.getElementsByTagName('div')[0])
</script>
</head>
<body>
<script type="text/javascript">
// undefined
console.log('body 中首部 : ',document.getElementsByTagName('div')[0])
</script>
<div>
正常的image
<img src="image/01.jpg" alt="图片" />
</div>
<script type="text/javascript">
// div
console.log('body 中尾部 : ',document.getElementsByTagName('div')[0])
</script>
</body>
</html>

CSS

从理论上讲CSS的解析和DOM没有直接关系文档不必等待CSS的解析,但是有一个问题,在JavaScript中可以处理CSS样式表,如果此时样式还没有被构建那么会存在很多问题。每个浏览器针对这个情况的处理都是不一样的,在Firefox中会在加载和解析样式表时阻止所有的script。webkit仅在脚本尝试访问可能不存在的样式时阻止脚本的执行,等待样式加载完毕。

构造渲染树

在构造DOM树时浏览器还会同时构造渲染树,这个最终完成的渲染树会绘制到页面中。

每一个DOM都会创建一个RenderObject,用于处理自身与子元素的布局与在页面中的位置。

1
2
3
4
5
6
7
8
class RenderObject{
virtual void layout(); // 布局
virtual void paint(PaintInfo); // 绘制
virtual void rect repaintRect(); // 重新绘制
Node* node; // DOM树
RenderStyle* style; // 计算完毕的样式
RenderLayer* containgLayer; //对应z-index的渲染层
}

最终渲染树与DOM的关系

渲染树对应的 DOM树,关系不是一对一的。被隐藏的DOM它创建的渲染器并不会被插入到渲染树中如display:none的元素。还有一些渲染对象有对应的 DOM 节点,但是不与DOM树的位置相一致。浮动和绝对定位的元素不在文档流中,会被放置在树的不同部分,并映射到真实渲染器。

img

计算样式

构建渲染树需要计算每个渲染对象的样式属性。样式包括各种来源如样式表、内联元素、视觉元素(bgColor属性)等。将元素的样式所有样式进行计算得出最后需要渲染的样式,可以通过getComputedStyle获取计算后的样式

  • getComputedStyle 可以获取伪元素的样式

样式的计算会遵循CSS权重规则

等级例子权重值行内样式style1000ID#nav100属性选择器/class/伪类:hover10元素名/伪元素::after1

布局

参考

https://web.dev/howbrowserswork/

https://xie.infoq.cn/article/5d36d123bfd1c56688e125ad3

https://developer.chrome.com/blog/inside-browser-part2

https://developer.chrome.com/blog/page-lifecycle-api/#why-arent-the-load-or-domcontentloaded-events-mentioned

  • 标题: 浏览器架构
  • 作者: 爱吃猪头爱吃肉
  • 创建于: 2023-04-21 07:29:28
  • 更新于: 2023-04-21 07:31:44
  • 链接: https://zsasjy.github.io/2023/04/21/Browser架构/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
推荐文章
搭建react开发环境 搭建react开发环境 webpack常用配置汇总 webpack常用配置汇总 less使用指南 less使用指南 Docker基本用法 Docker基本用法 formily2学习笔记 formily2学习笔记 前端 CSS 代码规范 前端 CSS 代码规范
 评论