diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index 6e944f9..aeffb74 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -3188,6 +3188,51 @@ mod tests { ); } + #[cfg(feature = "render")] + #[tokio::test(flavor = "current_thread")] + async fn intersection_observer_does_not_refire_while_target_stays_intersecting() { + let dom = parse_html( + r#" +
+
+ "#, + ); + let mut rt = ObscuraJsRuntime::new(); + rt.set_dom(dom); + rt.set_viewport(1280.0, 720.0); + rt.run_page_init(); + + let result = rt + .evaluate_for_cdp( + r#" + new Promise(resolve => { + const feed = document.getElementById("feed"); + let loaded = 0; + const observer = new IntersectionObserver(entries => { + for (const entry of entries) { + if (!entry.isIntersecting) continue; + for (let i = 0; i < 10; i++) { + const card = document.createElement("div"); + card.textContent = "Item " + loaded++; + feed.appendChild(card); + } + } + }); + observer.observe(document.getElementById("sentinel")); + setTimeout(() => resolve([ + loaded, + feed.querySelectorAll("div").length, + ]), 200); + }) + "#, + true, + true, + ) + .await + .unwrap(); + assert_eq!(result.value.unwrap(), serde_json::json!([10, 10])); + } + #[cfg(feature = "render")] #[tokio::test(flavor = "current_thread")] async fn intersection_observer_recomputes_after_style_mutation_and_resize() { diff --git a/render-repros/paired-corpus.py b/render-repros/paired-corpus.py index 33e1ac6..35b81cb 100755 --- a/render-repros/paired-corpus.py +++ b/render-repros/paired-corpus.py @@ -615,6 +615,20 @@ def main(): "and content size immediately before paint" ), }, + "methodology_limits": { + "pixel_metrics": ( + "raw full-canvas diagnostics only; they are a tripwire, not a " + "fidelity verdict" + ), + "controlled_scroll": ( + "Obscura currently computes CSSOM scroll metrics in an " + "asset-light JS layout cache, while screenshot paint performs " + "a separate resource-aware layout. Recorded scroll offsets " + "therefore prove API state but do not by themselves prove that " + "both images show the same semantic bottom. Inspect landmarks " + "and content-height deltas until those layouts are shared." + ), + }, "pages": [], } manifest["obscura_identity_probe"] = probe_obscura_identity(args.obscura_bin)