Skip to content

fix(markdown): serialize the current iframe document in place - #1592

Open
lanyue-llk wants to merge 6 commits into
codex/content-rendered-statefrom
codex/webfetch-frames
Open

lanyue-llk wants to merge 6 commits into
codex/content-rendered-statefrom
codex/webfetch-frames

Conversation

@lanyue-llk

@lanyue-llk lanyue-llk commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
PR #1592 at a glance

Problem

Frame-aware Markdown could read a stale iframe document, append frame text outside its DOM position, resolve child resources against the wrong document, and run a second conversion policy that disagreed with ordinary Markdown. HTML export also serialized each child twice just to recover its URL.

Change

Serialize the current child document at its iframe position. Root and child documents use the same visibility, accessible-content, media, strip, and URL rules from #1334–#1337. Each child still resolves relative destinations against its own base URL. Hidden, empty, script-only, and about:blank frames add no placeholder text; a failed external frame remains a source link when it has a useful URL. Fragment links inside a child stay in that child's URL namespace.

HTML export reads the live frame URL directly, preserves open and closed shadow roots, and applies strip options only to the export copy. This exports already-loaded DOM; it does not add PDF or DOCX parsing.

Focused verification

The Moli fixtures cover:

  • a navigated iframe whose old document must disappear;
  • nested and cross-origin frames retained in DOM order;
  • computed and inherited visibility inside child documents;
  • child links, images, video sources and posters resolved against the child base;
  • empty, hidden, script-only, and nested about:blank frames omitted without losing the owner's useful fallback link;
  • root URLs and fragment anchors unchanged by enabling frame inclusion;
  • strip options applied to child light DOM as well as open and closed shadow roots;
  • per-document scripting serialization.
cargo nextest run --locked -p moli --lib \
  -E 'test(markdown_with_frames_) | test(iframe_media_urls_) | test(html_with_frames_) | test(frame_light_dom_)' \
  --no-fail-fast

Browser Eval reproduction

Build this head, register the exact executable and SHA-256 as moli-candidate in an instance provider file, then run the frozen Browser Eval cases through the certified native-fetch entry point:

uv run --frozen python scripts/assemble_webfetch_dataset.py \
  --assembly evaluation/webfetch-seven-family-430/dataset-assembly.json \
  --profile /absolute/path/to/report-profile.json \
  --output /tmp/webfetch-430.json

uv run --frozen python scripts/run_certified_benchmark.py \
  --dataset /tmp/webfetch-430.json \
  --output /tmp/moli-1592-browser-eval \
  --work-root /tmp/moli-1592-browser-eval-work \
  --providers moli-candidate \
  --case-ids dynamic-019-selenium-dev-selenium,dynamic-iframe \
  --provider-config /absolute/path/to/moli-candidate-providers.json \
  --workers 1 \
  --max-failure-retries 0 \
  --retrieval-mode native

dynamic-019-selenium-dev-selenium and dynamic-iframe exercise frame serialization directly. dynamic-015-015-testpages-iframe-list also depends on external-child load completion, so it is a cross-PR integration case rather than part of this PR-only command. That observation creates no Git dependency on #1584. Moli contains no case-ID or site-specific branch.

Stack

This PR is stacked only on #1337 (codex/content-rendered-state), which completes the shared Markdown policy introduced by #1334–#1337. It does not depend on #1584, #1591, or #1593.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Spider Bench A/B

✅ All benchmark browser service runs completed; results are informational.

Public HEAD: 184 / 240 rows; 38 / 48 sites produced rows; 10 unexpected empty sites.

✅ Deterministic fixture contract is clean.

Common ancestor 829f1989da7b → HEAD 8e017b87f115; benchmark order: base-first; workflow: success.

Public 48-site run · informational

Metric Base HEAD Δ
Run status success success —
Extracted rows / contract 184 / 240 184 / 240 0 (0.00%)
Contract fill 76.67% 76.67% 0.00 pp (0.00%)
Sites with rows 38 / 48 38 / 48 0 (0.00%)
Sites meeting row contract 36 / 48 36 / 48 0 (0.00%)
Unexpected empty sites 10 10 0 (0.00%)
Partial-row sites 2 2 0 (0.00%)
Site outcomes extracted: 36; HTTP error + rows: 2; empty: 7; HTTP error + empty: 1; snapshot error + empty: 2 extracted: 37; HTTP error + rows: 1; empty: 7; HTTP error + empty: 1; snapshot error + empty: 2 —
Observed duration 40.74 s 42.39 s +1.65 s (+4.04%)
Estimated CPU 36.43 s 40.57 s +4.14 s (+11.37%)
Peak PSS 243.95 MiB 248.30 MiB +4.35 MiB (+1.78%)
Peak RSS 247.76 MiB 252.27 MiB +4.51 MiB (+1.82%)
Peak threads 26 26 0 (0.00%)
Mismatched sites 0 0 0
Timeout with items 0 0 0
Category rows Base HEAD Δ
news 35 / 40 35 / 40 0
stocks 20 / 40 20 / 40 0
tech 35 / 40 35 / 40 0
sports 27 / 40 27 / 40 0
games 37 / 40 37 / 40 0
life 30 / 40 30 / 40 0

