diff --git a/moli-renderer-v8/src/context_bootstrap/range_live.rs b/moli-renderer-v8/src/context_bootstrap/range_live.rs index d1bab6c0c4..4c65d336ae 100644 --- a/moli-renderer-v8/src/context_bootstrap/range_live.rs +++ b/moli-renderer-v8/src/context_bootstrap/range_live.rs @@ -2,13 +2,6 @@ use super::selection::selection_update_composed_boundaries_for_child_removal; use super::*; use crate::document_runtime::DomHandle; -pub(super) fn clear_live_range_registry(scope: &mut v8::PinScope<'_, '_>) { - let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else { - return; - }; - unsafe { &mut *host_ptr }.clear_live_range_registry(); -} - fn range_container_handle<'s>( scope: &mut v8::PinScope<'s, '_>, host_ptr: *mut JsContextHost, diff --git a/moli-renderer-v8/src/context_bootstrap/range_surface.rs b/moli-renderer-v8/src/context_bootstrap/range_surface.rs index ea540ef0e3..3c42e9fa2a 100644 --- a/moli-renderer-v8/src/context_bootstrap/range_surface.rs +++ b/moli-renderer-v8/src/context_bootstrap/range_surface.rs @@ -14,7 +14,6 @@ use super::range_algorithms::{ range_geometry_client_rects, range_geometry_dom_rect, range_insert_node_at_boundary, range_intersects_node_native, range_string_contents, range_surround_contents, root_handle, }; -use super::range_live::clear_live_range_registry; use super::*; mod accessors; @@ -26,7 +25,7 @@ mod geometry; mod install; mod template; -pub(super) use install::{install_range_template_bindings, reset_range_runtime_state}; +pub(super) use install::install_range_template_bindings; pub(super) use template::{ build_abstract_range_template, build_range_constructor_template, build_static_range_constructor_template, diff --git a/moli-renderer-v8/src/context_bootstrap/range_surface/install.rs b/moli-renderer-v8/src/context_bootstrap/range_surface/install.rs index 48f75fa4f9..f0b508d254 100644 --- a/moli-renderer-v8/src/context_bootstrap/range_surface/install.rs +++ b/moli-renderer-v8/src/context_bootstrap/range_surface/install.rs @@ -15,7 +15,6 @@ use super::content::{ range_surround_contents_callback, range_to_string_callback, }; use super::geometry::{range_get_bounding_client_rect_callback, range_get_client_rects_callback}; -use super::*; use crate::web_api_interfaces; use moli_webapi_declare::WebApiFunctionTemplate; @@ -126,10 +125,6 @@ struct AbstractRangePrototypeDeclaration { common_ancestor_container: (), } -pub(in crate::context_bootstrap) fn reset_range_runtime_state(scope: &mut v8::PinScope<'_, '_>) { - clear_live_range_registry(scope); -} - pub(in crate::context_bootstrap) fn install_range_template_bindings<'s>( scope: &mut v8::PinScope<'s, '_, ()>, template: v8::Local<'s, v8::FunctionTemplate>, diff --git a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs index 0dc469d976..b1a84af0f1 100644 --- a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs +++ b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs @@ -12,7 +12,6 @@ use super::{ navigation_bootstrap::install_window_location_history_navigation_runtime_state, navigator_runtime::{bind_window_navigator_identity_seed, install_navigator_runtime_state}, performance_runtime::install_default_window_performance_seed, - range_surface::reset_range_runtime_state, trusted_types::install_trusted_types_runtime_state, web_storage::{ install_storage_runtime_state, window_local_storage_getter, window_session_storage_getter, @@ -1956,7 +1955,6 @@ pub(crate) fn finish_context_bootstrap( install_window_global_accessors(scope, global); native_bridge::install_detached_bridge_methods(scope); - reset_range_runtime_state(scope); initialize_file_api_runtime_queues(scope, global)?; super::exposed_interfaces::capture_eager_intrinsic_interfaces( scope, diff --git a/moli-renderer-v8/src/native_bridge/context_host/live_ranges.rs b/moli-renderer-v8/src/native_bridge/context_host/live_ranges.rs index b089108db3..cb211bbfc7 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/live_ranges.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/live_ranges.rs @@ -1,10 +1,6 @@ use super::*; impl JsContextHost { - pub(crate) fn clear_live_range_registry(&mut self) { - self.range_record_registry.clear(); - } - pub(crate) fn live_ranges_is_empty(&mut self) -> bool { self.range_record_registry.active_is_empty() } diff --git a/moli-renderer-v8/src/native_bridge/context_host/range_records.rs b/moli-renderer-v8/src/native_bridge/context_host/range_records.rs index 82a1aa7cab..9e6593cd1b 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/range_records.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/range_records.rs @@ -70,6 +70,8 @@ pub(super) struct RangeRecord { end: RangeBoundaryPoint, } +// Shared by all realms in this JsContextHost. Creating another Window must not +// invalidate existing wrappers; their weak finalizers retire individual records. pub(super) struct RangeRecordRegistry { lifetime_token: u64, active_live_records: HashSet, @@ -93,15 +95,6 @@ impl RangeRecordRegistry { } } - pub(super) fn clear(&mut self) { - self.drain_finalized_live_records(); - self.active_live_records.clear(); - self.live_record_wrappers.clear(); - self.records.clear(); - self.by_boundary_container.clear(); - self.by_boundary_child_before.clear(); - } - pub(super) fn active_is_empty(&mut self) -> bool { self.drain_finalized_live_records(); self.active_live_records.is_empty() diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs index 9540cd999a..cf94ec2d65 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs @@ -2650,6 +2650,18 @@ fn pointer_event_sequences_preserve_identity_and_secure_context_exposure() { assert_eq!(insecure, "false|function|false|true"); } +#[test] +fn live_ranges_survive_initialization_of_child_and_sibling_realms() { + let mut vm = new_storage_test_vm("https://range-realm-lifetime.test/"); + assert_eq!( + vm.eval(include_str!( + "../../../../tests/fixtures/range-realm-lifetime.js" + )) + .expect("new realms must preserve live ranges in existing documents"), + "" + ); +} + #[test] fn character_data_setters_apply_replace_all_live_range_offsets() { let mut vm = new_storage_test_vm("https://character-data-range.test/"); diff --git a/moli-renderer-v8/tests/fixtures/range-realm-lifetime.js b/moli-renderer-v8/tests/fixtures/range-realm-lifetime.js new file mode 100644 index 0000000000..d98e2105bc --- /dev/null +++ b/moli-renderer-v8/tests/fixtures/range-realm-lifetime.js @@ -0,0 +1,45 @@ +(() => { + const root = document.documentElement || document.appendChild(document.createElement('html')); + const body = document.body || root.appendChild(document.createElement('body')); + const fixture = body.appendChild(document.createElement('div')); + const text = fixture.appendChild(document.createTextNode('abcd')); + const range = document.createRange(); + range.setStart(text, 1); + range.setEnd(text, 3); + const selection = getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + const failures = []; + const check = (condition, label) => { if (!condition) failures.push(label); }; + + const frame = fixture.appendChild(document.createElement('iframe')); + const other = frame.contentWindow; + const doc = other.document; + const childBody = doc.body || doc.documentElement.appendChild(doc.createElement('body')); + const childText = childBody.appendChild(doc.createTextNode('wxyz')); + const childRange = new other.Range(); + childRange.setStart(childText, 1); + childRange.setEnd(childText, 3); + const childSelection = other.getSelection(); + childSelection.addRange(childRange); + + const sibling = fixture.appendChild(document.createElement('iframe')); + sibling.contentDocument.body.appendChild(sibling.contentDocument.createElement('span')); + check(range.startContainer === text && range.startOffset === 1 && + range.endContainer === text && range.endOffset === 3, 'parent range survives new realms'); + check(childRange.startContainer === childText && childRange.startOffset === 1 && + childRange.endContainer === childText && childRange.endOffset === 3, 'child range survives sibling realm'); + check(selection.getRangeAt(0) === range && childSelection.getRangeAt(0) === childRange, + 'selections retain associated range identity'); + + text.insertData(0, '!'); + childText.deleteData(0, 1); + check(range.startOffset === 2 && range.endOffset === 4 && range.toString() === 'bc', + 'parent range still tracks mutations'); + check(childRange.startOffset === 0 && childRange.endOffset === 2 && childRange.toString() === 'xy', + 'child range still tracks mutations'); + const clone = childRange.cloneRange(); + check(clone instanceof other.Range && clone.toString() === 'xy', 'child range remains usable in its realm'); + fixture.remove(); + return failures.join('\n'); +})()