WebKit Inside: CSS 样式表的匹配时机
WebKit Inside: CSS 的解析 介绍了 CSS 样式表的解析过程,这篇文章继续介绍 CSS 的匹配时机。
无外部样式表
内部样式表和行内样式表本身就在 HTML 里面,解析 HTML 标签构建 DOM 树时内部样式表和行内样式就会被解析完毕。因此如果 HTML 里面只有内部样式表和行内样式,那么当 DOM 树构建完毕之后,就可以进行样式表的匹配了。
假设 HTML 里面的行内样式在 <div>标签,那么 CSS 匹配样式时机如下图所示:

如果 HTML 里面除了内部样式表或者行内样式,还有外部样式表,那么情形比较复杂。
由于引入外部样式表的 <link>标签可以位于 <head>标签中,也可以位于<body>标签中,这两种情形下,匹配时机不一样。
外部样式表位于 head
如果 HTML 里面有外部样式表和内部样式表,HTML 代码如下:
<html>
<head>
<meta charset='utf-8' />
<title>EasyHTML</title>
<style text="text/css">
/* 内部样式表 */
div {
background-color: red;
}
</style>
<!-- 外部样式表-->
<link rel="stylesheet" href="cs.css" />
</head>
<body>
<div>kkk</div>
</body>
</html>
外部样式表 CSS 文件如下:
div {
background-color: blue;
font-size: 20px;
}
如果在 DOM 树构建完成之前,外部样式表就已经下载回来并且解析,那么,当 DOM 树构建完成之后,就可以直接进行样式表的匹配。
但是如果在 DOM 树构建完成之后,外部样式表还没有下载回来,那么即使内部样式表已经解析完成了,也不会进行任何样式表的匹配。调用堆栈如下图所示:

在函数 TreeResolver::resolveElement中,此时第一行 if里面 m_didSeePendingStyleSheet为真,因此不会进行任何样式的匹配。
由于没有进行样式匹配,无法构建渲染树,当然也不会布局和绘制,在外部样式表的下载过程中,页面是空白的。因此 CSS 的下载虽然不阻塞 DOM 树的构建,但是阻塞渲染。
变量m_didSeePendingStyleSheet在函数TreeResolver::resovle里面设置,如果位于 <head>标签里面的外部样式表还未下载成功,这个变量就是 true。设置好 m_didSeePendingStyleSheet变量,函数 TreeResolver::resove 最终会调用到TreeResolver::resolveElement里面。
TreeResolver::resolve相关代码如下所示:
std::unique_ptr<Update> TreeResolver::resolve()
{
...
// 1. 设置 m_didSeePendingStyleSheet 变量
m_didSeePendingStylesheet = m_document.styleScope().hasPendingSheetsBeforeBody();
...
// 2. TreeResolver::resolveElement 函数由下面这个函数调用进去
resolveComposedTree();
...
return WTFMove(m_update);
}
上面代码注释 1 处设置m_didSeePendingStyleSheet。
代码注释 2 处,函数 TreeResolver::resolveComposedTree会调用到TreeResolver::resolveElement。
当外部样式表下载完毕,仍会回调到函数TreeResolver::resove,调用堆栈如下:

由于此时变量m_didSeePendingStyleSheet设置为false,样式表可以正常进行匹配。

