toyBrowser实现过程问题总结

474 阅读4分钟

源码地址:github.com/marunzhou/t…

总体思路

  1. 新建server.js充当服务器,返回response
  2. 新建browser文件夹充当浏览器,其下新建以下处理文件
    • client.js 浏览器,用来发送Request,调用解析Response响应报文
    • parser.js 解析器,使用状态机(FSM)解析响应报文body主体形在dom树,同时使用css包解析style内容生成css的ast树,为Dom树上的元素添加style
    • layout.js 布局,这里采用Flex布局的计算方式进行编排,计算元素的宽高、留白及换行
    • render.js 渲染器,简单的采用image包,将布局渲染成一张图片

报文的解析

以下是需要处理的响应报文

HTTP/1.1 200 OK
Content-Type: text/plain
X-Foo: bar
Date: Fri, 26 Jun 2020 01:28:31 GMT
Connection: keep-alive
Transfer-Encoding: chunked

277

<html meaa=a>
    <head>
        <style>
            #container {
                width: 500px;
                height: 300px;
                display: flex;
                background-color: rgb(0,0,255);
            }
            #container #myid {
                width: 200px;
                height: 100px;
                background-color: rgb(255,0,0);
            }
            #container .c1 {
                flex: 1;
                background-color: rgb(0,255,0);
            }
        </style>
    </head>
    <body>
<div id="container">
    <div id="myid"></div>
    <div class="c1"></div>
</div>
    </body>
</html>
0


看完响应报文需要注意几个问题:

  1. 响应头中有句 Transfer-Encoding: chunked,其结构大致是: 返回的消息被分为多个数据块, 每个数据块有两部分, 长度 + 数据, 这两部分都以CRLF(即\r\n)结尾. 而终止块是一个特殊的数据块, 其长度为0,其报报文的格式如下图

    image

  2. Content-Length 的问题,如果报文中包含Transfer-Encoding: chunked首部, 那么Content-Length将被忽略,这是在说浏览器的处理,但长度还是有的,报文中的277就是body主体的length,但这是一个十六进制数,不是十进制需要注意。lenth的不准确会导致读取出错,无法结束或提前截断

noneConentLength

Content-Length的问题的详解:blog.piaoruiqing.com/2019/09/08/…

  1. chunked的应用场景,MDN上是这么解释的:

MDN-Transfer-Encoding: developer.mozilla.org/zh-CN/docs/…

  1. 状态机(FSM)具体定义

我们需要对响应头以下的主体内容进行解析,这种解析的方式就使用状态。简单的来说就是,一个字一个字的读取,读取不到不同的字符执行不同的状态操作,直到EOF结束.

对于html来说大概就是开始标签、结束标签、文本字符

开始标签:html
  开始标签:head
  结束标签:head
  开始标签:body
    字符串:lagou
  结束标签:body
结束标签:html

那么css呢? 当解析到结束标签时,会使用css包解析style组成ast树,重点关注rules下的selectors & declarations.property & declarations.value 我们例子会使用这几部分,在解析html的过程,用来匹配选择器,并计算css优先级

body {
  background: #eee;
  color: #888;
}
// 解析后如下:
 {
  "type": "stylesheet",
  "stylesheet": {
    "rules": [
      {
        "type": "rule",
        "selectors": [
          "body"
        ],
        "declarations": [
          {
            "type": "declaration",
            "property": "background",
            "value": "#eee",
            "position": {
              "start": {
                "line": 2,
                "column": 3
              },
              "end": {
                "line": 2,
                "column": 19
              }
            }
          },
          {
            "type": "declaration",
            "property": "color",
            "value": "#888",
            "position": {
              "start": {
                "line": 3,
                "column": 3
              },
              "end": {
                "line": 3,
                "column": 14
              }
            }
          }
        ],
        "position": {
          "start": {
            "line": 1,
            "column": 1
          },
          "end": {
            "line": 4,
            "column": 2
          }
        }
      }
    ]
  }
}
  1. css的优先级表示及比较 可以看到 specificity 方法中,创建了一个一维数组[0,0,0,0],其每个位置分别表示内联、id、类、标签名,前者优先级高于后者,两个css的比较也是相同位置进行对比
function specificity(selector) {
   // 内联, id, 类, 标签名
   let p = [0, 0, 0, 0]
   let selectorParts = selector.split(" ")

   for (var part of selectorParts) {
      if (part.charAt(0) === '#') { // index = 1
         p[1] += 1
      } else if (part.charAt(0) === '.') { // index = 2
         p[2] += 1
      } else { // index = 3
         p[3] += 1
      }
   }

   return p
}

function compare(sp1, sp2) {
   if (sp1[0] - sp2[0]) {
      return sp1[0] - sp2[0]
   }
   if (sp1[1] - sp2[1]) {
      return sp1[1] - sp2[1]
   }
   if (sp1[2] - sp2[2]) {
      return sp1[2] - sp2[2]
   }

   return sp1[3] - sp3[3]
}

总结

  1. CSS选择器匹配时,为什么从右向左进行的?

因为元素的匹配是自下而上,由内向外的,跟冒泡的方式一样。这一点在html解析过程中可以得到验证

  1. css放在body后面会发生什么事情?

toybrowser中style的解析,在标签结束的时候执行,可这时候元素的style已计算完成,要匹配上当下的style,必须重新跑一遍解析过程,确保样式的正确,重排或许就这么上场了吧?

  1. 解析html选择状态机而不选择正则?

个人觉得要用正则处理形成ast是件很麻烦的事情,试想要如何将闭合标签内的层级精确的放在父级上,且多层嵌套在一起,就相当不容易了。不如状态机来得明确,虽然看着if很多,有点low的感觉,但不确很适用。