从URL到页面展示:浏览器背后的进程协作之旅

浏览器的多进程架构

在数字世界的繁忙工厂中,进程是确保每个任务得以顺利完成的关键单位,每个进程能包含多个线程。它们就像独立的车间,各自负责不同的作业,确保整个工厂的高效运转。

现代浏览器是基于多进程的, 每个浏览器标签页就是一个小小的独立世界,它们各自运行在自己的空间里,而浏览器就是这些世界的宇宙,通过精心设计的多进程架构来管理它们。

浏览器中核心的进程包含:

  • browser 进程: 该进程是浏览器的主进程,处理除了 tab 页之外的功能, 如:地址栏、标签、前进后退、网络资源管理等。browser 进程包含UI thread 用于绘制 button 等元素, network thread 用于请求资源, storage thread 控制文件读写权限。
  • renderer 进程: 默认每个 tab 页面一个进程, 负责该 tab 页所有展示的内容。
  • GPU 进程: 用于处理 GPU 任务。
  • plugin 进程: 处理插件相关的任务, 如 flash
  • extension 进程: 处理谷歌插件的任务。

多进程架构的优缺点:

  • 好处在于某个 tab 的 render process 崩溃时不会影响其他的 tab 页, 甚至 chrome 现在对每个 iframe 都申请了一个 render process。
  • 坏处就在于 tab 页一多, 那么浏览器就会占据较大的内存资源。

简单分析一个简单请求的过程

从点击到显示,一个网页请求的旅程充满了复杂而奇妙的步骤。每当你在浏览器中键入一个地址,就是一场从用户界面到网络通讯的精彩接力赛的开始。

从浏览器地址栏输入 url , 浏览器请求获取数据并且展示在页面上, 这个过程调用了哪些线程?

  1. 首先当你输入 url 的时候, UI thread 会判断这个是一个 搜索问题还是一个 url , 如果是搜索问题则请求搜索引擎否则请求 url。

  2. 确认之后, UI thread 会发起一个网络请求, 此时 network thread 会使用对应的协议建立请求。此时, netowrk thread 可能会收到 301 中状态码, 此时会告诉 UI thread 资源被重定向, UI thread 重新发起请求。

  3. network thread 得到响应之后会根据响应数据的类型进行不同的处理, 如果响应的数据是 html 文件, network thread 会将 html 数据传递给 renderer process 。如果是 zip 文件这种就会传递数据给下载管理器。如果不符合浏览器的安全策略,如发生了跨域此时就会报错,不需要调用 renderer process。

  4. 如果 network thread 认为浏览器需要跳转至对应的页面, 它会告诉 UI thread 数据已经准备好, UI thread 会找一个 renderer process 并传递数据。 在 network thread 请求数据的同时, UI thread 就已经开始请求 renderer process 了, 在 network thread 告诉 UI thread 数据已经获取时, renderer process 已经待命了。

  5. 由于 UI thread 是属于 browser process 的, 与 renderer process 通信需要通过 IPC , 使用 stream 的方式传递 html 数据。 一旦 browser process 接收到 renderer process 已经 commit 的确认之后结束导航阶段, 开始文档加载阶段。

  6. 此时,地址栏会更新,展示出新页面的网页信息。history tab 会更新,可通过返回键返回导航来的页面,为了让关闭 tab 或者窗口后便于恢复,这些信息会存放在硬盘中。。

  7. 当 renderer process 渲染页面结束之后, 它会通过 IPC 通知 browser process 结束loading,此处也是 onload 事件发生的地方。至此,整个流程结束。

renderer process 的调度过程

renderer process 负责对应 tab 页的渲染, 一般会包含 a main thread, a raster thread, a compositor thread, 如果使用了 web worker 或者 service worker , 还会有 worker threads .

上图展示了一帧的步骤, 有的步骤不是每帧都会执行。

