你不知道的document对象(中):document对象的初始化会经历哪些过程?

308 阅读6分钟

本文为HTML标准解读系列文章,其他文章详见这里

上一篇文章中,我们探讨了document对象是如何选择浏览上下文的。选择浏览上下文,其实是document对象的创建与初始化过程的第一步。本篇文章我将继续基于HTML标准,主要是7.11小节,揭示整个初始化的过程,并且会对一些重点的部分进行展开讨论。

document对象的创建和初始化过程

HTML标准7.11.10中,对document对象的创建与初始化过程列出了详细的算法,整个过程有20个步骤,一些步骤下面还有子算法... 但是整个过程可以归纳总结为如下几步:

  1. 根据COOP策略,获取对应的浏览上下文;
  2. 创建document关联的window对象;
  3. 创建document对象,并同时设置其基本信息与安全特性;
    • 基本信息:TypeContent TypeoriginURLtiming info
    • 安全特性:
      • policy container 策略容器
      • permission policy 权限策略
      • active sandboxing flag set 激活的沙箱标志组
      • cross-origin opener policy 跨域程序打开策略(COOP)
  4. 设置document的readyStateloading
  5. 初始化CSP策略
  6. 根据请求设置document的referrer属性
  7. 处理相关header头
    • Refresh header
    • early hint
    • Linkheader
  8. 返回document对象

这里的第一步在上一篇文章已经做了详细的讲解,这里就不再啰嗦,接下来我会从不同的方面展开这里的步骤。

创建window对象

大多数时候,window对象与document对象是一对一的关系,并且他们可以通过window.documentdocument.defaultView相互访问。

但是,如果一个document对象没有对应的浏览上下文,那么他也不会有对应window对象。比如document.implementation.createDocument(),并不会走上面提到的算法,而是有自己的初始化过程。所以访问它的document.defaultView的值为null

除此以外,在满足特定条件的时候,一个window与document还能是一对二的关系。

基本信息:TypeContent Type

在初始化过程中,document的typecontent type的值会直接决定后续浏览器对document的处理过程。

type的值只有两种:"xml""html"xml表示这是一个XML文档,html表示这是一个HTML文档。这个值会决定后续解析内容是使用XML解析器还是HTML解析器。

Content type,表示内容类型。不同的类型,也会有不同的处理步骤,以一个type=html的document为例:

  • 如果内容是HTML文件,即Content type "text/html":浏览器就会启动HTML解析器来解析内容,这是我们最常使用的过程。
  • 如果内容是文本资源,比如text/plaintext/javascript:浏览器会把文本内容包裹在一个pre元素中进行再进行解析。
  • 如果内容是多媒体资源,比如image/pngvideo/mp4:浏览器会自动创建一个对应的多媒体元素,如image元素、video元素,来包裹资源内容,并不再启用HTML解析器,直接跳过解析的过程,
  • 如果内容是需要加载其他插件的资源,比如application/pdf:浏览器会自动创建一个embed元素包裹对应的资源,并不再启用HTML解析器,直接跳过解析的过程,
  • ...

以上列举的例子,你都可以自己的本地进行测试,比如拖不同类型的文件进浏览器,看看生成的document的HTML是长什么样子的。

document关联的安全策略/特性

根据上面的算法,可以看到,与document相关的安全策略/特性包括这些:

  • 一个policy container(策略容器),容器里面包含的策略有:
    • CSP列表
    • embedder policy (跨源程序嵌入器策略)
    • referrer policy
  • permission policy (权限策略)
  • active sandboxing flag set (激活的沙箱标志组)
  • cross-origin opener policy (跨源程序打开策略/COOP)

你可能会感觉这些策略很零散,WHATWG也是这么想的,所以它也在策略容器下面的解释文本作了个标记,说正在把其他的策略迁移到容器里面,未来我们可能会看到更多散落在外面的策略都放在策略容器里面。

深入讲解这些策略超出了本文所涉及的内容,我用一张表给大家总结他们的内容,并贴上了对应的参考链接。

安全特性描述参考
CSP策略控制页面可以加载哪些资源、运行哪些脚本等等。MDN
CSP标准
embedder policy控制加载资源是否必须经过服务端的显式授权(使用CORP)。这篇文章把来龙去脉讲清楚了
标准解释
referrer policy控制发送请求的时候,referer头要带多少信息。MDN
标准
permission policy启用或禁用浏览器特定的功能,如相机功能。标准
active sandboxing flag set使用iframe的sandbox属性可以显式声明这个组,但是每一个顶层浏览上下文的document自己也有一个这样的组,起始的时候为空。标准解释
cross-origin opener policy决定两个浏览上下文是否共享同一个浏览上下文组,功能与noopener类似。这篇文章把来龙去脉讲清楚了
标准解释

相关header头处理

处理header头是整个初始化步骤的最后几步了:

  • Refresh header:如果声明了这个header头,指示需要定时刷新页面,跟<meta http-equiv="refresh" content="30">做的事情是一样的;
  • early hint: 使用103的状态码,表示提前加载一些资源;
  • Linkheader:跟link标签的作用一样。

document的状态更新

细心的读者可能会发现,在上面的初始化步骤中,document的readyState是没有被更新的。没错,在初始化完毕之后,document还要等HTML解析器把文档的内容解析完,才会把readyState更新为interactive,然后等到页面中所有的资源加载完毕,readyState才会变成complete

标准列出的具体的过程,几个关键节点如下所示:

  1. 完成document对象的初始化;
  2. 完成上面提到的内容的解析处理,这一步之后,不管实际步骤需不需要使用到HTML解析器,都会标记HTML解析已经完成了;
  3. 更新document的状态readyStateinteractive
  4. 执行所有defer script与没有标记asyncmodule script
  5. 触发document对象上的DOMContentLoaded事件;
  6. 执行剩余的script
  7. 更新document的状态readyStatecomplete
  8. 触发window的load事件。

总结

本文,我为你展示了document对象从创建、初始化、到完成加载的整个过程。因为实际的过程是一个非常复杂的系统,我并不能在一篇文章里事无巨细地给你讲全,我只是把这个过程中最重要的一些节点罗列了出来,为你提供整个过程的「宏观认知」。这种宏观认知就像树干一样,不管你是要自己读标准,还是要深入研究具体的分支细节,都能给你提供坚实的基础。