mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 08:00:49 +00:00
refactor(renderer): key DOM wrappers by exact handles
This commit is contained in:
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, '_>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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"}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user