14 KiB
Visual observation:PR 0 验证结果与架构决策
日期:2026-09-08。生产基线:main 3c5f838。分支:codex/visual-observation-design。
本次只新增设计、合成 fixture 和诊断工具。没有生产算法变更,没有 commit/push。 总体设计及后续 PR 依赖见 架构与实施计划。 本文是历史证据;新会话请按 验收清单 执行相关检查并说明结果,不继承本文的通过结论。
1. 环境与证据等级
| 环境 | 实际版本/说明 |
|---|---|
| PATH 中的 bsk | 0.1.7,未用于有效页面基线 |
| 仓库已有 target/debug/bsk | 0.2.0;update --check 报告发布最新版也是 0.2.0,没有安装更新 |
| daemon / connected extension | 0.2.0,protocol 1.1 |
| 页面基线 Chrome | 151.0.0.0,由 bsk browsers 报告 |
| 生产扩展构建 | pnpm ext:build,用户 reload 当前 main 基线构建后采集 |
| 独立协议实验 Chrome | 152.0.7977.76,临时无登录 profile;与用户浏览器不是同一个运行实例 |
| 基线测试 | VOM 47 tests / 2 files;相关扩展 217 tests / 14 files;全部通过 |
页面结果是实际 bsk + reload 扩展的结果。协议实验是独立 Chrome 的真实 CDP 响应。 临时 replay 是将合成响应送入 main adapter 的诊断,不代表另一条真实浏览器完整运行。 不同证据不混用,版本号本身不证明构建 commit。
所有内部页面原文、地址、iframe 标识、截图和用户信息仅保存在本机临时目录,不进入仓库。 本文仅保存匿名化事实,仓库 fixture 完全独立构造。
2. 原始问题已确认:表格加载完成后仍有观察缺口
有效就绪条件:前台可见页面已经绘制出网格、列标题和六行表格。不能使用以下替代条件:
document.readyState === complete。- AX 已有嵌入应用的工具栏。
- 页面出现 iframe。
- 背景截图仍为站点加载 logo。
本次初始后台窗口恰好出现“AX 已有工具栏,图像仍为加载占位”的情况。这些样本已排除。 在前台绘制完成后重新取得匹配的截图和 observe,结果为:
| 事实 | 结果 |
|---|---|
| 实际截图 | 可见列标题、网格与六行表格 |
| main observe | 63 个 ref,含外围和嵌入应用工具栏 |
| truncated | false |
| 表格列内容 | 没有相应文本表示 |
| visual ref | 没有 |
| 直接打开已观察到的嵌入应用 | 发现一个无 aria-label 的 Canvas,CSS 尺寸 1450×835、backing store 2900×1670,DPR 2 |
直接打开嵌入应用用于验证其渲染介质;其宽度变化是独立 tab 布局,不能拿这组坐标当父页面中的 crop。 父页面的截图/VOM 对照与直接嵌入页的 DOM 证据分别记录。
结论:主要能力缺口是视觉内容没有观察入口;不属于这次有效样本的文本预算耗尽。 新架构不能承诺从 Canvas 自动恢复单元格语义;正确结果是保留工具栏,并提供可截图的区域入口。
3. 合成最小复现
table.html → 跨站点 grid.html:一个 iframe、DOM 工具栏、一个无名 Canvas。
Canvas 绘制三列、六行,所有文字与原页面无关。
main 实际输出的关键部分:
@view 1440x923
@e1 button "Page action"
Iframe "Table application"
navigation
@e2 button "Insert record"
@e3 button "Filter"
ref_count=3,truncated=false。截图已人工确认包含 Canvas column A/B/C 和六行。
这些像素文字既没有文本表示,也没有 visual ref。该最小复现已覆盖原场景的关键结构,
无需站点权限或生产选择器。
4. CSS 与语义/交互分离
真实浏览器实验确认:
- aria-hidden 祖先、inert 祖先、pointer-events:none 不会使 Canvas 自动视觉消失。
visibility:hidden祖先下的visibility:visible子 Canvas 可以恢复可见。hidden元素被display:block覆盖时仍可绘制。- opacity:0 祖先会使子树透明;不能仅检查 Canvas 自己的 opacity。
- display:none 无 layout bounds;visibility:hidden 可以仍有 bounds。
- AX fallback 文本不包含绘制的
PIXEL TABLE 42,不能用 fallback 存在作为发现去重条件。
决定:事实层分别保存 computed visibility、透明继承、语义暴露和交互属性。 视觉发现不读取 semantic keep/drop 结果;祖先索引只能传播符合属性语义的状态。
checkVisibility() 的实验作用仅限 CSS suppression,不用它证明 overflow、viewport 或遮挡结果。
常规 overflow 由独立几何规则负责;任意遮挡不进入首版精确能力范围。
5. 单位、frame 投影与截图
5.1 DPR 与截图像素是不同契约
独立 Chrome 上对相同 120×40 CSS clip 的结果:
| Emulated DPR | DOMSnapshot Canvas w×h | Screenshot w×h | layoutViewport/cssLayoutViewport 宽度比 |
|---|---|---|---|
| 1 | 120×40 | 120×40 | 1 |
| 1.5 | 120×40 | 180×60 | 1 |
| 2 | 120×40 | 240×80 | 1 |
因此该比值不能用作通用截图 DPR。原始 DOMSnapshot 单位与截图 raster scale 必须分别适配, 输出 PNG 尺寸是最终预算事实。不能把本组 DPR emulation 结果泛化为所有浏览器缩放组合。
5.2 三种缩放分别记账
- CSS
zoom:1.25:120px 宽 Canvas 的布局 bounds 为 150px,运行时几何同步变化。 - Emulation.setPageScaleFactor(1.5):记录为 page-scale/pinch 类证据,不充当 UI zoom。
- 用户 Chrome 原生 UI 缩放到 90%:界面明确显示 90%,DPR 从 2 变为约 1.8,innerWidth 从 1440 变为 1600,visualViewport.scale 仍为 1;Canvas CSS 宽约 120.0000076,截图仍是 2880px 宽。main VOM 视口为 1600×1025。已恢复 100%。
现有 bsk 的 Meta+- CDP 输入未改变浏览器缩放,所以该无效果尝试不算缩放验证; 有效样本来自原生浏览器 UI 操作。真实 UI zoom 仅验证了上述点,不能宣称完整 zoom 矩阵已通过。
决定:使用 CSS 视口空间作为发现/截图区域公共契约;精确单位 adapter 的实现还必须在 PR 1 跑计划内组合回归,不用一个比例同时解决所有协议字段。
5.3 Frame 空间与 main 已知偏移
fixture local Canvas 原点为 (17,23)。
| 场景 | 实测 owner content 原点(target root) | 正确 top Canvas 原点 | main parser replay |
|---|---|---|---|
| 同 target frame,10px border | (50,610) | (67,633) | (57,623) |
| 上述 frame 内 nested,另有 7px border | (87,707) | (104,730) | (87,713) |
| OOPIF,10px border | (410,610) | (427,633) | 本表不混入非完整 replay 的 OOPIF 结果 |
根 target 的 DOMSnapshot 含三份 document(top/same/nested);OOPIF 在单独 target 中只返回自己的 document。
probe 通过实际 TargetInfo.type=iframe 验证 OOPIF,而非仅根据 origin 推断进程隔离。
子 frame scrollY=10 后,runtime local y 从 23 变为 13,snapshot document y 仍为 23。 owner scale(1.25) 后 content quad 宽度变为 375,child innerWidth 仍为 300。
决定:frame-local 原始矩形必须先减该 document 的滚动,再经过一次权威投影;同 target 的 owner quad 已是 target-root 坐标,不能重复逐层加偏移。禁止 border-box 替代 content origin。
Box 种类也必须进入契约:带 5px border 的 120px Canvas,其 snapshot/border quad 宽 130,
content quad 宽 120。视觉入口采用 border-box,实时验证同种 box;iframe 投影则采用 content quad。
带 border/padding 的 overflow owner,snapshot.clientRects 对应 [clientLeft,clientTop,clientWidth,clientHeight],
本 fixture 为 [5,5,68,38]。裁剪边界应从 client box 派生,不能直接拿 ancestor border bounds。
单轴 overflow-x:clip;overflow-y:visible 的 computed style 保持分轴,必须分轴传播。
CSS zoom 1.25 后,上述 owner 的 bounds 变为 [745,455.625,97,59.5],clientRects 仍为
[5,5,68,38],offsetRects 为 [596,365,78,48]。直接按 border-width 比例推导 client box
会混入不同空间与整数取整。首版将有 CSS zoom/DOM transform 的 overflow 祖先组合明确降级,
不承诺精确 crop;普通未变换 overflow 及 iframe content-quad 投影保留支持。
6. Document 与 ref 时效
真实 CDP 实验:
| 操作 | frameId | loaderId | document backend | anchor |
|---|---|---|---|---|
| history.replaceState | 不变 | 不变 | 不变 | 仍属于当前树 |
| 跨 document 导航 | 不变 | 变化 | 变化 | 旧目标不可继续视为当前内容 |
| document.open/write/close | 不变 | 不变 | 本次实验不变 | 旧 Canvas 失去连接,documentElement 被替换 |
决定:身份取 target attachment epoch + frameId + loaderId + documentElement backend identity; 工具执行时仍检查 anchor.isConnected 及其所属 documentElement。不能仅凭 loaderId 判断有效。 DOMSnapshot 可提供根元素 backend identity,frame graph 需要保留 loaderId;当前 main graph 没有该字段,这是后续数据契约的明确修改点。
12 次静态几何采样完全相同;0.5px 移动可被检测;真实 90% 缩放引入约 1e-5 CSS px 的 数值误差。首版采用最大边误差 0.25 CSS px 作为保守工程阈值,超出要求重新 observe。 这不是大规模统计得到的通用稳定性保证;改变阈值需更新其测试及行为文档。
既有 eN 被下一次观察重用后,单靠旧字符串不能检测它来自上一代。内部 generation 不消除 该协议限制。本次不改变协议,只验证当前存储目标的身份/几何。Canvas 可以在身份与几何 不变时重绘;截图始终表达执行时图像,不承诺观察时像素快照。
7. 既有路径回归基线:用于防止负优化
诊断复制当前 renderer 到临时目录,在三个明确循环点加计数;生产文件未改变。 复现命令见 fixture README。
| 输入 | Render stack visits | DOM context candidates | Same-container sibling visits |
|---|---|---|---|
| 100 headings + 100 View buttons | 201 | 10,000 | 15,050 |
| 200 headings + 200 View buttons | 401 | 40,000 | 60,100 |
| 400 headings + 400 View buttons | 801 | 160,000 | 240,200 |
| 2,000 transparent wrappers + control | 2,001 | 0 | 0 |
| 4,000 transparent wrappers + control | 4,001 | 0 | 0 |
显式栈访问线性并通过深树输入;本组宽树上下文计数呈二次增长。这里记录的是现有路径的 特征,供后续同输入比较是否发生回退,不是本次要求修复的问题,也不是 Canvas 新算法的模板。 计数不是完整 CPU profile,更不能替代 Canvas 发现、裁剪、选择和截图各自的性能测试。
处理边界:
- 既有语义、DOM 操作和截图路径对照 main 不发生性能回退;不把历史算法优化列为本次前置依赖。
- Canvas 新路径按自己的规模和访问模式论证算法;发现、裁剪、去重、选择和截图分别验收。
- 精确 crop key 去重的选择来自首版的覆盖等价契约和新增成本分析,与 #165 采用什么算法无关。
- PR 7/8 分别报告既有路径回归、无 Canvas 页面的额外成本、有 Canvas 页面的新增成本。 新功能有合理的额外成本不等于旧算法负优化;重复执行旧工作则属于需要消除的回退。
8. 锁定首版策略与退化
| 决策 | 首版契约 | 理由/验收 |
|---|---|---|
| 去重 | 同 document + 同直接 parent + 四条 crop 边完全相等 | 无模糊聚类;误差导致重复比误合并更可控 |
| 索引容量 | 50,000 keys;耗尽后其余候选继续未去重选择 | 内存有界,不因早期小图耗尽而停止后续发现 |
| K | min(预算推导容量, 512) | 所有行仍逐条核算可变成本 |
| 默认视觉预算 | 未给总 maxTokens 时 2048 estimated tokens | 有显式 maxTokens 时全部纳入同一预算 |
| 摘要预留 | 固定提示行按实际估算器成本预留 | 不用不可验证的固定 token 常量表示某段文本 |
| 几何时效 | 最大边差 > 0.25 CSS px 即重新观察 | 身份和裁剪策略变化独立拒绝 |
| 图像 | max edge 2048、max pixels 4,000,000、最多两次 capture | 成功必须以实际尺寸满足后置条件 |
| 外部几何并发 | 4 | 单操作缓存;操作结束释放 |
| 新增纯计算取消 | 每 256 项形成工作块并让出事件循环 | 不仅是轮询无法及时更新的 signal |
这些数值是可审查的首版资源政策,不是声称已证明用户体验最优的测量结果。 参数可以在对应模块 PR 中经新的证据调整,但必须一并更新契约及测试。
进一步收敛:首版精确区域支持轴向矩形、普通 overflow 链、正向轴对齐平移/缩放与 frame。 旋转、透视、clip-path、mask、活动 pinch-zoom 进入明确的 geometry-unsupported 降级, 不给看似精确的 region ref,提示现有视口截图途径。不会把包围矩形偷换成精确区域。
9. 已执行的检查与下一阶段门槛
已执行:
- main VOM tests:47 passed。
- main 相关 extension tests:217 passed,含 frame/element geometry、observation、interaction、ref-store、VOM。
pnpm ext:build成功,用户 reload 后采集有效页面基线。- 独立 Chrome browser contract probe:39 个断言通过;可复现命令见 fixture README。
- main 合成 capture replay、显式栈/上下文操作计数。
- 实际 Chrome UI 90% zoom 样本,恢复 100%。
- 原始表格与合成嵌入式表格均人工查看截图,并与未截断 observe 对照。
PR 0 交付的是已明确边界的设计和基线,不是未来功能已经通过:新的 discovery、typed ref、 freshness resolver、pixel planner 和 renderer 接线尚未实现。后续每个 PR 必须验证自己的 生产实现,不能把本次浏览器事实验证替代实现验收。
人工证据不设置默认 CI 对内网站点的访问依赖。合成 probe 可按需执行;页面原文不进仓库。 后续 PR 的职责和验收保持在主设计中,本文负责记录证据、决策及没有作出的保证。