⚠️ 更正(2026-09-24):2026-09-19 记录的本批 cascade 提交58efc45/fea6b84/7d86720与 主仓库415034c/767ecb3/90d7be9在仓库中查无此对象——那一轮工作运行于云端 任务且从未推送、会话卡死,全部丢失(见docs/audit-2026-09-24-full-scan.md§3 实证据)。 cascade 侧于 2026-09-24 重做:登记四角border-<corner>-radius长属性 +border-radius简写(1–4 值含 x/y 斜杠)、background-repeat/position/size三属性 +background简写 三分量展开(commit1c7c322,已落盘本地 cascade 仓库待 push);renderer 侧98b5005修background-position百分比语义(应为(盒−图)×p%)+ 补 HTML 级圆角/cover/contain 像素 覆盖、d356488加 registry↔消费方一致性闸门、B-7修visibility:hidden仍描 outline (e48...段)。三条 batch 现已按 e2e 像素断言重新验证为完成,下文 ①/②/③ 的"落地" 描述仍准确,仅提交哈希以上述为准。最后更新: 2026-09-24 | 高频 CSS 补全第一批(border-radius + opacity/visibility + background-repeat/position/size)(cascade 重做
1c7c322,主仓库98b5005/d356488,三条 batch 全按 goal.md 退出条件落地,见上方更正)①
border-radius从零到通——修复前完全未注册(声明整条被丢)。落地:cascade 注册border-radius简写 +border-<corner>-radius四角长属性(各含横/纵半径),filter按 §3.1 的 1–4 值规则展开四角(px/百分比,非法值整条丢弃);rendererRenderCommand::Rect.border_radius缺省0(无 radius 时与旧矩形逐字节一致,回归底线),后端用 kappa 贝塞尔近似构造圆角路径(build_rounded_rect_path),背景、边框与背景图统一按圆角裁剪。验证:cascade 简写 1–4 值展开单测、renderer e2e 像素(圆角矩形角点在外框外无墨迹、中心仍填充;无 radius 与原矩形一致)。②
background-repeat/background-position/background-size——修复前三属性未注册、绘制按初始值硬编码(repeat 平铺、起点 0 0、natural size)。落地:cascade 注册三属性 +background简写加展开 repeat/position/size 三分量(缺省 repeat /0% 0%/ auto);rendererRect增背景参数(RepeatStyle/BackgroundPosition/BackgroundSize),draw_background_image应用平铺模式(repeat/repeat-x/repeat-y/no-repeat)、起点偏移(关键字/px/百分比)、尺寸缩放(auto/px/百分比/cover/contain 子集)。验证:cascade 注册/简写展开三分量单测、renderer 命令级 + e2e 像素(no-repeat单块、position: center居中、size: cover铺满 vs 原图)。③
opacity+visibility消费——修复前visibility/opacity已注册但全 crate 零消费方。落地:paint 依 CSS Color L3 §5.1 解析 opacity 并 clamp 到 [0,1],(0,1) 时对整棵子树发Opacity/EndOpacity组(opacity:0整棵子树早退、1不发组保持逐字节回归);后端对 opacity 组用离屏 Pixmap 递归渲染整组后再按 α source-over 混合回主画布(组内可嵌套 opacity,正确叠加),与裁剪分组句配对;visibility继承语义下 hidden 元素跳过自身背景/边框/图绘制但仍占布局(layout 层不读 visibility),后代显式visible照常绘制叠于隐藏底上。验证:cascade 注册值/继承单测(visibility 继承、opacity 不继承)、renderer 命令级(组流/0 早退/1 无组)+ 后端离屏单测(α 混合、嵌套裁剪)+ 4 条 e2e 像素(hidden 保布局但无墨迹、hidden 父 + visible 子仍绘、opacity 0.5 白底红 → 粉 ~127、opacity 1 与基线逐字节一致)。全量(2026-09-24 重验):workspace cargo test 全绿(renderer 66 单测 + 45 端到端 + 51 paint 命令级)、cascade 269 单测;三仓库
fmt --all -- --check与clippy --all-targets -- -D warnings干净(doctest 因沙箱管道上限无法 spawn rustc,属环境限制非代码缺陷,见审计 §5)。上一轮(2026-09-18)M-3 batch 3c + 图像管线 + @media 重写 + inline 收口(四项,cascade
031c7cd/ea3e4c6/5882097,layoutbc78ec5,主仓库f248f36/3cb8d00/60d19f4/a5a929f):①
white-space+ 空白折叠(batch 3c)——修复前测量与绘制都直接吃原始文本(源码缩进与换行原样进 cosmic-text,每处源码换行被当真实换行),这是所有真实页面文字排版的系统性偏差。落地:cascade 新增WhiteSpace(collapse/preserve_newlines/wrap,按 CSS Text L3 §4 汇总表逐值派生)与apply_white_space(§4.1.2 Phase I 折叠:space/tab 序列合一;§4.1.3 segment break:CJK 全宽两侧删除、拉丁两侧转空格;pre-line保强制换行但仍折空格;pre/pre-wrap/break-spaces全保留;首尾折叠空白移除),layout 测量与 renderer 绘制调同一函数(单一来源,延续 batch 3 的口径);layout 新增InheritedText.white_space与NodeContext::Text.wrap(nowrap/pre时不换行,测量缓存键随之归一化),纯空白节点仅在"可折叠"时丢弃;rendererRenderCommand::Text.wrap让后端按同一语义决定是否传换行宽度;UA 表补 §15.3.3 的listing/plaintext/pre/xmp { white-space: pre }。验证:cascade 10 条单测(6 关键字三列矩阵 + 折叠矩阵)、layout 5 条(含"折叠后的多行源文本与手写单行测出完全相同排版"的等值断言)、renderer 3 条 e2e 像素(折叠等值 / pre 保强制换行 / nowrap 溢出单行)、chrome<pre>像素断言。②
background-image+ 图像解码绘制管线(BG-1,batch 5 前置)——修复前background-image未注册(声明在 filter 阶段即被丢)、background简写显式丢弃 image 分量、renderer 无任何图像命令。落地:cascade 注册(initialnone)+ 简写展开 image 分量(Token::Url无引号形态与url()函数带引号形态都认,渐变函数原样透传待实现);renderer 新增image模块(ImageBits:RGBA8 + 内在尺寸,自有类型符合解耦 ADR;PNG 解码走 tiny-skia 内置读取器——png本就在依赖图内,未新增依赖)、RenderCommand::Rect.image、后端 Repeat pattern 平铺(绘制序 color → image → border,符合 CSS Backgrounds L3 §2)、PaintInput.images(绝对 URL → 像素,相对解析留在加载层);chrome 新增images模块(按各表自身 location 解析url()、absolutize_image_urls就地改写声明使 key 一致、DocumentFetcher::fetch_bytes复用同一 scheme 策略、上限与失败非致命),NavigationDoc/WebView/app/文件模式/render_html_file全链路携带。验证:cascade 6 条、renderer 4 条命令级 + 3 条 e2e 像素(平铺覆盖盒 / 三层绘制序 / 资源缺失页面照常)、chrome 12 条模块 + 3 条离线 e2e(data:图生效、file://相对路径图生效、http 页引用file://图被 scheme 策略拒绝而背景色照常)。③
@media求值重写(MQ-V)——修复前 unknown 直接当 false(not <未知语法>会错判为真)、not只取反紧随项而非整条 query、only未实现、缺运算符被静默接受、feature 只认 px。落地:按 MQ L4 §3 实现Tristate(Kleene 三值,false and unknown = false、true or unknown = true、其余 unknown 吸收;not unknown仍为 unknown;二值上下文收敛为 false)、not修饰整条 query、only透明、malformed →not all且在下一个顶层逗号处恢复、em/rem 按初始 16px 换算、新增orientation;sheet 级media属性与规则级@media共用同一求值器。验证:cascade 15 条新测试(求值矩阵含三值语义与 malformed 恢复)+ chrome 2 条 e2e 像素(media属性特性查询、orientation)。④ inline
style收口(IS-V)——实测其实现早已在(from_style_attr= §6.1 准则 4、specificity(0,0,0)、简写/--*/!important全通),故本轮为验证与收口:修两处与代码矛盾的陈旧注释("(1,0,0,0)" 与"条件评估推迟")、文档化 §5.4.5 解析入口与完整排序语义、补 4 条端到端像素(内联胜更高特异性作者规则 / 作者!important胜内联普通 / 内联!important胜作者!important/ 内联简写四边框全绘)。全量:cascade 262、layout 132、workspace 300 全绿;三仓库 fmt 与 clippy
-D warnings干净。上一轮(2026-09-14)CS-1(外链 CSS 与其他 CSS 来源接入):
<link rel=stylesheet>从零到通——采集/抓取/@import展开/UA 样式表四条线落地。修复前实测(规划文档证据表):<style>靠字符串扫描提取(忽略全部属性、能命中注释/脚本里的<style)、外链 CSS 完全无实现(navigation.rs注释明示"不在本轮范围")、无任何 base URL 与相对 URL 解析能力、CssStyleSheet的location/media/title/alternate/disabled字段无人填也无人读、CssRule::Import解析后被 cascade 直接跳过、无 UA 样式表(layout 用硬编码 8 标签跳过表顶替,规范 §15.3.1 的 15 个非渲染标签里area/datalist/basefont/noembed/noframes/param/rp会出盒,p/h1/body无默认边距字号)。落地:muskitty-network新增url模块(resolve/file://↔ 路径 / 子资源 scheme 策略 /data:解码,委托 URL Standard 参考实现urlcrate,pub 签名只出&str,30cfca6);chrome 新增stylesheets模块(DOM 先序 = 文档序采集、rel词表/type/media/title/alternate/disabled语义、首个可用<base href>、抓取去重缓存 + 单表 8 MiB/每文档 64 表/深度 16 上限、失败非致命、@import加载期就地展开含循环与layer()/supports()前缀的显式跳过)、render_page_with_sheets多表入口、WebView.sheets取代 css 字符串、文件模式与热重载(监视 HTML + 其file://样式表,改 CSS 也重载,6f25857);cascade 消费 sheet 级disabled/alternate/media(6ed08a0,独立仓库);chrome 新增最小 UA 样式表(HTML §15 Rendering,只用有消费方的属性,0f7df1e)+ layout 的硬编码跳过表注释化为防御(ef63936)。验证:chrome 108 lib + 3 headless + 6 probe + 9 离线多文件 e2e(相对路径 / 后表胜出 / 404 非致命 /media=print跳过 /disabled跳过 /@import链以导入表为基准 / 成环限时 /data:表 / 内嵌@import以文档为基准,全像素断言)+ 7 UA 像素断言(7 个原出盒标签零墨迹、body 8px、h1 2em 与边距、[hidden]与作者覆盖、列表/引用缩进、hr 线);cascade 231 / layout 127 / workspace 269 全绿(chrome 108+3+6+9+7 / renderer 51+17+42 / network 12+10+4 doc-tests);fmt 与 clippy-D warnings干净。上一轮(2026-09-13)W-3b(An+B 符号保真,WPT 选择器套件收尾):
css/selectors/parsing从 94.3%(479/508) 提到 99.8%(507/508)——第七节剩下的 28 例全是同一根因:§7 区分<signed-integer>(integer 类型 + 有符号字符)与<signless-integer>(无符号字符),而 tokenizer 的consume_a_number算出了 sign 又丢弃(§4.3.13 第 7 步本应返回),使5与+5在 token 层同类。修复:Numeric增has_sign(css-tokenizer 0.2.0 → 0.3.0,加 pub 字段属破坏性变更,aaeebd9);下游字面量机械跟进(css-parserf0e7a56/ cssfc1621e/ cascade0f32951/ layouta22bd84,共 28 处合成值,无一处读该标志);selectorsfinish_after_n落实 §7 三类 B 形态(n +5✓ /n 5✗ /n- 5✓ /n- +5✗ /5n + +5✗,裸<integer>符号不限,9371ab6)。HARD_ASSERT_100增至 11 个夹具,符号矩阵回归 38 条断言。剩余唯一 1 例为已记录的 tentative 夹具自相矛盾(:has-slotted(div + div))。全链复跑:tokenizer 85 / css-parser 100 / css-values 150 / cssom 104 / html5-parser 113 / selectors 186 / cascade 225 / layout 127 / workspace 218上一轮(2026-09-13)W-3 第一批(WPT 选择器对齐):
::part(<ident>+)/::slotted(<compound-selector>)/:host(<compound-selector>)/:state()/:heading[()]/:has-slotted落地,补齐:lang()/:dir(),落实伪元素后置规则与 real-selector-list 禁伪元素(:not(::before)invalid vs:is(::before)forgiving-valid),并修掉 css-tokenizer 的--/--0被切成Delim(23ec4ec)——75.6% → 94.3%。上一轮(2026-09-13)M-3 batch 3(line-height + text-transform):
line-height从font_size * 1.2硬编码近似改为按 CSS Inline L3 §4.2 精确解析(normal/数/长度/百分比,测量与绘制共用 cascadetext_props单一来源),text-transform端到端生效(Unicode 全尺寸映射,布局前改写)——cascadeb66d0e4/ layout1730e94/ rendererbdd1dae。上一轮(2026-09-12)M-3 batch 2(方向性边框 + outline 端到端):
border-<side>方向性简写与border-width/style/color的 1–4 值形式此前整条声明被丢弃,layout 从不映射 border(边框不占空间、box-sizing: border-box对边框无效),rendererBorder为单组值、outline-*零消费方。重建为四边模型:cascade 12 条方向性长属性 +outline简写 +thin/medium/thick→1/3/5px(f6c05fa);layout 填 taffy border rect + §4.1 used width(3e1c3a2);renderer 四边独立 Border + 逐边矩形填充 +RenderCommand::Outline(e48cdff)。Phase 3(Layout 层)已完成并剥离:
muskitty-layoutv0.1.0 已拆为独立 git 仓库(muskitty-dev org)。 Phase 4(Renderer)B-3 / B-4 已完成:muskitty-renderer(tiny-skia 后端)DOM→CSS→Layout→Render 全链路打通,最小 demo(HTML+CSS → PNG)工作。 主仓库 workspacemembers = ["crates/muskitty-renderer", "crates/muskitty-network", "crates/muskitty-chrome"];renderer/network/chrome 在主仓库内(未剥离),其余 11 个 crate 已剥离为独立 git 仓库(muskitty-dev org),由fetch-crates.ps1/fetch-crates.sh一次性拉取。
| 模块 | 状态 | 规范覆盖 | 测试通过率 | crates.io | 独立仓库 |
|---|---|---|---|---|---|
| muskitty-html5-tokenizer | ✅ 完成 | §13.2.5.1–§13.2.5.80 (80/80) | 99.8% (7022/7036) | v0.1.4 | muskitty-dev/muskitty-html5-tokenizer |
| muskitty-html5-parser | ✅ 完成 | §13.2.6 (全 insertion mode + 关键算法) | WPT tree-construction 99.0% (1905/1924, 14 script-on skipped) | v0.2.1 | muskitty-dev/muskitty-html5-parser |
| muskitty-dom | ✅ 完成 | DOM Living Standard §4–§7 | 单元测试全绿 | v0.2.1 | muskitty-dev/muskitty-dom |
| muskitty-css-tokenizer | ✅ 完成 | CSS Syntax §4.3 (§4.3.1–§4.3.13) + span tracking + Numeric::has_sign(§4.3.13 第 7 步的 sign,供 An+B 区分 signed/signless) |
单元全绿 + WPT css/css-syntax tokenizer 层 100% (99/99) | v0.2.1 已发布(含 --/--0 ident 修复)/ 本地 0.3.0 待发布(has_sign,破坏性) |
muskitty-dev/muskitty-css-tokenizer |
| muskitty-css-parser | ✅ 完成 | CSS Syntax §5 (§5.2-§5.5 + §5.4.1/§5.4.2 grammar hooks + §5.5.6 original_text) | 单元全绿 + WPT css/css-syntax parser 层 100% (27/27) | v0.3.1 | muskitty-dev/muskitty-css-parser |
| muskitty-css | ✅ 完成 (facade) | 组合 tokenizer + parser | — | v0.6.0 | muskitty-dev/muskitty-css |
| muskitty-selectors | ✅ 完成 | Selectors L4 §3/§4/§5/§6/§13/§14/§15/§17/§18 + L5 §state/§heading + Shadow L1(::part/::slotted/:host()/:has-slotted)+ An+B 符号保真(§7 signed/signless) |
单元全绿 + WPT selectors/parsing 99.8% (507/508),11 个夹具硬断言 | v0.2.1 | muskitty-dev/muskitty-selectors |
| muskitty-css-values | ✅ 完成 | CSS Values L4 §4/§5/§6/§8/§9 + CSS Variables §2/§3 | 单元全绿 + WPT 数值语法 100% (16/16) | v0.1.1 | muskitty-dev/muskitty-css-values |
| muskitty-cssom | ✅ 完成 | CSSOM §3/§8.1/§8.4/§8.5/§8.6 | 104 测试全绿 | v0.1.1 | muskitty-dev/muskitty-cssom |
| muskitty-cascade | ✅ 完成 | CSS Cascade L5 §4.1-§4.4/§5/§6.1/§7 + Backgrounds L3 §3.1/§4(background-image 注册 / border 方向性简写)+ UI L4 §4(outline 简写)+ Inline L3 §4.2 / Text L3 §2.1/§4(line-height / text-transform / white-space 语义单一来源)+ Media Queries L4 §2.2/§3(Kleene 三值求值:not/only/unknown/em-rem/orientation/malformed 恢复)+ CS-1d(sheet 级 disabled/alternate/media 门控) | 262 测试全绿 | v0.1.1 | muskitty-dev/muskitty-cascade (已剥离) |
| muskitty-layout | ✅ 完成 | CSS Display L3 §2 + Box Model L3 §2/§3(含 border 盒模型)+ Flexbox L1 §4-§8 + Inline L3 §4.2(行高测量)+ Text L3 §2.1/§4(转换后文本 + white-space 折叠后文本测量,nowrap/pre 单行语义) + taffy 0.12 集成 | 132 测试全绿 | v0.1.1 | 🔗 muskitty-dev/muskitty-layout (已剥离) |
| muskitty-renderer | ✅ Phase 4 B-3/B-4 + 图像管线 (BG-1) | tiny-skia 后端:DOM→CSS→Layout→Render 全链路 + HTML+CSS→PNG demo + 背景图:ImageBits 自有类型 PNG 解码(tiny-skia 内置读取器,无新依赖)、Rect.image、Repeat pattern 平铺(color→image→border 绘制序);white-space 与 layout 同源;Text.wrap |
53 + 27 + 46 测试全绿 | 本地 v0.1.0 (未发布) | 主仓库内 (未剥离) |
| muskitty-chrome | ✅ 窗口层 + CSS 来源接入 (CS-1) + 背景图 (BG-1) | 自绘 chrome(标签栏/工具栏/地址栏)+ winit 窗口 + 地址栏导航(http/https/file)+ 样式表来源:DOM 文档序采集(<style>/<link>)、外链抓取(scheme 策略/上限/去重)、@import 加载期展开、最小 UA 样式表(HTML §15,含 pre { white-space: pre })、文件热重载(监视 HTML + 外链 CSS)+ 背景图子资源:按各表 location 解析 url() → 绝对化 → 抓取 → PNG 解码(data:/file:///http(s),失败非致命) |
117 lib + 3 headless + 3 images e2e + 6 probe + 11 离线 e2e + 8 UA 像素,全绿 | 本地 v0.1.0 (未发布) | 主仓库内 (未剥离) |
| DOM 完整 API (Events/Style/innerHTML) | ✅ 完成 (2026-08-09) | Events → dom event.rs · element.style → cssom element_style.rs · innerHTML/outerHTML → html5-parser serialize.rs+parse_fragment |
dom/cssom 全绿 + html5-parser WPT 99.0% (1889/1908) | — | — |
| muskitty-network | 🚧 Phase 5 接驳 | NetworkFetcher trait 抽象 + reqwest 后端 + fetch_blocking 同步入口 + url 模块(WHATWG URL 解析/相对化/file↔路径/子资源 scheme 策略/data: 解码);chrome 地址栏导航 + 子资源(样式表)抓取已接驳(远期自研 HTTP/1.1+2+3 栈,见 plan) |
12 + 10 + 4 doc-tests 全绿 (wiremock 离线);chrome navigation/stylesheets 离线 e2e 全绿 | 本地 v0.1.0 (未发布) | 主仓库内 (未剥离) |
14 个 html5lib tokenizer 失败说明:3 个 xmlViolation(infoset 强制转换,规范范围外)+ 11 个 <?...> PI 边界(test2/test3,html5lib 测试套件过时,期望 Comment 但现行 WHATWG §13.2.5.72-76 规定产生 ProcessingInstruction)。代码遵循现行 WHATWG 规范,测试套件过时。对浏览器级应用无影响(真实网页几乎不会触发这些边界)。
按 muskitty-browser-roadmap.md 的 6 个 Phase 全部完成(Phase 6 推迟):
- 新建
crates/muskitty-dom/,无外部依赖 Node/NodeType/NodeKind:Rc<RefCell<Node>>共享所有权模型- 节点类型:
Element/Text/Comment/Document/DocumentType/DocumentFragment/ProcessingInstruction Attribute/Namespace(HTML/SVG/MathML)- 树操作 API:
append_child/insert_before/remove_child/replace_child/set_text_content - 只读遍历:
first_child/last_child/previous_sibling/next_sibling/parent_element/Descendants迭代器 - 单元测试:
crates/muskitty-dom/tests/node.rs
HtmlTreeConstructor结构体:open_elements/active_formatting_elements/insertion_mode/head_element/form_element/foster_parenting/frameset_ok/scripting_flag/template_insertion_modesInsertionMode枚举(全 23 个模式)dispatch()主分发器:foreign content 优先,然后按 insertion mode- 顶层入口
parse(input: &str) -> Rc<RefCell<Node>> ParseError枚举与错误收集
分批实现,每批独立提交(P3-a / P3-b / P3-c / P3-d 系列):
| 批次 | 内容 | 规范 |
|---|---|---|
| P3.1 | Initial / BeforeHtml / BeforeHead / InHead / AfterHead | §13.2.6.2–§13.2.6.5 |
| P3.2 | InBody 核心(段落、标题、列表、div/span、字符插入、implied end tags) | §13.2.6.4 |
| P3.3 | InBody 进阶(active formatting elements 重建、格式化标签、列表嵌套) | §13.2.6.4 |
| P3.4 | InTable / InTableText / InCaption / InColumnGroup / InTableBody / InRow / InCell | §13.2.6.7–§13.2.6.13 |
| P3.5 | InSelect / InSelectInTable / InTemplate / AfterBody / InFrameset / AfterFrameset / AfterAfterBody / AfterAfterFrameset | §13.2.6.14–§13.2.6.22 |
| P3.6 | Text 模式(text/script/textarea/title 内容收集) | §13.2.6.5 |
| P3-d | 边界修复(adoption agency / foster parenting / foreign content 交互) | §13.2.6.4.7 / §13.2.6.4.9 / §13.2.6.5 |
- Adoption Agency Algorithm:§13.2.6.4.7 完整步骤,含 Noah's Ark clause
- Foster Parenting:§13.2.6.3 + §13.2.6.2 foster parent location 完整算法(template/table 优先级、before table 插入)
- Foreign Content:§13.2.6.5 完整 — MathML/SVG namespace、attribute adjustment、integration points、breakout list
- generate_implied_end_tags / reconstruct_active_formatting_elements / reset_insertion_mode 等辅助算法
- 测试 fixture:
crates/muskitty-html5-parser/tests/data/tree-construction/*.dat(56 个 .dat 文件) - 测试 harness:
crates/muskitty-html5-parser/tests/html5lib_tree_construction.rs - DOM 序列化器(html5lib
#document格式) - 通过率:100% (1716/1716),204 skipped(document-fragment 192 + script-on 12)
- gap report:
crates/muskitty-html5-parser/tests/tree_construction_gap_report.md
- Events(DOM §4.4/4.5/4.6)→ muskitty-dom
src/event.rs:add/remove_event_listener+dispatch_event(捕获/目标/冒泡三阶段,Event状态机,零依赖纯 leaf)。 - element.style(CSSOM §4)→ muskitty-cssom
src/element_style.rs:扩展 trait(dom 为 source of truth,parse→mutate→serialize→写回 attribute,无缓存对象)。 - innerHTML/outerHTML(HTML §13.4.2 / §13.6.4-5)→ muskitty-html5-parser
src/serialize.rs+parse_fragment:fragment parsing(context 重建、reset 替换、tokenizer 初态、unwrap)+ 序列化(Normal/RawText/EscapableRawText 转义、void、template content);harness 解锁 document-fragment 用例,WPT 99.0% (1889/1908),12 script-on 跳过。已知遗留:18 个 foreign-context fragment + tests_innerHTML_1 #76(select-context,WPT 夹具早于 2016 reset 删除 select 分支,现行 WHATWG reset 无 select 分支 → InBody 按规范插入 input)。
三个子任务各自独立 commit + push(dom/cssom/html5-parser 独立仓库)。
按 roadmap Layer 2 推进。子阶段 1(CSS Syntax Module §4.3 tokenizer + §5 parser)按 phase2-css-parser-cp1-to-cp8.md 完成。
CSS Syntax Module §4.3.1–§4.3.13 全部实现,详见 C-2 至 C-7 commits 历史。Token 类型:ident/function/at-keyword/hash/string/url/number/unicode-range/delim/whitespace/colon/semicolon/comma/{/}/[/]/(/)/EOF/CDO/CDC。
| 批次 | 内容 | 规范 | commit |
|---|---|---|---|
| CP-1 | §5.2 CSS Parsing Results 数据结构 (Stylesheet/Rule/AtRule/QualifiedRule/Declaration/ComponentValue/Function/SimpleBlock/BlockKind) | Overview.md L1625-1721 | fcb35e6 |
| CP-2 | §5.3 TokenStream struct + 9 操作 (next_token/is_empty/consume_token/discard_token/mark/restore_mark/discard_mark/discard_whitespace + new 构造器) | L1722-1814 | ab82733 |
| CP-3 | §5.5.7-§5.5.11 底层算法 (consume_a_list_of_component_values / consume_a_component_value / consume_a_simple_block / consume_a_function / consume_a_unicode_range_value) | L2745-2872 | 239cd34 |
| CP-4 | §5.5.6 declaration 算法 (consume_a_declaration + consume_the_remnants_of_a_bad_declaration + strip_important + is_custom_property_name + has_top_level_curly_block_with_other_values) | L2639-2741 | 7f143b7 |
| CP-5 | §5.5.1-§5.5.5 上层算法 (consume_a_stylesheets_contents / consume_an_at_rule / consume_a_qualified_rule / consume_a_block / consume_a_blocks_contents + BlockContents + split_block_contents + looks_like_custom_property_in_prelude + ParseError 标记类型) | L2223-2562 | 980e429 |
| CP-6 | §5.4.3-§5.4.10 entry points (parse_a_stylesheet / parse_a_stylesheets_contents / parse_a_blocks_contents / parse_a_rule / parse_a_declaration / parse_a_component_value / parse_a_list_of_component_values / parse_a_comma_separated_list_of_component_values + normalize_from_string) | L2005-2204 | cacad17 |
| CP-7 | lib.rs 顶层 API (parse_stylesheet / parse_rule / parse_declaration / parse_component_value / parse_list_of_component_values / parse_comma_separated_list_of_component_values) + crate-level doc | — | b1e15f4 |
| CP-8 | cleanup + Cargo.toml v0.2.0 (MSRV 1.82) + README + PROGRESS.md 更新 | — | (本 commit) |
§5.4.1 / §5.4.2 grammar hooks✅ 已完成(2026-07-19):在src/grammar.rs实现Grammartrait +parse_a_grammar+parse_a_comma_separated_list_with_grammar;selectors crate 通过parser/grammar.rs::SelectorGrammar接入,§18 Parse A Selector 走 §5.4.1 路径。§5.5.6✅ 已完成(2026-07-22,CV-0b):original_textfor custom propertyCssTokenizer加next_token_with_span+position();TokenStream加with_source构造器 +source_slice方法 +token_spans/source字段;consume_a_declaration对 custom property 捕获original_text。- §5.5.6
unicode-rangedescriptor re-tokenization:需要 source-text tracking 用于重新分词。基础设施已就绪(CV-0b),待实际需求出现后补。
| 测试文件 | 测试数 | 覆盖内容 |
|---|---|---|
tests/parser_types.rs |
6 | §5.2 数据结构构造 + Default |
tests/token_stream.rs |
8 | §5.3 TokenStream 9 操作 |
tests/parser_algorithms_cp3.rs |
10 | §5.5.7-§5.5.11 底层算法 |
tests/parser_algorithms_cp4.rs |
8 | §5.5.6 declaration 算法 |
tests/parser_algorithms_cp5.rs |
12 | §5.5.1-§5.5.5 上层算法 |
tests/parser_entry_points.rs |
11 | §5.4.3-§5.4.10 entry points |
src/lib.rs doctests |
7 | 顶层 API 集成测试 |
| 总计 | 62 | 全部通过 |
每个 CP commit 前依次执行(任一失败不提交):
cargo fmt -p muskitty-css -- --check
cargo test -p muskitty-css
cargo check -p muskitty-css
cargo clippy -p muskitty-css --all-targets -- -D warningsCP-1 至 CP-7 全部满足零 fmt diff、零 warning、全部测试通过。
consume_a_qualified_rule 返回 Result<Option<QualifiedRule>, ParseError> 三态:
Ok(Some(rule))— 成功消费一个 rule。Ok(None)— "return nothing"(EOF 或 stop_token 触发)。Err(ParseError)— "invalid rule error"(如 top-level 的--foo: ...形 prelude 触发 §5.5.3 L2377-2383)。
consume_a_blocks_contents 用 mark/restore_mark 模式处理 declaration 与 qualified-rule prelude 的歧义:先尝试按 declaration 解析;若返回 None(不是 declaration),restore_mark 回到 mark 位置再按 qualified-rule 解析。
§5.5.5 的输出是 rules 与 declaration-lists 的混合 list。Rule enum 加 Declarations(Vec<Declaration>) variant 精确建模。consume_a_blocks_contents 返回时把所有 pending decls flush 为 Rule::Declarations。后续 CSSOM 可以将其 materialize 为 CSSStyleDeclaration 或 CSSNestedDeclarations。
完整覆盖 WHATWG §13.2.5.1–§13.2.5.80,详见 先前版本 的状态表。
| 功能 | 文件 | 状态 |
|---|---|---|
| Tokenizer trait | trait_def.rs |
✅ |
| HtmlTokenizer struct | impls.rs |
✅ |
| reconsume 机制 | impls.rs |
✅ |
| pending_tokens 多 token 发射 | impls.rs |
✅ |
| 全量 WHATWG 命名实体表 | entities.rs |
✅ 2,231 条 |
| 实体查找(二分搜索) | entities.rs |
✅ |
| Windows-1252 替换表 | impls.rs |
✅ |
| 类别 | 数量 | 处理 |
|---|---|---|
<?...> 处理指令边界(test2/test3) |
11 | Phase 2 期间顺带修 |
| xmlViolation(infoset 强制转换) | 3 | 已从基线排除(CLAUDE.md: WHATWG 是 ground truth) |
全 23 个 insertion mode 完整实现,无 stub:
Initial / BeforeHtml / BeforeHead / InHead / InHeadNoscript / AfterHead / InBody / Text / InTable / InTableText / InCaption / InColumnGroup / InTableBody / InRow / InCell / InSelect / InSelectInTable / InTemplate / AfterBody / InFrameset / AfterFrameset / AfterAfterBody / AfterAfterFrameset
| 算法 | 位置 | 规范 |
|---|---|---|
| Adoption Agency | parser/helpers.rs::adoption_agency |
§13.2.6.4.7 |
| Foster Parenting | parser/helpers.rs::foster_parent_location / insert_node |
§13.2.6.2 / §13.2.6.3 |
| Foreign Content dispatch | parser/foreign.rs::dispatcher_routes_to_foreign |
§13.2.6 / §13.2.6.5 |
| Active Formatting Elements 重建 | parser/helpers.rs::reconstruct_active_formatting_elements |
§13.2.6.4 |
| generate_implied_end_tags | parser/helpers.rs |
§13.2.6.4.1 |
| reset_insertion_mode | parser/dispatch.rs |
§13.2.6.1 |
| has_element_in_scope (全 5 种 scope) | parser/helpers.rs |
§13.2.6.4.2 |
| commit | 修复 |
|---|---|
b55e31a |
Foreign Content (SVG/MathML) 与 Template 交互修复(84.8% → 96.4%) |
f901a0d |
Phase 5 测试集成 + bug fixes |
4764216 |
foster parenting / adoption agency / 插入模式规范修复 |
523d816 |
ProcessingInstruction 节点支持 |
122677d |
reconstruct active formatting elements 经由 foster parenting 插入 |
8635e92 |
applet/marquee/object 起始与结束标签处理 |
10e4cda |
AfterAfterFrameset DOCTYPE/whitespace/ 委派 InBody |
b55e31a |
Foreign Content + Template 交互修复 |
dae15ed |
<a> 起始标签 AFE 搜索范围与重构顺序 |
1285460 |
Noah's Ark clause 移除最早而非最晚的匹配条目 |
d7c4eb2 |
harness parse_dat 截断 #document 内嵌空行(tricky01.dat #9) |
1b889fc |
adoption agency furthest block 匹配 SVG 元素为 special(adoption01.dat #13) |
5f767da |
in-cell/applet/marquee/object end-tag namespace 检查(namespace-sensitivity.dat #1) |
11 个 crate 已剥离为独立 git 仓库(位于 muskitty-dev org 下,含 cascade/cssom,2026-08-09 剥离),并通过 GitHub Actions 自动发布到 crates.io(发布状态见下表)。muskitty-renderer、muskitty-network、muskitty-chrome 作为主仓库 workspace member 开发(未剥离、未发布)。主仓库 d:\Muskitty 的 workspace members = ["crates/muskitty-renderer", "crates/muskitty-network", "crates/muskitty-chrome"],exclude 列表排除 11 个已剥离 crate。新设备 clone 主仓库后通过 fetch-crates.ps1 / fetch-crates.sh 一次性拉取。
| crate | 版本 | crates.io 发布时间 | 仓库 |
|---|---|---|---|
| muskitty-dom | 0.2.1 | 2026-09-13 | muskitty-dev/muskitty-dom |
| muskitty-html5-tokenizer | 0.1.4 | 2026-09-13 | muskitty-dev/muskitty-html5-tokenizer |
| muskitty-html5-parser | 0.2.1 | 2026-09-13 | muskitty-dev/muskitty-html5-parser |
| muskitty-css-tokenizer | 0.2.1 | 2026-09-13(本地 0.3.0 待发布,has_sign 破坏性变更随下次 tag 走) | muskitty-dev/muskitty-css-tokenizer |
| muskitty-css-parser | 0.3.1 | 2026-09-13 | muskitty-dev/muskitty-css-parser |
| muskitty-css | 0.6.0 | 2026-08-09 | muskitty-dev/muskitty-css |
| muskitty-selectors | 0.2.1 | 2026-09-13 | muskitty-dev/muskitty-selectors |
| muskitty-css-values | 0.1.1 | 2026-09-13 | muskitty-dev/muskitty-css-values |
| muskitty-cssom | 0.1.1 | 2026-09-13 | muskitty-dev/muskitty-cssom |
| muskitty-layout | 0.1.1 | 2026-09-13 | muskitty-dev/muskitty-layout |
| muskitty-cascade | 0.1.1 | 2026-09-13 | muskitty-dev/muskitty-cascade |
每个独立 crate 仓库统一采用:
- CI workflow(
.github/workflows/ci.yml):6 个 job(Check / Unit Tests / Integration Tests / Format / Clippy / MSRV 1.82);通过scripts/setup-deps.sh克隆 path 依赖到../相对路径;通过GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}注入避免 anonymous clone 被限流。 - Publish workflow(
.github/workflows/publish.yml):tag-triggered(v*);幂等设计(先查询 crates.io API,若版本已存在则跳过);成功后通过softprops/action-gh-release创建 GitHub Release。 CARGO_REGISTRY_TOKENsecret:通过gh secret set配置到每个独立仓库。
- workspace 协调中心:保留
d:\Muskitty\Cargo.toml作为 workspace 根(members = ["crates/muskitty-renderer", "crates/muskitty-network", "crates/muskitty-chrome"]+exclude = [11 个已剥离 crate]),便于本地开发时一次性构建所有 crate。 - 新 crate 孵化器:
muskitty-renderer/muskitty-network/muskitty-chrome作为 workspace member 在主仓库内开发(未剥离);已剥离 crate 各自独立维护。 - 文档中心:保留
PROGRESS.md/CLAUDE.md/AGENTS.md/goal.md/docs/plans/作为项目级文档。
按 roadmap Layer 2 推进。
目标:实现 CSS 解析层,为 Layer 3 (Layout) 提供 cascade + computed values。
入场门槛(已满足):
- Layer 1 通过率 ≥80% ✅(100%)
- DOM Core API 完整 ✅
子阶段(每个独立 commit + 测试):
-
CSS 语法 tokenizer — CSS Syntax Module §5
- token 类型(ident/function/at-keyword/hash/string/url/number/...)
- 状态机:ident / string / url / number / unicode-range
- 输入预处理(§5.3)+ filter CR(§5.4)
-
选择器解析 — Selectors Level 4
- 简单选择器(类型/通用/类/ID/属性)
- 组合器(后代/子代/相邻兄弟/一般兄弟)
- 伪类(结构性、UI、动态暂留 stub)
- 伪元素
- selector 匹配引擎(基于 DOM Core)
-
值解析 — CSS Values Module
- 长度/百分比/角度/时间/分辨率
- calc() / min() / max()
- var() 与自定义属性
-
样式表数据结构 — CSSOM
- Stylesheet / Rule / Declaration / AtRule
- CSSStyleRule / CSSMediaRule / CSSImportRule 等
-
Cascade + Computed values
- 重要度/层叠顺序/来源排序
- 继承 / initial / inherit / unset
- 计算值 / 使用值 / 实际值
ground truth:WPT CSS 测试套件(css/ 目录)
规范依据:
- CSS Syntax Module: https://drafts.csswg.org/css-syntax-3/
- Selectors Level 4: https://drafts.csswg.org/selectors-4/
- CSS Cascading: https://drafts.csswg.org/css-cascade-5/
- CSSOM: https://drafts.csswg.org/cssom-1/
远期 Layer 路线(不展开细节):
| Layer | Crate | 入场门槛 |
|---|---|---|
| 3 | muskitty-layout | Layer 2 cascade + computed values 测试通过 |
| 4 | muskitty-renderer | Layer 3 能产出布局盒 |
| 5 | muskitty-network | 可与 Layer 2–4 并行 |
主仓库作为 workspace 协调中心;11 个 crate 各自独立 git 仓库(在 exclude 列表中),muskitty-renderer / muskitty-network / muskitty-chrome 作为 workspace member 在主仓库内开发(未剥离)。具体每个独立 crate 的内部结构见各自仓库的 README。
d:\Muskitty\ # 主仓库 (Ink-dark/MusKitty)
├── Cargo.toml # workspace 根:members = [renderer, network, chrome], exclude = [11 个已剥离 crate]
├── .gitignore # 排除已剥离 crate 目录
├── crates.json # crate 清单单一来源:standalone (11) + bundled (3)
├── fetch-crates.ps1 / .sh # 一次性拉取 11 个独立 crate 的脚本(清单读 crates.json)
├── PROGRESS.md # 本文件
├── CLAUDE.md / AGENTS.md # 硬约束
├── goal.md # 当轮任务清单与退出条件
├── docs/ # 项目级文档(plans/ audit/ archive/)
├── .trae/archive/ # 阶段规划文档
└── crates/ # 子 crate
├── muskitty-renderer/ # 主仓库成员 (v0.1.0, 未剥离, tiny-skia 后端)
├── muskitty-network/ # 主仓库成员 (v0.1.0, 未剥离, NetworkFetcher + reqwest + url 模块)
├── muskitty-chrome/ # 主仓库成员 (v0.1.0, 未剥离, 自绘 chrome + winit + 导航 + 样式表来源)
├── muskitty-cascade/ # → muskitty-dev/muskitty-cascade (v0.1.1, 独立仓库)
├── muskitty-cssom/ # → muskitty-dev/muskitty-cssom (v0.1.1, 独立仓库)
├── muskitty-layout/ # → muskitty-dev/muskitty-layout (v0.1.1, 独立仓库)
├── muskitty-dom/ # → muskitty-dev/muskitty-dom (v0.2.1, 独立仓库)
├── muskitty-html5-tokenizer/ # → muskitty-dev/muskitty-html5-tokenizer (v0.1.4, 独立仓库)
├── muskitty-html5-parser/ # → muskitty-dev/muskitty-html5-parser (v0.2.1, 独立仓库)
├── muskitty-css-tokenizer/ # → muskitty-dev/muskitty-css-tokenizer (v0.2.1 已发布 / 本地 0.3.0, 独立仓库)
├── muskitty-css-parser/ # → muskitty-dev/muskitty-css-parser (v0.3.1, 独立仓库)
├── muskitty-css/ # → muskitty-dev/muskitty-css (v0.6.0, 独立仓库)
├── muskitty-selectors/ # → muskitty-dev/muskitty-selectors (v0.2.1, 独立仓库)
└── muskitty-css-values/ # → muskitty-dev/muskitty-css-values (v0.1.1, 独立仓库)
未来 crate 预留:无(Layer 5 的 muskitty-network 已落地并接驳 chrome 导航)。
主仓库近期(完整历史见 git log;各独立 crate 的历史在其自身仓库):
2b3923c [deps] Cargo.lock follows muskitty-html5-tokenizer's 0.1.3 -> 0.1.4 release bump
5c94f73 [chore] track deny.toml and ignore the local tool/diagnostic artifacts in the root
cf3c15b [docs] close CS-1 (stylesheet sources): PROGRESS row, goal completion record, plan marked implemented
0f7df1e [chrome] CS-1f: ship a minimal UA stylesheet and inject it into every render entry
6f25857 [chrome] CS-1b/c/e/g: load stylesheets in document order (external <link>, @import, fetch policy, hot reload)
30cfca6 [network] CS-1a: add the URL primitive layer (resolve / file:// <-> path / subresource scheme policy / data: decoding)
4ecace5 [deps] Cargo.lock follows the merged release bumps (cascade 0.1.1 / css-parser 0.3.1 / layout 0.1.1 / selectors 0.2.1 + tokenizer 0.3.0)
5172c1e [docs] record the W-3b merge/reconciliation outcome (architect's patch releases, merge resolutions, cascade filter.rs gap fix)
0702153 [docs] close W-3b (An+B sign fidelity): WPT selectors 99.8% (507/508)
c1cb1cc [docs] close W-3 batch 1 (WPT selectors alignment): 75.6% -> 94.3%
43742b8 [docs] stop tracking docs/cat-fetch-skill.md (personal tool doc)
e5b538f [docs] record the toolchain recovery in the batch-3 notes (stable 1.98.1 re-verification)
9522d32 [build] re-green the lint gate after the toolchain update to rustc/clippy 1.98
9ad3041 [tools] crate list single source of truth (crates.json)
3d32f46 [docs] align all documentation with current reality (2026-09-08)
... (完整历史见 git log)
历史各阶段(Phase 2/3/4 各子阶段的完整 commit 序列,如 css-values CV-1CV-6、
css-parser CP-1CP-8、tokenizer/parser P3-d 系列修复)同样见 git log 与
.trae/archive/ 内的各阶段规划文档,此处不再重复罗列。
Phase 2 子阶段 3 — CSS Values Module✅ 已完成(2026-07-22)并已提取发布(2026-07-24)到 crates.io。Phase 2 子阶段 4 — CSSOM✅ 已完成(2026-07-22)并已提取发布(2026-07-24)到 crates.io。Phase 2 子阶段 5 — Cascade + Computed values✅ 已完成(2026-07-23)。Cascade 收尾(Phase 3 前置)✅ 已完成(2026-08-01):inlinestyle属性收集已实现。Phase 3 — Layout✅ 已完成(2026-08-01):taffy 0.12 集成,46 个测试全绿,审计修复 7 个 bug。已剥离为独立仓库。Phase 4 — Renderer✅ B-3/B-4 已完成(2026-08-02):muskitty-renderer(tiny-skia 后端)DOM→CSS→Layout→Render 全链路打通,HTML+CSS → PNG demo 工作。全项目审计修复✅ 已完成(2026-08-09):B1-B14 全部完成,P0/P1/P2 清零,见docs/audit-2026-08-08-full-scan.md修复状态汇总。收尾:P2-1(绝对长度单位 ✅)/ P3-2(calc 求值 ✅)/ PERF-10(map_style 优化 ✅)/ 简写展开(margin/padding/flex/background/font ✅,cascaded6d7208)/ 布局 B6 flex 简写端到端解锁(layout44b9b1d)。DOM 完整 API 扩展✅ 已完成(2026-08-09):Events → muskitty-domevent.rs;element.style → muskitty-cssomelement_style.rs;innerHTML/outerHTML → muskitty-html5-parserserialize.rs+parse_fragment(WPT harness 解锁 fragment 用例,99.0%)。- Tokenizer 遗留:14 个 html5lib 失败已确认非 bug,保持现状。
文本渲染 + 布局增强 + 窗口化✅ 已完成(2026-08-16):文本渲染(layout 测量3d18bf4+ renderer glyph 渲染50bc822)、position 定位(d721a0b)、overflow 裁剪(d09bc78)、grid 布局(cascade2c27d7d+ layoute690d4e)、winit 窗口化(07eb0b8)。均已推送远端。T-3 换行 + 字体属性✅ 已完成(2026-08-22);修正(2026-08-29,0d3ec6b):文本测量高度公式误把 cosmic-textrun.line_y(基线,含行内居中偏移 + max_ascent)当行顶 → 文本盒 ~2.2em(应为line_top + line_height= 1.2em);同时纯空白文本节点(源码换行/缩进)此前被排版为实体盒(换行符 → 2 行高)逐级下推后续内容——两者叠加即"汉字纵向位移"。修复 + 回归测试(whitespace_only_text_node_takes_no_space)。1.2em 近似行高仍保留(精确 line-height 解析推迟)。原记录:taffy measure function 换行(layout8f18108)、font-family/font-weight 测量(layout056ec23)、renderer 换行/字重/对齐(renderer8a28bc8/fc6f971/20606b8)。补齐端到端用例时发现并修复多行叠行 bug(draw_text漏加run.line_y,glyph 行内局部坐标需加行顶偏移)。line-height 仍为font_size * 1.2近似,精确解析推迟。外部依赖解耦✅ 已完成(2026-08-16):layout(taffy/cosmic-textbf52557)、renderer(tiny-skia Pixmap98af15d)、network(reqwest Error8cfbdfd)公共 API 均不再暴露外部依赖类型,上层可抽离。W-2 DPI(HiDPI 缩放)✅ 已完成(2026-08-29):Backend::render/render_page/render_html_file加 scale 参数——layout 用逻辑视口(CSS px),栅格化物理分辨率round(logical×scale)(renderer0192cae/ shell pageb0a645f);窗口流读 hidpi scale、脏检查含 scale、ScaleFactorChanged重绘(shelld53ca2f)。整数 1x/2x 有 scale=1-vs-2 单测兜底(非插值);render_file示例 scale=1 输出不变。W-3 输入(InputEvent 抽象 + shell 快捷键层 + 事件分发结构)✅ 已完成(2026-08-29):input.rs 类型 + 纯函数match_shortcut(shell953f890)、PlatformWindow::handle_event页面层入口(7fed8bb)、App 事件接线 + 转换函数(0962c74)、文档同步(C-4)。架构修正:快捷键层在App::dispatch_input(Esc 需event_loop.exit()、Ctrl+R 需渲染管线,均 App 独有),handle_event为页面层(W-3 无命中测试恒false,仅立分发结构);页面级命中测试单列延后。M-3 batch 1(border 简写 + media 视口接线)✅ 已完成(2026-08-29):border:简写按 CSS Backgrounds & Borders L3 §4.4 展开为 border-width/style/color(顺序无关<width>||<style>||<color>、每类至多一次、缺失类别取注册表初始值 medium/none/currentcolor,cascadeb87b820;renderer 端到端 paint 测试证明extract_border消费,de48621);media 视口接线——compute_styles用StyleTreeOptions.viewport_width/height构造MediaContext(cascadefcde127),render_page传逻辑布局视口(shellf0c5619),默认 1920×1080 行为不变。M-3 余项:@layer 排序已完整(audit B8,无需再做);background-image / revert 真语义 / 方向性 border(border-left 等)/ outline 延后(renderer 无 image 消费方;revert 需低 origin/层回滚零真实页面需求)。 15b.M-3 batch 2(方向性边框 + outline 端到端)✅ 已完成(2026-09-12):本轮把 batch 1 的"统一属性"模型补成完整的四边模型,并补上 outline。修复前实测的三层缺口——① cascade 对border-<side>简写与border-width/style/color的 1–4 值形式不展开(registry 未命中 → 整条声明丢弃,border-left: 4px solid red静默无效);②border-<side>-style四向未注册;③ layoutstyle.border从未赋值(边框不占空间、box-sizing: border-box对边框无效,CSS Box Model L3 §2-§3 违背);④ rendererBorder单组宽/色/样式且用 inset-rect stroke;⑤outline-*注册但零消费方。落地:cascade 展开 12 条方向性长属性(border)/3 条(border-<side>)/4 条(border-width|style|color多值,分量类型校验)+outline简写 +thin/medium/thick→1/3/5px computed 归一化 + 注册border-<side>-style、移除三个简写的 registry 条目(f6c05fa);layout 填 taffy border rect + §4.1 used width(style none/hidden → 0,3e1c3a2);renderer 四边独立Border{top,right,bottom,left: Option<SideBorder>}+ 逐边矩形填充(方块拼角,miter 近似已文档化)+parse_border_style覆盖 §4.2 全集(hidden 不绘制;double/groove/ridge/inset/outset 由静默丢弃改为 solid 近似)+currentcolor用元素文字色 + 新增RenderCommand::Outline(子节点之后发出 → 绘制在 border box 外、后代之上;outline-style:auto→solid、outline-color:auto→文字色;outline-offset未注册固定 0)(e48cdff)。验证:cascade 210(89+31+73+16+1)、layout 121、renderer 102(50+37+15)、chrome 94 全绿,含全链路像素断言(仅左边框只染左侧 6px;5px border 撑大 border box 至 30px;outline 落在 10px margin 盒外 3px);fmt/clippy-D warnings干净。M-3 剩余缺口按批次 3–6 排期(文本属性 → opacity/z-index/visibility → border-radius/背景图 → 布局消费方),总账见 docs/plans/2026-09-12-css-completion.md。
15c. M-3 batch 3(line-height 精确解析 + text-transform 端到端) ✅ 已完成(2026-09-13):修复前——line-height 是 layout 与 renderer 两处各自的 font_size * 1.2 硬编码近似(T-3 遗留),任何 line-height 声明对换行、盒高与行位置完全无效;text-transform 注册但零消费方(大写/小写/首字大写全不生效)。落地:cascade 新增 text_props 模块作为测量与绘制共用的单一语义来源——used_line_height_px(px 直用/<number> 乘自身 font-size/百分比/normal=1.2×font-size;负值与 NaN 非法回退 normal,0 合法按规范行重叠,+inf 钳到 2^25 以免非有限值进入 cosmic-text Metrics)与 apply_text_transform(Unicode 全尺寸映射 ß→SS,capitalize 按空白切词近似规范的分段规则;none/未知关键字借用原文);line-height: <percentage> 在 computed value 阶段按自身 font-size 转 px,<number> 保持数字形态随值继承(子元素按自身 font-size 折算,Chrome getComputedStyle 同语义)(cascade b66d0e4);layout NodeContext::Text 新增 line_height 字段、measure_text 收行高参数、文本叶在建树时即应用转换(测量即转换后文本),并把持续膨胀的继承文本参数收敛为 InheritedText 结构体(font-size/family/weight + line-height/text-transform)(layout 1730e94);renderer RenderCommand::Text 新增 line_height,paint 经同一 cascade 函数解析行高并在生成命令时改写内容,backend draw_text 不再自算行高(renderer bdd1dae)。验证:cascade 225(101+31+73+19+1)、layout 121(含 6 项新测量用例:40px 行高使换行盒高为 40 的整数倍、line-height:2 在 16/32px 字号下为 32/64px、150%→24px、"转换后文本与字面量大写测出完全相同排版"的等式断言)、renderer 110(51+42+17,含全链路像素:60px 行高把末行墨迹下推 >40px 且总墨迹差 <10%、uppercase 对同串产生不同墨迹)、chrome 94 全绿;fmt/clippy -D warnings 干净。验证口径教训:文本类像素断言只用等值/不等与位置,不用"大写更宽/更高"这类字体相关量值比较(首版即因 l 的 ascender 高于大写而误报);行高像素断言要求画布足够高,否则高行高的末行被裁会令墨迹计数失真。批次 3 余项(letter-spacing/word-spacing 需自建 advance 契约、font-style: italic 受系统字体面影响、white-space/text-indent/direction)排 3b/3c。
-
W-4 Headless 后端(HeadlessWindow + render_to_png + 无窗口测试)✅ 已完成(2026-08-29):HeadlessWindow: PlatformWindow无窗口实现(present保存最近帧、可save_png,无外部依赖类型可公开构造,shell070e013);lib 顶层render_to_png全管线 → PNG 便捷函数 +page::encode_png编码出口(tiny-skia 升正式依赖但类型不入 pub API;window_demo 示例声明 required-features,shell25610a6);无窗口集成测试——render_to_pngPNG 解码像素与render_page直接渲染逐字节一致、HeadlessWindow 帧/编码产物一致、scale=2 分辨率核对(shell2c83e44),--no-default-features下 check/test/clippy 全绿,feature gate 兑现 CI 无窗口价值。 -
W-5 多标签状态管理(WebViewCollection + 标签快捷键 + 脏位延迟更新)✅ 已完成(2026-08-29):webview.rs(不 feature 门控)——WebView(内容 + 每标签渲染状态 +needs_repaint/close_scheduled脏位)+WebViewCollection(新建/延迟关闭/切换,active 不变量,切换自动标脏,shella618c4e);标签快捷键 Ctrl+T/W/19/PageUp/PageDown(W-5 全部完成。ShortcutAction扩展 +Key::PageUp/PageDown,match_shortcut5 条新单测,dispatch_input接线,Ctrl+T 开默认内容、全部关闭退出,shell9e5c3dc);脏位延迟更新——shell 动作只标脏 + request_repaint,RedrawRequested统一 flush(关标签延迟移除/空则退出/脏或 stale 才重渲染,shellea99d22)。范围裁剪:favicon 占位、tab strip 不做。窗口化轨道 W-1 -
muskitty-chrome 窗口层(自绘 chrome,取代 shell)✅ 已完成(2026-08-29):决策见 ADR2026-08-29-chrome-window-layer(egui GPU 管线冲突 / iced 框架开销,选 Chromium Views 式自绘合成)。chrome::model(布局纯函数 9 测)/paint(tiny-skia + cosmic-text 0.13 + swash outline,7 像素测)/input(hit_test/apply 6 测)/compositor(页面+chrome 同帧合成)/app(winit + softbuffer、标签集合、脏位 flush)。功能:多标签快捷键、地址栏(Ctrl+L/输入/回车提交 → http/https/file 顶级文档导航,navigation模块 2026-09-06 接驳 muskitty-network;不支持 scheme 留占位页;(tab, epoch)过期导航丢弃)、文件热重载(mtime 200ms 轮询)、render_window_to_png无窗口 CI 测试(62 条,--no-default-features全绿)。真窗口验证(自动化 + 用户实测)发现并修复按键双发(漏 Pressed 过滤)与首帧 R/B 通道互换(rgba_to_0rgb与 softbuffer0x00RRGGBB契约不符,9562f0f)。muskitty-shell退役删除(84b07a4,git rename 保留历史),W-1~W-5 语义由 chrome 承接。 -
CS-1 外链 CSS 与其他 CSS 来源接入✅ 已完成(2026-09-14):规划见 docs/plans/2026-09-13-external-css-and-css-sources.md(含逐条 file:line 现状证据、决策 D1–D7、规范本地源行号)。四层落地——① URL 基建:muskitty-network::url(resolve相对解析、file_url_from_path/path_from_file_url、is_fetchable_subresourcescheme 策略(http(s) 页拒file://、file 页允 http(s)、data:恒允、其余拒)、decode_data_url(媒体类型 + forgiving base64 + 百分号解码);委托urlcrate(WHATWG URL 参考实现,纯 Rust,原为 reqwest 传递依赖)——不引它的替代方案是手写 RFC 3986 子集,取舍写在模块文档)。② chromestylesheets模块:DOM 先序(= 文档序)采集<style>/<link>,rel词表(next stylesheet仍建链)、type非text/css不抓、空/缺href忽略、首个可解析<base href>生效、media/title/alternate/disabled落位到CssStyleSheet;抓取按 scheme 分发(http(s) 必须 2xx +text/css,缺 Content-Type 按样式表默认类型;file 本地读;data 解码;BOM 嗅探 + UTF-8 为准的编码策略),单表 8 MiB / 每文档 64 表 /@import深度 16 上限、按 URL 去重缓存(失败也缓存、每个引用点各建一张表——规范 §4.2.4 "每个 link 是独立资源")、失败非致命;@import加载期就地展开(合法位置判定、以导入表自身 URL 为基准、条件导入包@media、循环/深度/失败跳过、layer()/supports()前缀整条跳过并记录)。③ 接线:render_page_with_sheets多表入口(旧单表入口保留)、NavigationDoc携带 final_url + sheets(样式表在导航线程内抓,UI 线程零子资源 IO)、WebView.sheets取代 css 字符串、file 模式/file://导航走同一路径、热重载监视集合扩到 HTML + 其file://样式表。④ cascade 消费 sheet 级字段(disabled/alternate/media属性的整表门控,独立仓库6ed08a0)与 最小 UA 样式表(HTML §15 Rendering:15 标签display:none清单 +[hidden]+ body 8px + 流内容 1em/40px 边距 + 标题:heading(n)表展开 + 列表缩进 + hr;只用当前有消费方的属性,未写的规则(italic/larger-smaller/sub/sup/:link颜色/text-decoration/content-visibility/表格/表单控件/presentational hints)与两处偏差(bolder→bold、noscript无条件隐藏)逐条记录在ua.css头部;UA 表由渲染入口统一注入,OnceLock缓存)。范围与偏差:外链为 render-blocking(无首屏渐进渲染);编码非 UTF-8 走 lossy(触发条件已记录);url()相对解析随图像管线另排。验证:chrome 108 lib + 3 headless + 6 probe + 9 离线 e2e(真TcpListener多文件 + 像素级断言)+ 7 UA 像素断言;cascade 231 / layout 127 / workspace 269;fmt 与 clippy-D warnings干净。
时间:2026-07-23 → 2026-08-01
最终交付:muskitty-layout v0.1.0(本地,未剥离),46 个测试全绿。
| 子阶段 | 内容 | 状态 |
|---|---|---|
| L-0 | crate 骨架 + Cargo.toml + lib.rs 文档 | ✅ |
| L-1 | LayoutTree 类型(taffy TaffyTree + NodeId 映射) | ✅ |
| L-2 | ComputedStyle → taffy Style 映射(style_map.rs) | ✅ |
| L-3 | DOM + ComputedStyle → LayoutTree 转换(convert.rs) | ✅ |
| L-4 | 布局计算(compute_layout 函数 + LayoutResult) | ✅ |
| L-5 | 单元测试(35 style_map + 8 compute) | ✅ |
| L-6 | 端到端集成测试(7 个:HTML+CSS → cascade → layout → result) | ✅ |
2026-08-01 对照 CSS Display L3 / Box Model L3 / Flexbox L1 / Box Alignment L3 / Cascade L5 规范审计,发现并修复 7 个 bug:
| # | Bug | 优先级 | 修复 |
|---|---|---|---|
| B2 | align-items: normal 错误回退为 STRETCH |
P1 | 显式映射 normal → FLEX_START |
| B1 | display: inline-flex/inline-grid 映射为 Block |
P1 | 显式分支 inline-flex → Flex, inline-grid → Grid |
| B3 | box-sizing 默认值用 taffy 的 BorderBox 而非 CSS 初始值 ContentBox |
P2 | 初始化改为 ContentBox + 未知值回退到 ContentBox |
| B4 | gap: 10px 20px 双值未解析 |
P2 | 新增 extract_gap_pair 正确分离 row-gap/column-gap |
| B7 | 集成测试断言过弱(c2.x >= c1.x) | P2 | 收紧为 c2.x ~= c1.x + c1.width |
| B8 | 集成测试 margin 断言过松(x >= 19.0) | P3 | 收紧为 x == 20.0 ± 1.0 |
| B5 | flex-grow/flex-shrink 接受负值 |
P3 | 添加 >= 0.0 检查 |
B6(flex 简写未实现):✅ 已修复(2026-08-09)——cascade 在 collect 阶段展开 flex 简写为 grow/shrink/basis(cascade d6d7208),布局端到端测试验证(layout 44b9b1d)。
按 phase2-selectors-sp1-to-sp8.md 8 个 SP batch 全部完成,覆盖 Selectors Level 4 §3 / §4 / §5 / §6 / §13 / §14 / §15 / §17 / §18。
| SP | 内容 | 状态 |
|---|---|---|
| SP-1 | §3 数据模型 + parser 框架 | ✅ |
| SP-2 | §5 / §6.5 / §6.6 type / universal / class / id 解析 | ✅ |
| SP-3 | §6 attribute selectors | ✅ |
| SP-4 | §13 tree-structural pseudo + An+B 解析 | ✅ |
| SP-5 | §4 logical combinations (is/not/where/has) 解析 | ✅ |
| SP-6 | §15 combinators + complex selector | ✅ |
| SP-7 | §17 specificity | ✅ |
| SP-8 | §18 matching engine + lib API(含 DOM 端到端测试) | ✅ |
测试矩阵:
| 测试文件 | 测试数 | 覆盖内容 |
|---|---|---|
tests/parser_types.rs |
6 | §3 数据结构 + Combinator / PseudoClassArgument |
tests/parser_simple.rs |
10 | type / universal / class / id / ns 解析 |
tests/parser_attribute.rs |
11 | §6 属性选择器(presence/exact/~/` |
tests/parser_pseudo_tree.rs |
12 | §13 tree-structural + An+B 解析 |
tests/parser_nth_of.rs |
4 | §13.3 nth-child(An+B of S?) 解析 |
tests/parser_logical.rs |
10 | §4 is/not/where/has 解析(forgiving / 非forgiving) |
tests/parser_complex.rs |
12 | §15 combinators + mixed + trailing 拒绝 |
tests/specificity.rs |
22 | §17 A/B/C triplet + is/not/has/nth-of 取最大 |
tests/matching_basic.rs |
19 | §5 / §6 simple-selector 匹配 + §15 组合器匹配 |
tests/matching_pseudo.rs |
29 | §13 tree-structural + An+B + §4 logical 匹配 |
tests/matching_dom.rs |
10 | 端到端 DOM 匹配 + query_selector(_all) |
| 总计 | 145 | 全部通过 |
架构:
- Parser (
src/parser/) — 复用muskitty-css::tokenize,构建SelectorList/ComplexSelector/CompoundSelector/SubclassSelector/PseudoClass/PseudoElement。无 DOM 依赖。 - Specificity (
src/specificity.rs) — 按 §17 计算 A/B/C 三元组。:is()/:not()/:has()取参数最大值;:where()贡献 0。 - Matching (
src/matching/) — 通过Elementtrait 抽象元素 5 个 aspect(§3 L858-873);右-左走序匹配(§18 L4902-4919)。包含 simple_matcher / pseudo_matcher / dom_impl 子模块。
延后项:
:has()多 compound 相对选择器(:has(.a > .b))— SP-8 仅支持单 compound;多 compound 返回false。- 命名空间严格匹配(
ns|tag)— 当前保守处理为"任意命名空间均可"。 - WPT 子集集成 — 推迟到拆仓后做。
- §7-§12 UI / location / linguistic / resource / display / input 伪类 — 解析已支持,匹配 stub 返回
false。
crate 成熟度满足拆分独立 git 仓库的条件(1952 LoC src + 1123 LoC tests,145 测试全绿,覆盖 §3 / §4 / §5 / §6 / §13 / §14 / §15 / §17 / §18)。已于 2026-07-19 剥离为独立仓库 muskitty-dev/muskitty-selectors(Hard extraction,自有 [workspace] 块,path 依赖指向 ../muskitty-css 等同级 crate)。v0.1.0 已发布到 crates.io(2026-07-19T12:11:16Z)。
muskitty-css-parser/src/grammar.rs:新增Grammartrait +parse_a_grammar+parse_a_comma_separated_list_with_grammar(CSS Syntax Module Level 3 §5.4.1/§5.4.2)。muskitty-selectors/src/parser/cv_adapter.rs:ComponentValue → Token 适配器,让 Selectors parser 复用 CSS Syntax 解析路径。muskitty-selectors/src/parser/grammar.rs:SelectorGrammar+RelativeSelectorGrammar,让 §18 Parse A Selector 走 §5.4.1 路径。muskitty-css/src/parser/mod.rs:重新导出 grammar 模块。- 测试:css-parser 74 / selectors 150 / workspace 263 全部通过。
按 2026-07-22-css-values-module.md 7 个 CV batch 全部完成,覆盖 CSS Values Level 4 §4 / §5 / §6 / §8 / §9 + CSS Variables Level 1 §2 / §3。
设计原则:解析与求值分离——本 crate 只构建类型化 AST,不做数值计算和 var() 替换求值(留到子阶段 5 Cascade)。
| CV | 内容 | 规范 | commit |
|---|---|---|---|
| CV-0a | CssTokenizer 加 next_token_with_span + position() |
工程基础设施 | — |
| CV-0b | TokenStream::with_source + source_slice + §5.5.6 original_text |
CSS Syntax §5.3 / §5.5.6 | — |
| CV-1 | 数值类型:Length/Percentage/Number/Integer/Angle/Time/Frequency/Resolution/Ratio | §4.4-§4.7, §5, §6 | 084809a |
| CV-2 | 文本类型:Keyword/CustomIdent/DashedIdent/CssString/Url | §3 | c18c153 |
| CV-4 | VarReference 解析(name + fallback,支持嵌套 var()) | CSS Variables §3 | 6edd8f9 |
| CV-3 | MathExpression AST + calc/min/max/clamp 递归下降解析 | §9 | 0c3f519 |
| CV-5 | ValuesGrammar impl Grammar + ToCss 序列化 | §5.4.1, §8.1, §9.7 | 44e80cb |
| CV-6 | lib 顶层 API + doctest | — | d9a8a9b |
测试矩阵:
| 测试文件 | 测试数 | 覆盖内容 |
|---|---|---|
tests/numeric.rs |
33 | 9 个数值类型(正/负/单位/范围检查) |
tests/textual.rs |
25 | 5 个文本类型(keyword/ident/string/url + CSS-wide keyword 排除) |
tests/math.rs |
36 | calc/min/max/clamp + 常量 + 嵌套 + 错误处理 |
tests/var.rs |
12 | var() 解析(name/fallback/嵌套/空 fallback) |
tests/integration.rs |
33 | Grammar hook 入口 + 序列化 roundtrip |
src/lib.rs doctests |
9 | 顶层 API doctest |
| 总计 | 148 | 全部通过 |
架构:
- numeric.rs — 9 个数值类型,带单位枚举 + §4.4 范围检查。
single_non_ws_cv辅助函数过滤 whitespace。 - textual.rs — 5 个文本类型。
CSS_WIDE_OR_RESERVED排除 initial/inherit/unset/default/none。 - math.rs —
MathExpression枚举(Length/Percentage/Number/Constant/Var/Negate/Sum/Product/Quotient/Min/Max/Clamp)+CalcParser递归下降解析器(calc-sum → calc-product → calc-value,左结合)。 - var.rs —
VarReference { name, fallback },from_function支持 calc() 内嵌套 var()。 - grammar.rs —
ValuesGrammarimplGrammartrait,ValueKind16 变体,CssValue包装枚举。 - serialize.rs —
ToCsstrait + 14 类型实现。MathExpression 序列化遵循 §9.7(+两侧空格,*//无空格,Negate →(-1 * expr))。
延后项(推迟到子阶段 5 Cascade):
- calc() 数值计算(需要布局上下文解析百分比)
- min()/max()/clamp() 比较
- var() 替换求值(§3 的 4 步算法,需要元素上下文 + 循环检测)
- 三角/指数/round/mod/rem/sign/abs(CSS Values 4 新增,布局用不到)
crate 已剥离为独立 git 仓库(muskitty-dev/muskitty-css-values)并发布到 crates.io v0.1.0(2026-07-24)。
按 2026-07-22-cssom.md 5 个 OM batch 全部完成,覆盖 CSSOM §3 / §8.1 / §8.4 / §8.5 / §8.6。
设计原则:单向转换——语法→语义。css-parser 产出语法层 Stylesheet(CSS Syntax §5.2),CSSOM crate 将其转换为 CSSOM 语义层 CssStyleSheet(§8.1)。转换后 CSSOM 树独立存在,不反向引用 css-parser 的 Stylesheet,避免生命周期耦合。
| OM | 内容 | 规范 |
|---|---|---|
| OM-1 | crate 骨架 + CssDeclaration + CssStyleDeclaration |
§8.5 / §8.6 |
| OM-2 | CssRule 枚举 + 8 种 rule 类型(Style/Import/Media/Namespace/Supports/LayerBlock/LayerStatement/Container)+ OtherRule fallback |
§8.4 |
| OM-3 | CssStyleSheet 顶层容器 + 元数据(location/media/title/alternate/disabled) |
§8.1 |
| OM-4 | 从 css-parser Stylesheet → CSSOM CssStyleSheet 单向转换层;at-rule 按 name 分发(import/media/namespace/supports/layer/container/other) |
§8.4 / §8.6 |
| OM-5 | 序列化(§3 serializing idioms + §8.4-§8.6 rule/declaration/block 序列化)+ ToCss trait |
§3 / §8.4-§8.6 |
测试矩阵:
| 测试文件 | 测试数 | 覆盖内容 |
|---|---|---|
src/declaration.rs (单元) |
10 | CssDeclaration + CssStyleDeclaration CRUD + cascade 语义 |
src/rule.rs (单元) |
13 | CssRule 枚举 + type_id + has_child_rules + 各 rule 类型 |
src/stylesheet.rs (单元) |
5 | CssStyleSheet 容器 + iter + clone |
src/serialize.rs (单元) |
13 | §3 idioms + 数字格式化 + declaration/block/rule 序列化 |
tests/convert.rs (端到端) |
20 | parse → convert → 验证结构(所有 rule 类型 + 嵌套 + 边界) |
tests/integration.rs (roundtrip) |
19 | parse → convert → serialize → 验证输出 |
src/lib.rs doctests |
1 | 顶层 API |
| 总计 | 81 | 全部通过 |
架构:
- declaration.rs —
CssDeclaration { name, value: Vec<ComponentValue>, important }(§8.5)+CssStyleDeclaration { declarations, readonly }(§8.6)。get_property返回最后一个匹配(cascade 语义)。 - rule.rs —
CssRule枚举(9 变体),用 enum 而非 trait 对象(值语义、pattern matching、避免Rc<RefCell<>>)。type_id返回 §8.4 的 rule type 常量。 - stylesheet.rs —
CssStyleSheet { location, media, title, alternate, disabled, css_rules }。省略 DOM 集成字段(parent/owner node/origin-clean 等)。 - convert.rs —
from_stylesheet(&Stylesheet) -> CssStyleSheet。at-rule 按 name 分发;@import从 prelude 提取 href(string/url)+ media;@layer根据 block 有无决定 LayerBlock/LayerStatement;嵌套裸声明(Rule::Declarations)合并到父 style 块。 - serialize.rs —
ToCsstrait + §3 serializing idioms(serialize_identifier/serialize_string/serialize_url)。Token/ComponentValue/Function/SimpleBlock 序列化。CssRule 用 match 分发到各 rule 类型序列化。
关键设计决策:
- 枚举 vs trait 对象:选枚举。CSSOM rule 类型是规范固定的集合,无需开放扩展;枚举值语义、pattern matching 清晰、避免所有权复杂度。
- 嵌套裸声明处理:CSS nesting 中
Rule::Declarations(§5.5.5)简化合并到父CssStyleRule.style,不实现CSSNestedDeclarations(推迟)。 - Declaration value 存
Vec<ComponentValue>:不做值类型化(那是 muskitty-css-values 的工作),CSSOM 层只关心声明结构。
延后项:
- mutation API(insertRule/deleteRule/setProperty)— Cascade 只读
- CSSStyleSheet construction(JS API)— DOM 集成阶段
- shorthand 序列化合并 — 需要属性数据库,子阶段 5 Cascade
- CSSKeyframesRule / CSSFontFaceRule / CSSPageRule — 按需
- MediaList 接口 — 简化为
Vec<ComponentValue>,子阶段 5 - computed flag / owner node / updating flag — DOM 集成阶段
- CSSNestedDeclarations — 嵌套裸声明暂合并到父 style
crate 已剥离为独立 git 仓库(muskitty-dev/muskitty-cssom)并发布到 crates.io v0.1.0(2026-07-24)。
按计划文档 docs/plans/2026-07-23-cascade.md 推进,7 个批次(CC-1 ~ CC-7)全部完成。规范来源:CSS Cascading and Inheritance Level 5(本地 d:\csswg\css-cascade-5\Overview.md)。
| 批次 | 内容 | 规范 | commit |
|---|---|---|---|
| CC-1 | 前置:CSSOM Origin 加 #[derive(Default)](默认 Author);新建 muskitty-cascade crate 骨架(依赖 selectors v0.1.0 features=["dom"]) |
— | 58ac2bb |
| CC-2 | 属性注册表(20 个内置 CSS 属性:PropertyDefinition / PercentageBasis / lookup_property) |
§4.1 / §7.1 | 58ac2bb |
| CC-3 | Filtering:collect_declared_values 递归遍历 stylesheet + 选择器匹配 → DeclaredValue 列表 |
§5 | 36f0c65 |
| CC-4 | Cascade 排序:按 §6.1 准则 1/4/6/7(Origin×Importance / Element-attached / Specificity / Order)排序 | §6.1 | e73ba1a |
| CC-5 | Defaulting:apply_defaulting 实现 initial/inherit/unset 三种 CSS-wide 关键字 + 无声明时的继承/初始值回退 |
§7.3.1-§7.3.3 / §7.1-§7.2 | 69c520d |
| CC-6 | Computed Value:compute_value 解析相对长度(em/rem/vh/vw/vmin/vmax → px)、百分比(font-size 基于 parent font-size)、var() 替换(自定义属性查找 + 递归 fallback) |
§4.4 | 13d66be |
| CC-7 | 端到端集成测试:完整 pipeline DOM + CssStyleSheet[] → filter → cascade → defaulting → compute |
全链路 | aab7395 |
| 测试文件 | 测试数 | 覆盖内容 |
|---|---|---|
src/registry.rs (单元) |
7 | 属性注册表 lookup / case-insensitive / 继承标志 / 非继承标志 / percentage basis / 属性数量 |
src/filter.rs (单元) |
— | Filtering 内部逻辑(通过 CC-3 tests/filter.rs 覆盖) |
tests/filter.rs (集成) |
14 | 选择器匹配 → DeclaredValue 收集(type/class/id/important/media/nesting/specificity/origin/layer/import/multi-sheet) |
src/cascade.rs (单元) |
9 | §6.1 完整排序顺序(origin×importance / style attr / specificity / order) |
src/defaulting.rs (单元) |
13 | initial/inherit/unset 关键字 + 无声明继承/初始值 |
src/compute.rs (单元) |
12 | em/rem/vh/vw/vmin/vmax / font-size 百分比 / var() 替换 + fallback / 混合值 |
tests/integration.rs (端到端) |
15 | 完整 pipeline:single rule / specificity / important / order / defaulting(initial/inherit/unset/no-decl)/ em / % / author vs UA / important UA vs important author / 多属性 / var() 全链路 / 非匹配选择器 |
src/lib.rs doctests |
1 | 顶层 API 编译验证 |
| 总计 | 71(7 + 14 + 9 + 13 + 12 + 15 + 1) | 全部通过 |
工作区回归:
cargo test --workspace全部通过(含 css-values / cssom / cascade / selectors / html5-parser / dom / css 等;当时的 per-crate 数字见上表,当前总量 269)——本行为 2026-07 阶段历史记录。
DOM (DomElement) + CssStyleSheet[]
│
▼ filter::collect_declared_values (§5)
Vec<DeclaredValue>
│ 字段:property / value(Vec<ComponentValue>) / important /
│ origin / specificity / order / from_style_attr
▼ cascade::cascade_for_element (§6.1)
HashMap<String, Vec<DeclaredValue>> (按属性分组,每组按 sort key 降序)
│ cascade::cascade_winner → 取首项
▼
Option<&DeclaredValue> (cascaded value)
│ defaulting::apply_defaulting (§7)
│ - 检测 CSS-wide 关键字:initial / inherit / unset
│ - 无声明:继承属性 → parent_computed;非继承属性 → initial_value
▼
ComputedValue (Keyword | Raw(Vec<ComponentValue>))
│ compute::compute_value (§4.4)
│ - resolve_dimension: em/rem/vh/vw/vmin/vmax → px
│ - resolve_percentage: font-size 基于 parent_font_size
│ - resolve_var: var(--name, fallback) 递归替换
▼
ComputedValue::Resolved(Vec<ComponentValue>)
DeclaredValue(src/style.rs):Cascade 输入项。携带property: String/value: Vec<ComponentValue>/important: bool/origin: Origin/specificity: Specificity/order: usize/from_style_attr: bool。ComputedValue(src/style.rs):Cascade 输出。三态枚举:Keyword(String)— CSS-wide 关键字解析结果或初始值关键字(如"black")。Raw(Vec<ComponentValue>)— 已 defaulting 但未 compute 的中间态。Resolved(Vec<ComponentValue>)— 完整 computed value(单位已解析、var() 已替换)。
ComputeContext<'a>(src/compute.rs):computed value 解析上下文。字段:parent_font_size: f64/root_font_size: f64/viewport_width: f64/viewport_height: f64/custom_properties: &'a HashMap<String, Vec<ComponentValue>>。提供new(custom_properties)构造器(font-size 默认16.0,viewport 默认1920.0×1080.0)。PropertyDefinition(src/registry.rs):{ name: &'static str, initial_value: &'static str, inherited: bool, percentages: PercentageBasis }。20 个内置属性覆盖 color/background-color/font-/margin/padding*/display/opacity/width/height/visibility/text-align 等。Origin:从muskitty-cssom重导出(src/origin.rs),UserAgent/User/Author,#[derive(Default)]默认Author。
cascade_sort_key 返回 (u8, u8, (u32,u32,u32), usize) 元组,按 §6.1 准则降序排列(用 Reverse 包裹):
| 准则 | 实现 | 备注 |
|---|---|---|
| 1. Origin × Importance | (origin, important) → u8(UA+important=6 / User+important=5 / Author+important=4 / Author=3 / User=2 / UA=1) |
已实现 |
| 2. Context (Shadow DOM) | — | 推迟(无 Shadow DOM 支持) |
| 3. Scope | — | 推迟 |
| 4. Element-Attached Styles | from_style_attr: bool → u8 (1/0) |
已实现 |
| 5. Layers | — | 推迟(@layer 仅作为容器透传,未参与排序) |
| 6. Specificity | (a, b, c) 三元组 |
已实现 |
| 7. Order of Appearance | order: usize |
已实现 |
- 输入用
Vec<ComponentValue>而非类型化值:Cascade 层只关心声明的结构关系(谁赢、是否继承),值类型化是 CSS Values 层的工作。保持与 css-parser/cssom 的Vec<ComponentValue>一致。 - Computed Value 三态:
Keyword/Raw/Resolved区分 defaulting 后的关键字结果、未 compute 的原始值、已 compute 的解析值。下游(layout)根据变体决定是否进一步处理。 - var() 解析依赖
ComputeContext.custom_properties:Cascade 本身不收集自定义属性(那是 §4.2 specified value 阶段的工作,需要完整的 inheritance 传递),CC-6 假设 custom_properties 已由上层准备好并传入 context。简化但实用。 - Percentage basis:
font-size实现PercentageBasis::ParentFontSize和RootFontSize(均解析为 px)。其他属性的百分比保留原样(layout 阶段处理),因为 percentage basis 依赖 layout 上下文(如 width 基于 containing block)。 - Filtering 简化:
collect_declared_values无条件收集@media/@layer/@supports/@container及Otherchild rules 内的规则(不做 media query 求值或 layer 优先级排序),仅作为容器透传。理由:media query 和 layer 的语义是改变 cascade 优先级,应在 cascade 排序层处理;filtering 只负责"哪些声明匹配此元素"。 @import/@namespace跳过:不参与 cascade(import 应在加载时展开为独立 sheet;namespace 影响选择器匹配,selectors 层已处理)。- Origin 从 cssom 重导出:避免在 cascade crate 重新定义 Origin 枚举,保持单一来源。cssom 的
Origin加#[derive(Default)]默认Author。
Inline✅ 已完成(2026-08-01):style属性收集filter.rs::collect_from_style_attr从 DOMstyle属性解析声明,from_style_attr = true,specificity 归零、由准则 4 单独排序。§6.1 准则 4 在真实 pipeline 中生效。✅ 已清理:cascademuskitty-css-values死依赖Cargo.toml不再声明muskitty-css-values,直接用muskitty-css的ComponentValue/Token。- §6.1 准则 2 Context(Shadow DOM):无 Shadow DOM 支持,推迟。
- §6.1 准则 3 Scope:
@scope规则未实现,推迟。 - §6.1 准则 5 Layers:
@layer排序优先级未实现(仅作为容器透传规则)。已在docs/audit-2026-08-08-full-scan.md修复计划 B8(层号跟踪 + 5 元排序键)中,待实施。 - §7.3.4
revert/ §7.3.5revert-layer:依赖 Origin/Layer 完整支持。已在 audit 修复计划 B7(当"无 cascaded value"处理)中,待实施。 - §4.2 Specified Value 阶段(自定义属性 inheritance + var() substitution 全局解析):CC-6 简化为 context 传入,完整实现需要 DOM 树遍历 + 自定义属性 cascade。
- §4.5 Used Value / §4.6 Actual Value:依赖 layout(containing block、viewport),推迟到 Phase 3。
- Animation origin(§6.1 准则外的 animation declarations):未实现。
- Shorthand 展开为 longhand:未实现(如
background: red不展开为background-color: red)。需要属性数据库。
crate 已剥离为独立 git 仓库(muskitty-dev/muskitty-cascade),未发布到 crates.io。
来源:docs/audit-2026-09-05-perf-security.md(7 高危 + 中危若干,威胁模型:敌意网页 → panic = 远程 DoS)。本轮按 goal.md F-0~F-15 完成 P0 + P1 全部修复,P2/P3(纯性能/加固)显式延后。各 crate 仓库独立 commit + push,每项退出条件含新回归测试 + 全量测试绿 + clippy/fmt 零告警。
| # | 修复 | 仓库 | 审计项 |
|---|---|---|---|
| F-0 | h2 0.4.15→0.4.19(RUSTSEC-2026-0258) | 主仓库 | 依赖漏洞 |
| F-1 | taffy 边界钳制(NaN→0、±inf→±2^25px)+ 修正"taffy 拒绝 NaN/Inf"的错误文档 | muskitty-layout | S-5 |
| F-2 | var() 解析深度上限 32(线性链栈溢出 DoS) | muskitty-cascade | S-2 |
| F-3 | CSS 嵌套限深 1024→200(浏览器平齐) | muskitty-css-parser | S-M1 |
| F-4 | An+B i128 运算 + 复杂选择器单元数上限 1024 | muskitty-selectors | S-M2 / S-3 |
| F-5+F-6 | 重复属性 HashSet 化(T3 O(n²))+ 步数兜底降级 EOF、上限随输入缩放 | muskitty-html5-tokenizer | T3 / H-M6 + 新发现:合法大 tag(10 万属性)触发固定 1M 步 panic |
| F-7 | AFE 列表硬上限 256(Noah's Ark 绕过 O(n²)) | muskitty-html5-parser | S-4b |
| F-8 | selectedness 增量备忘录(<select>+65k <option> O(n²)) |
muskitty-html5-parser | S-4a |
| F-9 | 非 Element fragment 上下文兜底 <div>(不再 expect panic) |
muskitty-html5-parser | H-M2 |
| F-10 | 裁剪栈单 rect 表示 + Mask 懒重建(内存 O(画布),原 O(N×画布)) | 主仓库 renderer | S-1 |
| F-11 | 1×1 回退时同步钳制物理尺寸(报告尺寸与缓冲一致) | 主仓库 renderer | R-M1 |
| F-12 | FontSystem/SwashCache 随 backend 持久化(每次 render 重扫系统字体) | 主仓库 renderer | R-M3 |
| F-13 | render_page Result 化 + flush 优雅降级(不跨模块 expect) |
主仓库 chrome | S-7 |
| F-14 | 响应体 64 MiB 流式上限 + 30s/10s 超时 | 主仓库 network | S-6 |
| F-15 | goal.md/PROGRESS.md 同步 + 审计报告修复标注 | 主仓库 | — |
Mimosa 深度扫描(scan-2026-09-05T13-39-49):424 依赖包 0 命中漏洞;5 个发现均在非引擎源码(rustdoc 生成物 target/doc/static.files/*.js ×3 误报、根目录工具脚本 .ghlogin.py/gen_entities.py 路径穿越提示 ×2),引擎源码零发现。
按 audit-2026-09-06-vuln-arch-perf.md(第八节批次 1)由架构师在 muskitty-dev 各 crate 仓库落地(ink-dark/p0-fixes 分支 → PR),主仓库侧完成合并、全量验证与集成(goal.md 2026-09-07 轮):
| # | 修复 | 仓库 | 审计项 |
|---|---|---|---|
| V-1 | var() 替换输出 token 预算 100k/属性(emitted 全局计数 + extend 前预检,超限 guaranteed-invalid;缓存构建同受约束) | muskitty-cascade 34f94b5(PR #1) |
V-1 |
| CSS-P1 | 规则级嵌套守卫:consume_a_block enter/leave_nesting + 花括号平衡恢复 | muskitty-css-parser efbdfb8(PR #1) |
CSS-P1 |
| SEL-1 | 祖先链记忆化(D^k→D×k)+ 100k 匹配步数预算(正道方案,优于纯计数器止血) | muskitty-selectors 271fd50(PR #1) |
SEL-1 |
| SEL-2 | :has 嵌套解析禁止(has_depth 全链穿透)+ 裸参数化伪类拒绝 + 候选上限 10k;WPT has 夹具硬断言(380→384,75.6%) | muskitty-selectors 95388df(PR #1) |
SEL-2 |
| SEL-3(P1) | 逻辑组合嵌套 parse/match 双侧 bound | muskitty-selectors 27646a5(PR #2) |
SEL-3 |
| CAS-1/2/3(P1) | ComputedValue/声明值 Arc 化 + registry 哈希化 + 未声明属性盲算消除 | muskitty-cascade 8515331(PR #2) |
CAS-1/2/3 |
验证:cascade 197 / css-parser 84 / selectors 169 测试全绿(fmt/clippy 零警告),主仓库 cargo test --workspace 集成全绿;审计勘误(:not 内 :has 实为合法,以 WPT 夹具为准)记录于该审计第十节。