Skip to content

Latest commit

 

History

History
772 lines (609 loc) · 84.8 KB

File metadata and controls

772 lines (609 loc) · 84.8 KB

MusKitty — Progress Dashboard

⚠️ 更正(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 简写 三分量展开(commit 1c7c322,已落盘本地 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/百分比,非法值整条丢弃);renderer RenderCommand::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);renderer Rect 增背景参数(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,layout bc78ec5,主仓库 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 时不换行,测量缓存键随之归一化),纯空白节点仅在"可折叠"时丢弃;renderer RenderCommand::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 注册(initial none)+ 简写展开 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 参考实现 url crate,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-parser f0e7a56 / css fc1621e / cascade 0f32951 / layout a22bd84,共 28 处合成值,无一处读该标志);selectors finish_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/数/长度/百分比,测量与绘制共用 cascade text_props 单一来源),text-transform 端到端生效(Unicode 全尺寸映射,布局前改写)——cascade b66d0e4 / layout 1730e94 / renderer bdd1dae。

上一轮(2026-09-12)M-3 batch 2(方向性边框 + outline 端到端):border-<side> 方向性简写与 border-width/style/color 的 1–4 值形式此前整条声明被丢弃,layout 从不映射 border(边框不占空间、box-sizing: border-box 对边框无效),renderer Border 为单组值、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-layout v0.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)工作。 主仓库 workspace members = ["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 规范,测试套件过时。对浏览器级应用无影响(真实网页几乎不会触发这些边界)。

Phase 1 (HTML 解析层) — 已收尾

按 muskitty-browser-roadmap.md 的 6 个 Phase 全部完成(Phase 6 推迟):

Phase 1 — muskitty-dom crate + DOM Core 类型 ✅

  • 新建 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

Phase 2 — Tree Construction 骨架 ✅

  • HtmlTreeConstructor 结构体:open_elements / active_formatting_elements / insertion_mode / head_element / form_element / foster_parenting / frameset_ok / scripting_flag / template_insertion_modes
  • InsertionMode 枚举(全 23 个模式)
  • dispatch() 主分发器:foreign content 优先,然后按 insertion mode
  • 顶层入口 parse(input: &str) -> Rc<RefCell<Node>>
  • ParseError 枚举与错误收集

Phase 3 — Insertion Mode 实现 ✅

分批实现,每批独立提交(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

Phase 4 — 关键算法 ✅

  • 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 等辅助算法

Phase 5 — html5lib Tree Construction 测试集成 ✅

  • 测试 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

Phase 6 — DOM 完整 API 扩展 ✅ 已完成 (2026-08-09)

  • 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 独立仓库)。

Phase 2 (CSS 解析层) — 子阶段 1-5 已完成

按 roadmap Layer 2 推进。子阶段 1(CSS Syntax Module §4.3 tokenizer + §5 parser)按 phase2-css-parser-cp1-to-cp8.md 完成。

Tokenizer (§4.3) — 早期 commits

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。

Parser (§5) — CP-1 至 CP-8

批次 内容 规范 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 实现 Grammar trait + 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 original_text for custom property ✅ 已完成(2026-07-22,CV-0b):CssTokenizer 加 next_token_with_span + position();TokenStream 加 with_source 构造器 + source_slice 方法 + token_spans/source 字段;consume_a_declaration 对 custom property 捕获 original_text。
  • §5.5.6 unicode-range descriptor re-tokenization:需要 source-text tracking 用于重新分词。基础设施已就绪(CV-0b),待实际需求出现后补。

子阶段 1 测试矩阵

测试文件 测试数 覆盖内容
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 warnings

CP-1 至 CP-7 全部满足零 fmt diff、零 warning、全部测试通过。

TokenStream 设计要点

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 解析。

Rule::Declarations 变体

§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。

Tokenizer 状态详情

状态实现 (80/80) ✅

完整覆盖 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 ✅

14 个失败分类

类别 数量 处理
<?...> 处理指令边界(test2/test3) 11 Phase 2 期间顺带修
xmlViolation(infoset 强制转换) 3 已从基线排除(CLAUDE.md: WHATWG 是 ground truth)

Tree Construction 状态详情

Insertion Mode 覆盖

全 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

84.8% → 100% 关键修复(P3-d 系列)

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 一次性拉取。

crates.io 发布状态(截至 2026-09-18;2026-09-13 架构师批量发了一批 patch 版本对齐已发布代码)

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

CI/CD 模式

每个独立 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_TOKEN secret:通过 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/ 作为项目级文档。

Phase 2 规划:muskitty-css (CSS 解析层)

按 roadmap Layer 2 推进。

目标:实现 CSS 解析层,为 Layer 3 (Layout) 提供 cascade + computed values。

入场门槛(已满足):

  • Layer 1 通过率 ≥80% ✅(100%)
  • DOM Core API 完整 ✅

子阶段(每个独立 commit + 测试):

  1. 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)
  2. 选择器解析 — Selectors Level 4

    • 简单选择器(类型/通用/类/ID/属性)
    • 组合器(后代/子代/相邻兄弟/一般兄弟)
    • 伪类(结构性、UI、动态暂留 stub)
    • 伪元素
    • selector 匹配引擎(基于 DOM Core)
  3. 值解析 — CSS Values Module

    • 长度/百分比/角度/时间/分辨率
    • calc() / min() / max()
    • var() 与自定义属性
  4. 样式表数据结构 — CSSOM

    • Stylesheet / Rule / Declaration / AtRule
    • CSSStyleRule / CSSMediaRule / CSSImportRule 等
  5. Cascade + Computed values

    • 重要度/层叠顺序/来源排序
    • 继承 / initial / inherit / unset
    • 计算值 / 使用值 / 实际值

