这是来自谷歌官方博客: inside modern browser 的四篇系列文章,也有人作了全文翻译,参见承香墨影。本文可看做一个abstract,对全文进行了重点摘要,以期读完后对chrome浏览器的体系结构,处理机制有整体的了解。有个经典的前端面试问题:在导航栏输入url敲下回车后发生了什么?本文可以看做一个比较全面、深入一点的回答。
文章目录
浏览器体系结构
- CPU 一个接一个地处理多项任务
- GPU 同时、跨内核处理多个简单任务
开启application时,机器的物理硬件在最底层,中间是操作系统,最上层才是应用本身。
浏览器没有一定之规。有的是单进程多线程,有的是多进程,一个进程有少量的多个线程IPC。
进程 | 功能 |
---|---|
Browser | controls ‘chrome’ part of the app and privileged parts such as network requests and file access |
Renderer | anything inside of the tab where a website is displayed |
Plugin | any plugins used by the web like flash |
GPU | Handles GPU tasks in isolation from other processes. It is separated into different process because GPUs handles requests from multiple apps and draw them in the same surface. |
- 出于安全和沙箱考虑,每个tab有独立的renderer process
- 因为每个进程有独立的内存空间,这会造成一定的资源浪费(can’t share common infrasructure like v8), chrome限制了它可以启动的进程数量。如果达到了限制,则会把多个tab上那些同源的网页合并到一个进程中。
- chrome 正在做的事情是,把浏https://www.cnblogs.com/peida/archive/2013/03/05/2943698.html览器程序的每个部分作为一项可拆分的服务。当 Chrome 在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程,从而提供更高的稳定性,但如果它位于资源约束的设备上,Chrome 会将服务整合到一个进程中,从而节省内存占用。
- 整合进程以节省资源的策略已经被用于其他平台,如andriod上。
每个frame有独立渲染进程,站点隔离
导航过程
tab之外的一切都是浏览器进程控制。浏览器进程包括:
线程 | 功能 |
---|---|
UI线程 | 绘制按钮和输入框 |
网络线程 | 处理网络请求和堆栈 |
存储线程 | 控制对文件的读取 |
输入url是由UI线程控制的
- 处理输入: isSearchInput? to search engine : to the site you requested
- 开始导航: UI线程控制loading spinner, 网络线程建立连接
- 读取res:网络线程,response’s Content-Type header不一定可信,嗅探MIME TYPE判断响应体的格式,安全检查,CORB。
- html ----> 交给renderer进程渲染
- zip ----> 下载管理器download
- UI线程找到一个渲染进程,优化:在步骤2的同时,UI线程主动去找或者开启一个渲染进程。
- commit navigation:浏览器进程和渲染进程IPC,完成导航。地址栏、session 历史更新。
导航commit后,渲染进程结束渲染,所有onload事件都触发、执行完毕后,IPC告诉浏览器进程,UI线程停止loading spinner
导航到别的站点前,会处理所有的beforeunload事件
新导航会新开一个渲染进程,老的渲染新城处理unload事件等
- service worker:在渲染进程中运行,从缓存中加载资源
- 导航预加载:加载资源和开启service worker同时进行
渲染进程
渲染进程包括:
- 主线程
- service worker/web worker 线程
- compositor thread
- raster thread
link, img 会preload, 但script 会阻塞html继续解析,直至script被执行完
-
async/defer
-
js module
-
< link rel='proload >
-
先计算每个node 的样式,主线程再算出整体的布局树,包括x,y 坐标和盒模型的大小
-
layout tree 类似dom tree,但只包含页面可见部分的结构。display: none 会被忽略。visibility: hidden不会,伪元素如果有content也不会。
-
这之后要决定绘制的顺序(层次)。z-index。主线程会生成paint records,一条条记录绘制的顺序。
-
repaint 很费cpu。 大多数浏览器刷新页面的速度是60次/秒。最佳实践是把js操作分成小粒度,用requestAnimationFrame()来调用。
-
组合。turn all above information into pixels(rasterizing/位图化)。
-
css: will-change 提醒浏览器该元素要分成单独的层。
-
compositor frame 被提交给浏览器进程。如果滚动,compositor thread 会生成一个新的compositor frame 给GPU。
componsitor处理输入交互
对浏览器而言,任何用户的gesture都属于input
- 浏览器进程先获取到gesture, 再把event的类型和坐标发给渲染进程
- 渲染进程找到事件的目标,and run attached event listeners.
- compositor thread marks ‘non-fast scrollable region’, a region of the page that has event handlers attached. 如果事件在该区域发生了,compositor thread就发送input event 给主线程。如果是在其他区域有input, compositor thread 继续组装新的frame,不用等main thread.
- 事件委托可能会不必要地扩大了non-fast scrollable region, 造成平滑滚动效果被减弱。想要暗示浏览器的组装器继续组装而不是等待主线程,可以这么做:
// 但实际上,chrome会报错说can't prevent default inside passive...建议直接用css: touch-action来改。
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
- 离散的事件立即分发,连续事件如wheel, mousewheel, mousemove, pointermove, touchmove 会延迟分发,卡在requestAnimationFrame的每一帧变换上。(input事件的传达速度多在60fps以上,这是一种优化)
- getCoalescedEvents 来获取这些被合并的事件
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
})