WebKit Inside: CSS 样式表的匹配时机介绍了当 HTML 页面有不同 CSS 样式表引入时,CSS 样式表开始匹配的时机。后续文章继续介绍 CSS 样式表的匹配过程,但是在匹配之前,首先需要收集页面里面的 Active 样式表。
1 Active 样式表
在一个 HTML 文件里面,可能会使用标签与标签引入许多样式表,但是这些样式表并不一定都同时在文档里面生效。有时根据业务需求,可能会只使用页面里的部分样式表。比如有一个换肤需求,页面里面可能会使用标签引入 4 张样式表,代码如下:
- <link href="reset.css" rel="stylesheet" />
-
- <link href="default.css" rel="stylesheet" title="Default Style" />
- <link href="fancy.css" rel="alternate stylesheet" title="Fancy" />
- <link href="basic.css" rel="alternate stylesheet" title="Basic" />
上面样式表reset.css所在的标签有rel="stylesheet"属性,没有title属性,这种样式表被称为 Persisten 样式表,会一直被启用。
样式表default.css所在的标签有rel="stylesheet"和title属性,这种样式表被称为 Preferred 样式表。Preferred 样式表是默认启用。一个页面只能有一个 Preferred 样式表。
样式表fancy.css和basic.css所在标签有rel="alternate stylesheet"和title属性,这种样式表被称为 Alternate 样式表。这些样式表默认下是不启用的,但是可以提供给用户选择。一旦用户选择了一个 Alternate 样式表,Preferred 样式表就会别禁用。
根据 标签语法,样式表reset.css和default.css会在页面里面使用,而样式表fancy.css和basic.css会暂时不使用。
一般这种场景会给一个按钮让用户切换皮肤,当用户选择切换到Fancy皮肤时,样式表default.css就失效,样式表fancy.css就会启用。但是不管用户如何切换,样式表reset.css始终有效。更多信息可以参考 MDN Alternative Style Sheet[1]。
在用户没有换肤之前,样式表reset.css和default.css样式表就属于 Active 样式表,当用户选择切换之后,样式表reset.css和fancy.css就是 Active 样式表。
在进行 CSS 样式表匹配之前,WebKit 首先要收集页面里面所有的 Active 样式表,然后依次遍历这些 Active 样式表的 CSS Rule 进行匹配。
2 相关类图

上面类图里Style::SCope类持有负责进行样式表匹配的Style::Resolver类,同时它内部还有 3 个重要的数据成员:m_styleSheetCandidateNodes是一个哈希链表,用来按顺序存储 HTML 文件里面的 与节点,也就是 HTMLStyleElment对象和HTMLLinkElement对象。
m_activeStyleSheets是一个 Vector,类似数组,用来顺序存储页面里面的 Active 样式表。
m_styleSheetsForStyleSheetList也是一个 Vector,用来顺序存储页面里面的所有样式表。
3 获取 Candidate Node
无论内部样式表,还是外部样式表,当 WebKit 解析到 标签或者标签时,都会调用Style::Scope::addStyleSheetCandidateNode方法,将自己添加到Style::Scope的实例变量m_styleSheetCandidateNode里面。
以内部样式表为例,下面是调用堆栈:

函数Style::Scope::addStyleSheetCandidateNode的代码如下:
- void Scope::addStyleSheetCandidateNode(Node& node, bool createdByParser)
- {
- if (!node.isConnected())
- return;
-
- // Until the exists, we have no choice but to compare document positions,
- // since styles outside of the body and head continue to be shunted into the head
- // (and thus can shift to end up before dynamically added DOM content that is also
- // outside the body).
- // 1. createByParser 代表当前的 node 是从 HTML 文件里解析出来的,而不是通过 JavaScript 代码动态创建的;
- // m_document.bodyOrFrameset 方法判断当前页面是否解析出了 标签和
- // 如果前这两个条件为真,那么节点直接添加到变量 m_styleSheetCandidateNodes;
- // 还有一种情形 m_styleSheetCandidateNodes 当前还没有添加任何节点
- if ((createdByParser && m_document.bodyOrFrameset()) || m_styleSheetCandidateNodes.isEmptyIgnoringNullReferences()) {
- m_styleSheetCandidateNodes.add(node);
- return;
- }
-
- // Determine an appropriate insertion point.
- // 2. 如果上述条件不满足,就会走到这里,这里会将当前节点与 m_styleSheetCandidateNodes 里已有的