refactor(renderer): key DOM wrappers by exact handles

This commit is contained in:
ldm0
2026-08-21 11:23:44 +08:00
committed by Donough Liu
parent b97719d675
commit 213634ba62
24 changed files with 96 additions and 350 deletions
+10 -10
View File
@@ -68,21 +68,21 @@ pub(crate) struct NativeBridgeBindings {
fn wrapper_kind_for_handle(handle: &BridgeHandle) -> WrapperKind {
match handle {
BridgeHandle::Window => WrapperKind::Window,
BridgeHandle::ClassList(_, _, _) => WrapperKind::ClassList,
BridgeHandle::Dataset(_, _) => WrapperKind::Dataset,
BridgeHandle::Style(_, _) => WrapperKind::Style,
BridgeHandle::ComputedStyle(_, _, _) => WrapperKind::ComputedStyle,
BridgeHandle::Node(_, _) => WrapperKind::Node,
BridgeHandle::ClassList(_, _) => WrapperKind::ClassList,
BridgeHandle::Dataset(_) => WrapperKind::Dataset,
BridgeHandle::Style(_) => WrapperKind::Style,
BridgeHandle::ComputedStyle(_, _) => WrapperKind::ComputedStyle,
BridgeHandle::Node(_) => WrapperKind::Node,
}
}
fn prototype_name_for_handle(host_ptr: *mut JsContextHost, handle: &BridgeHandle) -> &'static str {
match handle {
BridgeHandle::Window => "Window",
BridgeHandle::ClassList(_, _, _) => "DOMTokenList",
BridgeHandle::Dataset(_, _) => "DOMStringMap",
BridgeHandle::Style(_, _) | BridgeHandle::ComputedStyle(_, _, _) => "CSSStyleProperties",
BridgeHandle::Node(node_handle, _) => {
BridgeHandle::ClassList(_, _) => "DOMTokenList",
BridgeHandle::Dataset(_) => "DOMStringMap",
BridgeHandle::Style(_) | BridgeHandle::ComputedStyle(_, _) => "CSSStyleProperties",
BridgeHandle::Node(node_handle) => {
let runtime = unsafe { &*host_ptr };
if runtime.dom_host().is_shadow_root(*node_handle) {
"ShadowRoot"
@@ -275,7 +275,7 @@ impl NativeBridgeBindings {
window::sync_window_wrapper_function_identity(scope, wrapper);
return;
}
let BridgeHandle::Node(node_handle, _) = handle else {
let BridgeHandle::Node(node_handle) = handle else {
return;
};
let child_handle = {
@@ -136,12 +136,12 @@ pub(crate) fn callback_value_dom_handle(
let object = v8::Local::<v8::Object>::try_from(value).ok()?;
let (_, handle) = bridge_handle_from_object(scope, object).ok()?;
match handle {
BridgeHandle::Node(handle, _) => Some(handle),
BridgeHandle::Node(handle) => Some(handle),
BridgeHandle::Window
| BridgeHandle::ClassList(_, _, _)
| BridgeHandle::Dataset(_, _)
| BridgeHandle::Style(_, _)
| BridgeHandle::ComputedStyle(_, _, _) => None,
| BridgeHandle::ClassList(_, _)
| BridgeHandle::Dataset(_)
| BridgeHandle::Style(_)
| BridgeHandle::ComputedStyle(_, _) => None,
}
}
@@ -15,20 +15,17 @@ impl NativeDomBridge {
host_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*host_ptr }.runtime_reset_generation();
self.wrap_bridge_handle(scope, host_ptr, BridgeHandle::Node(handle, generation))
self.wrap_bridge_handle(scope, host_ptr, BridgeHandle::Node(handle))
}
pub(crate) fn cached_handle_wrapper<'s>(
&self,
scope: &mut v8::PinScope<'s, '_>,
host_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*host_ptr }.runtime_reset_generation();
let reflector_id = self
.identity
.existing_reflector_id(BridgeHandle::Node(handle, generation))?;
.existing_reflector_id(BridgeHandle::Node(handle))?;
self.identity.cached_wrapper(scope, reflector_id)
}
@@ -40,15 +37,6 @@ impl NativeDomBridge {
.retire_default_world_wrappers_for_realm(realm_token);
}
pub(crate) fn rebind_wrapper_generation(
&mut self,
old_generation: u64,
new_generation: u64,
) -> Option<usize> {
self.identity
.rebind_generation(old_generation, new_generation)
}
pub(crate) fn wrap_window<'s, 'i>(
&mut self,
scope: &mut v8::PinScope<'s, 'i>,
@@ -87,8 +75,7 @@ impl NativeDomBridge {
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let creation_context = receiver.get_creation_context(scope)?;
let generation = unsafe { &*host_ptr }.runtime_reset_generation();
let bridge_handle = BridgeHandle::Node(handle, generation);
let bridge_handle = BridgeHandle::Node(handle);
if creation_context == scope.get_current_context() {
return self.wrap_bridge_handle(scope, host_ptr, bridge_handle);
}
@@ -107,11 +94,10 @@ impl NativeDomBridge {
runtime_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
self.wrap_bridge_handle(
scope,
runtime_ptr,
BridgeHandle::ClassList(handle, generation, DomTokenListKind::Class),
BridgeHandle::ClassList(handle, DomTokenListKind::Class),
)
}
@@ -121,11 +107,10 @@ impl NativeDomBridge {
runtime_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
self.wrap_bridge_handle(
scope,
runtime_ptr,
BridgeHandle::ClassList(handle, generation, DomTokenListKind::Part),
BridgeHandle::ClassList(handle, DomTokenListKind::Part),
)
}
@@ -135,11 +120,10 @@ impl NativeDomBridge {
runtime_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
self.wrap_bridge_handle(
scope,
runtime_ptr,
BridgeHandle::ClassList(handle, generation, DomTokenListKind::Rel),
BridgeHandle::ClassList(handle, DomTokenListKind::Rel),
)
}
@@ -149,12 +133,7 @@ impl NativeDomBridge {
runtime_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
self.wrap_bridge_handle(
scope,
runtime_ptr,
BridgeHandle::Dataset(handle, generation),
)
self.wrap_bridge_handle(scope, runtime_ptr, BridgeHandle::Dataset(handle))
}
pub(crate) fn wrap_style<'s, 'i>(
@@ -163,8 +142,7 @@ impl NativeDomBridge {
runtime_ptr: *mut JsContextHost,
handle: DomHandle,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
self.wrap_bridge_handle(scope, runtime_ptr, BridgeHandle::Style(handle, generation))
self.wrap_bridge_handle(scope, runtime_ptr, BridgeHandle::Style(handle))
}
pub(crate) fn wrap_computed_style<'s, 'i>(
@@ -174,23 +152,22 @@ impl NativeDomBridge {
handle: DomHandle,
descriptor: ComputedStyleDescriptor,
) -> Option<v8::Local<'s, v8::Object>> {
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
self.wrap_bridge_handle(
scope,
runtime_ptr,
BridgeHandle::ComputedStyle(handle, generation, descriptor),
BridgeHandle::ComputedStyle(handle, descriptor),
)
}
pub(crate) fn resolve_node_handle(&self, reflector_id: ReflectorId) -> Option<DomHandle> {
match self.bridge_handle(reflector_id) {
Some(BridgeHandle::Node(handle, _)) => Some(handle),
Some(BridgeHandle::Node(handle)) => Some(handle),
Some(
BridgeHandle::Window
| BridgeHandle::ClassList(_, _, _)
| BridgeHandle::Dataset(_, _)
| BridgeHandle::Style(_, _)
| BridgeHandle::ComputedStyle(_, _, _),
| BridgeHandle::ClassList(_, _)
| BridgeHandle::Dataset(_)
| BridgeHandle::Style(_)
| BridgeHandle::ComputedStyle(_, _),
)
| None => None,
}
@@ -215,7 +215,6 @@ fn bridge_create_live_collection_callback(
return;
};
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
let tag_name_html_document = (query_kind == LiveCollectionQueryKind::TagName).then(|| {
unsafe { &*runtime_ptr }
.dom_host()
@@ -226,7 +225,6 @@ fn bridge_create_live_collection_callback(
collection_kind,
query_kind,
root,
generation,
query,
include_root,
tag_name_html_document,
@@ -182,7 +182,6 @@ pub(in crate::native_bridge) fn build_live_collection_for_node<'s>(
query: Option<String>,
include_root: bool,
) -> v8::Local<'s, v8::Object> {
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
let tag_name_html_document = (query_kind == LiveCollectionQueryKind::TagName).then(|| {
unsafe { &*runtime_ptr }
.dom_host()
@@ -196,7 +195,6 @@ pub(in crate::native_bridge) fn build_live_collection_for_node<'s>(
collection_kind,
query_kind,
root,
generation,
query,
include_root,
tag_name_html_document,
@@ -64,7 +64,6 @@ fn collection_named_value<'s>(
collection_kind: CollectionKind::RadioNodeList,
query_kind: LiveCollectionQueryKind::FormControlsByName,
root: descriptor.root,
generation: descriptor.generation,
query: Some(key.to_owned()),
include_root: false,
tag_name_html_document: None,
@@ -369,7 +369,6 @@ fn live_collection_named_value<'s>(
collection_kind: CollectionKind::RadioNodeList,
query_kind: LiveCollectionQueryKind::FormControlsByName,
root: descriptor.root,
generation: descriptor.generation,
query: Some(key),
include_root: false,
tag_name_html_document: None,
@@ -118,14 +118,13 @@ impl JsContextHost {
scope: &mut v8::PinScope<'_, '_>,
document_handle: DomHandle,
) {
let host_ptr = self as *mut JsContextHost;
let mut stack = vec![document_handle];
while let Some(current) = stack.pop() {
let children = self.dom_host().child_handles(current).collect::<Vec<_>>();
stack.extend(children);
if let Some(wrapper) = self
.native_bridge_mut()
.cached_handle_wrapper(scope, host_ptr, current)
.cached_handle_wrapper(scope, current)
{
let undefined = v8::undefined(scope);
set_private_value(
@@ -1234,7 +1234,6 @@ fn document_html_collection_getter<'s>(
}
return;
}
let generation = runtime.runtime_reset_generation();
let (query_kind, query, tag_name_html_document) = match kind {
DocumentCollectionAccessorKind::Forms => (LiveCollectionQueryKind::Forms, None, None),
DocumentCollectionAccessorKind::Images => (LiveCollectionQueryKind::Images, None, None),
@@ -1256,7 +1255,6 @@ fn document_html_collection_getter<'s>(
collection_kind: CollectionKind::HtmlCollection,
query_kind,
root: handle,
generation,
query,
include_root: true,
tag_name_html_document,
@@ -492,7 +492,7 @@ pub(crate) fn paired_detached_native_object_for_handle<'s>(
let runtime = unsafe { &mut *runtime_ptr };
runtime
.native_bridge_mut()
.cached_handle_wrapper(scope, runtime_ptr, handle)
.cached_handle_wrapper(scope, handle)
}?;
get_private_object(scope, wrapper, DOM_PARSER_FOREIGN_NODE_SLOT)
}
@@ -196,21 +196,6 @@ fn clear_window_event_handlers(scope: &mut v8::PinScope<'_, '_>) {
}
}
fn require_collision_free_wrapper_generation_rebind(
rebound: Option<usize>,
old_generation: u64,
new_generation: u64,
) -> usize {
// Page.setDocumentContent may replace a document before page script has
// materialized any DOM wrapper. Some(0) is therefore a successful no-op;
// None alone reports an identity-key collision.
rebound.unwrap_or_else(|| {
panic!(
"wrapper identity collision while rebinding document replacement from generation {old_generation} to {new_generation}"
)
})
}
impl JsContextHost {
fn prepare_root_document_replacement(
&mut self,
@@ -218,7 +203,6 @@ impl JsContextHost {
host_ptr: *mut JsContextHost,
document_handle: DomHandle,
) {
let old_generation = self.runtime_reset_generation();
self.dispatch_document_open_descendant_frame_unload_lifecycle(scope, document_handle);
self.clear_event_callbacks_for_document_replacement(document_handle, true);
custom_elements::with_custom_element_reaction_scope(scope, host_ptr, |scope| {
@@ -229,16 +213,6 @@ impl JsContextHost {
);
});
self.open_root_document(scope);
let new_generation = self.runtime_reset_generation();
let rebound = self
.native_bridge_mut()
.rebind_wrapper_generation(old_generation, new_generation);
let _ = require_collision_free_wrapper_generation_rebind(
rebound,
old_generation,
new_generation,
);
}
/// Replaces the active root document through the native document stream.
@@ -988,17 +962,3 @@ fn boundary_after_protected_inert_root(
let offset = u32::try_from(index + 1).ok()?;
Some((parent, offset))
}
#[cfg(test)]
mod tests {
#[test]
fn document_replacement_accepts_an_empty_wrapper_identity_store() {
let mut identity = crate::native_bridge::identity::BridgeIdentityStore::default();
let rebound = identity.rebind_generation(4, 5);
assert_eq!(
super::require_collision_free_wrapper_generation_rebind(rebound, 4, 5),
0
);
}
}
@@ -88,7 +88,7 @@ pub(crate) fn mutate_live_element_attribute_for_inspector(
if let Some((namespace, local_name)) = attr_cache_target
&& let Some(wrapper) = unsafe { &mut *runtime_ptr }
.native_bridge_mut()
.cached_handle_wrapper(scope, runtime_ptr, handle)
.cached_handle_wrapper(scope, handle)
{
clear_live_attr_cache_entry_ns(scope, wrapper, namespace.as_deref(), &local_name);
}
@@ -6,12 +6,12 @@ pub(super) fn class_list_runtime_handle_and_kind_from_object(
) -> std::result::Result<(*mut JsContextHost, DomHandle, DomTokenListKind), String> {
let (runtime_ptr, handle) = bridge_handle_from_object(scope, object)?;
match handle {
BridgeHandle::ClassList(handle, _, kind) => Ok((runtime_ptr, handle, kind)),
BridgeHandle::ClassList(handle, kind) => Ok((runtime_ptr, handle, kind)),
BridgeHandle::Window
| BridgeHandle::Node(_, _)
| BridgeHandle::Dataset(_, _)
| BridgeHandle::Style(_, _)
| BridgeHandle::ComputedStyle(_, _, _) => {
| BridgeHandle::Node(_)
| BridgeHandle::Dataset(_)
| BridgeHandle::Style(_)
| BridgeHandle::ComputedStyle(_, _) => {
Err("wrapper did not contain a DOMTokenList identity".to_owned())
}
}
@@ -46,12 +46,12 @@ fn dataset_runtime_and_handle_from_object(
) -> std::result::Result<(*mut JsContextHost, DomHandle), String> {
let (runtime_ptr, handle) = bridge_handle_from_object(scope, object)?;
match handle {
BridgeHandle::Dataset(handle, _) => Ok((runtime_ptr, handle)),
BridgeHandle::Dataset(handle) => Ok((runtime_ptr, handle)),
BridgeHandle::Window
| BridgeHandle::Node(_, _)
| BridgeHandle::ClassList(_, _, _)
| BridgeHandle::Style(_, _)
| BridgeHandle::ComputedStyle(_, _, _) => {
| BridgeHandle::Node(_)
| BridgeHandle::ClassList(_, _)
| BridgeHandle::Style(_)
| BridgeHandle::ComputedStyle(_, _) => {
Err("wrapper did not contain a DOMStringMap identity".to_owned())
}
}
@@ -255,12 +255,10 @@ pub(in crate::native_bridge) fn form_elements_getter_function<'s>(
rv.set_null();
return;
};
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
let descriptor = LiveCollectionDescriptor {
collection_kind: CollectionKind::FormControlsCollection,
query_kind: LiveCollectionQueryKind::FormControls,
root: handle,
generation,
query: None,
include_root: false,
tag_name_html_document: None,
@@ -440,12 +438,10 @@ pub(in crate::native_bridge) fn fieldset_elements_getter_function<'s>(
rv.set_null();
return;
};
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
let descriptor = LiveCollectionDescriptor {
collection_kind: CollectionKind::HtmlCollection,
query_kind: LiveCollectionQueryKind::FormControls,
root: handle,
generation,
query: None,
include_root: false,
tag_name_html_document: None,
@@ -571,7 +567,6 @@ pub(in crate::native_bridge) fn form_named_getter(
collection_kind: CollectionKind::RadioNodeList,
query_kind: LiveCollectionQueryKind::FormControlsByName,
root: handle,
generation: runtime.runtime_reset_generation(),
query: Some(key),
include_root: false,
tag_name_html_document: None,
@@ -631,7 +626,6 @@ pub(in crate::native_bridge) fn form_named_descriptor(
collection_kind: CollectionKind::RadioNodeList,
query_kind: LiveCollectionQueryKind::FormControlsByName,
root: handle,
generation: runtime.runtime_reset_generation(),
query: Some(key),
include_root: false,
tag_name_html_document: None,
@@ -557,12 +557,10 @@ pub(in crate::native_bridge) fn select_options_getter_function<'s>(
rv.set_null();
return;
};
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
let descriptor = LiveCollectionDescriptor {
collection_kind: CollectionKind::OptionsCollection,
query_kind: LiveCollectionQueryKind::Options,
root: handle,
generation,
query: None,
include_root: false,
tag_name_html_document: None,
@@ -582,12 +580,10 @@ pub(in crate::native_bridge) fn select_selected_options_getter_function<'s>(
rv.set_null();
return;
};
let generation = unsafe { &*runtime_ptr }.runtime_reset_generation();
let descriptor = LiveCollectionDescriptor {
collection_kind: CollectionKind::HtmlCollection,
query_kind: LiveCollectionQueryKind::SelectedOptions,
root: handle,
generation,
query: None,
include_root: false,
tag_name_html_document: None,
@@ -5,15 +5,13 @@ use super::super::{
node_runtime_and_handle_from_args_or_detached,
node_runtime_and_handle_from_object_or_detached, receiver_has_detached_state,
require_element_method_receiver, require_parent_node_receiver, set_wrapped_node_or_null,
stale_node_runtime_and_handle_from_object, throw_incompatible_method_receiver,
throw_native_selector_error_for_selector,
throw_incompatible_method_receiver, throw_native_selector_error_for_selector,
},
};
use super::forms::control_matches_validity_pseudo;
use crate::{
util::{
call_object_method, constructor_prototype, object_chain_contains, object_number_property,
object_property_as_object, v8_string, v8str, walk_object_chain,
call_object_method, object_number_property, object_property_as_object, v8_string, v8str,
},
webidl,
};
@@ -195,27 +193,6 @@ pub(in crate::native_bridge) fn node_matches_callback<'s>(
super::super::document::detached_matches_method_callback(scope, args, rv);
return;
}
if stale_node_is_element_receiver(scope, args.this()) {
let Some(parsed) = webidl::parse_args::<ElementMatchesArgs>(scope, &args) else {
return;
};
if let Some((runtime_ptr, _)) =
stale_node_runtime_and_handle_from_object(scope, args.this())
{
match unsafe { &*runtime_ptr }.matches(
unsafe { &*runtime_ptr }.document_handle(),
&parsed.selectors,
) {
Ok(_) => rv.set_bool(false),
Err(error) => {
throw_native_selector_error_for_selector(scope, &parsed.selectors, &error)
}
}
} else {
rv.set_bool(false);
}
return;
}
let Ok((runtime_ptr, handle)) =
node_runtime_and_handle_from_object_or_detached(scope, args.this())
else {
@@ -256,23 +233,6 @@ pub(in crate::native_bridge) fn node_matches_callback<'s>(
}
}
fn stale_node_is_element_receiver<'s>(
scope: &mut v8::PinScope<'s, '_>,
receiver: v8::Local<'s, v8::Object>,
) -> bool {
if stale_node_runtime_and_handle_from_object(scope, receiver).is_none() {
return false;
}
let global = scope.get_current_context().global(scope);
let Some(element_prototype) = constructor_prototype(scope, global, "Element") else {
return false;
};
object_chain_contains(
&walk_object_chain(scope, receiver, "__proto__"),
element_prototype,
)
}
fn node_matches_needs_owner_document_query<'s>(
scope: &mut v8::PinScope<'s, '_>,
node: v8::Local<'s, v8::Object>,
@@ -113,7 +113,7 @@ fn style_object_forces_empty_computed<'s>(
return value.boolean_value(scope);
}
match bridge_handle {
BridgeHandle::ComputedStyle(handle, _, descriptor) => match descriptor.target {
BridgeHandle::ComputedStyle(handle, descriptor) => match descriptor.target {
ComputedStyleTargetKey::ChildFrame(frame_handle) => {
let runtime = unsafe { &*runtime_ptr };
let empty = !child_frame_target_document_is_current(runtime, handle, frame_handle)
@@ -14,12 +14,12 @@ pub(in crate::native_bridge::element::styles) fn style_runtime_and_handle_from_o
) -> std::result::Result<(*mut JsContextHost, DomHandle, StyleMode), String> {
let (runtime_ptr, handle) = bridge_handle_from_object(scope, object)?;
match handle {
BridgeHandle::Style(handle, _) => Ok((runtime_ptr, handle, StyleMode::Inline)),
BridgeHandle::ComputedStyle(handle, _, _) => Ok((runtime_ptr, handle, StyleMode::Computed)),
BridgeHandle::Style(handle) => Ok((runtime_ptr, handle, StyleMode::Inline)),
BridgeHandle::ComputedStyle(handle, _) => Ok((runtime_ptr, handle, StyleMode::Computed)),
BridgeHandle::Window
| BridgeHandle::Node(_, _)
| BridgeHandle::ClassList(_, _, _)
| BridgeHandle::Dataset(_, _) => {
| BridgeHandle::Node(_)
| BridgeHandle::ClassList(_, _)
| BridgeHandle::Dataset(_) => {
Err("wrapper did not contain a CSSStyleDeclaration identity".to_owned())
}
}
+24 -65
View File
@@ -15,45 +15,11 @@ use super::{JsContextHost, RuntimeObservableContextToken};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(super) enum BridgeHandle {
Window,
Node(DomHandle, u64),
ClassList(DomHandle, u64, DomTokenListKind),
Dataset(DomHandle, u64),
Style(DomHandle, u64),
ComputedStyle(DomHandle, u64, ComputedStyleDescriptor),
}
impl BridgeHandle {
fn with_rebound_generation(&self, old_generation: u64, new_generation: u64) -> Option<Self> {
match self {
Self::Window => None,
Self::Node(handle, generation) if *generation == old_generation => {
Some(Self::Node(*handle, new_generation))
}
Self::ClassList(handle, generation, kind) if *generation == old_generation => {
Some(Self::ClassList(*handle, new_generation, *kind))
}
Self::Dataset(handle, generation) if *generation == old_generation => {
Some(Self::Dataset(*handle, new_generation))
}
Self::Style(handle, generation) if *generation == old_generation => {
Some(Self::Style(*handle, new_generation))
}
Self::ComputedStyle(handle, generation, descriptor)
if *generation == old_generation =>
{
Some(Self::ComputedStyle(
*handle,
new_generation,
descriptor.clone(),
))
}
Self::Node(_, _)
| Self::ClassList(_, _, _)
| Self::Dataset(_, _)
| Self::Style(_, _)
| Self::ComputedStyle(_, _, _) => None,
}
}
Node(DomHandle),
ClassList(DomHandle, DomTokenListKind),
Dataset(DomHandle),
Style(DomHandle),
ComputedStyle(DomHandle, ComputedStyleDescriptor),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -223,7 +189,6 @@ pub(super) struct LiveCollectionDescriptor {
pub(super) collection_kind: CollectionKind,
pub(super) query_kind: LiveCollectionQueryKind,
pub(super) root: DomHandle,
pub(super) generation: u64,
pub(super) query: Option<String>,
pub(super) include_root: bool,
pub(super) tag_name_html_document: Option<bool>,
@@ -237,7 +202,6 @@ pub(super) struct LiveCollectionResolutionCache(
#[derive(Debug)]
struct LiveCollectionResolutionCacheEntry {
runtime_generation: u64,
query_version: u64,
handles: Rc<[DomHandle]>,
}
@@ -247,7 +211,6 @@ impl PartialEq for LiveCollectionDescriptor {
self.collection_kind == other.collection_kind
&& self.query_kind == other.query_kind
&& self.root == other.root
&& self.generation == other.generation
&& self.query == other.query
&& self.include_root == other.include_root
&& self.tag_name_html_document == other.tag_name_html_document
@@ -261,7 +224,6 @@ impl Hash for LiveCollectionDescriptor {
self.collection_kind.hash(state);
self.query_kind.hash(state);
self.root.hash(state);
self.generation.hash(state);
self.query.hash(state);
self.include_root.hash(state);
self.tag_name_html_document.hash(state);
@@ -270,20 +232,13 @@ impl Hash for LiveCollectionDescriptor {
impl LiveCollectionDescriptor {
pub(super) fn resolve(&self, host: &JsContextHost) -> Rc<[DomHandle]> {
let runtime_generation = host.runtime_reset_generation();
if self.generation != runtime_generation {
return Rc::from(Vec::<DomHandle>::new());
}
let query_version = host.dom_host().query_version();
if let Some(handles) = self
.resolution_cache
.0
.borrow()
.as_ref()
.filter(|entry| {
entry.runtime_generation == runtime_generation
&& entry.query_version == query_version
})
.filter(|entry| entry.query_version == query_version)
.map(|entry| entry.handles.clone())
{
return handles;
@@ -333,7 +288,6 @@ impl LiveCollectionDescriptor {
};
let handles = Rc::<[DomHandle]>::from(handles);
*self.resolution_cache.0.borrow_mut() = Some(LiveCollectionResolutionCacheEntry {
runtime_generation,
query_version,
handles: handles.clone(),
});
@@ -359,8 +313,15 @@ impl Default for LiveCollectionStore {
impl LiveCollectionStore {
fn register(&mut self, descriptor: LiveCollectionDescriptor) -> u32 {
let collection_id = self.next_id;
self.next_id += 1;
self.descriptors.insert(collection_id, descriptor);
self.next_id = self
.next_id
.checked_add(1)
.expect("live collection id overflow");
let replaced = self.descriptors.insert(collection_id, descriptor);
assert!(
replaced.is_none(),
"live collection ids must never be reused"
);
collection_id
}
@@ -387,8 +348,15 @@ impl Default for StaticHandleCollectionStore {
impl StaticHandleCollectionStore {
fn register(&mut self, handles: Vec<DomHandle>) -> u32 {
let collection_id = self.next_id;
self.next_id += 1;
self.handles.insert(collection_id, handles);
self.next_id = self
.next_id
.checked_add(1)
.expect("static handle collection id overflow");
let replaced = self.handles.insert(collection_id, handles);
assert!(
replaced.is_none(),
"static handle collection ids must never be reused"
);
collection_id
}
@@ -553,15 +521,6 @@ impl BridgeIdentityStore {
self.reflectors.key_for_id(reflector_id)
}
pub(super) fn rebind_generation(
&mut self,
old_generation: u64,
new_generation: u64,
) -> Option<usize> {
self.reflectors
.rekey_matching(|handle| handle.with_rebound_generation(old_generation, new_generation))
}
pub(super) fn cached_wrapper<'s>(
&self,
scope: &mut v8::PinScope<'s, '_>,
+1 -1
View File
@@ -86,6 +86,6 @@ pub(crate) fn object_is_native_event_target_wrapper_or_detached<'s>(
return true;
}
bridge_handle_from_object(scope, object)
.is_ok_and(|(_, handle)| matches!(handle, BridgeHandle::Node(_, _) | BridgeHandle::Window))
.is_ok_and(|(_, handle)| matches!(handle, BridgeHandle::Node(_) | BridgeHandle::Window))
}
pub(crate) use traversal::install_traversal_template_bindings;
+5 -17
View File
@@ -1236,29 +1236,17 @@ pub(crate) fn node_runtime_and_handle_from_object(
) -> std::result::Result<(*mut JsContextHost, DomHandle), String> {
let (runtime_ptr, handle) = super::bridge_handle_from_object(scope, object)?;
match handle {
super::BridgeHandle::Node(handle, _) => Ok((runtime_ptr, handle)),
super::BridgeHandle::Node(handle) => Ok((runtime_ptr, handle)),
super::BridgeHandle::Window
| super::BridgeHandle::ClassList(_, _, _)
| super::BridgeHandle::Dataset(_, _)
| super::BridgeHandle::Style(_, _)
| super::BridgeHandle::ComputedStyle(_, _, _) => {
| super::BridgeHandle::ClassList(_, _)
| super::BridgeHandle::Dataset(_)
| super::BridgeHandle::Style(_)
| super::BridgeHandle::ComputedStyle(_, _) => {
Err("wrapper did not contain a node identity".to_owned())
}
}
}
pub(crate) fn stale_node_runtime_and_handle_from_object(
scope: &mut v8::PinScope<'_, '_>,
object: v8::Local<'_, v8::Object>,
) -> Option<(*mut JsContextHost, DomHandle)> {
let (runtime_ptr, handle) = super::bridge_handle_from_object(scope, object).ok()?;
let super::BridgeHandle::Node(handle, generation) = handle else {
return None;
};
(generation != unsafe { &*runtime_ptr }.runtime_reset_generation())
.then_some((runtime_ptr, handle))
}
pub(crate) fn object_is_node_wrapper_or_detached<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
+2 -94
View File
@@ -1,7 +1,4 @@
use std::{
collections::{HashMap, HashSet},
hash::Hash,
};
use std::{collections::HashMap, hash::Hash};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ReflectorId(u64);
@@ -82,7 +79,7 @@ where
return Reflector { id: existing, key };
}
self.next_id += 1;
self.next_id = self.next_id.checked_add(1).expect("reflector id overflow");
let id = ReflectorId(self.next_id);
self.ids_by_key.insert(key.clone(), id);
self.keys_by_id.insert(id, key.clone());
@@ -106,45 +103,6 @@ where
self.keys_by_id.get(&id).cloned()
}
pub fn rekey(&mut self, old_key: K, new_key: K) -> Option<ReflectorId> {
if old_key == new_key {
return self.ids_by_key.get(&old_key).copied();
}
if self.ids_by_key.contains_key(&new_key) {
return None;
}
let id = self.ids_by_key.remove(&old_key)?;
self.ids_by_key.insert(new_key.clone(), id);
self.keys_by_id.insert(id, new_key);
Some(id)
}
pub fn rekey_matching(
&mut self,
mut replacement: impl FnMut(&K) -> Option<K>,
) -> Option<usize> {
let replacements = self
.ids_by_key
.keys()
.filter_map(|old_key| replacement(old_key).map(|new_key| (old_key.clone(), new_key)))
.collect::<Vec<_>>();
let mut destinations = HashSet::with_capacity(replacements.len());
if replacements
.iter()
.any(|(_, new_key)| !destinations.insert(new_key.clone()))
|| replacements.iter().any(|(old_key, new_key)| {
old_key != new_key && self.ids_by_key.contains_key(new_key)
})
{
return None;
}
for (old_key, new_key) in &replacements {
self.rekey(old_key.clone(), new_key.clone())?;
}
Some(replacements.len())
}
pub fn len(&self) -> usize {
self.ids_by_key.len()
}
@@ -204,54 +162,4 @@ mod tests {
assert_eq!(registry.len(), 2);
assert!(!registry.is_empty());
}
#[test]
fn reflector_registry_rekeys_an_existing_identity_without_changing_its_id() {
let mut registry = ReflectorRegistry::default();
let reflector = registry.intern(7_u32);
assert_eq!(registry.rekey(7, 9), Some(reflector.id()));
assert_eq!(registry.existing(7), None);
assert_eq!(
registry.existing(9).map(|entry| entry.id()),
Some(reflector.id())
);
assert_eq!(registry.key_for_id(reflector.id()), Some(9));
assert_eq!(registry.len(), 1);
}
#[test]
fn reflector_registry_does_not_rekey_over_an_existing_identity() {
let mut registry = ReflectorRegistry::default();
let first = registry.intern(7_u32);
let second = registry.intern(9_u32);
assert_eq!(registry.rekey(7, 9), None);
assert_eq!(registry.existing(7), Some(first));
assert_eq!(registry.existing(9), Some(second));
}
#[test]
fn reflector_registry_rekeys_a_matching_key_set_atomically() {
let mut registry = ReflectorRegistry::default();
let first = registry.intern((7_u32, 1_u32));
let second = registry.intern((9_u32, 1_u32));
let unchanged = registry.intern((11_u32, 2_u32));
assert_eq!(
registry.rekey_matching(|(value, generation)| {
(*generation == 1).then_some((*value, 3))
}),
Some(2)
);
assert_eq!(
registry.existing((7, 3)).map(|entry| entry.id()),
Some(first.id())
);
assert_eq!(
registry.existing((9, 3)).map(|entry| entry.id()),
Some(second.id())
);
assert_eq!(registry.existing((11, 2)), Some(unchanged));
}
}
@@ -5384,7 +5384,10 @@ fn document_open_preserves_document_identity_and_detaches_the_replaced_tree() {
const oldBody = document.body;
const oldText = oldNode.firstChild;
const oldClassList = oldNode.classList;
const oldDataset = oldNode.dataset;
const oldStyle = oldNode.style;
const oldDocumentMains = document.getElementsByTagName("main");
const oldBodyChildren = oldBody.children;
const oldShadowHost = document.createElement("section");
oldBody.append(oldShadowHost);
const oldShadow = oldShadowHost.attachShadow({ mode: "open" });
@@ -5423,7 +5426,17 @@ fn document_open_preserves_document_identity_and_detaches_the_replaced_tree() {
oldNodeParentPreserved: oldNode.parentNode === oldBody,
oldTextIdentityPreserved: oldNode.firstChild === oldText,
oldClassListIdentityPreserved: oldNode.classList === oldClassList,
oldDatasetIdentityPreserved: oldNode.dataset === oldDataset,
oldStyleIdentityPreserved: oldNode.style === oldStyle,
documentCollectionIdentityPreserved:
document.getElementsByTagName("main") === oldDocumentMains,
documentCollectionTracksReplacement:
Array.from(oldDocumentMains, node => node.id).join(","),
detachedCollectionIdentityPreserved:
oldBody.children === oldBodyChildren,
detachedCollectionKeepsOldTree:
Array.from(oldBodyChildren, node => node.id || node.localName).join(","),
oldNodeStillMatches: oldNode.matches('#old'),
oldBodyConnected: oldBody.isConnected,
oldShadowIdentityPreserved: oldShadowHost.shadowRoot === oldShadow,
oldShadowChildIdentityPreserved: oldShadow.firstChild === oldShadowChild,
@@ -5440,7 +5453,7 @@ fn document_open_preserves_document_identity_and_detaches_the_replaced_tree() {
assert_eq!(
result,
r#"{"sameDocument":true,"oldNodeConnected":false,"oldNodeText":"old text","oldNodeOwnerPreserved":true,"oldNodeParentPreserved":true,"oldTextIdentityPreserved":true,"oldClassListIdentityPreserved":true,"oldStyleIdentityPreserved":true,"oldBodyConnected":false,"oldShadowIdentityPreserved":true,"oldShadowChildIdentityPreserved":true,"oldShadowText":"shadow text","oldShadowConnected":false,"listenerRuns":{"node":0,"document":0,"window":0,"handler":0,"preDetachedNode":1,"preDetachedHandler":1},"oldLookupMissing":true,"newText":"new text"}"#
r#"{"sameDocument":true,"oldNodeConnected":false,"oldNodeText":"old text","oldNodeOwnerPreserved":true,"oldNodeParentPreserved":true,"oldTextIdentityPreserved":true,"oldClassListIdentityPreserved":true,"oldDatasetIdentityPreserved":true,"oldStyleIdentityPreserved":true,"documentCollectionIdentityPreserved":true,"documentCollectionTracksReplacement":"new","detachedCollectionIdentityPreserved":true,"detachedCollectionKeepsOldTree":"old,section","oldNodeStillMatches":true,"oldBodyConnected":false,"oldShadowIdentityPreserved":true,"oldShadowChildIdentityPreserved":true,"oldShadowText":"shadow text","oldShadowConnected":false,"listenerRuns":{"node":0,"document":0,"window":0,"handler":0,"preDetachedNode":1,"preDetachedHandler":1},"oldLookupMissing":true,"newText":"new text"}"#
);
}