外部样式表位于 body
把上面 HTML 里面的外部样式表挪到<body>标签,其他不变:
<html>
<head>
<meta charset='utf-8' />
<title>EasyHTML</title>
<style text="text/css">
/* 内部样式表 */
div {
background-color: red;
}
</style>
</head>
<body>
<!-- 外部样式表-->
<link rel="stylesheet" href="cs.css" />
<div>kkk</div>
</body>
</html>
这种情形下的匹配时机会发生变化。
如果位于<body>标签的外部样式标在 DOM 树构建完成之前下载完成,那么匹配时机和上面位于<head>标签的外部样式表一样,也就是 DOM 树构建完成就进行匹配。
如果 DOM 树构建完成之后,位于<body>标签的外部样式表还未下载成功,此时由于内部样式表已经解析完成,WebKit 会对现有已解析样式表进行匹配,匹配完成之后会构建渲染树,相关代码如下:
void Document::resolveStyle(ResolveStyleType type)
{
...
Style::TreeResolver resolver(*this, WTFMove(m_pendingRenderTreeUpdate));
// 1. 进行 CSS 样式表匹配
auto styleUpdate = resolver.resolve();
...
if (styleUpdate) {
// 2. 样式表匹配完成,这里会进行渲染树构建
updateRenderTree(WTFMove(styleUpdate));
frameView.styleAndRenderTreeDidChange();
}
...
if (m_renderView->needsLayout())
// 3. 渲染树构建完毕,这里会发起布局
frameView.layoutContext().scheduleLayout();
...
}
上面代码注释 1 处进行 CSS 样式表匹配。
代码注释 2 处现有已解析样式表匹配完毕,会进行渲染树的构建。
代码注释 3 处,如果条件允许,会进行布局计算。
但是很遗憾,如果位于<body>标签的外部样式表没有下载完成,因此不满足布局条件,代码运行不到上面代码注释 3 处,调用堆栈如下:

虽然有了渲染树,但是由于没有布局,也就不会进行绘制,在外部样式表下载过程中,页面同样是白色的。CSS 样式表下载依然阻塞渲染。
下面看一下上图判断是否可以布局的代码,代码如下:
bool Document::shouldScheduleLayout() const
{
...
// 1. 因为 isVisuallyNonEmpty 方法返回了 false,导致了布局条件不满足
if (view() && !view()->isVisuallyNonEmpty())
return false;
...
return true;
}
上面代码注释 1 处由于方法LocalFrameView::isVisuallyNonEmpty返回了false,导致布局条件不满足。
方法LocalFrameView::isVisuallyNonEmpty代码如下:
bool isVisuallyNonEmpty() const { return m_contentQualifiesAsVisuallyNonEmpty; }
这个方法返回了变量m_contentQualifiesAsVisuallyNonEmpty的值,这个变量被设置为true的方法为LocalFrameView::checkAndDispatchDidReachVisuallyNonEmptyState,代码如下:
void LocalFrameView::checkAndDispatchDidReachVisuallyNonEmptyState()
{
// 1. qualifiesAsVisuallyNonEmpty 回调函数
auto qualifiesAsVisuallyNonEmpty = [&] {
...
// 2. isMoreContentExpected 回调函数
auto isMoreContentExpected = [&]() {
...
auto& resourceLoader = documentLoader->cachedResourceLoader();
// 3. 如果外部样式表已经下载成功,页面没有其他请求,这里返回 false,说明没有其他内容需要加载了
if (!resourceLoader.requestCount())
return false;
// 4. 如果页面还有其他请求,代码运行到这里
auto& resources = resourceLoader.allCachedResources();
for (auto& resource : resources) {
...
if (resource.value->type() == CachedResource::Type::CSSStyleSheet || resource.value->type() == CachedResource::Type::FontResource)
// 5. 如果正在加载的请求里面有样式表类型后者字体资源,那么这里返回 true,说明还需要等待这些资源加载
return true;
}
return false;
};
// Finished parsing the main document and we still don't yet have enough content. Check if we might be getting some more.
if (finishedParsingMainDocument)
// 6. 调用 isMoreContentExpected 回调函数
return !isMoreContentExpected();
return false;
};
if (m_contentQualifiesAsVisuallyNonEmpty)
return;
// 7. 调用 qualifiesAsVisuallyNonEmpty 回调函数
if (!qualifiesAsVisuallyNonEmpty())
return;
// 8. 这里设置 m_contentQualifiesAsVisuallyNonEmpty 为 true
m_contentQualifiesAsVisuallyNonEmpty = true;
...
}
上面代码注释 1 处定义了qualifiesAsVisuallyNonEmpty回调函数。
代码注释 2 定义了isMoreContentExpected回调函数。
代码注释 7 处调用了回调函数qualifiesAsVisuallyNonEmpty。
在qualifiesAsVisuallyNonEmpty回调函数里面,调用了回调函数isMoreContentExpected,如代码注释 6 所示。
回调函数isMoreContentExpected里面会判断当前是否还有其他请求,如果代码注释 3 所示。如果没有其他请求了,isMoreContentExpected 函数返回 false,表明没有其他内容要加载了。因此,此时代码会运行到代码注释 8 处,将变量m_contentQualifiesAsVisuallyNonEmpty设置为true。
如果页面还有其他资源的请求,比如外部样式表还在请求,那么回调函数isMoreContentExpected会运行到代码注释 5 处。这里会判断请求资源类型是否是样式表或者字体资源,如果是这两种资源之一,这里返回 true。这样,代码会运行到注释 7 处,直接返回而不设置变量m_contentQualifiesAsVisuallyNonEmpty。
因此,如果位于<body>标签的外部样式表还在下载,那么就会在上面代码注释 7 返回,所以不会进行布局。
如果外部样式表下载成功并解析之后,会调用Document::resolveStyle方法,这个方法会进行样式表的匹配,渲染树的构建,布局的调用,代码如下:
void Document::resolveStyle(ResolveStyleType type)
{
...
Style::TreeResolver resolver(*this, WTFMove(m_pendingRenderTreeUpdate));
// 1. 样式表匹配
auto styleUpdate = resolver.resolve();
...
if (styleUpdate) {
// 2. 构建渲染树
updateRenderTree(WTFMove(styleUpdate));
// 3. 设置 m_contentQualifiesAsVisuallyNonEmpty = true 的方法在这里调用
frameView.styleAndRenderTreeDidChange();
}
...
if (m_renderView->needsLayout())
// 4. 调用布局方法
frameView.layoutContext().scheduleLayout();
...
}
上面代码注释 1 处进行样式表匹配。
代码注释 2 进行渲染树构建。
代码注释 3 这个方法内部会调用LocalFrameView::checkAndDispatchDidReachVisuallyNonEmptyState方法设置变量m_contentQualifiesAsVisuallyNonEmpty。由于外部样式表已经下载成功,此时变量m_contentQualifiesAsVisuallyNonEmpty就会被设置成true。
由于上面的设置,后续代码注释 4 处的布局方法调用就可以成功了。
这种情形下匹配时机如下图所示:

