经典题目:从输入URL到页面呈现发生了什么
8/18/2021 • ☕️ 2 min read
chrome浏览器是多进程架构,最上层的是浏览器进程(标签页外面的一切都由浏览器进程处理),然后是实用程序进程、渲染进程、GPU进程、插件进程。
所以,当我们在地址栏输入aminer.cn
导航
-
浏览器进程下面的UI线程去绘制浏览器的按钮和地址栏
-
UI线程会判断用户输入的是查询字符串还是URL
-
如果输入的是URL,UI线程会通知网络线程发起网络调用,获取网站内容(此时会先检查强缓存,如果命中直接返回网站内容)
-
网络线程进行DNS查询(如果之前解析过这个域名,则直接命中缓存)、建立TLS连接(对于HTTPS)(如果返回301此时网络线程会跟UI线程沟通,再对另一个URL发送请求)
-
通过三次握手建立 TCP 连接(即总共发送3个数据包确认已经建立连接)建立客户端和服务器之间的连接。然后进行数据包发送,接收方接收到数据包后必须要向发送方确认,否则会吃重复发送数据包,最后通过四次挥手断开链接
-
构建请求行、请求头和请求体,发送 HTTP 请求
-
收到响应数据,网络线程会检查接收到的前几个字节。响应的Content-Type头部应该包含数据类型,如果没有这个字段,则需要MIME类型嗅探
-
如果是一个zip文件或其他文件,那就意味着是一个下载请求,需要把数据传给下载管理器。
-
如果响应是HTML文件,要进行“安全浏览”检查,所有查检完毕,网络线程确认浏览器可以导航到用户请求的网站,于是会通知UI线程数据已经准备好了。UI线程会联系渲染器进程渲染网页。
-
提交导航。数据和渲染器进程都有了,就可以通过IPC从浏览器进程向渲染器进程提交导航。渲染器进程也会同时接收到不间断的HTML数据流。当浏览器进程收到渲染器进程的确认消息后,导航完成,文档加载阶段开始。
-
此时,地址栏会更新,安全指示图标和网站设置UI也会反映新页面的信息。当前标签页面的会话历史会更新,后退/前进按钮起作用。为便于标签页/会话在关闭标签页或窗口后恢复,会话历史会写入磁盘。
-
提交导航之后,渲染器进程将负责加载资源和渲染页面(具体细节后面介绍)。而在“完成”渲染后(在所有iframe中的onload事件触发且执行完成后),渲染器进程会通过IPC给浏览器进程发送一个消息。此时,UI线程停止标签页上的旋转图标。
渲染
-
构建DOM。渲染器进程收到导航的提交消息后,开始接收HTML,其主线程开始解析文本字符串(HTML),并将它转换为DOM(Document Object Model,文档对象模型)。
-
加载子资源。网站都会用到图片、CSS和JavaScript等外部资源。浏览器需要从缓存或网络加载这些文件。主线程可以在解析并构建DOM的过程中发现一个加载一个,但这样效率太低。为此,Chrome会在解析同时并发运行“预加载扫描器”,当发现HTML文档中有
或时,预加载扫描器会将请求提交给浏览器进程中的网络线程。
-
JavaScript可能阻塞解析。如果HTML解析器碰到标签,会暂停解析HTML文档并加载、解析和执行JavaScript代码。defer与async因为JavaScript有可能通过document.write()修改文档,进而改变DOM结构
-
主线程要解析CSS并计算每个DOM节点的样式
-
主线程会遍历DOM元素及其计算样式,然后构造一棵布局树,这棵树的每个节点将带有坐标和大小信息。布局树与DOM树的结构类似,但只包含页面中可见元素的信息。如果元素被应用了display: none,则布局树中不会包含它(visibility: hidden的元素会包含在内)。类似地,通过伪类p::before{content: 'Hi!'}添加的内容会包含在布局树中,但DOM树中却没有。
-
主线程会遍历布局树并创建绘制记录。绘制记录(绘制顺序)是对绘制过程的注解,比如“先画背景,然后画文本,最后画矩形”
-
主线程会遍历布局树并创建分层树,然后主线程就会把这些信息提交给合成器线程。合成器线程接下来负责将每一层转换为像素——栅格化。一层有可能跟页面一样大,此时合成器线程会将它切成小片(tile),再把每一片发给栅格化线程。栅格化线程将每一小片转换为像素后将它们保存在GPU的内存中。will-change则是用来提醒浏览器该分层了
-
合成器线程会安排栅格化线程优先转换视口(及附近)的小片。而构成一层的小片也会转换为不同分辨率的版本,以便在用户缩放时使用。
-
创建好的合成器帧会通过IPC提交给浏览器进程。与此同时,为更新浏览器界面,UI线程可能还会添加另一个合成器帧;或者因为有扩展,其他渲染器进程也可能添加额外的合成器帧。所有这些合成器帧都会发送给GPU,以便最终显示在屏幕上。如果发生滚动事件,合成器线程会再创建新的合成器帧并发送给GPU。
交互
-
当用户交互比如触摸事件发生时,浏览器进程首先接收到该手势。但是,浏览器进程仅仅知道手势发生在哪里,因为标签页中的内容是渲染器进程处理。因此浏览器进程会把事件类型(如touchstart)及其坐标发送给渲染器进程。渲染器进程会处理这个事件,即根据事件目标来运行注册的监听程序。
-
非快速滚动区”(non-fast scrollable region)。我们知道,运行JavaScript是主线程的活儿。在页面合成后,合成器线程会给附加了事件处理程序的页面区域打上“Non-Fast Scrollable Region”的记号。有了这个记号,合成器线程就可以在该区域发生事件时把事件发送给主线程。