Public-site content, timing, and memory are noisy. This single A/B run reports evidence only; site outcome counts explain missing rows without treating them as a deterministic regression.

CPU and memory timelines

These are bounded 40-point views of the same complete process-tree samples used by the HTML report. CPU uses 100% per occupied logical core; memory lines are RSS first and PSS second when complete PSS samples are available.

Base

xychart-beta
    title "Base CPU"
    x-axis "Elapsed seconds" [1.035, 2.036, 2.536, 3.537, 4.539, 5.541, 7.043, 8.045, 9.047, 10.55, 11.552, 12.552, 13.554, 15.057, 16.059, 17.061, 18.063, 18.563, 19.564, 20.564, 21.566, 22.066, 23.067, 24.069, 25.069, 26.07, 27.573, 28.574, 29.575, 31.078, 32.078, 33.079, 34.08, 35.582, 36.583, 37.585, 38.585, 40.087]
    y-axis "CPU percent" 0 --> 200
    line [16, 103.84, 113.85, 81.98, 105.75, 127.78, 13.99, 109.83, 129.75, 69.86, 125.77, 139.92, 5.99, 77.85, 69.88, 3.99, 37.94, 148.01, 69.84, 109.98, 125.75, 101.87, 117.86, 133.81, 134.02, 83.95, 109.88, 115.88, 101.95, 4, 55.95, 135.79, 79.91, 9.99, 137.6, 143.8, 53.94, 117.85]
Loading
xychart-beta
    title "Base memory: RSS then PSS"
    x-axis "Elapsed seconds" [0.035, 1.035, 2.036, 2.536, 3.537, 4.539, 5.541, 7.043, 8.045, 9.047, 10.55, 11.552, 12.552, 13.554, 15.057, 16.059, 17.061, 18.063, 18.563, 19.564, 20.564, 21.566, 22.066, 23.067, 24.069, 25.069, 26.07, 27.573, 28.574, 29.575, 31.078, 32.078, 33.079, 34.08, 35.582, 36.583, 37.585, 38.585, 40.087, 40.74]
    y-axis "Memory MiB" 0 --> 500
    line [13.28, 74.7, 118.57, 106.07, 107.93, 125.94, 140.85, 145.31, 145.03, 148.67, 135.72, 143.61, 127.48, 115.21, 130.32, 144.98, 117.51, 129.53, 160.42, 143.89, 141.27, 178.77, 247.76, 177.48, 182.02, 201.69, 133.96, 172.77, 140.95, 142.21, 138.19, 143.61, 152.99, 155.28, 125.49, 149.25, 149.49, 130.65, 155.94, 0]
    line [9.94, 71.19, 114.84, 102.31, 104.19, 122.19, 138.04, 140.84, 141.27, 145.01, 131.94, 139.88, 123.69, 111.42, 126.49, 141.14, 113.63, 125.64, 156.58, 138.58, 137.55, 174.96, 243.95, 173.61, 178.19, 197.71, 129.96, 168.86, 137.07, 138.47, 134.35, 139.76, 149.13, 151.42, 121.67, 145.39, 147.31, 126.82, 152.36, 0]
Loading

HEAD

xychart-beta
    title "HEAD CPU"
    x-axis "Elapsed seconds" [1.043, 2.046, 2.547, 3.548, 4.548, 5.549, 6.05, 7.052, 8.555, 9.556, 10.556, 12.059, 13.061, 14.062, 15.563, 16.563, 17.564, 19.066, 20.066, 21.568, 22.57, 23.572, 25.074, 26.074, 27.076, 28.579, 29.58, 30.581, 32.083, 33.084, 34.086, 34.587, 35.588, 36.589, 38.092, 39.093, 40.094, 41.596]
    y-axis "CPU percent" 0 --> 200
    line [17.95, 91.58, 93.83, 113.96, 137.94, 135.95, 129.73, 115.79, 127.65, 59.98, 83.87, 129.74, 67.87, 155.86, 18, 18, 0, 61.96, 124.01, 99.85, 107.78, 121.81, 129.95, 57.95, 119.86, 119.82, 87.91, 79.93, 51.93, 57.94, 111.75, 103.88, 79.93, 45.99, 53.84, 113.95, 85.93, 109.68]
