Files
moli/todo.md
2026-08-11 00:10:12 +08:00

114 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TODO
## Objective
把网络、render owner、CDP 调度保持在 async-first 模型上:Web API / CDP 发起网络只启动或恢复请求,不在 render owner 上同步等网络;跨 live `PageVm` / render owner / CDP dispatcher 的接口保持 async;本地纯投影可以继续是同步函数。
## Current State
- CDP 生产入口已是 async-first:domain dispatcher 只保留 `dispatch_async(...)` / `process_async(...)`,测试侧 `TestContext::process(...)`、`block_on_test_future(...)`、`test_async.rs` 已删除。
- `moli_core::page::Page` 的 public sync command facade 已清掉:runtime eval、testing outcome、loader lifecycle、subresource interception、cookie facade override、client rect / node removal / object resolution 等跨 render owner 调用只保留 async API。
- renderer owner 同步 command path 已清掉:`RendererPageHandle::run_sync_command(...)`、owner-local store sync branch、sync page-command dispatcher、blocking pending-navigation follow 都不存在。
- `RendererPageHandle::drop` 只做 detached cleanup;生产 CDP loaded-page teardown / `Page.close` 已显式 `close_async().await`。剩余 `drop(page)` 命中主要是 runtime inline tests,包括 detached cleanup 覆盖。
- teardown 复核已完成:`drop(page)` 只命中 `#[cfg(test)] mod tests` 内的 detached cleanup / shared-host cleanup / abort-restore 回归;CDP loaded page replacement、target reset、`Page.close` 生产路径都显式 await `close_async()`。`Page::close_async()` 文档已明确 deterministic teardown vs Drop best-effort 边界。
- create-page 期间 `location.*` 触发 navigation 已走 `TriggeredNavigation -> RenderRuntimeTurn::FollowPendingLocationNavigation`,进入 render-runtime pending turn 队列,不再在原始 V8 调用栈里递归重建页面。
- live-page snapshot / `networkidle` / `domstable` 路径已经进入 render-runtime pending-turn 队列:`RunLivePageCommand`、`WaitLivePageNetworkIdle`、`WaitLivePageDomStable` 会在需要时转成 `FollowLivePagePendingLocationNavigation`,每个 follow turn 最多跟随一次 pending `location.*` navigation。
- pending navigation 回归已覆盖正常链和循环链:page load、`Runtime.evaluate` 后 snapshot refresh、`networkidle` / `domstable` wait 驱动。循环链会稳定报错;`Runtime.evaluate` 触发循环后 owner-local entry 会恢复,随后显式 `close_async()` 能正常清理。
- live `Page::wait_for_network_idle` / `Page::wait_for_dom_stable` 已有正常 chained navigation、timeout navigation loop、普通 timeout、caller cancellation 的 entry-restoration 回归:正常链会等到最终 `/target?from=chain-mid` snapshot;循环链、普通 timeout 或 caller cancellation 后 owner entry 会恢复,显式 `close_async()` 能清理;取消 `networkidle` 后直接 drop page 也能通过 detached cleanup 最终移除 owner entry。
- live-page pending navigation turn 已落地在代码和 focused regression 中。实现选择保留现有“await 后 snapshot 反映最终导航”的外部语义,用 `RenderRuntimePendingTurn.reply_tx` 停住原 reply,用 `LivePagePendingNavigationCompletion` 保存命令 continuation。
- live wait cancellation 已在 turn boundary 处理:`networkidle` / `domstable` wait 不再用一个长 owner task 跑到原始 timeout,而是按短 pending-turn slice 重排;如果原 reply receiver 被 drop,下一轮会恢复 held entry 并停止 parked command。
- 注意:`domstable` 的 slice 当前比 `networkidle` 长,因为 DOM stability 的 `last_snapshot/stable_since` 还在单次 `wait_for_dom_stable()` 调用栈内;如果要更细 cancellation,需要先把这份状态搬进 continuation。
- 长 live-page command wait 也已收进 pending-turn 模型:`WaitForSelector` / `WaitForScriptTruthy` 不再通过通用 `RunLivePageCommand` 长 await 持有 entry,而是各自用短 wait slice;caller cancellation 后显式 `close_async()` 能清理 owner entry。
- `DrainTimeoutsBestEffort` 仍是通用 async page command,但已有 `aborting_async_page_command_restores_local_host_entry` 覆盖:丢弃 in-flight command future 后 owner entry 会恢复,后续 page command 仍可 dispatch。当前不再为它单独拆 pending turn。
- 已系统复核 `RendererPageCommand`:真正长等待已经是 dedicated pending turn;剩余 generic `RunLivePageCommand` 是 runtime eval/protocol dispatch、input、isolated world/runtime binding、DOM 小投影或小变更、child-frame projection、Fetch pending-subresource continue/fail/fulfill、settings/cookie facade 等单次 owner-local 操作。`Runtime.evaluate awaitPromise` 当前不在 generic command 内做长轮询,只做 wrapper 求值和一次 promise token readback;轮询式 promise wait 仍由 selector/script truthy 专用 turn 承担。
- API 面复核已完成:`Page` 上触碰 renderer owner 的操作仍只通过 async 方法;同步 public 方法只读 cached snapshot / DOM projection / stable id。CDP domain 生产入口仍只有 `dispatch_async(...)` / `process_async(...)`。
- `RendererOwnerLocalStore` 现在只暴露单步 live-page 操作 helper:dispatch 一个 async page command、wait 一个 `networkidle` / `domstable` 单元、follow 一个 pending navigation turn、commit 一个 snapshot。旧的 `continue_pending_location_navigation_on_entry(...)` inline loop 已删除。
- 剩余 `block_on` 命中主要是测试 harness 自建 tokio runtime、`moli` CDP socket 专用 current-thread runtime、render-runtime 专用线程入口、phase-one inline test module;未看到“async facade 内部偷偷 block_on render owner”的生产路径。
- 已清理的命名假阳性:CDP `sync_loaded_page_*` -> `apply_loaded_page_*`,owner-local access / snapshot refresh 的 `sync_*` -> `refresh_*`,ScriptVm post-parse runtime work 的 `sync_*` / `block_on_dynamic_loads` -> `refresh_*` / `wait_for_dynamic_loads`。
- async/render-owner 主线当前进入维护态:后续只在新增跨 owner/dispatcher 行为时继续用本文件的 guardrails 复核。
## Now
- 不要继续机械清所有 `sync_*`。很多 JS/DOM 层函数是在同步更新 V8 object surface,例如 location/history/navigation/CSS/IndexedDB surface,这不是旧同步等待模型。
- 继续推进前,优先复扫是否出现新的危险同步等待:
- `block_on_test_future|test_async|ctx\\.process\\(|TestContext::process`
- `run_sync_command|block_on_in_any_runtime|dispatch_renderer_page_command_sync|follow_pending_location_navigation_blocking`
- `dispatch_page_command_sync|dispatch_unit_page_command_sync|decode_.*_page_command_sync`
- 当前最有价值的结构项已经完成第一阶段:live-page pending navigation follow 不再是 owner-local inline loop;`networkidle` / `domstable` / selector / script truthy 等长 wait 都已有 turn-boundary cleanup;`DrainTimeoutsBestEffort` 的 abort restore 也有覆盖;`RendererPageCommand` 剩余 generic 命令已复核为单次 owner-local 操作。接下来不要继续机械拆所有 async 命令,只有新命令能跨网络、timer、DOM mutation、promise settlement 或 navigation chain 多轮等待时,才需要 dedicated pending turn 或明确的 cancellation/restoration 回归。
- 如果继续找产品行为事项,优先按当前 WPT/current-state 文档和代码重新建 focused TODO,而不是依赖已删除的阶段性 docs。`navigation.forward()` 的 `--dump-dom + virtual-time-budget` probe 不可靠,但 CDP-driven probe 可作为 oracle;forward result-promise 细节已解阻,现有 focused tests 已覆盖。
## Next
1. 巩固 live-page pending navigation turn:
- 设计事实现在以实现和 focused regression 为准。
- pending-turn state 已落地:`RendererPageToken + RendererPageLocalEntry + PageVmInitStage + follow_count + LivePagePendingNavigationCompletion`。
- `Runtime.evaluate` / wait driver 触发 `location.*` 后的错误恢复、普通 wait timeout、caller cancellation 和 cleanup 行为已有 focused coverage。
- `WaitForSelector` / `WaitForScriptTruthy` 已从通用 `RunLivePageCommand` 长 await 中拆出,有 focused cancellation coverage。
- `DrainTimeoutsBestEffort` 已重新跑 abort restore coverage,当前不拆。
- `RendererPageCommand` 分类已通过当前代码复核;后续新增命令先判断是否允许留在 generic `RunLivePageCommand`。
- 目标仍是:页面脚本只产生 navigation request;后续由 render owner 的 top-level turn 继续,而不是在当前 command 内连续 follow。
2. 继续补 navigation 边界测试:
- live `Page::wait_for_network_idle` / `Page::wait_for_dom_stable` 的正常 chained navigation 和 loop restoration 回归已补。
- live `Page::wait_for_network_idle` / `Page::wait_for_dom_stable` 的普通 timeout entry-restoration 回归已补。
- caller cancellation 回归已补;selector / script truthy cancellation 回归也已补。下一步如果继续动 wait continuation,优先补新的边界类型,不要再重复正常链、普通 timeout 或 cancellation。
- 保留当前正常链 / 循环链回归作为行为锁。
3. 复核 teardown:
- 已完成:生产路径需要确定性 teardown 时均显式 `close_async()`。
- 已完成:`drop(page)` 只作为测试里的 best-effort cleanup 行为锁;取消 live wait 后 drop page 的 eventual cleanup 已有 focused 回归。
- 后续新增 owner/CDP teardown 时继续按这个规则:生产路径 await `close_async()`;`Drop` 只作为兜底,不作为业务完成信号。
4. 保持 API 面收敛:
- 已完成:`Page` API 复核确认跨 render owner / live page / CDP dispatcher helper 仍是 async。
- 已完成:本地纯计算 / snapshot projection helper 保持同步,没有为了统一命名强行 async 化。
- 后续新增 API 时继续按这个规则:跨 owner/dispatcher async,本地 cache/projection sync。
## Later
- `History` / `Navigation` 的后续事项应重新开 focused current-state/TODO 入口:当前重点是只在 Chromium/CDP/WPT 证据稳定时补 traversal/result-object 边界;`navigation.forward()` result-promise 细节已由 CDP probe 解阻,当前不需要新增重复断言。
- 评估 CDP WebSocket server 的 dedicated current-thread runtime 是否可以在更大的 thread-affinity 抽象下统一,但不要把 `CdpConnection` future 放回 axum multithread executor。
- 全量清理时再跑一次 workspace 级 `cargo nextest run --no-fail-fast`,当前每轮只跑 focused tests。
## Verification
本轮重新确认:危险同步入口前三组扫描无输出;`continue_pending_location_navigation_on_entry` 已无命中;`RendererPageCommand` 剩余 generic 命令已按 single-operation / dedicated long-wait turn 分类;teardown 复核确认 `drop(page)` 只在 runtime tests,生产 CDP/owner teardown 显式 await `close_async()`;API 面复核确认 `Page` 同步 public 方法只读 cache/projection,跨 owner 操作仍为 async;`cargo fmt --all --check`、`cargo check -p moli-renderer-v8 --tests`、`cargo check -p moli --tests`、pending navigation focused nextest、`git diff --check` 均通过。新补的 live wait 正常链、loop restoration、普通 timeout restoration、caller cancellation restoration、cancellation 后 detached drop cleanup、selector/script truthy cancellation focused tests 也通过;`DrainTimeoutsBestEffort` 的 abort restore 回归仍通过。
最近通过:
```bash
cargo fmt --all --check
cargo check -p moli-renderer-v8 --tests
cargo check -p moli --tests
cargo check -p moli-cdp --tests
cargo check -p moli --tests
cargo nextest run -p moli-renderer-v8 refresh_post_parse_runtime_work post_parse_lifecycle_driver_task_execution_marks_only_host_event_tasks_for_runtime_work_refresh --no-fail-fast
cargo nextest run -p moli async_page_command_snapshot_follow_can_adopt_pending_location_navigation location_navigation_keeps_same_renderer_page_id location_navigation_refreshes_owner_vm_incarnation_for_dispatch follows_location_reload_during_page_load --no-fail-fast
cargo nextest run -p moli rejects_location_navigation_loop_during_page_load async_page_command_snapshot_rejects_chained_location_navigation_loop follows_chained_location_navigation_during_page_load async_page_command_snapshot_follow_can_adopt_pending_location_navigation --no-fail-fast
cargo nextest run -p moli rejects_timeout_location_navigation_loop_during_network_idle_wait rejects_timeout_location_navigation_loop_during_domstable_wait rejects_location_navigation_loop_during_page_load follows_chained_location_navigation_during_page_load follows_async_location_search_during_network_idle_wait follows_async_location_search_during_domstable_wait --no-fail-fast
cargo nextest run -p moli async_page_command_snapshot_rejects_chained_location_navigation_loop async_page_command_snapshot_follows_chained_location_navigation follows_timeout_chained_location_navigation_during_network_idle_wait follows_timeout_chained_location_navigation_during_domstable_wait rejects_timeout_location_navigation_loop_during_network_idle_wait rejects_timeout_location_navigation_loop_during_domstable_wait --no-fail-fast
cargo nextest run -p moli async_page_command_snapshot_rejects_chained_location_navigation_loop async_page_command_snapshot_follows_chained_location_navigation live_page_networkidle_follows_chained_location_navigation live_page_domstable_follows_chained_location_navigation live_page_networkidle_rejects_chained_location_navigation_loop_and_restores_entry live_page_domstable_rejects_chained_location_navigation_loop_and_restores_entry follows_timeout_chained_location_navigation_during_network_idle_wait follows_timeout_chained_location_navigation_during_domstable_wait rejects_timeout_location_navigation_loop_during_network_idle_wait rejects_timeout_location_navigation_loop_during_domstable_wait --no-fail-fast
cargo nextest run -p moli live_page_networkidle_timeout_restores_entry_for_close live_page_domstable_timeout_restores_entry_for_close --no-fail-fast
cargo nextest run -p moli live_page_networkidle_cancellation_restores_entry_for_close live_page_domstable_cancellation_restores_entry_for_close --no-fail-fast
cargo nextest run -p moli live_page_networkidle_cancellation_allows_detached_drop_cleanup --no-fail-fast
cargo nextest run -p moli wait_for_selector_finds_late_element_via_renderer_wait_loop wait_for_selector_times_out_for_missing_selector wait_for_selector_reports_invalid_selector_errors wait_for_selector_cancellation_restores_entry_for_close wait_for_script_truthy_observes_delayed_fetch_state_change wait_for_script_truthy_times_out_for_false_predicate wait_for_script_truthy_reports_invalid_expression_errors wait_for_script_truthy_cancellation_restores_entry_for_close --no-fail-fast
cargo nextest run -p moli aborting_async_page_command_restores_local_host_entry --no-fail-fast
cargo nextest run -p moli fetch_registers_page_in_renderer_registry_until_page_is_dropped fetch_registers_page_in_renderer_registry_until_page_is_closed_async live_page_networkidle_cancellation_allows_detached_drop_cleanup dropping_page_only_removes_its_entry_from_shared_local_host renderer_owner_recreates_local_host_after_last_page_drops page_drop_removes_owner_entry_via_bound_slot --no-fail-fast
cargo nextest run -p moli navigation_forward_result_promises_settle_after_async_traversal navigation_back_result_promises_settle_after_async_traversal navigation_traverse_to_result_promises_settle_after_async_traversal navigation_forward_dispatches_currententrychange_traverse_event_surface --no-fail-fast
git diff --check
```
## Re-Evaluate Before Continuing
先跑这些扫描,确认没有旧入口回流:
```bash
rg -n "block_on_test_future|test_async|ctx\\.process\\(|TestContext::process|load_page_via_runtime_for_tests|load_navigation_request_via_runtime_for_tests" moli-cdp/src -g '*.rs'
rg -n "run_sync_command|block_on_in_any_runtime|dispatch_renderer_page_command_sync|follow_pending_location_navigation_blocking" moli-renderer-v8/src moli/src moli-cdp/src -g '*.rs'
rg -n "dispatch_page_command_sync|dispatch_page_command_sync_readonly|dispatch_unit_page_command_sync|decode_.*_page_command_sync|run_sync_command" moli-core/src/page/mod.rs
rg -n "pub fn process\\(|process\\(conn, cmd, out\\)" moli-cdp/src/domains -g '*.rs'
rg -n "drop\\(page|close_async\\(" moli/src moli-cdp/src moli/src -g '*.rs' -g '!**/tests.rs' -g '!**/tests/**'
```
前三组预期无输出;`drop(page|close_async)` 当前预期只看到 `Page::close_async`、CDP teardown / `Page.close` 显式 close,以及 runtime inline tests。