前言本文是作者翻译了MarioKosaka写的系列文章insidelookatmodernwebbrowser。这里的翻译并不是指直译,而是根据个人的理解来表达作者想表达的意思,并且会尽量补充一些相关的内容,帮助大家更好的理解。导航时发生的情况本文是探讨Chrome内部工作原理的四部分系列文章中的第二篇。在上一篇文章中,我们了解了不同的进程或线程如何负责浏览器的不同部分。的。在本文中,我们将深入探讨每个进程和线程如何通信和协作来为我们呈现网站内容。我们来看一个最简单的用户浏览网页的场景:你在浏览器的导航栏中输入一个URL,然后按下回车键,浏览器就会从互联网上获取相关数据并显示该网页。在本文中,我们将重点关注从网站请求数据的这种简单场景,以及浏览器为准备呈现页面所做的工作-即导航过程。一切都从浏览器进程开始。我们在上一篇文章CPU、GPU、内存和多进程架构中提到,浏览器中选项卡之外发生的一切都是由浏览器进程控制的。浏览器进程有很多线程(workerthreads)负责不同的任务,包括绘制浏览器顶部按钮和导航栏输入框等组件的UI线程(UIthread),网络线程(networkthread)管理网络请求,以及控制文件读写的存储线程(storagethread)等。当你在导航栏中输入一个URL时,UI线程实际上正在处理你的输入。UI、网络和存储线程属于浏览器进程
一个简单的导航步骤1:处理输入当用户开始在导航栏上输入时,UI线程(首先是UIthread做的就是问:“你输入的字符串是一些搜索关键字(searchquery)还是一个URL地址?”。因为对于Chrome浏览器来说,导航栏中输入的可能是可以直接请求的域名或者关键字用户想要在搜索引擎(如谷歌)中搜索的信息,所以当用户在导航栏中输入信息时,UI线程要进行一系列的分析,以确定是将用户输入发送给搜索引擎,还是直接发送给搜索引擎请求您输入的站点资源。
UI线程正在询问输入的字符串是搜索关键字还是URL步骤2:开始导航当用户按下Enter键时,UI线程将调用网络线程(网络线程)发起网络请求以获取站点的内容。此时选项卡上会显示一个旋转的圆圈,表示正在加载资源,网络线程会针对请求进行DNS寻址、建立TLS连接等一系列操作。UI线程告诉网络线程跳转到mysite.com此时,如果网络线程收到服务器的HTTP301重定向响应,它会告诉UI线程重定向,然后它会再次发起新的网络请求。第三步:读取响应当网络线程收到HTTP响应的body(payload)流时,如果有必要,它会先检查流的前几个字节,以确定响应body的具体媒体类型(MIMEType).响应体的媒体类型一般可以通过HTTP头的Content-Type来确定,但Content-Type有时会丢失或错误。在这种情况下,浏览器需要执行MIME类型嗅探以确定响应类型。MIME类型嗅探不是一件容易的事。可以从Chrome源码中的注释中了解到,不同的浏览器是如何根据不同的Content-Type来判断主题属于哪种媒体类型的。响应头有Content-Type信息,响应体有真实数据如果响应体是HTML文件,浏览器会渲染得到的响应数据进程(rendererprocess)进行下一步。如果获取到的响应数据是压缩文件(zip文件)或其他类型的文件,响应数据将交给下载管理器(downloadmanager)进行处理。网络线程询问响应数据是否是来自安全来源的HTML文件网络线程还将在将内容交给渲染进程之前对其执行安全浏览检查。如果请求的域名或响应的内容与已知的病毒站点相匹配,则网络线程将向用户显示警告页面。此外,网络线程还会进行CORB(CrossOriginReadBlocking)检查,确保敏感的跨站数据不会被发送到渲染进程。第4步:查找渲染器进程在网络线程完成所有检查并能够确定浏览器应该导航到请求的站点之后,它告诉UI线程所有数据都已准备就绪。UI线程收到网络线程的确认后,会为这个网站寻找渲染器进程(rendererprocess)来渲染界面。网络线程告诉UI线程找一个渲染进程渲染界面由于网络请求可能需要几百毫秒才能完成,为了缩短导航需要的时间,浏览器会在前面的一些步骤中做一些优化。比如第二步,当UI线程向网络线程发送URL链接时,实际上已经知道他们要导航到哪个站点,所以当网络线程工作时,UI线程会主动启动一个URL此网络请求的链接。渲染线程。如果一切顺利(没有重定向之类的),在网络线程准备好数据后,页面的渲染进程就准备好了,这样就节省了创建新渲染进程的时间。但是,如果像网站这样的东西被重定向到不同的站点,刚才的渲染进程就不能用了,就会被丢弃,重新启动一个新的渲染进程。第五步:提交(commit)当导航到这一步时,数据和渲染进程都准备好了,浏览器进程(browserprocess)会通过IPC告诉渲染进程提交本次导航(commitnavigation)。另外,浏览器进程也会将刚刚接收到的响应数据流传递给相应的渲染进程,以便继续接收传入的HTML数据。一旦浏览器进程收到渲染线程的回复,导航已经提交(commit),导航过程结束,文档加载阶段正式开始。此时,导航栏会更新,安全指示器和站点设置UI会显示与新页面相关的站点信息。当前选项卡的会话历史记录也会更新,这样当您点击浏览器的前进和后退按钮时,您也可以导航到刚刚导航到的页面。为了在您关闭标签页或窗口(window)时恢复当前标签页和会话(session)内容,当前会话历史记录将被保存在磁盘上。浏览器进程通过IPC向渲染进程发起页面渲染请求附加步骤:初始加载完成(Initialloadcomplete)当导航提交后,渲染进程开始加载资源并呈现页面。渲染进程渲染页面的具体细节我会在后面的系列文章中讲述。一旦渲染进程“完成”渲染,它会通过IPC通知浏览器进程(注意,当页面上所有框架的onload事件都已触发并且相应的处理程序已完成执行时,就会发生这种情况),然后UI线程将停止导航栏上的旋转圆圈。我这里用了“完成”这个词,因为客户端的JavaScript可以在后面继续加载资源和改变视图的内容。渲染进程通过IPC告诉浏览器进程页面已经加载完毕导航到不同的站点最简单的导航场景已经描述完毕!但是如果此时用户在导航栏中输入不同的URL会怎样呢?如果是,浏览器进程将重新执行前面的步骤,完成新站点的导航。但是,浏览器进程在做这些事情之前,需要让当前渲染页面做一些收尾工作,具体是询问当前渲染进程是否需要处理beforeunload事件。beforeunload可以给用户一个“Areyousureyouwanttoleavethecurrentpage?”的二次确认弹框。当用户重新导航或关闭当前选项卡时。之所以浏览器进程在重新导航时会与当前渲染进程确认,是因为当前页面发生的一切(包括页面的JavaScript执行)都不是由它控制的,而是由渲染进程控制的,所以它不知道具体情况里面是什么。注意:不要随便给页面添加beforeunload事件监听器。你定义的监听函数会在页面重新导航时执行,所以这会增加重新导航的延迟。beforeunload事件监听函数只有在非常必要的时候才可以加上,比如用户在页面输入了数据,数据会随着页面的消失而消失。浏览器进程通过IPC告诉渲染进程它将离开当前页面并导航到新页面如果在页面内部发起重新导航怎么办?例如,用户点击页面上的链接或客户端JavaScript代码执行window.location="https://newsite.com"等代码。在这种情况下,渲染进程会先检查自己是否有为beforeunload事件注册的监听函数,如果有则执行。执行后的情况与之前的情况没有区别。唯一不同的是这次的导航请求是由渲染器进程向浏览器进程发起的。如果是重新导航到不同的站点(differentsite),会启动另一个渲染进程来完成这次重新导航,而当前渲染进程会继续处理当前页面的一些收尾工作,比如执行卸载事件的侦听器函数。页面生命周期状态概述本文将介绍页面的所有生命周期状态,PageLifecycleAPI将教您如何在页面中监控页面状态变化。浏览器进程告诉新的渲染进程渲染新页面并告诉当前渲染进程完成工作ServiceWorker场景这个导航进程最近的一个变化是引入了service工人的概念。因为可以使用Serviceworker来编写网站的网络代理,开发者可以对网络请求有更多的控制权,比如决定哪些数据缓存在本地,哪些数据需要从网络中获取等。如果开发者设置当前页面内容从serviceworker中的缓存中获取,当前页面的渲染不需要重新发送网络请求,大大加快了整个导航过程。这里需要注意的重要一点是,serviceworker实际上只是在渲染过程中运行的一些JavaScript代码。那么问题来了,当导航开始时,浏览器进程如何判断要导航的站点中是否有对应的serviceworker,并启动一个渲染进程来执行呢?实际上,当serviceworker被注册时,它的范围就会被记录下来(你可以通过文章TheServiceWorkerLifecycle了解更多关于serviceworker的范围)。当导航开始时,网络线程会根据请求的域名在注册的ServiceWorker范围内搜索对应的ServiceWorker。如果有一个serviceworker访问了URL,UI线程将为serviceworker启动一个渲染器进程来执行它的代码。服务工作者可以使用以前缓存的数据或发起新的网络请求。网络线程收到导航任务后会寻找对应的serviceworkerUI线程会启动渲染进程运行找到的serviceworker代码,代码在渲染过程中具体是由工作线程(workerthread)执行的request,浏览器进程和渲染进程之间的来回通信,包括serviceworker的启动时间,实际上增加了页面导航的延迟。导航预加载是一种通过在serviceworker启动时并行加载相应资源来加速整个导航过程效率的技术。预加载资源的请求头会有一些特殊的标志,让服务器决定是向客户端发送全新的内容还是只向客户端发送更新的数据。UI线程会在启动渲染进程的同时并行发送网络请求来运行serviceworker代码总结在这篇文章中,我们讨论了导航和浏览中发生的一些技术解决方案浏览器以优化导航效率。在下一篇文章中,我们将详细了解浏览器如何解析我们的HTML/CSS/JavaScript来呈现网页内容。继续关注我的技术动向。我是一个咄咄逼人的大葱。关注我,和我一起进步,成为独立的全栈工程师!文章首发于:窥探现代浏览器架构(二)关注我的个人主页公众号,获取我的最新技术推送!