Loading
xychart-beta
    title "HEAD memory: RSS then PSS"
    x-axis "Elapsed seconds" [0.041, 1.043, 2.046, 2.547, 3.548, 4.548, 5.549, 6.05, 7.052, 8.555, 9.556, 10.556, 12.059, 13.061, 14.062, 15.563, 16.563, 17.564, 19.066, 20.066, 21.568, 22.57, 23.572, 25.074, 26.074, 27.076, 28.579, 29.58, 30.581, 32.083, 33.084, 34.086, 34.587, 35.588, 36.589, 38.092, 39.093, 40.094, 41.596, 42.387]
    y-axis "Memory MiB" 0 --> 500
    line [13.39, 75.21, 116.64, 102.49, 112.36, 126.14, 145.88, 147.81, 145.49, 139.14, 119.66, 134.98, 138.49, 124.6, 141.37, 132.6, 131.02, 120.09, 118.98, 135.9, 209.04, 176.55, 197.1, 213.77, 133.51, 162.02, 153.56, 135.2, 142.8, 135.72, 153.93, 247.25, 252.27, 226.72, 149.62, 145.92, 172.9, 155.38, 168.11, 0]
    line [9.81, 71.27, 112.75, 98.56, 108.4, 122.2, 142.01, 143.81, 141.55, 135.05, 115.65, 130.99, 134.67, 120.6, 137.36, 128.59, 127.04, 116.1, 115.09, 131.92, 205.15, 172.57, 193.11, 209.78, 129.52, 158.27, 154.25, 131.29, 138.83, 131.76, 151.24, 239.08, 248.3, 222.39, 145.65, 141.87, 168.97, 151.41, 164.66, 0]
Loading

Deterministic fixture · required contract

The fixture exercises 15 routes: 8 are expected to emit rows and 7 intentionally exercise empty, loading, or timeout behavior.

Metric Base HEAD Δ
Run status success success —
Extracted rows / contract 40 / 40 40 / 40 0 (0.00%)
Contract fill 100.00% 100.00% 0.00 pp (0.00%)
Sites with rows 8 / 15 8 / 15 0 (0.00%)
Sites meeting row contract 15 / 15 15 / 15 0 (0.00%)
Unexpected empty sites 0 0 0
Partial-row sites 0 0 0
Site outcomes extracted: 7; HTTP error + rows: 1; empty: 3; timeout + empty: 4 extracted: 7; HTTP error + rows: 1; empty: 3; timeout + empty: 4 —
Observed duration 19.85 s 19.85 s -0.00 s (0.00%)
Estimated CPU 0.41 s 0.42 s +0.01 s (+2.18%)
Peak PSS 67.57 MiB 67.58 MiB +0.01 MiB (+0.02%)
Peak RSS 71.07 MiB 71.30 MiB +0.22 MiB (+0.31%)
Peak threads 18 18 0 (0.00%)
Mismatched sites 0 0 0
Timeout with items 0 0 0

Full HTML, JSON, CSV, logs, and page snapshots: workflow run and full spider-bench-results artifact.

The public-web A/B is informational. The exact HEAD fixture contract runs as a separate required CI check; timing and public-site content deltas remain non-blocking.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Sequential Navigation Soak A/B

✅ HEAD completed the 200-navigation resilience and memory observation.

One browser process, one target, and one CDP session navigate CSDN → SegmentFault → Huaban → example.com repeatedly. This run issued 200 Page.navigate commands per binary.

Common ancestor 829f1989da → HEAD 8e017b87f1; benchmark order: base-first; workflow: success.

Session resilience

Metric Base HEAD
Attempted / planned 200 / 200 200 / 200
Direct observable pass 195 / 200 197 / 200
Failures / recovered 5 / 5 3 / 3
Recovery failures 0 0
Lifecycle order violations 0 0
Network order violations 0 0
Wall time 534.6 s 489.2 s

A public-page failure is reported separately from an unrecoverable session. The soak requires all navigation attempts, zero failed recovery, zero lifecycle/network ordering violations, and complete resource evidence.

Process-tree memory

