浏览器结构及工作原理(前端浏览器工作原理)

可能每个前端工程师都想了解浏览器的工作原理。我们想知道从在浏览器地址栏输入网址到显示页面,短短几秒钟内浏览器做了什么;我们想知道为什么平时经常听到的各种代码优化方案都能起到优化的作用;我们希望了解更多关于浏览器的渲染过程。浏览器的多进程架构一个好的程序往往被分成几个独立又相互协作的模块,浏览器也是如此。以Chrome为例,它由多个进程组成,每个进程都有自己的核心职责。它们相互配合完成浏览器的整体功能,而每个进程又包含多个线程,一个进程中的多个线程也会协同工作,完成各自进程的职责。对于一些前端开发的学生来说,进程和线程的概念可能有点模糊。为了更好地理解浏览器的多进程架构,这里我们简单讨论一下进程和线程。进程(Process)和线程(thread)
进程就像一个有界的工厂,而线程就像工厂里的员工,他们可以做自己的事情,也可以相互合作做同样的事情。当我们启动一个应用程序时,计算机会创建一个进程,操作系统会为该进程分配一部分内存,应用程序的所有状态都会保存在这个内存中。应用程序还可以创建多个线程来协助其工作,这些线程可以共享这部分内存中的数据。如果应用程序被关闭,进程将被终止,相关的内存将被操作系统释放。比较形象的图示如下:一个进程也可以要求操作系统生成另一个进程来执行不同的任务,系统会为新的进程分配独立的内存。IPC(进程间通信)可用于两个进程之间的通信。许多应用将采用这种设计。如果一个正在工作的进程响应缓慢,重启这个进程不会影响其他应用程序的工作。如果你对进程和线程的理解还有疑问,可以参考下面这篇文章:http://www.ruanyifeng.com/blog/2013/04/processes _和_ threads.html浏览器架构有了以上知识作为基础,我们就可以更合理的讨论浏览器架构了。其实我们要开发一个浏览器,可以是单进程多线程应用,也可以是IPC通信。
不同的浏览器架构模型不同的浏览器采用不同的架构模型,这里没有标准。本文以Chrome为例来说明:Chrome采用多进程架构,在其顶层有一个浏览器进程来协调浏览器的其他进程。
具体来说,Chrome的主要进程及其职责如下:浏览器进程:
负责地址栏、书签栏、前进后退按钮等的工作。处理浏览器一些不可见的底层操作,比如网络请求、文件访问;渲染器进程:
在一个标签插件过程中负责所有与网页展示相关的事情:
控制一个网页中使用的所有插件,比如flashGPU Process,它处理GPU相关的任务。
Chrome还为我们提供了“任务管理器”,可以让我们方便地查看当前浏览器中运行的所有进程,以及每个进程占用的系统资源。右键单击可查看更多类别信息。相关面板可以通过“页面右上角的三个点-更多工具-任务管理器”打开。Chrome多进程架构的优缺点优点:一个渲染进程出现问题不会影响其他进程。更安全,在系统层面限制不同进程的权限。缺点:由于不同进程的内存不共享,不同进程的内存往往需要包含相同的内容。为了节省内存,Chrome限制了进程的最大数量,这是由设备的内存和CPU容量决定的。当达到此限制时,新打开的选项卡将共享同一站点的先前渲染过程。在Chrome中测试打开知乎主页后,可以在Mac i5 8g上启动40多个渲染进程,然后新打开的标签页会合并到现有的渲染进程中。把Chrome浏览器中不同程序的功能想象成服务,可以很方便的分成不同的进程,也可以合并成一个进程。以Broswer过程为例。如果Chrome运行在强大的硬件上,会把不同的服务拆分成不同的进程,这样Chrome的整体运行会更加稳定。但如果Chrome运行在资源较差的设备上,这些服务会合并到同一个进程中,这样可以节省内存。示意图如下。iframe的渲染——站点隔离在上面的流程图中,我们也可以看到有些流程中存在子帧,这是站点隔离机制的结果。Chrome 67默认启用站点隔离机制。这种机制允许同一选项卡下的跨站点iframe由单独的进程呈现,这样更安全。
Iframe将采用不同的渲染过程。站点隔离被认为是一个里程碑式的功能,它的成功实现是多年工程努力的结果。站点隔离不是简单地叠加多个流程。这个机制改变了底层iframe之间的通信方式,Chrome的其他功能也需要相应调整。比如devtools需要相应的支持,甚至Ctrl F也需要支持。关于站点隔离的更多信息,请参考以下链接:https://developers . Google . com/web/updates/2018/07/Site-Isolation。介绍完浏览器的基本架构模式,我们再来看看浏览器在一个常见的导航过程中发生了什么。导航过程怎么了?也许大多数人使用Chrome最多的场景就是在地址栏输入关键词进行搜索或者输入地址导航到某个网站。我们来看看浏览器是如何看待这个过程的。我们知道,浏览器选项卡之外的任务主要由浏览器进程控制,浏览器进程进一步划分了这些任务,并使用不同的线程来处理它们:
UI线程:控制浏览器上的按钮和输入框;网络线程:处理网络请求,从互联网获取数据;存储线程:控制文件的访问等。
主浏览器进程中的不同线程回到我们的问题。当我们在浏览器的地址栏输入文本,点击回车获取页面内容时,在浏览器的视图中过程可以分为以下几个步骤:1。处理输入UI线程需要判断用户输入的是URL还是查询;2.开始导航。当用户点击回车键时,UI线程通知网络线程获取网页内容,并控制选项卡上的微调器显示,表示正在加载。网络线程执行DNS查询,然后为请求建立TLS连接。
UI线程通知网络线程加载相关信息。如果网络线程收到重定向请求头比如301,网络线程会通知UI线程服务器请求重定向,然后会触发另一个URL请求。3.读取响应当请求响应返回时,网络线程会根据内容类型和MIME类型嗅探来判断响应内容的格式。
确定响应内容的格式。如果响应内容的格式是HTML,下一步将是将这些数据传输到呈现器进程。如果是zip文件或其他文件,相关数据将被传输到下载管理器。此时也将触发安全浏览检查。如果域名或请求的内容与已知的恶意网站相匹配,网络线程将显示一个警告页面。此外,CORB检测将触发,以确保敏感数据不会被传递到渲染过程。
4.找到渲染过程。当以上所有检查完成后,网络线程确定浏览器可以导航到请求的网页,网络线程会通知UI线程数据准备好了,UI线程会找一个渲染器进程渲染网页。
UI线程收到网络线程返回的数据后,寻找相关的渲染进程。因为网络请求得到响应需要时间,所以这里其实有一个加速方案。当UI线程向网络线程发送URL请求时,浏览器实际上知道它将导航到哪个站点。UI线程会提前并行搜索并启动一个渲染过程。如果一切正常,当网络线程接收到数据时,渲染进程将准备好,但如果遇到重定向,准备好的渲染进程可能不可用。此时,有必要重新开始新的渲染过程。5.确认导航经过以上流程,数据和渲染流程都可以使用。浏览器进程将向渲染器进程发送IPC消息以确认导航。一旦浏览器进程从呈现器进程接收到呈现确认消息,导航进程结束,页面加载进程开始。此时地址栏会更新显示新页面的网页信息。“历史记录”选项卡将被更新,您可以通过后退键返回到导航页面。为了便于在关闭标签或窗口后恢复,这些信息将被存储在硬盘中。
6.附加步骤一旦导航被确认,渲染器进程将使用相关资源来渲染页面。下面我们将重点介绍渲染过程。当渲染器进程完成渲染时(渲染结束是指该页面中的所有页面,包括所有iframe,触发onload),IPC信号会发送到浏览器进程,UI线程会停止在tab中显示spinner。
渲染器进程发送IPC消息,通知浏览器进程页面已加载。当然,以上过程只是网页第一帧的渲染。之后,客户端仍然可以下载额外的资源来呈现新的视图。这里我们可以明确一点,所有的JS代码实际上都是由renderer进程控制的,所以很多时候,在浏览web内容的过程中,不会涉及到其他进程。不过你可能听过beforeunload事件,这个事件再次涉及到浏览器进程和渲染器进程的交互。当前页面关闭时(标签页关闭,刷新等。),浏览器进程需要通知渲染器进程检查并处理相关事件。
浏览器向渲染进程发送IPC消息,通知它正在离开当前网站。如果导航是由渲染器进程触发的(例如,当用户点击一个链接,或者JS执行window . location=\ ‘ 3358newsite.com \ ‘)渲染器进程将首先检查是否有beforeunload事件处理程序,导航请求由渲染器进程传递给浏览器进程。如果您导航到一个新的网站,将启用一个新的呈现进程来处理新页面的呈现,而旧的进程将继续处理卸载等事件。有关页面生命周期的更多信息,请参考页面生命周期API。
浏览器向新的渲染进程发送IPC消息,通知新的页面被渲染,同时通知旧的渲染进程卸载。除了上面的进程,有些页面还有Service Worker(服务工作线程),让开发者对本地缓存以及何时从网络获取信息有更多的控制权。如果服务工作者被设置为从本地缓存加载数据,那么就不需要从网络获取更多数据。值得注意的是,service worker也是渲染过程中运行的JS代码,所以对于有Service Worker的页面,上述过程略有不同。当服务工作者被注册时,其范围将被保存。当有导航时,网络线程将检查注册服务工作者范围内的相关域名。如果有对应的服务工作者,UI线程会找到一个渲染器进程来处理相关代码。服务工作者可以从缓存加载数据,从而终止对网络的请求,或者从网络请求新数据。
服务人员根据具体情况处理。有关服务人员的更多信息,请参考:https://developers . Google . com/web/foundations/primers/service-workers/life cycle。如果服务人员最终决定在线获取数据,浏览器进程和渲染器进程之间的交互实际上会延迟数据的请求时间。导航预加载是一种与服务工作者并行加速资源加载的机制。服务器可以通过请求头识别这样的请求,并做出相应的处理。更多信息请参考:3359 developers . Google . com/web/updates/2017/02/navigation-preload渲染过程是如何工作的?渲染进程几乎负责Tab中的一切,渲染进程的核心目的是将HTML CSS JS转化为用户可以交互的网页。渲染过程主要包括以下线程:
渲染进程1中包含的线程。主线程2。工作线程3。作曲线程4。光栅线程我们将逐步介绍不同线程的职责。在此之前,我们先来看看渲染过程。1.构建DOM当渲染进程收到导航的确认信息并开始接受HTML数据时,主线程会将文本字符串解析成DOM。将html渲染成DOM的方法是由HTML标准定义的。2.加载二次元资源网页往往包含图片、CSS、JS等附加资源。这些资源需要从网络或缓存中获取。主进程可以在构建DOM的过程中逐个请求它们。为了加速预加载扫描仪,它将同时运行。如果html中有相等的标签,预加载扫描器会将这些请求传递给浏览器进程中的网络线程,以下载相关资源。3.3的下载和执行。应满足JS
呈现过程的主线程计算每个元素节点的最终样式值。5.获取布局如果想要渲染一个完整的页面,除了要知道每个节点的具体样式之外,还需要知道每个节点在页面上的位置。布局其实就是寻找所有元素的几何关系的过程。具体过程如下:通过遍历DOM和相关元素的计算样式,主线程会构建一个布局树,包含每个元素的坐标信息和盒子大小。布局树类似于DOM树,但是它只包含页面上可见的元素。如果元素设置了display:none,则该元素不会出现在布局树上。虽然伪元素在DOM树上是不可见的,但是它们在布局树上是可见的。
6.绘制每个元素即使我们知道不同元素的位置和样式信息,我们仍然需要知道不同元素的绘制顺序,才能正确绘制整个页面。在绘图阶段,主线程遍历布局树以创建绘图记录。画图可以看作是一个笔记,记录每个元素的画图顺序。
主线程根据布局树构建绘图记录。7.复合框架熟悉PS等绘图软件的童鞋当然熟悉图层的概念。现代Chrome其实就是用这个概念来组合不同的图层。复合(Composite)是一种将页面分成不同层,分别进行栅格化,然后组合成帧的技术。不同层的组合由合成器线程(合成器线程)完成。主线程将遍历布局树以创建层树,具有will-change CSS属性的元素将被视为单独的层。
主线程遍历布局树以生成层树。您可能希望在每个元素中添加will-change,但是合并太多图层可能比在每个帧中栅格化页面的一些小部分要慢。为了更合理的使用层数,请参考只有合成器和管理层层数的属性。一旦层树被创建并且渲染顺序被确定,主线程将通知合成器线程这个信息,合成器线程将光栅化每个层。有些图层可以达到整个页面的大小,所以合成器线程将它们分成多个图块,并将每个图块发送到光栅线程,光栅线程将每个图块光栅化并存储在GPU内存中。
光栅线程光栅化每个图块,并将其存储在GPU内存中。一旦拼贴被光栅化,合成器线程就收集称为绘制四边形的拼贴信息来创建合成帧。然后,复合帧将通过IPC消息传递给浏览器进程。因为浏览器的UI改变或者其他扩展的渲染过程也可以添加复合帧,这些复合帧将被传递到GPU以在屏幕上显示。如果发生滚动,合成器线程将创建另一个合成帧,并将其发送到GPU。
合成器线程向GPU渲染合成器发送合成帧的好处是,其工作与主线程无关,合成器线程不需要等待样式计算或JS执行,这也是合成器相关动画最流畅的原因。如果一个动画涉及到布局或者画图的调整,就会涉及到主线程的重算,自然会慢很多。浏览器处理事件。浏览器处理不同的事件以满足各种交互需求。在这一部分,我们来看看从浏览器的角度来看什么是事件。这里,我们主要考虑鼠标事件。在浏览器的视图中,用户的所有手势都是输入、鼠标滚动、悬停、点击等等。当用户在屏幕上触发触摸等手势时,首先接收手势信息的是浏览器进程。但是,浏览器进程将只感知手势发生的位置,以及tab中内容的处理是否由呈现进程控制。当事件发生时,浏览器进程将事件类型及其相应的坐标发送给呈现进程,呈现进程然后找到事件对象并执行绑定到它的所有相关事件处理函数。
从事件浏览器进程到渲染进程,之前我们提到过,合成器可以独立于主线程在合成光栅化层中平滑滚动。如果页面中没有绑定相关事件,合成器线程可以独立于主线程创建合成帧。如果页面绑定到相关的事件处理程序,主线程将必须工作。这个时候合成器线程会做什么?这里涉及到一个技术术语“理解非快速可滚动区”。由于JS执行是主线程的工作,所以在合成页面时,合成器线程会将与事件处理程序绑定的区域标记为非快速可滚动区域。如果有这个标记,合成器线程将把这里发生的事件发送给主线程。如果这些区域中没有发生事件,合成器线程将直接合成新的帧,而不等待主线程的响应。
对于涉及非快速滚动区的事件,合成器线程会通知主线程来处理。web开发中常用的事件处理模式是事件委托。基于事件冒泡,我们经常在顶层绑定事件:复制代码。
document . body . addevent listener(‘ touch start ‘,event={ if(event . target===area){ event . prevent default();}});上面的做法很常见,但是如果从浏览器上看,整个页面就变成了非快速滚动区。这意味着即使操作是在页面未绑定事件处理程序的区域,合成器线程每次输入都需要与主线程通信并等待反馈,独立处理合成帧的平滑合成器模式就失效了。
因为事件被绑定在顶部,所以整个页面变成了非快速可滚动区域。为了防止这种情况,我们可以将passive: true作为参数传递给事件处理程序,这样写不仅可以让浏览器监听相关事件,还可以让合并器线程在等待主线程响应之前构建一个新的合并帧。复制代码
document . body . addevent listener(‘ touch start ‘,event={ if(event . target===area){ event . prevent default()} },{ passive : true });不过上面的写法可能会带来另一个问题,假设你只想在某个区域水平滚动。使用passive: true可以实现平滑滚动,但是垂直滚动可能发生在event.preventDefault()之前,可以通过event.cancelable. Copy代码来阻止
document . body . addevent listener(‘ pointer move ‘,event={ if(event . cancelable){ event . prevent default();//阻止原生滚动/* *在这里做你想让应用做的事情*/} },{ passive : true });还可以使用css属性touch-action来完全消除事件处理程序的影响,比如复制代码。
# area { touch-action : pan-x;}找到事件对象。当合并器线程向主线程发送输入事件时,主线程将首先执行命中测试,以找到相应的事件目标。点击测试将根据渲染过程中生成的绘制记录来查找事件坐标下存在的元素。
主线程根据绘图记录搜索事件相关元素。事件的优化一般我们屏幕的刷新率是60fps,但是有些事件的触发量会超过这个值。出于优化的目的,Chrome会合并连续的事件(如wheel、mousewheel、mousemove、pointer move、touchmove)并延迟执行,直到下一帧渲染完成。并且诸如keydown、keyup、mouseup、mousedown、touchstart和touchend之类的不连续事件将被立即触发。
Chrome会将连续的事件合并到下一个帧触发器中。虽然合并事件可以提升性能,但是如果你的应用是绘画等,就很难画出平滑的曲线。此时,您可以使用getCoalescedEvents API来获取组合事件。代码如下:复制代码。
window . addevent listener(‘ pointer move ‘,event={ const events=event . getcoalescedevents();for(let events of events){ const x=event . pagex;const y=event.pageY//使用x和y坐标画一条线。}});
我花了很长时间整理以上内容,整理的过程还是蛮有收获的。也希望这张纸条能给你启发。如果你有任何问题,请过来讨论。本文经作者授权转载。原文链接是:https://zhuanlan.zhihu.com/p/47407398参考链接。
https://developers . Google . com/web/updates/2018/09/inside-browser-part 1https://developers . Google . com/web/updates/2018/09/inside-browser-part 2https://developers . Google . com/web/updates/2018/09/inside-browser-part 4https://www . html 55

其他教程

车载信息娱乐系统开发(汽车车载娱乐系统)

2022-9-2 1:58:15

其他教程

探索与决策(“探索”还是“利用”?这是我们每天面临的隐藏决策)

2022-9-2 2:00:24

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索