fix(dom): clear document handlers without invoking author setters

This commit is contained in:
ldm0
2026-11-02 06:03:25 +08:00
parent af1fca33b9
commit f0430b350b
8 changed files with 35 additions and 27 deletions
@@ -217,7 +217,7 @@ impl JsContextHost {
ChildDocumentCommitResult::from_install(install)
} else {
self.disconnect_shared_worker_clients_for_child_context(handle);
self.clear_child_window_document_event_state(scope, handle);
self.clear_child_window_event_listeners(handle);
self.replace_child_custom_elements_registry_for_document_commit(scope, handle);
self.clear_child_browsing_context_current_document(handle);
ChildDocumentCommitResult::ready(None)
@@ -355,7 +355,7 @@ impl JsContextHost {
FrameLocalWindowOwnerTransition::Replaced { .. } => {
self.disconnect_shared_worker_clients_for_child_context(handle);
if !preserve_window_event_state {
self.clear_child_window_document_event_state(scope, handle);
self.clear_child_window_event_listeners(handle);
}
self.replace_child_custom_elements_registry_for_document_commit(scope, handle);
}
@@ -779,7 +779,7 @@ impl JsContextHost {
if replaces_existing_document {
self.disconnect_shared_worker_clients_for_child_context(handle);
}
self.clear_child_window_document_event_state(scope, handle);
self.clear_child_window_event_listeners(handle);
self.replace_child_custom_elements_registry_for_document_commit(scope, handle);
self.clear_child_browsing_context_current_document(handle);
self.clear_pending_service_worker_child_client(handle);
@@ -1,4 +1,4 @@
use super::{JsContextHost, child_frame_runtime::WINDOW_EVENT_HANDLER_PROPERTIES};
use super::JsContextHost;
use crate::{
context_bootstrap::{
EVENT_DISPATCHING_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT, EVENT_STOP_PROPAGATION_SLOT,
@@ -81,20 +81,6 @@ impl JsContextHost {
ChildWindowEventRegistrationId(self.next_child_window_event_registration_id)
}
pub(in crate::native_bridge::context_host) fn clear_child_window_document_event_state(
&mut self,
scope: &mut v8::PinScope<'_, '_>,
handle: DomHandle,
) {
if let Some(window) = self.child_window_proxy_records.live_window(scope, handle) {
let null = v8::null(scope).into();
for name in WINDOW_EVENT_HANDLER_PROPERTIES {
let _ = window.set(scope, v8str(scope, name).into(), null);
}
}
self.clear_child_window_event_listeners(handle);
}
pub(crate) fn child_window_event_listener_callback_ids(
&self,
handle: DomHandle,
@@ -676,7 +676,7 @@ impl JsContextHost {
),
);
host.clear_child_window_document_event_state(scope, child_handle);
host.clear_child_window_event_listeners(child_handle);
host.clear_event_callbacks_for_document_replacement(document_handle, false);
for child in children {
let _ = remove_child_to_current_reaction_queue(
@@ -2,6 +2,7 @@ use super::*;
mod child_window;
mod compilation;
mod document_replacement;
mod object_values;
mod parser_merged_body;
mod popup;
@@ -13,18 +13,31 @@
before[key] === after[key]);
}
function withWindow(owner, run) {
if (owner !== 'root') throw new Error('unexpected document owner');
// Finish a replacement stream first so write() starts a destructive stream.
document.open();
document.close();
return run(window);
let frame;
let target;
if (owner === 'root') target = window;
else if (owner === 'popup') target = open();
else {
frame = document.createElement('iframe');
document.body.appendChild(frame);
target = frame.contentWindow;
}
try {
// Finish a replacement stream first so the write case is destructive.
target.document.open();
target.document.close();
return run(target);
} finally {
if (frame) frame.remove();
if (owner === 'popup') target.close();
}
}
function replace(target, operation) {
if (operation === 'open') target.document.open();
else target.document.write('<!doctype html><body>replacement');
target.document.close();
}
for (const owner of ['root']) {
for (const owner of ['child', 'popup', 'root']) {
for (const operation of ['open', 'write']) {
for (const mode of ['native', 'deleted', 'replaced', 'throwing-setter', 'read-only']) {
check(`${owner}:${operation}:${mode}`, () => withWindow(owner, target => {
@@ -51,7 +64,15 @@
} finally { Object.defineProperty(target, 'onresize', native); }
}));
}
check(`${owner}:${operation}:pending-content-cleared`, () => withWindow(owner, target => {
target.document.body.setAttribute('onresize', '}');
replace(target, operation);
let errors = 0;
try {
target.onerror = () => { ++errors; return true; };
return target.onresize === null && errors === 0;
} finally { target.onerror = null; }
}));
}
}
const failures = rows.filter(row => !row.pass);
@@ -1,7 +1,7 @@
use super::*;
#[test]
fn root_document_replacement_clears_native_handlers_without_changing_public_descriptors() {
fn document_replacement_clears_native_handlers_without_changing_public_descriptors() {
let mut vm = new_storage_html_test_vm("https://document-handler-cleanup.test/");
vm.eval(include_str!("document_replacement.js")).unwrap();
assert_eq!(