Metric Base HEAD Δ
Boundary samples 200 200 0
Boundary RSS samples 200 200 0
Boundary PSS samples 200 200 0
Periodic peak RSS 248.6 MiB 233.0 MiB -15.6 MiB
Periodic peak PSS 244.9 MiB 229.6 MiB -15.3 MiB
RSS · first 10 avg 165.6 MiB 162.5 MiB -3.1 MiB
RSS · last 10 avg 173.5 MiB 169.5 MiB -4.0 MiB
RSS · last − first window +7.9 MiB +6.9 MiB -0.9 MiB
RSS · warm slope / 100 nav +5.5 MiB +3.0 MiB -2.5 MiB
PSS · first 10 avg 161.8 MiB 158.9 MiB -2.9 MiB
PSS · last 10 avg 169.8 MiB 165.7 MiB -4.1 MiB
PSS · last − first window +8.0 MiB +6.8 MiB -1.2 MiB
PSS · warm slope / 100 nav +5.6 MiB +3.0 MiB -2.7 MiB
Peak file descriptors 59 60 1
Peak threads 31 29 -2

HEAD memory by 50-navigation quarter

HEAD range Avg RSS Final RSS Peak RSS Avg PSS Final PSS Peak FD
1–50 163.1 MiB 145.6 MiB 204.5 MiB 159.5 MiB 142.1 MiB 53
51–100 167.8 MiB 155.6 MiB 204.4 MiB 164.2 MiB 151.9 MiB 55
101–150 169.8 MiB 161.9 MiB 204.9 MiB 166.2 MiB 158.2 MiB 53
151–200 171.8 MiB 158.8 MiB 208.8 MiB 168.2 MiB 154.9 MiB 54

Raw reports and per-navigation boundary samples: workflow run and full sequential-navigation-soak-results artifact.

Public-site timing and memory are observational. Use the A/B deltas and trend shape as evidence, not as a deterministic performance threshold.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

CI Regression Report

Source CI run · source state at render: in_progress · artifacts: 6/6

Check Status Signal
Release regression ✅ HEAD/base failures 0/0; raw binary +0.024655%
Frontend differential ❌ 1,017/1,020 cases matched; 3 issues; 1 Chromium reference recoveries
Agent episodes ✅ 8/8 Moli episodes passed; 0 failures
Runtime/CDP contracts ✅ 26 contract cases; 0 failures
CDP smoke ✅ 52/52 groups passed; 621 scenarios
WebMainBench ✅ 545/545 completed; 0 unexpected failures
Release regression — ✅ HEAD/base failures 0/0; raw binary +0.024655%

Package and image size

Metric Base HEAD Delta Delta %
Raw binary 153.30 MiB 153.34 MiB +38.70 KiB +0.024655%
Stripped binary 106.57 MiB 106.61 MiB +36.00 KiB +0.032989%
gzip binary 52.81 MiB 52.82 MiB +11.28 KiB +0.020867%
Rootfs 160.21 MiB 160.24 MiB +40.00 KiB +0.024383%
gzip rootfs 61.86 MiB 61.87 MiB +13.47 KiB +0.021259%

Startup latency and PSS

Case Base p50 HEAD p50 Delta Base PSS HEAD PSS Delta
Serve ready 27.17 ms 27.63 ms +0.46 ms 4.14 MiB 3.91 MiB −238.00 KiB
CDP first page 49.51 ms 50.20 ms +0.69 ms 4.13 MiB 3.94 MiB −199.50 KiB
CDP warm page 12.08 ms 12.74 ms +0.66 ms 50.90 MiB 50.39 MiB −524.50 KiB
CLI about:blank 38.26 ms 38.36 ms +0.10 ms 3.18 MiB 3.72 MiB +546.50 KiB
CLI local JS 43.80 ms 41.87 ms −1.93 ms 2.92 MiB 3.96 MiB +1.04 MiB

Concurrency matrix

Concurrency Base p50 HEAD p50 Delta Base PSS HEAD PSS Delta HEAD failures Unstable B/H
1 40.94 ms 40.77 ms −0.16 ms 648.00 KiB 643.00 KiB −5.00 KiB 0 0/0
5 41.18 ms 41.65 ms +0.47 ms 3.94 MiB 2.10 MiB −1.84 MiB 0 0/0
10 41.38 ms 41.84 ms +0.46 ms 1.54 MiB 2.35 MiB +829.00 KiB 0 0/0
25 41.27 ms 41.55 ms +0.28 ms 3.74 MiB 3.24 MiB −505.00 KiB 0 0/0
100 40.84 ms 41.25 ms +0.41 ms 1.87 MiB 1.57 MiB −299.00 KiB 0 0/0
Frontend differential — ❌ 1,017/1,020 cases matched; 3 issues; 1 Chromium reference recoveries
Match DOM mismatch Diagnostic mismatch Moli error Reference error Infrastructure error Mismatched frames Duration
0 0 0 0 3 0 0 4m 25.75s