ground truth:WPT CSS 测试套件(css/ 目录)

规范依据:

远期 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 提交历史(近期)

主仓库近期(完整历史见 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/ 内的各阶段规划文档,此处不再重复罗列。

下一步

  1. Phase 2 子阶段 3 — CSS Values Module ✅ 已完成(2026-07-22)并已提取发布(2026-07-24)到 crates.io。
  2. Phase 2 子阶段 4 — CSSOM ✅ 已完成(2026-07-22)并已提取发布(2026-07-24)到 crates.io。
  3. Phase 2 子阶段 5 — Cascade + Computed values ✅ 已完成(2026-07-23)。
  4. Cascade 收尾(Phase 3 前置) ✅ 已完成(2026-08-01):inline style 属性收集已实现。
  5. Phase 3 — Layout ✅ 已完成(2026-08-01):taffy 0.12 集成,46 个测试全绿,审计修复 7 个 bug。已剥离为独立仓库。
  6. Phase 4 — Renderer ✅ B-3/B-4 已完成(2026-08-02):muskitty-renderer(tiny-skia 后端)DOM→CSS→Layout→Render 全链路打通,HTML+CSS → PNG demo 工作。
  7. 全项目审计修复 ✅ 已完成(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 ✅,cascade d6d7208)/ 布局 B6 flex 简写端到端解锁(layout 44b9b1d)。
  8. DOM 完整 API 扩展 ✅ 已完成(2026-08-09):Events → muskitty-dom event.rs;element.style → muskitty-cssom element_style.rs;innerHTML/outerHTML → muskitty-html5-parser serialize.rs + parse_fragment(WPT harness 解锁 fragment 用例,99.0%)。
  9. Tokenizer 遗留:14 个 html5lib 失败已确认非 bug,保持现状。
  10. 文本渲染 + 布局增强 + 窗口化 ✅ 已完成(2026-08-16):文本渲染(layout 测量 3d18bf4 + renderer glyph 渲染 50bc822)、position 定位(d721a0b)、overflow 裁剪(d09bc78)、grid 布局(cascade 2c27d7d + layout e690d4e)、winit 窗口化(07eb0b8)。均已推送远端。
  11. T-3 换行 + 字体属性 ✅ 已完成(2026-08-22);修正(2026-08-29,0d3ec6b):文本测量高度公式误把 cosmic-text run.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 换行(layout 8f18108)、font-family/font-weight 测量(layout 056ec23)、renderer 换行/字重/对齐(renderer 8a28bc8/fc6f971/20606b8)。补齐端到端用例时发现并修复多行叠行 bug(draw_text 漏加 run.line_y,glyph 行内局部坐标需加行顶偏移)。line-height 仍为 font_size * 1.2 近似,精确解析推迟。
  12. 外部依赖解耦 ✅ 已完成(2026-08-16):layout(taffy/cosmic-text bf52557)、renderer(tiny-skia Pixmap 98af15d)、network(reqwest Error 8cfbdfd)公共 API 均不再暴露外部依赖类型,上层可抽离。
  13. W-2 DPI(HiDPI 缩放) ✅ 已完成(2026-08-29):Backend::render / render_page / render_html_file 加 scale 参数——layout 用逻辑视口(CSS px),栅格化物理分辨率 round(logical×scale)(renderer 0192cae / shell page b0a645f);窗口流读 hidpi scale、脏检查含 scale、ScaleFactorChanged 重绘(shell d53ca2f)。整数 1x/2x 有 scale=1-vs-2 单测兜底(非插值);render_file 示例 scale=1 输出不变。
  14. W-3 输入(InputEvent 抽象 + shell 快捷键层 + 事件分发结构) ✅ 已完成(2026-08-29):input.rs 类型 + 纯函数 match_shortcut(shell 953f890)、PlatformWindow::handle_event 页面层入口(7fed8bb)、App 事件接线 + 转换函数(0962c74)、文档同步(C-4)。架构修正:快捷键层在 App::dispatch_input(Esc 需 event_loop.exit()、Ctrl+R 需渲染管线,均 App 独有),handle_event 为页面层(W-3 无命中测试恒 false,仅立分发结构);页面级命中测试单列延后。
  15. 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,cascade b87b820;renderer 端到端 paint 测试证明 extract_border 消费,de48621);media 视口接线——compute_styles 用 StyleTreeOptions.viewport_width/height 构造 MediaContext(cascade fcde127),render_page 传逻辑布局视口(shell f0c5619),默认 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 四向未注册;③ layout style.border 从未赋值(边框不占空间、box-sizing: border-box 对边框无效,CSS Box Model L3 §2-§3 违背);④ renderer Border 单组宽/色/样式且用 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。

  1. W-4 Headless 后端(HeadlessWindow + render_to_png + 无窗口测试) ✅ 已完成(2026-08-29):HeadlessWindow: PlatformWindow 无窗口实现(present 保存最近帧、可 save_png,无外部依赖类型可公开构造,shell 070e013);lib 顶层 render_to_png 全管线 → PNG 便捷函数 + page::encode_png 编码出口(tiny-skia 升正式依赖但类型不入 pub API;window_demo 示例声明 required-features,shell 25610a6);无窗口集成测试——render_to_png PNG 解码像素与 render_page 直接渲染逐字节一致、HeadlessWindow 帧/编码产物一致、scale=2 分辨率核对(shell 2c83e44),--no-default-features 下 check/test/clippy 全绿,feature gate 兑现 CI 无窗口价值。

  2. W-5 多标签状态管理(WebViewCollection + 标签快捷键 + 脏位延迟更新) ✅ 已完成(2026-08-29):webview.rs(不 feature 门控)——WebView(内容 + 每标签渲染状态 + needs_repaint/close_scheduled 脏位)+ WebViewCollection(新建/延迟关闭/切换,active 不变量,切换自动标脏,shell a618c4e);标签快捷键 Ctrl+T/W/19/PageUp/PageDown(ShortcutAction 扩展 + Key::PageUp/PageDown,match_shortcut 5 条新单测,dispatch_input 接线,Ctrl+T 开默认内容、全部关闭退出,shell 9e5c3dc);脏位延迟更新——shell 动作只标脏 + request_repaint,RedrawRequested 统一 flush(关标签延迟移除/空则退出/脏或 stale 才重渲染,shell ea99d22)。范围裁剪:favicon 占位、tab strip 不做。窗口化轨道 W-1W-5 全部完成。

  3. muskitty-chrome 窗口层(自绘 chrome,取代 shell) ✅ 已完成(2026-08-29):决策见 ADR 2026-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 与 softbuffer 0x00RRGGBB 契约不符,9562f0f)。muskitty-shell 退役删除(84b07a4,git rename 保留历史),W-1~W-5 语义由 chrome 承接。

  4. 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_subresource scheme 策略(http(s) 页拒 file://、file 页允 http(s)、data: 恒允、其余拒)、decode_data_url(媒体类型 + forgiving base64 + 百分号解码);委托 url crate(WHATWG URL 参考实现,纯 Rust,原为 reqwest 传递依赖)——不引它的替代方案是手写 RFC 3986 子集,取舍写在模块文档)。② chrome stylesheets 模块: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 干净。

Phase 3 (Layout 层) — 已完成

时间: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) ✅

