Allow reusing disconnected intersection observers

This commit is contained in:
SGavrl
2026-07-31 02:56:06 +00:00
parent e7656d9d9c
commit 9455d18439
2 changed files with 66 additions and 1 deletions

View File

@@ -6114,7 +6114,17 @@ globalThis.IntersectionObserver = class IntersectionObserver {
});
}
observe(el) {
if (!el || !this._connected || this._targets.has(el)) return;
if (!el || this._targets.has(el)) return;
// `disconnect()` removes every current observation; it does not destroy
// the observer. Browsers allow the same object to observe targets again.
// Re-register lazily so dormant observers do not stay in the global
// geometry recomputation list forever.
if (!this._connected) {
this._connected = true;
if (!globalThis.__intersectionObservers.includes(this)) {
globalThis.__intersectionObservers.push(this);
}
}
this._targets.add(el);
this._previous.delete(el);
this._check([el], true);

View File

@@ -3453,6 +3453,61 @@ mod tests {
assert_eq!(result.value.unwrap(), serde_json::json!([10, 10]));
}
#[cfg(feature = "render")]
#[tokio::test(flavor = "current_thread")]
async fn intersection_observer_can_be_reused_after_disconnect() {
let dom = parse_html(
r#"<html style="margin:0"><body style="margin:0">
<div id="stale" style="height:10px"></div>
<div id="first" style="height:10px"></div>
<div id="second" style="height:10px"></div>
</body></html>"#,
);
let mut rt = ObscuraJsRuntime::new();
rt.set_dom(dom);
rt.set_viewport(200.0, 100.0);
rt.run_page_init();
let result = rt
.evaluate_for_cdp(
r#"
new Promise(resolve => {
const deliveries = [];
const observer = new IntersectionObserver(entries => {
deliveries.push(entries.map(entry => entry.target.id));
if (deliveries.length === 1) {
observer.disconnect();
observer.observe(document.getElementById("second"));
} else {
observer.disconnect();
resolve([
deliveries,
globalThis.__intersectionObservers.length,
]);
}
});
// A pending record from before disconnect must be discarded.
observer.observe(document.getElementById("stale"));
observer.disconnect();
observer.observe(document.getElementById("first"));
setTimeout(() => resolve(["timed out"]), 100);
})
"#,
true,
true,
)
.await
.unwrap();
assert_eq!(
result.value.unwrap(),
serde_json::json!([
[["first"], ["second"]],
0,
])
);
}
#[cfg(feature = "render")]
#[tokio::test(flavor = "current_thread")]
async fn intersection_observer_recomputes_after_style_mutation_and_resize() {