Recovered Chromium reference infrastructure failures

  • angular/custom-elements-form-boundaries/internals-validity-submit-transition — 1 failed attempt(s) before recovery · unknown
  • angular/custom-elements-form-boundaries/reset-callback-form-state — 1 failed attempt(s) before recovery · CDP connection failed: timed out
  • angular/custom-elements-form-boundaries/label-association-retarget — 1 failed attempt(s) before recovery · CDP connection failed: timed out
  • angular/custom-elements-form-boundaries/delayed-upgrade-association — 1 failed attempt(s) before recovery · CDP connection failed: timed out

First failing cases

  • angular/custom-elements-form-boundaries/internals-validity-submit-transition — reference_error · Chromium error CDP connection failed: timed out
  • angular/custom-elements-form-boundaries/reset-callback-form-state — reference_error · Chromium error CDP connection failed: timed out
  • angular/custom-elements-form-boundaries/label-association-retarget — reference_error · Chromium error CDP connection failed: timed out
Agent episodes · Moli vs Chromium — ✅ 8/8 Moli episodes passed; 0 failures
Fast contract Episodes Passed Assertions Ready p50 Episode p50
Moli 8 8 47/47 59.14 ms 25.42 ms
Chromium 8 8 47/47 255.17 ms 83.89 ms
Operation p50 Chromium Moli Delta Delta %
navigate 38.93 ms 6.02 ms −32.91 ms −84.53%
observe 1.21 ms 0.89 ms −0.33 ms −26.90%
fill 2.53 ms 3.13 ms +0.60 ms +23.78%
click 4.29 ms 4.04 ms −0.25 ms −5.78%
Idle-resource episode Peak RSS Peak PSS Average CPU Peak CPU Peak processes Sampler
Moli 67.02 MiB 64.09 MiB 0.79% 13.99% 1 ✅
Chromium 1327.04 MiB 352.59 MiB 1.11% 47.61% 13 ⚠️ PSS partial
Runtime and CDP session contracts — ✅ 26 contract cases; 0 failures
Suite Cases Failures Median case p50 Median case p95 Median peak PSS p50
Synthetic fetch 13 0 42.37 ms 42.97 ms 614.00 KiB
Long-lived CDP session 13 0 17.99 ms 18.25 ms —
CDP smoke — ✅ 52/52 groups passed; 621 scenarios

Workers: 4 · cumulative group time: 139.24 s · failed groups: 0

Slowest group Scenarios Duration Status
scrollbar-visibility 22 14.84 s ✅
target-lifecycle 24 10.96 s ✅
chromium-cdp 56 9.15 s ✅
dom-input 27 8.78 s ✅
multi-page 45 8.03 s ✅
WebMainBench · 545 pages — ✅ 545/545 completed; 0 unexpected failures
Completed Successful Expected DNS failure Unexpected failures Panics Crashes Timeouts Empty output
545/545 544 1 0 0 0 0 0

Frozen HTML · Linux · --wait done · 45 seconds per page · no retries. Content quality is evaluated separately.

Diagnostics: webmainbench-results — every page's Markdown, stderr, and exit status. Source run and artifacts

All artifact fields are parsed by the trusted default-branch renderer; missing or invalid inputs remain visible as unavailable.

@lanyue-llk lanyue-llk left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核提交 bef729588c74,发现一项 HTML 导出回退,建议修正后再合入,具体位置见行内评论。

按 iframe 的 DOM 位置读取当前文档、按各文档 base URL 解析资源地址,修复方向有必要。但该 PR 同时替换了 HTML 快照的序列化路径,原有 Shadow DOM 保留能力随之丢失。最小修正应保留快照原来的 shadow-root 和 scripting-policy 语义,同时让 strip 和嵌套 srcdoc 更新作用于导出副本。

已对照原快照调用链与 NativeDom 序列化实现,并用 Chrome 实测显式包含 shadow root 与普通 innerHTML 的输出差异。本机未运行 Moli 的原生 iframe 集成测试。该 PR 仍依赖 #1591。

Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated

@lanyue-llk lanyue-llk left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审 5e2f2376deb3:上次指出的 iframe Shadow DOM 丢失问题已在代码中修正,本轮未发现新的明确阻断问题。

导出副本恢复了 shadow-root bindings,子文档重新通过 DomHost::get_html 序列化,并沿原文档读取 scripting policy;strip 也覆盖 shadow root。新增真实页面测试覆盖 open/closed root、脚本和样式剥离、live DOM 保持原样,以及 sandbox 下 noscript 的转义差异,保护了上次遗漏的功能范围。嵌套 iframe 仍按逆序写入导出副本,Markdown 的位置和文档 base URL 路径也没有被这次补丁改坏。

本轮检查了最新补丁、完整导出路径和新增测试断言。本机仍缺少原生开发工具,未重新运行 Moli/V8 集成测试;此结论不将作者报告的测试通过写成本机实测。

