mirror of
https://github.com/lexmount/moli.git
synced 2026-10-31 00:06:19 +00:00
fix(dom): derive element creation options before validation
This commit is contained in:
@@ -360,7 +360,7 @@ pub(in crate::context_bootstrap) fn custom_elements_initialize_callback<'s>(
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
};
|
||||
if !custom_elements::registry_association_matches_document_default(
|
||||
if !custom_elements::registry_association_matches_document(
|
||||
unsafe { &*host_ptr },
|
||||
root_document,
|
||||
registry_association,
|
||||
|
||||
@@ -36,9 +36,7 @@ pub(crate) use definition_upgrade::{
|
||||
upgrade_existing_definition_for_child, upgrade_existing_definition_for_registry,
|
||||
};
|
||||
mod element_creation;
|
||||
pub(crate) use element_creation::{
|
||||
create_element_for_document_local_name_is_and_registry, is_name_from_create_options_value,
|
||||
};
|
||||
pub(crate) use element_creation::create_element_for_document_local_name_is_and_registry;
|
||||
mod element_state;
|
||||
use element_state::{
|
||||
definition_name_for_handle, set_dom_custom_element_is_name, set_dom_custom_element_state,
|
||||
@@ -136,9 +134,8 @@ pub(crate) use registry_install::{
|
||||
};
|
||||
mod registry_runtime;
|
||||
pub(crate) use registry_runtime::{
|
||||
mark_scoped_custom_elements_registry, registry_association_from_create_options_value,
|
||||
registry_association_from_value, registry_association_matches_document_default,
|
||||
registry_store_key,
|
||||
mark_scoped_custom_elements_registry, registry_association_from_value,
|
||||
registry_association_matches_document, registry_store_key,
|
||||
};
|
||||
mod registry_initializer;
|
||||
pub(crate) use registry_initializer::initialize_registry_for_subtree;
|
||||
|
||||
@@ -5,26 +5,7 @@ use super::element_state::{
|
||||
set_dom_custom_element_state, set_dom_element_prefix,
|
||||
};
|
||||
use crate::dom::native::CustomElementState;
|
||||
use crate::{document_runtime::DomHandle, native_bridge::JsContextHost, util::v8str};
|
||||
|
||||
pub(crate) fn is_name_from_create_options_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<String> {
|
||||
if value.is_null_or_undefined() {
|
||||
return None;
|
||||
}
|
||||
if value.is_string() {
|
||||
return value
|
||||
.to_string(scope)
|
||||
.map(|value| value.to_rust_string_lossy(scope));
|
||||
}
|
||||
let options = value.to_object(scope)?;
|
||||
options
|
||||
.get(scope, v8str(scope, "is").into())
|
||||
.and_then(|value| value.to_string(scope))
|
||||
.map(|value| value.to_rust_string_lossy(scope))
|
||||
}
|
||||
use crate::{document_runtime::DomHandle, native_bridge::JsContextHost};
|
||||
|
||||
pub(crate) fn create_element_for_document_local_name_is_and_registry<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::web_api_interfaces;
|
||||
use crate::{
|
||||
document_runtime::DomHandle,
|
||||
native_bridge::JsContextHost,
|
||||
util::{get_private_value, set_private_value, v8str},
|
||||
util::{get_private_value, set_private_value},
|
||||
};
|
||||
|
||||
pub(super) const CUSTOM_ELEMENTS_REGISTRY_CHILD_HANDLE_SLOT: &str =
|
||||
@@ -58,19 +58,7 @@ pub(crate) fn registry_association_from_value<'s>(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn registry_association_from_create_options_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<CustomElementRegistryAssociation> {
|
||||
if value.is_null_or_undefined() || value.is_string() {
|
||||
return None;
|
||||
}
|
||||
let options = value.to_object(scope)?;
|
||||
let registry = options.get(scope, v8str(scope, "customElementRegistry").into())?;
|
||||
registry_association_from_value(scope, registry)
|
||||
}
|
||||
|
||||
pub(crate) fn registry_association_matches_document_default(
|
||||
pub(crate) fn registry_association_matches_document(
|
||||
host: &JsContextHost,
|
||||
document_handle: DomHandle,
|
||||
association: CustomElementRegistryAssociation,
|
||||
|
||||
@@ -86,12 +86,6 @@ struct NativeBridgeDetachedCreationHelpersDeclaration {
|
||||
)]
|
||||
detached_create_cdata_section: (),
|
||||
|
||||
#[webapi(
|
||||
method = "__detachedCreateElement",
|
||||
callback = document::bridge_detached_create_element_callback
|
||||
)]
|
||||
detached_create_element: (),
|
||||
|
||||
#[webapi(
|
||||
method = "__adoptNodeIntoDocument",
|
||||
callback = document::bridge_adopt_node_into_document_callback
|
||||
|
||||
@@ -145,8 +145,11 @@ pub(crate) use css_state::{
|
||||
node_document_adopted_style_sheets_getter_function,
|
||||
node_document_adopted_style_sheets_setter_function, node_document_style_sheets_getter_function,
|
||||
};
|
||||
pub(in crate::native_bridge::document) use custom_element_options::parse_import_node_options;
|
||||
pub(in crate::native_bridge) use custom_element_options::validate_registry_association_for_document;
|
||||
pub(in crate::native_bridge::document) use custom_element_options::{
|
||||
CreateElementOptions, DocumentCreateElementArgs, DocumentCreateElementNsArgs,
|
||||
parse_import_node_options,
|
||||
};
|
||||
pub(crate) use detached_install::detached_iframe_current_content_document_handle;
|
||||
pub(super) use detached_install::install_detached_bridge_methods;
|
||||
pub(in crate::native_bridge) use detached_install::{
|
||||
@@ -207,6 +210,7 @@ pub(in crate::native_bridge) use detached_objects::{
|
||||
pub(in crate::native_bridge) use detached_objects::{
|
||||
detached_shadow_root_active_element_value, detached_shadow_root_selection_value,
|
||||
};
|
||||
pub(in crate::native_bridge::document) use detached_surface::create_detached_element_for_document;
|
||||
pub(in crate::native_bridge::document) use detached_surface::set_detached_document_content_type;
|
||||
pub(super) use detached_surface::{
|
||||
bridge_adopt_node_into_document_callback, bridge_clone_node_into_document_callback,
|
||||
@@ -220,7 +224,7 @@ pub(super) use detached_surface::{
|
||||
bridge_detached_child_element_count_callback, bridge_detached_child_nodes_callback,
|
||||
bridge_detached_children_callback, bridge_detached_clone_node_callback,
|
||||
bridge_detached_create_cdata_section_callback, bridge_detached_create_comment_callback,
|
||||
bridge_detached_create_document_fragment_callback, bridge_detached_create_element_callback,
|
||||
bridge_detached_create_document_fragment_callback,
|
||||
bridge_detached_create_processing_instruction_callback, bridge_detached_create_text_callback,
|
||||
bridge_detached_doctype_name_callback, bridge_detached_doctype_public_id_callback,
|
||||
bridge_detached_doctype_system_id_callback, bridge_detached_document_base_uri_callback,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use super::super::super::{
|
||||
custom_elements,
|
||||
util::{call_global_bridge_method, v8str},
|
||||
};
|
||||
use super::super::super::{custom_elements, util::call_global_bridge_method};
|
||||
use super::super::node::{
|
||||
node_arg_handle, node_is_document, node_or_foreign_arg_handle_allow_detached,
|
||||
node_runtime_and_handle_from_args, node_runtime_and_handle_from_args_or_detached,
|
||||
@@ -12,8 +9,9 @@ use super::super::{
|
||||
validate_qualified_element_name_and_namespace, validate_qualified_name_and_namespace,
|
||||
};
|
||||
use super::{
|
||||
XHTML_NS, clone_js_node_like_into_document_object, is_html_document, is_valid_pi_target,
|
||||
new_attr_object, normalize_namespace,
|
||||
DocumentCreateElementArgs, DocumentCreateElementNsArgs, XHTML_NS,
|
||||
clone_js_node_like_into_document_object, is_html_document, is_valid_pi_target, new_attr_object,
|
||||
normalize_namespace,
|
||||
};
|
||||
use super::{
|
||||
detached_adopt_node_method_callback, detached_create_attribute_method_callback,
|
||||
@@ -26,22 +24,6 @@ use super::{
|
||||
};
|
||||
use crate::webidl;
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createElement")]
|
||||
struct DocumentCreateElementArgs {
|
||||
#[webidl(required)]
|
||||
local_name: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createElementNS")]
|
||||
struct DocumentCreateElementNsArgs {
|
||||
#[webidl(required, nullable)]
|
||||
namespace: Option<String>,
|
||||
#[webidl(required)]
|
||||
qualified_name: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createTextNode")]
|
||||
struct DocumentCreateTextNodeArgs {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::helpers::{
|
||||
create_element_ns_for_document, create_element_options, create_element_wrapper_for_document,
|
||||
create_element_ns_for_document, create_element_wrapper_for_document,
|
||||
registry_association_for_create_element, registry_association_has_autonomous_definition,
|
||||
validate_create_element_name, validate_create_element_ns_name,
|
||||
};
|
||||
@@ -22,11 +22,9 @@ pub(in crate::native_bridge) fn bridge_create_element_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let mut options = create_element_options(scope, &args, 1);
|
||||
if options.registry_association.is_none() {
|
||||
options.registry_association =
|
||||
unsafe { &*runtime_ptr }.custom_element_registry_association(document_handle);
|
||||
}
|
||||
let Some(options) = parsed.options.flatten(scope, runtime_ptr, document_handle) else {
|
||||
return;
|
||||
};
|
||||
match create_element_wrapper_for_document(
|
||||
scope,
|
||||
args.this(),
|
||||
@@ -60,11 +58,9 @@ pub(in crate::native_bridge) fn bridge_create_element_ns_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let mut options = create_element_options(scope, &args, 2);
|
||||
if options.registry_association.is_none() {
|
||||
options.registry_association =
|
||||
unsafe { &*runtime_ptr }.custom_element_registry_association(document_handle);
|
||||
}
|
||||
let Some(options) = parsed.options.flatten(scope, runtime_ptr, document_handle) else {
|
||||
return;
|
||||
};
|
||||
if namespace.as_deref() == Some(XHTML_NS) {
|
||||
let (prefix, local_name) = parsed
|
||||
.qualified_name
|
||||
@@ -127,6 +123,7 @@ pub(in crate::native_bridge) fn bridge_create_element_ns_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
options.initialize_non_html_element(runtime_ptr, handle);
|
||||
unsafe { &mut *runtime_ptr }.capture_node_creation_stack_trace(scope, handle);
|
||||
match unsafe { &mut *runtime_ptr }
|
||||
.native_bridge_mut()
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use super::helpers::{
|
||||
create_element_ns_for_document, create_element_options, create_element_with_parts_for_document,
|
||||
create_element_ns_for_document, create_element_with_parts_for_document,
|
||||
create_element_wrapper_for_document, registry_association_for_create_element,
|
||||
registry_association_has_autonomous_definition, validate_create_element_name,
|
||||
validate_create_element_ns_name,
|
||||
};
|
||||
use super::*;
|
||||
use crate::native_bridge::document::validate_registry_association_for_document;
|
||||
use crate::native_bridge::set_wrapped_handle_or_null_for_receiver;
|
||||
|
||||
pub(in crate::native_bridge) fn node_create_element_callback<'s>(
|
||||
@@ -24,11 +23,11 @@ pub(in crate::native_bridge) fn node_create_element_callback<'s>(
|
||||
}
|
||||
return;
|
||||
}
|
||||
let Ok((runtime_ptr, handle)) = node_runtime_and_handle_from_args(scope, &args) else {
|
||||
rv.set_null();
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementArgs>(scope, &args) else {
|
||||
let Ok((runtime_ptr, handle)) = node_runtime_and_handle_from_args(scope, &args) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
if !validate_create_element_name(scope, &parsed.local_name) {
|
||||
@@ -39,19 +38,9 @@ pub(in crate::native_bridge) fn node_create_element_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
}
|
||||
let mut options = create_element_options(scope, &args, 1);
|
||||
if options.registry_association.is_none() {
|
||||
options.registry_association =
|
||||
unsafe { &*runtime_ptr }.custom_element_registry_association(handle);
|
||||
}
|
||||
if !validate_registry_association_for_document(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
options.registry_association,
|
||||
) {
|
||||
let Some(options) = parsed.options.flatten(scope, runtime_ptr, handle) else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if !is_html_document(runtime, handle) {
|
||||
let namespace = runtime
|
||||
.dom_host()
|
||||
@@ -68,6 +57,7 @@ pub(in crate::native_bridge) fn node_create_element_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
options.initialize_non_html_element(runtime_ptr, created_handle);
|
||||
unsafe { &mut *runtime_ptr }.capture_node_creation_stack_trace(scope, created_handle);
|
||||
set_wrapped_handle_or_null_for_receiver(
|
||||
scope,
|
||||
@@ -109,30 +99,20 @@ pub(in crate::native_bridge) fn node_create_element_ns_callback<'s>(
|
||||
}
|
||||
return;
|
||||
}
|
||||
let Ok((runtime_ptr, handle)) = node_runtime_and_handle_from_args(scope, &args) else {
|
||||
rv.set_null();
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementNsArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementNsArgs>(scope, &args) else {
|
||||
let Ok((runtime_ptr, handle)) = node_runtime_and_handle_from_args(scope, &args) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let namespace = normalize_namespace(parsed.namespace);
|
||||
if !validate_create_element_ns_name(scope, namespace.as_deref(), &parsed.qualified_name) {
|
||||
return;
|
||||
}
|
||||
let mut options = create_element_options(scope, &args, 2);
|
||||
if options.registry_association.is_none() {
|
||||
options.registry_association =
|
||||
unsafe { &*runtime_ptr }.custom_element_registry_association(handle);
|
||||
}
|
||||
if !validate_registry_association_for_document(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
options.registry_association,
|
||||
) {
|
||||
let Some(options) = parsed.options.flatten(scope, runtime_ptr, handle) else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if namespace.as_deref() == Some(XHTML_NS) {
|
||||
let (prefix, local_name) = parsed
|
||||
.qualified_name
|
||||
@@ -195,6 +175,7 @@ pub(in crate::native_bridge) fn node_create_element_ns_callback<'s>(
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
options.initialize_non_html_element(runtime_ptr, created_handle);
|
||||
unsafe { &mut *runtime_ptr }.capture_node_creation_stack_trace(scope, created_handle);
|
||||
set_wrapped_handle_or_null_for_receiver(
|
||||
scope,
|
||||
|
||||
@@ -85,11 +85,6 @@ pub(super) fn registry_association_has_autonomous_definition(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct CreateElementOptions {
|
||||
pub(super) is_name: Option<String>,
|
||||
pub(super) registry_association: Option<custom_elements::CustomElementRegistryAssociation>,
|
||||
}
|
||||
|
||||
pub(super) fn validate_create_element_name(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
local_name: &str,
|
||||
@@ -119,42 +114,3 @@ pub(super) fn validate_create_element_ns_name(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn create_element_options(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'_>,
|
||||
index: i32,
|
||||
) -> CreateElementOptions {
|
||||
let value = args.get(index);
|
||||
if value.is_null_or_undefined() {
|
||||
return CreateElementOptions {
|
||||
is_name: None,
|
||||
registry_association: None,
|
||||
};
|
||||
}
|
||||
if value.is_string() {
|
||||
// Legacy string createElement/createElementNS options are ignored by
|
||||
// current DOM custom-elements semantics; only dictionary `is` is used.
|
||||
return CreateElementOptions {
|
||||
is_name: None,
|
||||
registry_association: None,
|
||||
};
|
||||
}
|
||||
let Some(options) = value.to_object(scope) else {
|
||||
return CreateElementOptions {
|
||||
is_name: None,
|
||||
registry_association: None,
|
||||
};
|
||||
};
|
||||
let is_name = options
|
||||
.get(scope, v8str(scope, "is").into())
|
||||
.and_then(|value| value.to_string(scope))
|
||||
.map(|value| value.to_rust_string_lossy(scope));
|
||||
let registry_association = options
|
||||
.get(scope, v8str(scope, "customElementRegistry").into())
|
||||
.and_then(|value| custom_elements::registry_association_from_value(scope, value));
|
||||
CreateElementOptions {
|
||||
is_name,
|
||||
registry_association,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,163 @@ use crate::custom_elements;
|
||||
use crate::document_runtime::DomHandle;
|
||||
use crate::native_bridge::{JsContextHost, throw_dom_exception};
|
||||
use crate::util::{throw_type_error, v8str};
|
||||
use crate::web_api_interfaces;
|
||||
use crate::webidl;
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createElement")]
|
||||
pub(in crate::native_bridge::document) struct DocumentCreateElementArgs {
|
||||
#[webidl(required)]
|
||||
pub(in crate::native_bridge::document) local_name: String,
|
||||
#[webidl(with = create_element_options_argument)]
|
||||
pub(in crate::native_bridge::document) options: CreateElementOptions,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createElementNS")]
|
||||
pub(in crate::native_bridge::document) struct DocumentCreateElementNsArgs {
|
||||
#[webidl(required, nullable)]
|
||||
pub(in crate::native_bridge::document) namespace: Option<String>,
|
||||
#[webidl(required)]
|
||||
pub(in crate::native_bridge::document) qualified_name: String,
|
||||
#[webidl(with = create_element_options_argument)]
|
||||
pub(in crate::native_bridge::document) options: CreateElementOptions,
|
||||
}
|
||||
|
||||
#[derive(Default, webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "ElementCreationOptions")]
|
||||
pub(in crate::native_bridge::document) struct CreateElementOptions {
|
||||
// An omitted nullable member and an explicitly null registry have different
|
||||
// meanings. Keep its presence while reusing native interface conversion.
|
||||
#[webidl(name = "customElementRegistry", with = nullable_creation_registry)]
|
||||
pub(in crate::native_bridge::document) registry_association:
|
||||
Option<custom_elements::CustomElementRegistryAssociation>,
|
||||
#[webidl(name = "is")]
|
||||
pub(in crate::native_bridge::document) is_name: Option<String>,
|
||||
}
|
||||
|
||||
impl<'s> webidl::WebIdlConverter<'s> for CreateElementOptions {
|
||||
type Options = ();
|
||||
|
||||
fn convert(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
context: webidl::Context,
|
||||
_options: &(),
|
||||
) -> Result<Self, webidl::WebIdlError> {
|
||||
// Objects select the dictionary even if they are boxed strings or have
|
||||
// a toString method. Nullish values select the empty dictionary.
|
||||
if value.is_object() || value.is_null_or_undefined() {
|
||||
return webidl::parse_dictionary::<Self>(scope, value, context)
|
||||
.map(Option::unwrap_or_default);
|
||||
}
|
||||
// Other primitives select the legacy DOMString member, which the DOM
|
||||
// algorithm ignores. Conversion still rejects Symbols.
|
||||
webidl::convert::<webidl::DomString16>(scope, value, context)?;
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_element_options_argument<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
index: i32,
|
||||
) -> Result<CreateElementOptions, webidl::WebIdlError> {
|
||||
let prefix = if index == 1 {
|
||||
"Document.createElement"
|
||||
} else {
|
||||
"Document.createElementNS"
|
||||
};
|
||||
webidl::argument(
|
||||
scope,
|
||||
args,
|
||||
index,
|
||||
webidl::Context::argument(prefix, index as usize + 1),
|
||||
)
|
||||
}
|
||||
|
||||
fn nullable_creation_registry<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
member: &'static str,
|
||||
) -> Result<Option<custom_elements::CustomElementRegistryAssociation>, webidl::WebIdlError> {
|
||||
let context = webidl::Context::member("ElementCreationOptions", member);
|
||||
let Some(value) = webidl::property_result(scope, object, member, context)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
if value.is_undefined() {
|
||||
return Ok(None);
|
||||
}
|
||||
if value.is_null() {
|
||||
return Ok(Some(
|
||||
custom_elements::CustomElementRegistryAssociation::Null,
|
||||
));
|
||||
}
|
||||
let registry = webidl::convert_with_options::<webidl::InterfaceObject>(
|
||||
scope,
|
||||
value,
|
||||
context,
|
||||
&webidl::InterfaceOptions {
|
||||
name: web_api_interfaces::CustomElementRegistry::NAME,
|
||||
brand_check: web_api_interfaces::CustomElementRegistry::is_instance,
|
||||
},
|
||||
)?;
|
||||
let target = moli_webapi_declare::web_api_object_target(scope, registry.0)
|
||||
.expect("converted CustomElementRegistry has native identity");
|
||||
Ok(Some(
|
||||
custom_elements::CustomElementRegistryAssociation::Registry(
|
||||
custom_elements::registry_store_key(scope, target),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
impl CreateElementOptions {
|
||||
pub(in crate::native_bridge::document) fn initialize_non_html_element(
|
||||
&self,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
handle: DomHandle,
|
||||
) {
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
if let Some(registry) = self.registry_association {
|
||||
runtime.set_custom_element_registry_association(handle, registry);
|
||||
}
|
||||
if let Some(is_name) = &self.is_name {
|
||||
runtime
|
||||
.dom_host_mut()
|
||||
.set_custom_element_is_name(handle, Some(is_name.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::document) fn flatten(
|
||||
mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
document_handle: DomHandle,
|
||||
) -> Option<Self> {
|
||||
if self.registry_association.is_some() && self.is_name.is_some() {
|
||||
throw_dom_exception(
|
||||
scope,
|
||||
"NotSupportedError",
|
||||
9,
|
||||
"An explicit CustomElementRegistry cannot be combined with is.",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if !validate_registry_association_for_document(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
document_handle,
|
||||
self.registry_association,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
if self.registry_association.is_none() {
|
||||
self.registry_association =
|
||||
unsafe { &*runtime_ptr }.custom_element_registry_association(document_handle);
|
||||
}
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn validate_registry_association_for_document(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
@@ -12,7 +169,7 @@ pub(in crate::native_bridge) fn validate_registry_association_for_document(
|
||||
let Some(registry_association) = registry_association else {
|
||||
return true;
|
||||
};
|
||||
if custom_elements::registry_association_matches_document_default(
|
||||
if custom_elements::registry_association_matches_document(
|
||||
unsafe { &*runtime_ptr },
|
||||
document_handle,
|
||||
registry_association,
|
||||
|
||||
@@ -544,9 +544,6 @@ pub(in crate::native_bridge::document) fn build_detached_element_object<'s>(
|
||||
v8_string(scope, document_kind)?.into(),
|
||||
);
|
||||
let attributes = new_map_object(scope);
|
||||
if let Some(is_name) = is_name {
|
||||
detached_map_set(scope, attributes, "is", is_name);
|
||||
}
|
||||
let _ = state.set(scope, v8str(scope, "attributes").into(), attributes.into());
|
||||
let namespace_attributes = new_map_object(scope);
|
||||
let _ = state.set(
|
||||
@@ -573,10 +570,13 @@ pub(in crate::native_bridge::document) fn build_detached_element_object<'s>(
|
||||
);
|
||||
if let Some(is_name) = is_name {
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let _ = runtime.dom_host_mut().set_attribute(handle, "is", is_name);
|
||||
if runtime
|
||||
let _ = runtime
|
||||
.dom_host_mut()
|
||||
.set_custom_element_state(handle, CustomElementState::Undefined)
|
||||
.set_custom_element_is_name(handle, Some(is_name.to_owned()));
|
||||
if namespace_uri.as_deref() == Some(XHTML_NS)
|
||||
&& runtime
|
||||
.dom_host_mut()
|
||||
.set_custom_element_state(handle, CustomElementState::Undefined)
|
||||
{
|
||||
runtime.note_style_subtree_context_change(handle);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::*;
|
||||
use crate::detached_event_target::dispatch_detached_focus_event;
|
||||
use crate::dom_parser::map_live_value_to_foreign;
|
||||
use crate::native_bridge::document::{
|
||||
DocumentCreateElementArgs, DocumentCreateElementNsArgs, create_detached_element_for_document,
|
||||
};
|
||||
use crate::util::{
|
||||
context_host_ptr_from_global_bridge, get_private_object, get_private_value, set_private_value,
|
||||
};
|
||||
@@ -35,22 +38,6 @@ struct DetachedDomImplementationDeclaration<'scope> {
|
||||
owner_document: v8::Local<'scope, v8::Object>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createElement")]
|
||||
struct DetachedForwardCreateElementArgs {
|
||||
#[webidl(required)]
|
||||
local_name: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createElementNS")]
|
||||
struct DetachedForwardCreateElementNsArgs {
|
||||
#[webidl(required, nullable)]
|
||||
namespace: Option<String>,
|
||||
#[webidl(required)]
|
||||
qualified_name: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createCDATASection")]
|
||||
struct DetachedForwardCreateCdataSectionArgs {
|
||||
@@ -769,26 +756,19 @@ pub(in crate::native_bridge::document) fn detached_create_html_element_method_ca
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(parsed) = webidl::parse_args::<DetachedForwardCreateElementArgs>(scope, &args) else {
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(local_name) = v8_string(scope, &parsed.local_name) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
match call_global_bridge_method(
|
||||
match create_detached_element_for_document(
|
||||
scope,
|
||||
"__detachedCreateElement",
|
||||
&[
|
||||
args.this().into(),
|
||||
local_name.into(),
|
||||
v8str(scope, XHTML_NS).into(),
|
||||
v8str(scope, "html").into(),
|
||||
v8str(scope, "name").into(),
|
||||
args.get(1),
|
||||
],
|
||||
args.this(),
|
||||
&parsed.local_name,
|
||||
Some(XHTML_NS.to_owned()),
|
||||
"html",
|
||||
false,
|
||||
parsed.options,
|
||||
) {
|
||||
Some(value) => rv.set(value),
|
||||
Some(value) => rv.set(value.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
}
|
||||
@@ -798,26 +778,19 @@ pub(in crate::native_bridge::document) fn detached_create_xml_element_method_cal
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(parsed) = webidl::parse_args::<DetachedForwardCreateElementArgs>(scope, &args) else {
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(local_name) = v8_string(scope, &parsed.local_name) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
match call_global_bridge_method(
|
||||
match create_detached_element_for_document(
|
||||
scope,
|
||||
"__detachedCreateElement",
|
||||
&[
|
||||
args.this().into(),
|
||||
local_name.into(),
|
||||
v8::null(scope).into(),
|
||||
v8str(scope, "xml").into(),
|
||||
v8str(scope, "name").into(),
|
||||
args.get(1),
|
||||
],
|
||||
args.this(),
|
||||
&parsed.local_name,
|
||||
None,
|
||||
"xml",
|
||||
false,
|
||||
parsed.options,
|
||||
) {
|
||||
Some(value) => rv.set(value),
|
||||
Some(value) => rv.set(value.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
}
|
||||
@@ -827,32 +800,19 @@ pub(in crate::native_bridge::document) fn detached_create_html_element_ns_method
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(parsed) = webidl::parse_args::<DetachedForwardCreateElementNsArgs>(scope, &args)
|
||||
else {
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementNsArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(qualified_name) = v8_string(scope, &parsed.qualified_name) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let namespace = parsed
|
||||
.namespace
|
||||
.as_deref()
|
||||
.and_then(|namespace| v8_string(scope, namespace).map(Into::into))
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
match call_global_bridge_method(
|
||||
match create_detached_element_for_document(
|
||||
scope,
|
||||
"__detachedCreateElement",
|
||||
&[
|
||||
args.this().into(),
|
||||
qualified_name.into(),
|
||||
namespace,
|
||||
v8str(scope, "html").into(),
|
||||
v8str(scope, "qualified").into(),
|
||||
args.get(2),
|
||||
],
|
||||
args.this(),
|
||||
&parsed.qualified_name,
|
||||
parsed.namespace,
|
||||
"html",
|
||||
true,
|
||||
parsed.options,
|
||||
) {
|
||||
Some(value) => rv.set(value),
|
||||
Some(value) => rv.set(value.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
}
|
||||
@@ -862,32 +822,19 @@ pub(in crate::native_bridge::document) fn detached_create_xml_element_ns_method_
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(parsed) = webidl::parse_args::<DetachedForwardCreateElementNsArgs>(scope, &args)
|
||||
else {
|
||||
let Some(parsed) = webidl::parse_args::<DocumentCreateElementNsArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(qualified_name) = v8_string(scope, &parsed.qualified_name) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let namespace = parsed
|
||||
.namespace
|
||||
.as_deref()
|
||||
.and_then(|namespace| v8_string(scope, namespace).map(Into::into))
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
match call_global_bridge_method(
|
||||
match create_detached_element_for_document(
|
||||
scope,
|
||||
"__detachedCreateElement",
|
||||
&[
|
||||
args.this().into(),
|
||||
qualified_name.into(),
|
||||
namespace,
|
||||
v8str(scope, "xml").into(),
|
||||
v8str(scope, "qualified").into(),
|
||||
args.get(2),
|
||||
],
|
||||
args.this(),
|
||||
&parsed.qualified_name,
|
||||
parsed.namespace,
|
||||
"xml",
|
||||
true,
|
||||
parsed.options,
|
||||
) {
|
||||
Some(value) => rv.set(value),
|
||||
Some(value) => rv.set(value.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@ pub(in crate::native_bridge) use self::clone_adopt::{
|
||||
bridge_adopt_node_into_document_callback, bridge_clone_node_into_document_callback,
|
||||
bridge_detached_clone_node_callback,
|
||||
};
|
||||
pub(in crate::native_bridge::document) use self::document_methods::create_detached_element_for_document;
|
||||
pub(in crate::native_bridge) use self::document_methods::{
|
||||
bridge_create_cdata_section_not_supported_callback,
|
||||
bridge_detached_create_cdata_section_callback, bridge_detached_create_comment_callback,
|
||||
bridge_detached_create_document_fragment_callback, bridge_detached_create_element_callback,
|
||||
bridge_detached_create_document_fragment_callback,
|
||||
bridge_detached_create_processing_instruction_callback, bridge_detached_create_text_callback,
|
||||
};
|
||||
pub(in crate::native_bridge) use self::dom_implementation::{
|
||||
|
||||
+30
-64
@@ -1,6 +1,5 @@
|
||||
use super::super::*;
|
||||
use crate::custom_elements;
|
||||
use crate::native_bridge::document::validate_registry_association_for_document;
|
||||
use crate::native_bridge::document::CreateElementOptions;
|
||||
use crate::webidl;
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
@@ -40,19 +39,6 @@ struct DetachedDocumentCreateCdataSectionArgs {
|
||||
data: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Document.createElement")]
|
||||
struct DetachedDocumentCreateElementBridgeArgs {
|
||||
#[webidl(index = 1, required)]
|
||||
qualified_name: String,
|
||||
#[webidl(index = 2, required, nullable)]
|
||||
namespace_uri: Option<String>,
|
||||
#[webidl(index = 3, required)]
|
||||
document_kind: String,
|
||||
#[webidl(index = 4)]
|
||||
namespace_validation: Option<String>,
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn bridge_detached_create_text_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -193,29 +179,20 @@ pub(in crate::native_bridge) fn bridge_create_cdata_section_not_supported_callba
|
||||
);
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn bridge_detached_create_element_callback<'s>(
|
||||
pub(in crate::native_bridge::document) fn create_detached_element_for_document<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Ok(document) = v8::Local::<v8::Object>::try_from(args.get(0)) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<DetachedDocumentCreateElementBridgeArgs>(scope, &args)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let mut namespace_uri = normalize_namespace(parsed.namespace_uri);
|
||||
let document_kind = parsed.document_kind;
|
||||
let validation = parsed.namespace_validation.as_deref();
|
||||
let validation_error = if validation == Some("qualified") {
|
||||
validate_qualified_element_name_and_namespace(
|
||||
namespace_uri.as_deref(),
|
||||
&parsed.qualified_name,
|
||||
)
|
||||
.err()
|
||||
} else if !validate_element_name(&parsed.qualified_name) {
|
||||
document: v8::Local<'s, v8::Object>,
|
||||
qualified_name: &str,
|
||||
namespace_uri: Option<String>,
|
||||
document_kind: &str,
|
||||
split_qualified_name: bool,
|
||||
options: CreateElementOptions,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let mut namespace_uri = normalize_namespace(namespace_uri);
|
||||
let validation_error = if split_qualified_name {
|
||||
validate_qualified_element_name_and_namespace(namespace_uri.as_deref(), qualified_name)
|
||||
.err()
|
||||
} else if !validate_element_name(qualified_name) {
|
||||
Some((
|
||||
"InvalidCharacterError",
|
||||
5,
|
||||
@@ -226,42 +203,31 @@ pub(in crate::native_bridge) fn bridge_detached_create_element_callback<'s>(
|
||||
};
|
||||
if let Some((name, code, message)) = validation_error {
|
||||
throw_dom_exception(scope, name, code, message);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
let (runtime_ptr, document_handle) =
|
||||
node_runtime_and_handle_from_object_or_detached(scope, document).ok()?;
|
||||
let options = options.flatten(scope, runtime_ptr, document_handle)?;
|
||||
// Conversion and DOM exceptions use the binding's realm. Allocation uses
|
||||
// the Document's realm so native ownership does not depend on the caller.
|
||||
let context = document.get_creation_context(scope)?;
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
if namespace_uri.is_none()
|
||||
&& validation != Some("qualified")
|
||||
&& !split_qualified_name
|
||||
&& document_kind == "xml"
|
||||
&& detached_document_content_type_value(scope, document).as_deref()
|
||||
== Some("application/xhtml+xml")
|
||||
{
|
||||
namespace_uri = Some(XHTML_NS.to_owned());
|
||||
}
|
||||
let registry_association =
|
||||
custom_elements::registry_association_from_create_options_value(scope, args.get(5));
|
||||
let is_name = custom_elements::is_name_from_create_options_value(scope, args.get(5));
|
||||
if let Some(registry_association) = registry_association
|
||||
&& let Some(runtime_ptr) = crate::util::context_host_ptr_from_global_bridge(scope)
|
||||
&& let Some(document_handle) = detached_native_handle(scope, document)
|
||||
&& !validate_registry_association_for_document(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
document_handle,
|
||||
Some(registry_association),
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match build_detached_element_object(
|
||||
build_detached_element_object(
|
||||
scope,
|
||||
document,
|
||||
&parsed.qualified_name,
|
||||
qualified_name,
|
||||
namespace_uri,
|
||||
&document_kind,
|
||||
validation == Some("qualified"),
|
||||
is_name.as_deref(),
|
||||
registry_association,
|
||||
) {
|
||||
Some(element) => rv.set(element.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
document_kind,
|
||||
split_qualified_name,
|
||||
options.is_name.as_deref(),
|
||||
options.registry_association,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
(async () => {
|
||||
const checks = [];
|
||||
const observe = action => {try {return action();} catch (error) {return {exception: error.name, message: String(error)};}};
|
||||
const check = (name, actual, expected) => checks.push({name, actual, expected, passed: JSON.stringify(actual) === JSON.stringify(expected)});
|
||||
const capture = (action, sentinel, callee) => {
|
||||
try {action(); return null;} catch (error) {
|
||||
if (error === sentinel) return 'sentinel';
|
||||
if (callee && error.name === 'TypeError' && Object.getPrototypeOf(error) !== callee.TypeError.prototype) return 'wrong-realm-TypeError';
|
||||
return error.name;
|
||||
}
|
||||
};
|
||||
const htmlNS = 'http://www.w3.org/1999/xhtml';
|
||||
const frame = document.createElement('iframe'); document.body.appendChild(frame);
|
||||
let popup;
|
||||
try {
|
||||
const realms = [['main', window], ['iframe', frame.contentWindow]];
|
||||
if (globalThis.__elementOptionsIncludePopup !== false) {
|
||||
popup = window.open('', '_blank');
|
||||
if (!popup) throw new Error('popup unavailable');
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
realms.push(['popup', popup]);
|
||||
}
|
||||
for (const [ownerName, owner] of realms) {
|
||||
const docs = [
|
||||
['live', owner.document],
|
||||
['html', owner.document.implementation.createHTMLDocument('')],
|
||||
['xml', owner.document.implementation.createDocument(null, 'root')],
|
||||
['parsed-html', new owner.DOMParser().parseFromString('<!doctype html><body>', 'text/html')],
|
||||
['parsed-xml', new owner.DOMParser().parseFromString('<root/>', 'application/xml')],
|
||||
['new-document', new owner.Document()],
|
||||
];
|
||||
const scoped = new owner.CustomElementRegistry();
|
||||
for (const [docName, doc] of docs) {
|
||||
for (const [calleeName, callee] of realms) {
|
||||
for (const ns of [false, true]) {
|
||||
const prefix = `${ownerName}/${docName}/${calleeName}/${ns ? 'createElementNS' : 'createElement'}`;
|
||||
const method = callee.Document.prototype[ns ? 'createElementNS' : 'createElement'];
|
||||
const create = (tag, options, namespace = htmlNS) => ns ? method.call(doc, namespace, tag, options) : method.call(doc, tag, options);
|
||||
for (const valid of [false, true]) {
|
||||
for (const stop of [-1, 0, 1, 2, 3, 4]) {
|
||||
const log = [], sentinel = {};
|
||||
const argument = (text, label, position) => ({toString() {log.push(label); if (stop === position) throw sentinel; return text;}});
|
||||
const options = {
|
||||
get customElementRegistry() {log.push('registry'); if (stop === 2) throw sentinel; return undefined;},
|
||||
get is() {log.push('is'); if (stop === 3) throw sentinel; return argument('x-order', 'is-string', 4);},
|
||||
};
|
||||
const tag = argument(valid ? 'div' : 'a b', 'name', 1);
|
||||
const namespace = argument(htmlNS, 'namespace', 0);
|
||||
const labels = ns ? ['namespace', 'name', 'registry', 'is', 'is-string'] : ['name', 'registry', 'is', 'is-string'];
|
||||
const stopLabel = ['namespace', 'name', 'registry', 'is', 'is-string'][stop];
|
||||
const stopIndex = labels.indexOf(stopLabel);
|
||||
const expectedLog = stopIndex < 0 ? labels : labels.slice(0, stopIndex + 1);
|
||||
check(prefix + `/order-${valid}-${stop}`, [capture(() => create(tag, options, namespace), sentinel, callee), log], [stopIndex < 0 ? (valid ? null : 'InvalidCharacterError') : 'sentinel', expectedLog]);
|
||||
}
|
||||
}
|
||||
const plain = observe(() => create('button', undefined).outerHTML);
|
||||
for (const value of [undefined, null, 'x-ignored', 0, 1, false, true, 1n]) {
|
||||
check(prefix + `/legacy-${typeof value}-${String(value)}`, observe(() => create('button', value).outerHTML), plain);
|
||||
}
|
||||
for (const valid of [false, true]) check(prefix + `/symbol-${valid}`, capture(() => create(valid ? 'div' : 'a b', Symbol()), undefined, callee), 'TypeError');
|
||||
for (const value of [{}, {is: undefined}, {customElementRegistry: undefined, is: undefined}]) {
|
||||
check(prefix + `/missing-is-${checks.length}`, observe(() => [create('button', value).outerHTML, create('button', value).getAttribute('is')]), [plain, null]);
|
||||
}
|
||||
for (const value of [null, '', 'x-built-in', 1, false]) {
|
||||
const log = [], options = {get customElementRegistry() {log.push('registry'); return undefined;}, get is() {log.push('is'); return value;}};
|
||||
check(prefix + `/is-${String(value)}`, observe(() => [create('button', options).getAttribute('is'), log]), [null, ['registry', 'is']]);
|
||||
}
|
||||
for (const make of [() => new owner.String('x-ignored'), () => function options() {}, () => Object.create({is: 'x-inherited'})]) {
|
||||
const options = make(), log = [];
|
||||
Object.defineProperty(options, 'customElementRegistry', {get() {log.push('registry'); return undefined;}});
|
||||
Object.defineProperty(options, 'is', {get() {log.push('is'); return 'x-boxed';}});
|
||||
Object.defineProperty(options, 'toString', {value() {throw new Error('dictionary toString must not run');}});
|
||||
check(prefix + `/dictionary-object-${checks.length}`, [capture(() => create('button', options), undefined, callee), log], [null, ['registry', 'is']]);
|
||||
}
|
||||
for (const registry of [null, scoped]) {
|
||||
for (const value of [null, '', 'x-built-in']) {
|
||||
for (const valid of [false, true]) {
|
||||
const log = [], options = {get customElementRegistry() {log.push('registry'); return registry;}, get is() {log.push('is'); return value;}};
|
||||
check(prefix + `/registry-is-${checks.length}`, [capture(() => create(valid ? 'div' : 'a b', options), undefined, callee), log], [valid ? 'NotSupportedError' : 'InvalidCharacterError', ['registry', 'is']]);
|
||||
}
|
||||
}
|
||||
check(prefix + `/registry-identity-${checks.length}`, observe(() => create('div', {customElementRegistry: registry, is: undefined}).customElementRegistry === registry), true);
|
||||
}
|
||||
const revoked = owner.Proxy.revocable(scoped, {}); revoked.revoke();
|
||||
let traps = 0;
|
||||
const invalidRegistries = [{}, Object.create(scoped), Object.create(owner.CustomElementRegistry.prototype), new owner.Proxy(scoped, {get() {traps++; throw 42;}, getPrototypeOf() {traps++; throw 42;}}), revoked.proxy, false, 1, 'registry', Symbol()];
|
||||
for (const registry of invalidRegistries) {
|
||||
const log = [], options = {get customElementRegistry() {log.push('registry'); return registry;}, get is() {log.push('unexpected'); return 'x-built-in';}};
|
||||
check(prefix + `/invalid-registry-${checks.length}`, [capture(() => create('a b', options), undefined, callee), log, traps], ['TypeError', ['registry'], 0]);
|
||||
}
|
||||
const optionsProxy = new owner.Proxy({}, {get(target, key) {return key === 'customElementRegistry' ? undefined : key === 'is' ? 'x-proxy-options' : Reflect.get(target, key);}});
|
||||
check(prefix + '/options-author-proxy', capture(() => create('div', optionsProxy), undefined, callee), null);
|
||||
const revokedOptions = owner.Proxy.revocable({}, {}); revokedOptions.revoke();
|
||||
check(prefix + '/options-revoked-proxy', capture(() => create('a b', revokedOptions.proxy), undefined, callee), 'TypeError');
|
||||
for (let count = 0; count < (ns ? 2 : 1); count++) {
|
||||
let conversions = 0;
|
||||
const args = Array.from({length: count}, () => ({toString() {conversions++; throw 42;}}));
|
||||
check(prefix + `/arity-${count}`, [capture(() => method.call(doc, ...args), undefined, callee), conversions], ['TypeError', 0]);
|
||||
}
|
||||
const receivers = [{}, Object.create(doc), new owner.Proxy(doc, {})];
|
||||
const revokedDocument = owner.Proxy.revocable(doc, {}); revokedDocument.revoke(); receivers.push(revokedDocument.proxy);
|
||||
for (const receiver of receivers) {
|
||||
let conversions = 0;
|
||||
const value = {toString() {conversions++; throw 42;}};
|
||||
const args = ns ? [value, value, value] : [value, value];
|
||||
check(prefix + `/receiver-${checks.length}`, [capture(() => method.call(receiver, ...args), undefined, callee), conversions], ['TypeError', 0]);
|
||||
}
|
||||
if (ns) {
|
||||
const log = [], sentinel = {};
|
||||
check(prefix + '/namespace-before-flatten', [capture(() => create('p:div', {customElementRegistry: null, is: 'x-built-in'}, null), undefined, callee)], ['NamespaceError']);
|
||||
const options = {get customElementRegistry() {log.push('registry'); return undefined;}, get is() {log.push('is'); throw sentinel;}};
|
||||
check(prefix + '/options-before-namespace', [capture(() => create('p:div', options, null), sentinel, callee), log], ['sentinel', ['registry', 'is']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [name, w] of realms) {
|
||||
for (const ns of [false, true]) {
|
||||
const label = `${name}/definition-reentrancy/${ns}`;
|
||||
const registry = new w.CustomElementRegistry();
|
||||
let constructed = 0;
|
||||
class Created extends w.HTMLElement {constructor() {super(); constructed++;}}
|
||||
const options = {customElementRegistry: registry, get is() {registry.define('x-options-' + ns, Created); return undefined;}};
|
||||
check(label, observe(() => {
|
||||
const element = ns ? w.document.createElementNS(htmlNS, 'x-options-' + ns, options) : w.document.createElement('x-options-' + ns, options);
|
||||
return [element instanceof Created, element.ownerDocument === w.document, element.customElementRegistry === registry, constructed];
|
||||
}), [true, true, true, 1]);
|
||||
const sentinel = {};
|
||||
const badOptions = {customElementRegistry: registry, get is() {throw sentinel;}};
|
||||
check(label + '/no-construction-after-throw', [capture(() => ns ? w.document.createElementNS(htmlNS, 'x-options-' + ns, badOptions) : w.document.createElement('x-options-' + ns, badOptions), sentinel), constructed], ['sentinel', 1]);
|
||||
}
|
||||
}
|
||||
// Genuine cross-realm registries within one Page share the native host.
|
||||
const foreign = new frame.contentWindow.CustomElementRegistry();
|
||||
for (const ns of [false, true]) {
|
||||
check(`cross-realm-registry/${ns}`, observe(() => {
|
||||
const element = ns ? document.createElementNS(null, 'foreign', {customElementRegistry: foreign}) : document.createElement('foreign', {customElementRegistry: foreign});
|
||||
return element.customElementRegistry === foreign;
|
||||
}), true);
|
||||
}
|
||||
} catch (error) {checks.push({name: 'uncaught', actual: String(error), expected: 'no exception', passed: false});}
|
||||
finally { frame.remove(); if (popup) popup.close(); }
|
||||
globalThis.__uiEventResults = {complete: true, includePopup: Boolean(popup), total: checks.length, passed: checks.filter(c => c.passed).length, checks};
|
||||
return checks.every(c => c.passed);
|
||||
})()
|
||||
@@ -520,3 +520,32 @@ async fn dom_implementation_derives_receiver_and_argument_conversion_in_each_doc
|
||||
assert_eq!(vm.eval("__uiEventResults.includePopup").unwrap(), "false");
|
||||
assert_eq!(vm.eval("__uiEventResults.total").unwrap(), "4344");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn element_creation_options_convert_before_dom_validation_in_each_document_realm() {
|
||||
let loader = static_http_loader(std::iter::empty::<String>());
|
||||
let mut vm = new_page_task_executor_test_vm_with_loader(
|
||||
"https://element-creation-options.test/",
|
||||
&loader,
|
||||
);
|
||||
// Real popup Pages run the same fixture over CDP.
|
||||
vm.exec("globalThis.__elementOptionsIncludePopup = false;", None)
|
||||
.unwrap();
|
||||
vm.exec(include_str!("element_creation_options.js"), None)
|
||||
.unwrap();
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(globalThis.__uiEventResults?.complete === true)",
|
||||
"true",
|
||||
"element creation options conversion",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(__uiEventResults.checks.filter(row => !row.passed))")
|
||||
.unwrap(),
|
||||
"[]",
|
||||
);
|
||||
assert_eq!(vm.eval("__uiEventResults.includePopup").unwrap(), "false");
|
||||
assert_eq!(vm.eval("__uiEventResults.total").unwrap(), "3106");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user