renderer process 处理过程

  1. renderer process 在接收 html 数据时, main thread 将 html 字符串解析成 DOM。

  2. 如果在解析 html 元素时遇到了 img、link 标签时,preload scanner 会在 html 解析器解析到 tokens 时, 就告诉 browser process 的 network thread 请求对应资源。

  3. 如果 html 解析器遇到了 script 标签, main thread 会停止解析 html , 去加载解析并执行 js 代码。 因为 js 可能会改变 DOM 结构,如果 js 改变了 DOM 结构,那么就需要在 js 之后重新构成 DOM 结构。对于不改变 DOM 结构的 js 我们可以使用 defer 或者 async 属性。

  4. main thread 解析 css , 计算每个 node 节点的样式。

  5. 通过遍历 DOM 和每个元素的样式计算, 会得出 DOM 树和 css 树。

  6. 使用 DOM 树和 css 树生成 layout 树, layout 树与 DOM 树类似,但是不包含 display:none 这种不展示的元素。

  7. 即使知道了不同元素的位置及样式信息,我们还需要知道不同元素的绘制先后顺序才能正确绘制出整个页面。在绘制阶段,主线程会遍历布局树以创建绘制记录。绘制记录可以看做是记录各元素绘制先后顺序的笔记。

  8. 在获取了所有需要的信息之后, 浏览器使用合成技术来绘制每帧。合成技术会将一个页面分割成不同的层, 并对每层进行光栅化, 最终在 compositor thread 中重新组合。

  9. 在分层时, main thread 会遍历 layout 树创建 layer 树(即 update layer tree)。 添加了 will-change CSS 属性的元素,会被看做单独的一层。

  10. 一旦 layer 树被创建,渲染顺序被确定,主线程会把这些信息通知给 compositor thread, compositor thread 会栅格化每一层。有的层的可以达到整个页面的大小,因此,合成器线程将它们分成多个磁贴,并将每个磁贴发送到 raster threads,raster threads 会栅格化每一个磁贴并存储在 GPU 显存中。

  11. 一旦磁贴被光栅化,合成器线程会收集称为绘制四边形的磁贴信息以创建合成帧。 compositor thread 随后会通过 IPC 消息传递给 browser process ,由于浏览器的 UI 改变或者其它插件的渲染进程也可以添加合成帧,这些合成帧会被传递给 GPU 用以展示在屏幕上,如果滚动发生,合成器线程会创建另一个合成帧发送给 GPU。

合成器的优点:

其工作无关主线程,合成器线程不需要等待样式计算或者 JS 执行,这就是为什么合成器相关的动画 最流畅,如果某个动画涉及到布局或者绘制的调整,就会涉及到主线程的重新计算,自然会慢很多。

性能优化策略

在了解了浏览器如何通过多进程世界来管理各个标签页和插件之后,我们不难发现,每一次点击和每一次页面刷新背后,都有着复杂的进程和线程在默默工作。而正是这些看似隐秘的机制,为我们打开了性能优化的大门。如果我们能够精准地把握这些进程和线程的工作原理,就能够从根本上提升浏览器的性能,减少资源的浪费。以下根据上面的知识点列举常见的优化手段。

  1. 优化多进程资源管理 :考虑target="_blank"的使用场景,合理控制开启的标签页数量,防止浏览器消耗过多内存资源。

  2. 优化网络请求处理:对于常访问的网站启用服务工作线程(Service Workers)进行内容缓存,减少网络请求次数,提高加载速度。

  3. 使用智能加载技术:对于大型网页中的图片和资源采用懒加载(Lazy Loading)技术,只加载进入视口(Viewport)的资源,减少初次加载时间。

  4. 优化渲染流程

    • 利用CSS的will-change属性,为可能发生变化的元素创建独立的图层,优化动画和转换(Transforms)的性能。
    • 避免触发重排(Reflow)和重绘(Repaint),特别是在动画或滚动过程中。
    • 使用Web Workers移出主线程的复杂计算,避免阻塞页面渲染。
  5. 优化JavaScript执行

    • 尽量使用异步脚本加载(Async)或延迟脚本(Defer),减少对DOM构建的阻塞。
    • 避免在网页加载过程中执行大量或复杂的JavaScript代码,以减少对渲染进程的影响。
    • 对代码进行分割(Code Splitting),按需加载,减少单次加载的代码量。
  6. 优化CSS计算:精简和优化CSS选择器,减少浏览器对CSS样式解析和计算的负担。

  7. 合理使用缓存策略:合理设置HTTP缓存头部,对静态资源进行缓存,减少不必要的网络请求。

  8. 优化合成器线程(Compositor Thread)工作

    • 确保合成图层的数量最优化,避免过多无必要的图层分割。
    • 利用硬件加速,尽可能将图形计算交给GPU,减轻CPU的负担。

参考文章:

相关推荐
小小竹子6 分钟前
前端vue-实现富文本组件
前端·vue.js·富文本
小白小白从不日白15 分钟前
react hooks--useReducer
前端·javascript·react.js
下雪天的夏风27 分钟前
TS - tsconfig.json 和 tsconfig.node.json 的关系,如何在TS 中使用 JS 不报错
前端·javascript·typescript
diygwcom39 分钟前
electron-updater实现electron全量版本更新
前端·javascript·electron
Hello-Mr.Wang1 小时前
vue3中开发引导页的方法
开发语言·前端·javascript
程序员凡尘1 小时前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七5 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦7 小时前
JavaScript substring() 方法
前端
无心使然云中漫步7 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者7 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js