当前位置: 首页 > news >正文

WebKit Inside: CSS 样式表的匹配时机

WebKit Inside: CSS 的解析 介绍了 CSS 样式表的解析过程,这篇文章继续介绍 CSS 的匹配时机。

无外部样式表

内部样式表和行内样式表本身就在 HTML 里面,解析 HTML 标签构建 DOM 树时内部样式表和行内样式就会被解析完毕。因此如果 HTML 里面只有内部样式表和行内样式,那么当 DOM 树构建完毕之后,就可以进行样式表的匹配了。

假设 HTML 里面的行内样式在 <div>标签,那么 CSS 匹配样式时机如下图所示:

image

如果 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 树构建完成之后,外部样式表还没有下载回来,那么即使内部样式表已经解析完成了,也不会进行任何样式表的匹配。调用堆栈如下图所示:

image

在函数 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,调用堆栈如下:image

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

image

外部样式表位于 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 处,调用堆栈如下:

image

虽然有了渲染树,但是由于没有布局,也就不会进行绘制,在外部样式表下载过程中,页面同样是白色的。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 为 truem_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 处的布局方法调用就可以成功了。

这种情形下匹配时机如下图所示:image

相关文章:

WebKit Inside: CSS 样式表的匹配时机

WebKit Inside: CSS 的解析 介绍了 CSS 样式表的解析过程&#xff0c;这篇文章继续介绍 CSS 的匹配时机。 无外部样式表 内部样式表和行内样式表本身就在 HTML 里面&#xff0c;解析 HTML 标签构建 DOM 树时内部样式表和行内样式就会被解析完毕。因此如果 HTML 里面只有内部样式…...

<HarmonyOS第一课>从简单的页面开始——闯关习题及答案

加入鸿蒙应用开发公开课系统学习HarmonyOS应用开发 判断题 1.在Column容器中的子组件默认是按照从上到下的垂直方向布局的&#xff0c;其主轴的方向是垂直方向&#xff0c;在Row容器中的组件默认是按照从左到右的水平方向布局的&#xff0c;其主轴的方向是水平方向。&#xff…...

视频下载plus+:一款强大的视频下载小程序

引言 在当下&#xff0c;随着视频号的火爆和用户数量的不断增加&#xff0c;视频下载已经成为了众多用户追求的目标。尽管市面上有很多视频下载助手&#xff0c;但是很多人对于如何下载视频还是摸不着头脑。今天我将向大家推荐一款我自己使用并且非常好用的视频下载小程序——…...

扭线机控制

扭线机属于线缆加工设备&#xff0c;线缆加工设备种类非常多。有用于网线绞合的单绞&#xff0c;双绞机等&#xff0c;有关单绞机相关算法介绍&#xff0c;大家可以查看专栏相关文章&#xff0c;有详细介绍&#xff0c;常用链接如下&#xff1a; 线缆行业单绞机控制算法&#…...

Android App启动优化之启动框架

android启动优化是个比较重要的部分&#xff0c;也是一大难题&#xff0c;一个优秀的app首先给人第一感觉就是启动速度&#xff0c;启动速度非常影响用户的体验&#xff0c;那么我们今天展开说说启动优化相关的问题。 我们先来简单分析一下启动过程、启动优化方向&#xff0c;…...

zookeeper入门篇之分布式锁

文章目录 前言非公平锁公平锁 前言 上一篇说过&#xff0c;zookeeper是一个类似文件系统的数据结构&#xff0c;每个节点都可以看做是一个文件目录&#xff0c;也就是说&#xff0c;我们所创建的节点是唯一的&#xff0c;那么分布式锁的原理就是基于这个来的。 代码仓库&…...

leetcode解题思路分析(一百四十九)1297 - 1304 题

子串的最大出现次数 给你一个字符串 s &#xff0c;请你返回满足以下条件且出现次数最大的 任意 子串的出现次数&#xff1a; 子串中不同字母的数目必须小于等于 maxLetters 。 子串的长度必须大于等于 minSize 且小于等于 maxSize 。 首先能想到的是从MinSize开始遍历查找&am…...

你的librosa和scikit-learn打架了吗?

被这个问题困扰好久&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 我的原来版本librosa0.7.1 和 scikit-learn1.3.1 一直拆了按&#xff0c;按…...

理解自动驾驶感知技术

理解自动驾驶感知技术 文章目录 什么是自动驾驶感知技术&#xff1f;自动驾驶感知技术的关键组成部分1. 雷达&#xff08;Radar&#xff09;2. 摄像头&#xff08;Camera&#xff09;3. 激光雷达&#xff08;Lidar&#xff09;4. 超声波传感器&#xff08;Ultrasonic Sensors&a…...

一款简化Python自然语言处理的开源库

迷途小书童 读完需要 3分钟 速读仅需 1 分钟 1 简介 TextBlob 是一个 Python 库&#xff0c;用于处理文本数据的自然语言处理&#xff08;NLP&#xff09;任务。它提供了简单且易于使用的 API&#xff0c;使得对文本进行分析、情感分析、词性标注、名词短语提取等任务变得更加简…...

常用Redis界面化软件

对于Redis的操作&#xff0c;前期有过介绍【Centos 下安装 Redis 及命令行操作】。而在Redis的日常开发调试中&#xff0c;可使用可视化软件方便进行操作。 本篇主要介绍Redis可视化的两款工具&#xff1a;Redis Desktop Manager和AnotherRedisDesktopManager。 1、Redis Desk…...

电脑散热——液金散热

目录 1.简介 2.传统硅脂与液金导热区别 3.特点 4.优点 5.为什么液金技术名声不太好 6.使用方法 1.简介 凡是对于电脑基础硬件有所了解的人&#xff0c;都知道硅脂是如今高性能电脑设备中必不可少的东西。芯片表面和散热器接触面&#xff0c;虽然肉眼看上去是非常光滑的金属…...

多线程锁-synchronized字节码分析

从字节码角度分析synchronized实现 javap -c(v附加信息) ***.class 文件反编译 synchronized同步代码块 >>>实现使用的是monitorenter和monitorexit指令 synchronized普通同步方法 >>>调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置&#xf…...

SpringCloud学习笔记-Eureka的服务拉取

假设是OrderService里面拉取Eureka的服务之一User Service 1.依然需要在该服务里面引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependenc…...

COLLABORATIVE DESIGNER FOR SOLIDWORKS® 新功能

共享和标注 优点&#xff1a;收件人在浏览器中访问共享文 件&#xff0c;无需安装3DEXPERIENCE 平台应用程序。 • 与 SOLIDWORKS 中来自您组织内部或外部的任何人无缝 共享您的设计。 • 直接将评论和标注附加到您的设计作品中&#xff0c;便于立即获得 反馈。 支持 SOLIDWO…...

AMD CPU 虚拟机安装 macos 系统的各虚拟机系统对比

软硬件环境&#xff1a; CPU:AMD R7 7735HS 8核16线程 显卡&#xff1a;AMD R680M 集显 内存&#xff1a;32GB DDR5 硬盘&#xff1a;2TB SSD Windows11 1、VMware Workstation 我用的是17 的版本&#xff0c;使用方便&#xff0c;对于macos 12及以下的安装在需要修改vmx 文…...

php实战案例记录(20)时间比较

在PHP中&#xff0c;有几种常见的方法可以进行时间比较。以下是其中的一些方法&#xff1a; 使用比较运算符&#xff1a;可以使用比较运算符&#xff08;如小于"<“、大于”>“、小于等于”<“、大于等于”>“、等于”“、不等于”!"等&#xff09;来比…...

web中缓存的几种方式

看了构建高性能的web站点一书&#xff0c;对其中的集中web缓存进行一个总结 1 应用程序实现的动态页面缓存 应用程序把动态文件生成的html文件缓存到文件服务器&#xff0c;以后用户请求动态文件&#xff0c;直接从文件服务器加载对应的静态缓存的html文件返回给用户&#xff…...

Stable Diffusion生成图片

画质 masterpiece,best quality,illustration,extremely detail CG unity 8k wallpaper,ultra-detailed,depth of field 杰作&#xff0c;最佳质量&#xff0c;插图&#xff0c;极度详细的8K壁纸&#xff0c;超高详细度&#xff0c;景深 画风 Chinese ink painting,water color…...

MySQL增删查改(进阶1)

一、数据库约束 约束&#xff1a;按照一定条件进行规范的做事&#xff1b; 表定义的时候&#xff0c;某些字段保存的数据需要按照一定的约束条件&#xff1b; 1.null约束 字段null&#xff1a;该字段可以为空&#xff1b;not null&#xff1a;该字段不能为空不指定的话就是…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...

Modbus RTU与Modbus TCP详解指南

目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...