@lanyue-llk lanyue-llk left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR makes frame-aware Markdown walk each live child document at its <iframe>/<frame> position, using a composed moli_html2md::Dom view. It resolves child URLs against the child document's base and keeps the root document's Markdown unchanged. Strip options now apply at every document root, and HTML export inlines strip-applied child markup from an export DomHost that has the shadow-root bindings restored.

The core approach is sound. Frame order comes from collect_child_browsing_context_handles_in_document_order, which is a pre-order walk with visited-document and visited-handle cycle guards. That makes the .rev() deepest-first passes correct, and frame cycles cannot occur. html2md's iterative machine with MAX_DOM_OUTPUT_TREE_DEPTH bounds the composed tree, including deeply nested frames, so there is no stack-recursion risk. Cross-origin, nested and srcdoc frames are covered by tests, and the srcdoc base inherits from the parent as the spec requires.

Assessment: request changes, mainly because of the architectural collision with #1337 described below. The remaining findings are minor.

Overlap with #1337 (major, architecture). #1337 (refs/review/pr-1337) rewrites the same render_page_dump Markdown path to go through its own visibility::MarkdownDom adapter. That adapter does computed-style visibility filtering, resolves href/src/srcset/data-src/data-lazy-src/poster against the document base, and adds control_value. This PR adds a second, independent Dom adapter (FrameMarkdownDom) over raw NativeDom, with its own narrower URL overlay (href/src/poster only). Whichever merges second will conflict, and a naive merge leaves two problems:

  • with_frames=true Markdown would bypass #1337's visibility filter and URL resolution entirely, because render_markdown_with_frames wraps NativeDom directly. Hidden navigation and stale controls would then reappear only when frames are enabled. This contradicts this PR's stated invariant that the root Markdown is identical with and without frames.
  • The two PRs take opposite root-URL contracts. #1337 resolves root links against the document base. This PR asserts [Parent link](./parent-details) stays relative in both modes (markdown_with_frames_preserves_document_positions_nested_and_cross_origin_content, iframe_media_urls_use_their_own_document_base). One of these tests must change.

Child frames would also need their own per-document ExportStyle collection, since #1337 only collects styles from the root document_node_id(), and lazy data-src/srcset resolution. Suggested fix: make frame composition a layer over the single shared Markdown adapter. For example, give #1337's MarkdownDom an optional owner -> child root map and a per-document base URL, populated for each frame document. Then there is one adapter, one URL-resolution table and one visibility policy. Agree on the root-URL contract before either merges.

Tests. The new per-frame light-DOM strip loop (for (_, _, document) in &frames) is not exercised. The HTML test strips only shadow-root content, which the separate roots loop handles, and no Markdown test uses non-default strip options. Add a Markdown and an HTML case with <script>/style=/<nav> in a child document's light DOM, with the strip assertions flipping on the option.

CI. All checks pass. The Frontend differential ❌ is 3 Chromium reference infrastructure timeouts (reference_error), with 0 Moli or DOM mismatches, so it is unrelated.

Verdict: changes requested (posted as a comment because GitHub does not allow requesting changes on your own PR).

Automated review by Claude Code.

Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated
Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated
Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated
Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated
@lanyue-llk
lanyue-llk force-pushed the codex/webfetch-frames branch from 8e017b8 to b94b661 Compare October 22, 2026 05:03
@lanyue-llk
lanyue-llk changed the base branch from codex/webfetch-markdown to codex/content-rendered-state October 22, 2026 05:04
@lanyue-llk
lanyue-llk force-pushed the codex/content-rendered-state branch 2 times, most recently from f5e6630 to f04b0b1 Compare October 22, 2026 06:32
@lanyue-llk
lanyue-llk force-pushed the codex/webfetch-frames branch from b94b661 to 7dbeedb Compare October 22, 2026 06:41
@lanyue-llk
lanyue-llk force-pushed the codex/content-rendered-state branch 2 times, most recently from 9272068 to 6d0a254 Compare October 22, 2026 08:46

@lanyue-llk lanyue-llk left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of the updated version (86ed30b95)

This PR makes frame-aware Markdown and HTML export walk each live child document at its <iframe>/<frame> position. Composition is now a layer on #1337's shared visibility::MarkdownDom: frame_roots maps each owner to its child body, and URLs resolve through a per-document base map. HTML export serializes every child once, from the strip-applied export copy, and keeps the shadow-root bindings. Head 86ed30b95; reviewed by reading code only, with no build.

