• 浏览器是如何工作的


    浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工作原理,我们将看到从你在地址栏输入google.cn 到你看到google 主页过程中都发生了什么。接下来将基于一些开源浏览器的例子---firfox 、chrom及safari。

    浏览器的主要功能
    浏览器主要功能是将用户选择得web 资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、Image 及其他格式。用户用URI(统一资源标识符) 来指定所请求资源的位置。
    html 和css 规范中规定了浏览器解释 html 文档的方式,由W3C 组织对这些规范进行维护,W3C 是负责指定web 标准的组织。

    浏览器常见的用户界面元素包括:

    用来输入URI的地址栏

    · 前进、后退按钮

    · 书签选项

    · 用于刷新及暂停当前加载文档的刷新、暂停按钮

    · 用于到达主页的主页按钮


    浏览器的主要组件包括:

    1. 用户界面 -- 包括地址栏、后腿/前进按钮、书签目录等,也就是你所看到的除了用来显示你锁清秋页面的主窗口之外的其他部分

    2. 浏览器引擎- 用来查询及操作渲染引擎的接口

    3. 渲染引擎 - 用来显示请求的年日用 (例如如果请求内容为html 它负责解析html 及css,并将解析后的结果显示出来)

    4.网络 - 用来完成网络调用,例如http请求,他具有平台无关的接口,可以在不同平台上工作

    5. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口

    6.JS解释器-  用来解释执行js 代码

    7. 数据存储- 属于持久层,浏览器需要在硬盘中保存类型cookie 的各种数据,html5定义了web database 技术,这是一种轻量级完整的客户端存储技术。


    一、渲染引擎

    需要注意的是,不同于大部分浏览器,chrom 为每个tab 分配了各自的渲染引擎实例,每个tab 就是独立的进程。

    渲染引擎的职责是渲染,即在浏览器窗口中显示所请求的内容。

    默认情况下,渲染引擎可以显示html、xml 文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类数据,例如使用PDF阅读器插件,可以显示PDF格式,这里只讨论渲染引擎最主要的用途--- 显示应用了css 之后的html 及图片


    渲染引擎首先通过网络获得所请求文档的内容,通常以8K 分块的方式完成。

    下面是渲染引擎在取得内容之后的基本流程

    解析html 以构建dom 树 -> 构建render 树 -> 布局render树 -> 绘制render 树

    渲染引擎开始解析html,并将标签转化为内容树中的dom节点。接着,它解析外部css 文件及style 标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一个棵树 ---- render 树。

    render 树由一些包含有颜色和大小等属性的矩形组成,他们被将按照正确的顺序显示到屏幕上

    render 树构建好了之后,将会执行布局过程,它将确定要每个节点在屏幕上的确切坐标。在下一步就是绘制,即遍历render 树,并使用UI 后端层绘制每个节点。

    值得注意的是,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后在去构建和布局render 树。 它是解析完一部分内容就显示一部分内容,同事可能还在通过网络下载其余内容。


    二、解析

    解析一个文档也就是将其转换为具有一定意义的结构(编码可以理解和使用的东西) 。解析的结构通常是通过表达文档结构的节点树,称为解析树或语法树。

    HTML解析器的工作是将html 标识解析为解析树。

    解析树,是有DOM 元素及属性节点组成的。DOM是文档对象模型的缩写,它是html 文档对象标识,作为html 元素的外部接口供js 等调用。

    DOM和标签基本是一一对应的关系,例如,如下的标签:

    <html>
        <body>
            <p>
                Hello DOM
            </p>
            <div><img src=”example.png” /></div>
        </body>
    </html>

    将会被转换为DOM树:

    这里所谓的树包含了DOM节点是说树是由实现了DOM 接口的元素构建而成的,浏览器使用已被浏览器内容使用的其他属性的具体实现。

    解析算法

    正如前面章节中讨论的,html 不能被一般的自顶向下或者向上的解析器所解析

    原因是:

    1. 这门语言本身的宽松特性

    2.浏览器对一些常见的非法的html 有容错机制

    3. 解析过程往往是反复的,通常源码不会在解析过程中发生改变,但在html 中,脚本标签包含的"document.write" 可能添加标签,这说明在解析过程中实际上修改了输入

    不能使用正确解析技术,浏览器为html 定制了专属的解析器。


    html解析流程

    基本示例——符号化下面的html:

    <html>

    <body>

    Hello world

    </body>

    </html>

    初始状态为“Data State”,当遇到“<”字符,状态变为“Tag open state”,读取一个a-z的字符将产生一个开始标签符号,状态相应变为“Tag name state”,一直保持这个状态直到读取到“>”,每个字符都附加到这个符号名上,例子中创建的是一个html符号。

    当读取到“>”,当前的符号就完成了,此时,状态回到“Data state”,“<body>”重复这一处理过程。到这里,html和body标签都识别出来了。现在,回到“Data state”,读取“Hello world”中的字符“H”将创建并识别出一个字符符号,这里会为“Hello world”中的每个字符生成一个字符符号。

    这样直到遇到“</body>”中的“<”。现在,又回到了“Tag open state”,读取下一个字符“/”将创建一个闭合标签符号,并且状态转移到“Tag name state”,还是保持这一状态,直到遇到“>”。然后,产生一个新的标签符号并回到“Data state”。后面的“</html>”将和“</body>”一样处理。


    树的创建过程

    看一下示例中树的创建过程:

    <html>

    <body>

    Hello world

    </body>

    </html>

    构建树这一阶段的输入是符号识别阶段生成的符号序列。

    首先是“initial mode”,接收到html符号后将转换为“before html”模式,在这个模式中对这个符号进行再处理。此时,创建了一个HTMLHtmlElement元素,并将其附加到根Document对象上。

    状态此时变为“before head”,接收到body符号时,即使这里没有head符号,也将自动创建一个HTMLHeadElement元素并附加到树上。

    现在,转到“in head”模式,然后是“after head”。到这里,body符号会被再次处理,将创建一个HTMLBodyElement并插入到树中,同时,转移到“in body”模式。

    然后,接收到字符串“Hello world”的字符符号,第一个字符将导致创建并插入一个text节点,其他字符将附加到该节点。

    接收到body结束符号时,转移到“after body”模式,接着接收到html结束符号,这个符号意味着转移到了“after after body”模式,当接收到文件结束符时,整个解析过程结束。


    html树的构建过程

    解析结束时的处理 Action when the parsing is finished

    在这个阶段,浏览器将文档标记为可交互的,并开始解析处于延时模式中的脚本——这些脚本在文档解析后执行。

    文档状态将被设置为完成,同时触发一个load事件。


    浏览器容错 Browsers error tolerance

    详见 http://www.aiuxian.com/article/p-1832762.html



    三 、脚本解析 Parsing scripts

    web的模式是同步的,开发者希望解析到一个script 标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完,如果脚本是外因的,则网络必须先请求到这个资源 --- 这个过程也是同步的,会阻塞文档的解析直到资源被请求到。


    预解析
    webkit 和firefox 都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源,这种方式可以使 资源并行加载从而使整体速度更快。需要注意的是, 预解析并不改变dom树,它将这个工作留给主解析过程,自己只解析外面资源的引用,比如外部脚本、样式表及图片

    样式表
    样式表采用另一种不同的模式。理论上,既然样式表不能改变dom 树,也就没有必要停下文档的解析等待它们,然后存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很度问题,这看起来是个边缘情况,但确实很常见。
    firefox 在存在样式表还在加载和解析时阻塞所有的脚本,而chrom 只在当脚本视图访问某些可能被未被加载的样式表所影响的特定的样式属性时才阻塞这些脚本。

    渲染树的构造 Render tree construction

    当Dom树构建完成时,浏览器开始构建另一棵树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。
    Firefox将渲染树中的元素称为frames
    webkit则用renderer或渲染对象来描述这些元素。
    一个渲染对象知道怎么布局及绘制自己及它的children。

    RenderObject是Webkit的渲染对象基类,它的定义如下:

    class RenderObject{

    virtual void layout();

    virtual void paint(PaintInfo);

    virtual void rect repaintRect();

    Node* node; //the DOM node

    RenderStyle* style; // the computed style

    RenderLayer* containgLayer; //the containing z-index layer

    }

    每个渲染对象用一个和该节点的css盒模型相对应的矩形区域来表示,正如css2所描述的那样,它包含诸如宽、高和位置之类的几何信息。盒模型的类型受该节点相关的display样式属性的影响(参考样式计算章节)。下面的webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。


    webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。

    RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)

    {

    Document* doc = node->document();

    RenderArena* arena = doc->renderArena();

    ...

    RenderObject* o = 0;

    switch (style->display()) {

    case NONE:

    break;

    case INLINE:

    o = new (arena) RenderInline(node);

    break;

    case BLOCK:

    o = new (arena) RenderBlock(node);

    break;

    case INLINE_BLOCK:

    o = new (arena) RenderBlock(node);

    break;

    case LIST_ITEM:

    o = new (arena) RenderListItem(node);

    break;

    ...

    }

    return o;

    }

    元素的类型也需要考虑,例如,表单控件和表格带有特殊的框架。


    染树和Dom树的关系 The render tree relation to the DOM tree

    渲染对象和dom 元素相对应,但这种对应关系不是一对一的,不可见dom 元素不会被插入渲染树,例如head 元素。另外display 属性为none 的元素也不会在渲染树中出现(visibility 属性为hidden 的元素将出现在渲染树中。)

    四: 布局
    当渲染对象被创建并添加到树中,它们比你更没有位置和大小,计算这些值的过程称为layout 和reflow
    html 是基于流的布局模型, 意味着发部分时间,可以以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特性。所以布局可以在文档中从右到左、自上而下进行。也存在一些例外,比如html table
    坐标系统相对于根frame ,使用top 和left 坐标
    布局是一个递归的过程,由根渲染对象开始,它对应html 文档元素,布局继续递归的过程一些或所有的frame 层级,为我每个需要几何信息的渲染对象进行计算。
    根渲染对象的位置是0,1 ,它的大小是viewport -- 浏览器窗口的可见部分。
    所有的渲染对象都有一个layout 和reflow 方法,每个渲染对象调用需要布局的children 的layout 方法

    Dirty bit 系统

    为了不因为每个小变化都全部重新布局,浏览器使用一个dirty bit 系统,一个渲染对象发生了变化或是被添加了,就标记它及他的children 为dirty - 需要layout ,存在两个标识-dirty 及 children are dirty,
    children are dirty说明即使这个渲染对象可能没问题,但它至少有一个child需要layout。

     全局和增量layout 
    当layout 在整颗渲染树触发时,称为全局layout ,这可能在下面这些情况下发生
    1. 一个全局的样式改变影响所有的渲染对象,比如字号的改变
    2. 窗口resize 
    layout 也可以是增量的,这样只有标志为dirty 的渲染对象会重新布局(也将导致一些额外的布局)。增量layout会在渲染对象dirty 时异步触发,例如,当网络接收到新的内容并添加到dom树后,新的渲染对象会添加到渲染树中。


    增量layout
    增量layout 的过程是异步的,firefox为整理layout 生成了reflow 队列,以及一个调度执行这些处理命令。webkit 也有一个计时器用来执行增量layout 遍历树,为dirty状态的渲染对象重新布局
    另外,当脚本请求样式信息时,例如“offsetHeight”,会同步的触发增量布局。
    全局的layout 一般都是同步触发
    有些时候,layout 会被作为一个初始layout 之后的回调,比如滑动条的滑动

    优化
    当一个layout因为resize 或是渲染位置改版(并不是大小改变)而触发时,渲染对象的大小将会从缓存中读取,而不会重新计算。
    一般情况下,如果只有子树发生改变,则layout 并不从跟开始,这种情况发生在,变化发生在元素自身但并不影响其他周围元素,例如,将文本插入文本域(否则每次都将触发从根开始的重排)

    layout过程

    layout一般有下面这几个部分:

    1. 父渲染对象决定它自己的宽度

    2. 父渲染对象读取chilidren,并:

    1. 放置child渲染对象(设置它的x和y)

    2. 在需要时(它们当前为dirty或是处于全局layout或者其他原因)调用child渲染对象的layout,这将计算child的高度

    3. parent渲染对象使用child渲染对象的累积高度,以及margin和padding的高度来设置自己的高度-这将被parent渲染对象的parent使用

    4. 将dirty标识设置为false
    Firefox使用一个“state”对象(nsHTMLReflowState)做为参数去布局(firefox称为reflow),state包含parent的宽度及其他内容。
    Firefox布局的输出是一个“metrics”对象(nsHTMLReflowMetrics)。它包括渲染对象计算出的高度。

    宽度计算

    渲染对象的宽度使用容器的宽度、渲染对象样式中的宽度及margin、border进行计算。例如,下面这个div的宽度:

    <div style="30%"/>

    webkit中宽度的计算过程是(RenderBox类的calcWidth方法):

    · 容器的宽度是容器的可用宽度和0中的最大值,这里的可用宽度为:contentWidth= clientWidth() - paddingLeft() - paddingRight(),clientWidth 和 clientHeight 代表一个对象内部的不包括border和滑动条的大小

    · 元素的宽度指样式属性width的值,它可以通过计算容器的百分比得到一个绝对值

    · 加上水平方向上的border和padding

    到这里是最佳宽度的计算过程,现在计算宽度的最大值和最小值,如果最佳宽度大于最大宽度则使用最大宽度,如果小于最小宽度则使用最小宽度。最后缓存这个值,当需要layout 但宽度未改变时使用。

    Line breaking

    当一个渲染对象在布局过程中需要折行时,则暂停并告诉它的parent它需要折行,parent将创建额外的渲染对象并调用它们的layout。

    绘制 Painting

    绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件,这在UI的章节有更多的介绍。


    全局和增量

    和布局一样,绘制也可以是全局的-绘制完整的树-或增量的。在增量的绘制过程中,一些渲染对象以不影响整棵树的方式改变,改变的渲染对象使其在屏幕上的矩形区域失效,这将导致操作系统将其看作dirty区域,并产生一个paint事件,操作系统很巧妙的处理这个过程,并将多个区域合并为一个。Chrome中,这个过程更复杂些,因为渲染对象在不同的进程中,而不是在主进程中。Chrome在一定程度上模拟操作系统的行为,表现为监听事件并派发消息给渲染根,在树中查找到相关的渲染对象,重绘这个对象(往往还包括它的children)。


    绘制顺序

    css2定义了绘制过程的顺序。这个就是元素压入堆栈的顺序,这个顺序影响着绘制,堆栈从后向前进行绘制。

    一个块渲染对象的堆栈顺序是:
    1. 背景色
    2. 背景图
    3. border
    4. children
    5. outline

    Firefox显示列表
    Firefox 读取渲染树并为绘制的矩形创建一个显示列表,该列表以正确的绘制顺序包含这个矩形相关的渲染对象。用这样的方法,可以使重绘时只需查找一次树,而不需要多次查找——绘制所有的背景、所有的图片、所有的border等等。Firefox优化了这个过程,它不添加会被隐藏的元素,比如元素完全在其他不透明元素下面

    Webkit矩形存储

    重绘前,webkit将旧的矩形保存为位图,然后只绘制新旧矩形的差集。


    动态变化

    浏览器总是试着以最小的动作响应一个变化,所以一个元素颜色的变化将只导致该元素的重绘,元素位置的变化将大致元素的布局和重绘,添加一个Dom节点,也会大致这个元素的布局和重绘。一些主要的变化,比如增加html元素的字号,将会导致缓存失效,从而引起整数的布局和重绘。


    渲染引擎的线程
    渲染引擎是单线程的,除了网络操作之外,几户所有的事情都在单一的线程中处理,在firefox 和safari 中,这是浏览器的主线程,chrome 中这是tab的主线程
    网络操作由几个并行线程执行,并行连接的个数是受限的(通常是2-6个)。

    事件循环

    浏览器主线程是一个时间循环,它被设计为无限循环比保持执行过程的可用,等待事件(例如layout 和paint事件)并执行它们。下面是Firefox的主要事件循环代码。

    while (!mExiting)

    NS_ProcessNextEvent(thread);


    CSS盒模型


    所有的元素都有一个display属性,用来决定它们生成box的类型,例如:

    block-生成块状box

    inline-生成一个或多个行内box

    none-不生成box

    默认的是inline,但浏览器样式表设置了其他默认值,例如,div元素默认为block。


    定位策略 Position scheme

    这里有三种策略:

    1. normal-对象根据它在文档的中位置定位,这意味着它在渲染树和在Dom树中位置一致,并根据它的盒模型和大小进行布局

    2. float-对象先像普通流一样布局,然后尽可能的向左或是向右移动

    3. absolute-对象在渲染树中的位置和Dom树中位置无关

    static和relative是normal,absolute和fixed属于absolute。

    在static定位中,不定义位置而使用默认的位置。其他策略中,作者指定位置——top、bottom、left、right。

    Box布局的方式由这几项决定:box的类型、box的大小、定位策略及扩展信息(比如图片大小和屏幕尺寸)。

    Box类型

    Block box:构成一个块,即在浏览器窗口上有自己的矩形

    Inline box:并没有自己的块状区域,但包含在一个块状区域内

    block一个挨着一个垂直格式化,inline则在水平方向上格式化。

    Inline盒模型放置在行内或是line box中,每行至少和最高的box一样高,当box以baseline对齐时——即一个元素的底部和另一个box上除底部以外的某点对齐,行高可以比最高的box高。当容器宽度不够时,行内元素将被放到多行中,这在一个p元素中经常发生。

    定位 Position

    Relative

    相对定位——先按照一般的定位,然后按所要求的差值移动。

    Floats

    一个浮动的box移动到一行的最左边或是最右边,其余的box围绕在它周围。下面这段html:

    <p>

    <img style="float:right" src="images/image.gif" width="100" height="100">Lorem ipsum dolor sit amet, consectetuer...

    </p>

    将显示为:

    Absolute和Fixed

    这种情况下的布局完全不顾普通的文档流,元素不属于文档流的一部分,大小取决于容器。Fixed时,容器为viewport(可视区域)。

    图17:fixed

    注意-fixed即使在文档流滚动时也不会移动。

    Layered representation

    这个由CSS属性中的z-index指定,表示盒模型的第三个大小,即在z轴上的位置。Box分发到堆栈中(称为堆栈上下文),每个堆栈中靠后的元素将被较早绘制,栈顶靠前的元素离用户最近,当发生交叠时,将隐藏靠后的元素。堆栈根据z-index属性排序,拥有z-index属性的box形成了一个局部堆栈,viewport有外部堆栈,例如:

    <STYLE type="text/css">
    div {position: absolute;left: 2in;top: 2in;}
    </STYLE>
    <P>
        <DIV style="z-index: 3;background-color:red;  1in; height: 1in; "></DIV>
        <DIV style="z-index: 1;background-color:green; 2in; height: 2in;"> </DIV>
    </p>

    结果是:

    虽然绿色div排在红色div后面,可能在正常流中也已经被绘制在后面,但z-index有更高优先级,所以在根box的堆栈中更靠前。













  • 相关阅读:
    Linux的find命令
    Shell环境变量文件
    Spring事务配置的五种方式 巨全!不看后悔,一看必懂!
    高性能C++网络库libtnet实现:Connection
    log4j的一些配置
    MySQL的表分区
    MySQL中的datetime与timestamp比较
    如何成为一名优秀的web前端工程师
    双机热备份
    MySQL错误“Specified key was too long; max key length is 1000 bytes”的解决办法
  • 原文地址:https://www.cnblogs.com/zjx2011/p/8473007.html
Copyright © 2020-2023  润新知