审计修复(whatwg-spec-adversarial-review skill)

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)。

Phase 2 子阶段 2 — Selectors Level 4 ✅

按 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/) — 通过 Element trait 抽象元素 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)。

§5.4.1 / §5.4.2 Grammar Hooks 集成(2026-07-19)

  • muskitty-css-parser/src/grammar.rs:新增 Grammar trait + 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 全部通过。

Phase 2 子阶段 3 — CSS Values Module ✅

按 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 — ValuesGrammar impl Grammar trait,ValueKind 16 变体,CssValue 包装枚举。
  • serialize.rs — ToCss trait + 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)。

Phase 2 子阶段 4 — CSSOM ✅

按 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 — ToCss trait + §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)。

Phase 2 子阶段 5 — Cascade + Computed Values ✅

按计划文档 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 排序键设计(§6.1)

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 及 Other child 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 style 属性收集 ✅ 已完成(2026-08-01):filter.rs::collect_from_style_attr 从 DOM style 属性解析声明,from_style_attr = true,specificity 归零、由准则 4 单独排序。§6.1 准则 4 在真实 pipeline 中生效。
  • muskitty-css-values 死依赖 ✅ 已清理:cascade 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.5 revert-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。

审计修复轮(2026-09-05)✅

来源: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),引擎源码零发现。

审计修复轮(2026-09-06 P0 止血)✅

按 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 夹具为准)记录于该审计第十节。