Verdict: request changes. The design now matches what we asked for, and all four round-1 inline findings are fixed. However, the PR is based on #1337's round-3 head f04b0b151, while #1337 is now at 92720683b. GitHub reports the PR as CONFLICTING. Replaying only this PR's five commits onto 92720683b (git merge-tree --merge-base f04b0b151 92720683b 86ed30b95) conflicts in page_dump.rs and page_dump/visibility.rs. There is also one new hidden-content leak that only occurs with frames enabled.

Previous findings

# Finding (round 1) Status Evidence at 86ed30b95
I1 HTML export serializes each frame twice to read its URL FIXED page_dump.rs:133-140 now reads child_browsing_context_current_url(owner) and calls get_html once on the export DomHost. 86ed30b95 deletes document_lookup.rs (198 lines) and ChildBrowsingContextDocumentSnapshot, and no callers remain.
I2 Embedded content noise for empty or about:blank frames FIXED A frame without content is no longer in frame_roots, so html2md's own media() handles it. With no src it returns nothing (machine.rs:556). about: fails safe_url (media.rs:247-262). The test (fetch_dump/tests.rs, !framed.contains("Embedded content") with srcdoc="") would have failed on the old code. The fix introduces the predicate problem in N3.
I3 Second, private URL overlay (href/src/poster only) FIXED The single MarkdownDom resolves through resource_url_attributes using document_bases[owner_document] (visibility.rs:246-270). FrameMarkdownDom is gone.
I4 Emptiness probe re-converts nested frames, O(d·size) FIXED markdown_frame_roots (page_dump.rs:305-321) runs deepest-first, and frame_document_has_content stops at nested owners already in the map. That is one raw-DOM scan per document.
S1 Architectural overlap with #1337 (two adapters, two policies) FIXED There is one adapter. The per-document targets key (document, id) and the per-document root veil (visibility.rs:62-103,184-197) are correct generalizations.
S2 Opposite root-URL contracts RESOLVED (author adopted #1337's) The tests now assert [Parent link]({parent_url}parent-details) both with and without frames.
S3 Child documents need their own ExportStyle collection FIXED, but it conflicts with v4 page_dump.rs:21-36 collects every frame document. See N1.
S4 Child srcset/data-src not resolved FIXED This comes from the shared attribute inventory.
S5 Per-frame light-DOM strip loop is untested NOT FIXED See N4. The new HTML test strips only shadow-root content (handled by the roots loop at :108-110), and no Markdown test uses non-default strip options.
(earlier P2) Shadow DOM lost in iframe HTML snapshot still fixed Covered by html_with_frames_preserves_shadow_content_and_strips_only_the_export.

New findings

# Severity Location Summary
N1 major page_dump.rs:21-36, visibility.rs:246-251 The PR must be rebased onto #1337 92720683b. It conflicts with v4's body-only style collection and the names→tag rename.
N2 minor visibility.rs:364-399 Content of a visibility:hidden iframe (or one under a hidden ancestor) is emitted in frame mode.
N3 minor page_dump.rs:324-366 The content predicate disagrees with the converter. Its about: guard does nothing, and script, style or hidden content counts as content, so the embed link fallback disappears.
N4 minor page_dump.rs:95-97 The per-frame light-DOM strip loop is still untested for both HTML and Markdown. There is no <frame> test either.
N5 nit visibility.rs:80-82 Fragment ids from all composed documents share one namespace.
N6 nit page_dump.rs:71-83,117 Frame-mode Markdown always clones the whole DOM, including every frame document.

Inherited stack findings. The stack-v4 findings N2–N6 and #10 reach child documents unchanged through the shared classifier:

  • N5 (blockified flex/grid/float/abspos) applies to every frame body, because child styles are computed from the child's own stylist (styles.rs:701-709).
  • N3 (range/color defaults) and N6 (per-character inline-block) also apply to child documents.

None of these needs a frame-specific fix; they are fixed once in #1337/#1336. The frame-specific addition: every composed owner is reported as div (visibility.rs:387-391), so an inline iframe always breaks the surrounding paragraph (<p>See <iframe srcdoc="<b>x</b>"></iframe> here</p> gives See\n\n**x**\n\nhere). That is acceptable for embedded documents, but the README/PR description should state it.

Hygiene and CI.

  • Commit e1e746100 still introduces a render_markdown_with_frames path that ignores styles, and 7dbeedb5d then replaces it. Squash those two when rebasing so that no commit has frames bypass visibility.
  • The author's thread replies cite b94b6614b, which is not in the branch.
  • CI on 86ed30b95: Clippy, Format, CDP smoke, Python/Node harness and Windows storage pass. Nextest and both release builds are still pending. The bot A/B comments (Spider Bench, Soak, CI regression report) are for the old head 8e017b87f, so there is no A/B for this version.

Verdict: changes requested (posted as a comment because GitHub does not allow requesting changes on your own PR).

Automated review by Claude Code.

Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated
Comment thread moli-renderer-v8/src/runtime/page_dump/visibility.rs
Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated
Comment thread moli-renderer-v8/src/runtime/page_dump.rs
Comment thread moli-renderer-v8/src/runtime/page_dump/visibility.rs
Comment thread moli-renderer-v8/src/runtime/page_dump.rs Outdated
@lanyue-llk
lanyue-llk force-pushed the codex/content-rendered-state branch from 6d0a254 to 4e58ef8 Compare October 22, 2026 10:41
@lanyue-llk
lanyue-llk force-pushed the codex/webfetch-frames branch 3 times, most recently from a89fb18 to 8a423e7 Compare October 22, 2026 11:26
@lanyue-llk
lanyue-llk force-pushed the codex/content-rendered-state branch from 4e58ef8 to 19bfc78 Compare October 22, 2026 11:48

@lanyue-llk lanyue-llk left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final re-review (P0/P1 only) (8a423e752)

Verdict: request changes, for a mechanical reason only. Head 8a423e752. The frame code looks correct, and every round-2 finding is fixed and covered by a test. The one blocker is the stack: #1337 was force-pushed again after this rebase (4e58ef889 → 19bfc785f, rebased onto a newer lower stack), so GitHub reports this PR as CONFLICTING/DIRTY. Replaying this PR's six commits onto 19bfc785f is conflict-free (git merge-tree --merge-base 4e58ef889 19bfc785f 8a423e752). The page_dump/html2md files are byte-identical between 4e58ef889 and 19bfc785f, so a plain rebase should resolve it. I reviewed by reading the code only; nothing was built.

Previous findings

# Finding Status Evidence at 8a423e752
R2-N1 Conflict with #1337 (style collection, base-URL source) Code FIXED; branch conflicting again page_dump.rs:28-44: for each document it collects <html> plus the <body> subtree, falling back to the whole document when there is no body. Bases come from the single source dom_host.document_base_url_for_handle(doc), which document_base_url() itself delegates to. v6's tag naming is kept (visibility.rs:307-311). It needs a re-rebase onto 19bfc785f (see above).
R2-N2 Hidden iframe (or hidden ancestor) leaks child content FIXED visibility.rs:338-341 puts hidden owners into hidden_frame_owners, and first_child returns None for them (:506). An owner set back to visibility:visible still descends, which matches what a browser paints. Test: fetch_dump/tests.rs:705 (Hidden frame body absent).
R2-N3 Content predicate disagrees with the converter (script-only embeds lose the link) FIXED The hand-written predicate is gone. markdown_frame_roots (page_dump.rs:323-354) runs the same MarkdownDom + converter on the child root, so script/style (suppressed through display:none), hidden and about:blank-only children render empty and fall back to html2md's media link. Tests: [Map fallback](https://maps.example/embed) and [Empty fallback](...) at tests.rs:727-734.
R2-N4 Per-frame light-DOM strip untested FIXED frame_light_dom_obeys_strip_options_in_html_and_markdown (tests.rs:751).
R2-N5/N6 Fragment namespace; clone on default path FIXED Child #frag resolves against the child base, and child id/name are hidden (visibility.rs:520; html2md reads these only in anchors.rs). The default Markdown fast path no longer clones (page_dump.rs:89-100).

Checks requested for this round

  • Blockification across the frame boundary is correct. item_blocks and original_display use raw parent_node. The owner is classified by its own parent's flex/grid, and the child <body> by the child <html>, never by the owner. This matches CSS, where the child document is a separate formatting root. Inherited visibility likewise starts at Normal for the child <html>, because its parent is a Document node.
  • The reverse hidden-structure pass works per frame. node_order contains every document's nodes, so a wholly invisible child table or list is suppressed inside its own document. A hidden owner no longer descends, so no child content can keep a hidden parent table alive.
  • HTML export is safe. Child markup goes through set_attribute("srcdoc") and normal attribute serialization, so there is no raw injection.

New findings

No P0/P1 issues in the code. The only blocking item is the mechanical rebase above.

CI

On 8a423e752, Clippy, Format, CDP smoke, Python/Node harness and Windows storage pass. Nextest, Build release · HEAD and Build release · common ancestor are still queued (run 39770570147), so the new frame tests have not run in CI yet. Approve once the branch is rebased onto 19bfc785f and Nextest is green.

Verdict: changes requested (posted as a comment because GitHub does not allow requesting changes on your own PR).

Automated review by Claude Code.

Comment thread moli-renderer-v8/src/runtime/page_dump.rs
@lanyue-llk
lanyue-llk force-pushed the codex/webfetch-frames branch from 8a423e7 to 152871b Compare October 22, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant