浏览器是如何运作的

浏览器是如何运作的

1.浏览器发展史

  • 1991年Berners Lee建立了第一代网络浏览器WorldWideWeb,只支持显示文本图片
  • 1993年Mosaic问世,同时支持显示文本和图像
  • 1994年网景浏览器发布 ,只能显示简单的静态html,没有js,css。同年出现Opera。
  • 1995年微软发布IE1.0,IE2.0,自此第一次浏览器大战正式打响
  • 1996年微软发布的IE3.0和window系统集成一起,网景份额此时占86%
  • 1998年网景开源了firefox火狐来迎击IE
  • 1999年IE市场份额占据99%
  • 2003年苹果发布safari浏览器,被包含在所有苹果操作系统中
  • 2004年网景发布来firefox1.0版本,拉开第二次浏览器大战
  • 2005年苹果开源了safari浏览器内核webkit
  • 2008年谷歌以苹果开源项目webkit 创建了新的项目chromium, 在该项目的基础上谷歌发布了自己的浏览器产品Chrome,
  • 2015年微软放弃了IE,推出了以webkit内核的Edge浏览器
  • 2020年chrome已占据60%多的市场份额

2.浏览器结构图

  • 用户界面-用户界面用来展示除标签页窗口以外的其他用户界面内容
  • 浏览器引擎-
  • 渲染引擎-渲染引擎负责渲染用户请求的页面内容

浏览器是运行在操作系统上的一个应用程序 ,每个应用程序必须至少启动一个进程来执行其功能,每个程序往往需要运行很多任务,进程就会创建很多线程去帮助它执行一些小的任务。

当我们启动一个应用程序时,就会创建一个进程来执行任务代码,同时为该进程分配内存空间。该应用程序的状态都保存在内存空间里,当应用关闭时,该内存就会被回收。
进程可以启动更多的进程来执行任务,由于每个进程分配的内存空间是独立的,如果两个进程间需要传递数据,则需要通过进程间通信管道IPC来传递。很多引用程序都是多进程的结构,避免某一个进程卡死,由于进程间相互独立,这样不会影响整个应用程序。
进程可以将任务分为更多个细小的任务,然后通过创建多个线程并行执行不同的任务,同一进程下的线程之间是可以通信共享数据的。

3.浏览器是一个多进程的结构

  • 浏览器进程
    浏览器进程负责控制除标签页外的用户界面,包括地址栏,书签,前进,后退按钮以及负责与浏览器其他进程协调工作
  • 缓存进程
  • 网络进程
    负责发起接受网络请求
  • GPU进程
    负责整个浏览器界面的渲染
  • 插件进程
    负责网站使用的所有插件,例如flash,插件并不是指插件安装的扩展
  • 渲染器进程
    用来控制显示tab标签内的所有内容
    浏览器默认情况下会为每个标签页都创建一个进程(和启动浏览器选择的进程模型有关)

4.当你在浏览器输入地址时,浏览器内部会发生什么?

当你在浏览器输入内容时,浏览器进程的UI进程会捕捉你的输入内容,如果访问的是网址,则UI进程会启动一个网络线程来请求DNS进行域名解析,接着开始连接服务器来获取数据。如果输入的不是网址,而是关键词,浏览器就会使用默认配置的搜索引擎来查询。

当网络线程获取到数据后会通过SafeBrowsing来检查站点是否是恶意站点,如果是,会提示个警告页面阻止你继续访问,也可以强行继续访问。SafeBrowsing是谷歌内部的一套站点安全系统,通过检测该站点的数据判断是否安全。

当返回数据并安全校验通过时,网络线程就会通知UI线程(浏览器进程)准备好了,然后UI线程就会创建一个渲染器进程来渲染页面,浏览器进程就会通过进程通信管道IPC来把数据传递给渲染器进程,正式进入渲染流程。渲染器接受到的数据也就是html,渲染器的核心任务就是把html,js,css,image等资源渲染成用户可以交互的web页面。

渲染器进程的主线程将html进行解析,构造DOM数据结构。
html首先经过tokeniser标记化,通过词法分析将输入的html内容解析成多个标记,根据识别后的标记进行DOM树构造,在DOM树构造偶成中会创建document对象,然后以document为根节点的DOM树不断进行修改向其中添加各种元素。

html->tokeniser->tree construction->DOM
document->body->p->text

html的代码中往往会引入一些额外的资源,比如图片、css、js脚本等,图片和css资源需要经过网络下载或者从缓存中加载,这些资源不回阻塞html的解析,因为它们不会影响dom的生成。但是html解析遇到script标签,就会停止html的解析流程,转而去加载解析并且执行js,可以使用async或defer来异步加载执行js。在html解析完成后会获得一个DOM 树,但是还不知道dom树上每个节点长什么样子。

主线程需要解析css来确定每个dom节点的计算样式。

在知道DOM节点和每个节点的样式后,接下来需要直到每个节点放置的位置,也就是节点的坐标以及该节点需要占据的区域,这个阶段称为layout布局。

主线程通过遍历dom和计算好的样式来生成layout tree,layout tree上的每个节点都记录来x,y坐标以及边框尺寸。layout tree是和最后显示的节点一一对应的。

现在知道节点的形状大小,还需要知道以什么样的顺序来绘制,z-index属性会影响节点绘制的层级关系。按照dom层级顺序绘制节点是不对的,为了在屏幕上展示正确的层级,主线程遍历Layout tree 创建了一个绘制记录表(paint record),该表记录了绘制的顺序,这个阶段被称为绘制(paint)。

现在知道了节点绘制的顺序,到了把这些信息转化称像素点显示在屏幕上的时候了。这种行为被称为栅格化。
chrome早期栅格化只栅格用户可视区域界面,随着滚动再继续栅格,会带来延迟。现在使用另一种更复杂的栅格化,叫做合成(Composting),合成是一种将页面的各个部分分为多个图层,分别对其栅格化,并在合成器线程中单独进行合成页面的技术。
简单来说就是页面所有元素按照某种规则进行分图层,然后把图层都栅格化好了,然后只需把可视区域的内容组合成一帧展示给用户即可。

主线程遍历layout tree生成layer tree(图层),当layer tree生成完毕和绘制顺序确定后,主线程将这些信息传递给合成器线程,
合成器线程将每个图层栅格化,由于一层可能像页面的整个长度一样大,因此合成器线程将他们切分为许多图块,然后将每个图块发送给栅格化线程(Raster thread)。

栅格化线程栅格每个图块,并将它们存储在GPU内存中,当栅格化完成后,合成器线程将收集称为“draw quads”的图块信息,这些信息记录了图块在内存中的位置和在页面的哪个位置绘制图块的信息。根据这些信息合成器线程合成生成了一个合成器帧(Compositor Frame),然后这个合成器帧通过进程间通信管道IPC传递给浏览器进程,接着浏览器进程将合成器帧传送到GPU,然后GPU渲染展示到屏幕上。

页面上终于可以看到内容,当页面发生变化,比如滚动页面,则会生成一个新的合成器帧,新的帧再传送到GPU,再渲染到屏幕上。

5.总结

  • 浏览器进程中的网络线程请求获取到html数据后,通过IPC将数据传给渲染器进程的主线程
  • 主线程将html解析构造DOM树
  • 然后进行样式计算
  • 根据DOM树和生成好的样式成功layout tree
  • 通过遍历layout tree生成绘制顺序表paint
  • 接着遍历layout tree生成layer tree(图层)
  • 然后主线程将layer tree和绘制顺序信息一起传递给合成器线程
  • 合成器线程按照规则进行分图层,并把更小的tiles(图块)传递给栅格化线程进程栅格化
  • 栅格化完成后,合成器线程将收到栅格化线程传递过来的“draw quads”的图块信息
  • 根据这些信息合成器线程合成生成了一个合成器帧(Compositor Frame)
  • 然后这个合成器帧通过进程间通信管道IPC传递给浏览器进程
  • 浏览器进程再传送到GPU渲染,然后就展示到屏幕上了。

主线程->DOM->style->layout tree->paint->layer(这些在主线程执行)
->合成器线程->tiles(图块)
->栅格化线程->“draw quads”的图块信息->传递给合成器线程->合成器帧->浏览器进程->GPU->渲染屏幕

当我们改变元素的尺寸位置时,会重新计算样式,layout,paint以及后面所有流程,这种行为称为重排reflow

当我们改变元素颜色属性时,不会出发layout layer 布局,但会触发样式计算,这种行为称为重绘repaint

重排和重绘都会占用主线程,js也在主线程运行,如果他们都在主线程运行,就会出现抢占执行时间的问题,如果写了一个不断导致重排重绘的动画,浏览器则需要在每一帧都运行样式计算布局和绘制的操作,页面以每秒60帧频率刷新时才不会让用户感觉到页面卡顿,绘制动画时,如果js运行时间过长,没有及时归还主线程,就会导致页面绘制卡顿。

可以通过requestAnimationFrame()和transform解决这个问题。
合成器线程和栅格化线程不运行在主线程,不会受到主线程中js执行的影响,css中transform动画不需要样式计算布局绘制,不运行在主线程。