WebKit Inside: CSS 样式表的匹配时机的更多相关文章
- 2016年10月27日--css样式表
CSS样式表 样式表分类 1.内联样式表 和html联合显示,控制精确,但是可重用性差,冗余多. !doctype html> <html> <head> <met ...
- WEB入门 四 CSS样式表深入
学习内容 Ø CSS选择器深入学习 Ø CSS继承 Ø CSS文本效果 Ø CSS图片效果 能力目标 Ø 掌握CSS选择器的组合声 ...
- 3月22日 html(三)css样式表
CSS(Cascading Style Sheet,叠层样式表),作用是美化HTML网页. 一.样式表 (一)样式表的分类 1.内联样式表 和HTML联合显示,控制精确,但是可重用性差,冗余较多. 例 ...
- css样式表中四种属性选择器
学习此连接的总结http://developer.51cto.com/art/201009/226158.htmcss样式表中四种属性选择器1> 简易属性 tag[class]{ font-we ...
- 【3-24】css样式表分类、选择器、样式属性
一.css样式表分类: (一)内联样式表:代码写在标签内的样式表 控制精确 代码重用性差 优先级最高 格式:<p style="样式属性">内容</p> ...
- HTML css 样式表
CSS样式表 2.1.样式表的基本概念 2.1.1.样式表分类 1.内联样式表 和html联合显示,控制精确,但是可重用性差,冗余多. 例:<p style="font-size:14 ...
- CSS样式表的写作规范
推荐大家使用的CSS书写规范.顺序 写了这么久的CSS,但自己都没有按照良好的CSS书写规范来写CSS代码,东写一段西写一段,命名也是想到什么写什么,过一段时间自己都不知道写的是那一块内容, 这样会影 ...
- CSS样式表、JS脚本加载顺序与SpringMVC在URL路径中传参数与SpringMVC 拦截器
CSS样式表和JS脚本加载顺序 Css样式表文件要在<head>中先加载,这样网页显示时可以第一次就渲染出正确的布局和样式,网页就不会闪烁,或跳变 JS脚本尽可能放在<body> ...
- 3.22课·········CSS样式表
CSS(Cascading Style Sheet,叠层样式表),作用是美化HTML网页. /*注释区域*/ 此为注释语法 一.样式表 (一)样式表的分类 1.内联样式表 和HTML联合显示,控 ...
- 2016/2/19 css样式表 Cascading Style Sheet 叠层样式表 美化HTML网页
一.样式表 (一)样式表的分类 1.内联样式表 和HTML联合显示,控制精确,但是可重用性差,冗余较多. 例:<p style="font-size:14px;">内联 ...
随机推荐
- 人工智能智能城市(AIinSmartCities)领域的100篇热门博客文章标题如下:
目录 人工智能智能城市(AI in Smart Cities)领域的100篇热门博客文章标题如下: 1.<智能城市与大数据:未来城市大脑的发展方向> 2.<智能交通系统的设计与实现& ...
- FFmpeg合并视频和音频文件
使用IDM下载Bilibili的视频会出现音视频分离的问题,通常文件大的是视频(没有声音),文件小的是单独的音频. 将两个文件都下载下来后,可以使用FFmpeg将其合并成一个视频文件.首先去FFmpe ...
- 基于百度AI平台的人脸识别评分小程序
face-recognition-scoring-applet 开放源代码,遵循Apache License 2.0 效果展示 可切换摄像头.拍照.从相册选择 效果预览 小程序账号注册及配置 地址:h ...
- 使用 Sa-Token 实现不同的登录模式:单地登录、多地登录、同端互斥登录
一.需求分析 如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号. 同端互斥登录,指的就是:像腾讯QQ一样,在同一类型设备上只允许单地点登 ...
- 安装Hadoop单节点伪分布式集群
目录 安装Hadoop单节点伪分布式集群 系统准备 开启SSH 安装JDK 安装Hadoop 下载 准备启动 伪分布式模式安装 配置 配饰SSH免密登录本机 测试启动 单节点安装YARN 伪分布式集群 ...
- Centos 7安装JDK1.8
# 安装 yum install -y java-1.8.0-openjdk* # 添加环境变量 vim /etc/profile export JAVA_HOME=/usr/lib/jvm/java ...
- 基于LLVM的海量数据排序算法研究。(二维表的排序算法)
当待排序数据内容大于内存容量时,需将待排序内容分块,要进行排序的分块传入内存,未处于排序状态的存入外存,外存的读写时间是内存的百万倍,因此在内外存储器之间传输分块所消耗的 I/O 时间是大数据排序算法 ...
- 为什么 PostgreSQL 的适用性很强?
说起使用数量最大的数据库SQLite 它是全球最广泛部署的数据库引擎.它存在于你的手机中,存在于你的浏览器中,如果你搜索你的电脑,你也会在其中找到它的 .db 文件.SQLite 受到 Postgre ...
- vulnhub Necromancer wp
flag1 nmap -sU -T4 192.168.220.130 有666端口 nc -nvu 192.168.220.130 666 监听回显消息 tcpdump host 192.168.22 ...
- 文心一言 VS 讯飞星火 VS chatgpt (67)-- 算法导论6.5 6题
文心一言 VS 讯飞星火 VS chatgpt (67)-- 算法导论6.5 6题 六.在 HEAP-INCREASE-KEY 的第 5 行的交换操作中,一般需要通过三次赋值来完成.想一想如何利用IN ...