From b10c53ba6b71b18ad847377a85e1d8520e5c2ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=BClker?= Date: Wed, 12 Mar 2025 18:53:27 +0100 Subject: [PATCH] Set `is` value when constructing custom elements with the `new` operator (#35930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add doc comments throughout the code Signed-off-by: Simon Wülker * Set is-value for elements constructed with the "new" operator Signed-off-by: Simon Wülker * Allow custom elements to extend Signed-off-by: Simon Wülker * Update WPT expectations Signed-off-by: Simon Wülker --------- Signed-off-by: Simon Wülker --- components/script/dom/bindings/constructor.rs | 52 ++- components/script/dom/create.rs | 2 +- .../script/dom/customelementregistry.rs | 37 +- components/script/dom/element.rs | 2 + components/script/dom/node.rs | 12 +- .../custom-elements/builtin-coverage.html.ini | 333 ------------------ .../HTMLSlotElement.html.ini | 6 - 7 files changed, 71 insertions(+), 373 deletions(-) delete mode 100644 tests/wpt/meta/custom-elements/builtin-coverage.html.ini delete mode 100644 tests/wpt/meta/custom-elements/reactions/customized-builtins/HTMLSlotElement.html.ini diff --git a/components/script/dom/bindings/constructor.rs b/components/script/dom/bindings/constructor.rs index a5874ed4397..ae9305f0767 100644 --- a/components/script/dom/bindings/constructor.rs +++ b/components/script/dom/bindings/constructor.rs @@ -31,12 +31,12 @@ use crate::dom::bindings::codegen::Bindings::{ HTMLOptionElementBinding, HTMLOutputElementBinding, HTMLParagraphElementBinding, HTMLParamElementBinding, HTMLPictureElementBinding, HTMLPreElementBinding, HTMLProgressElementBinding, HTMLQuoteElementBinding, HTMLScriptElementBinding, - HTMLSelectElementBinding, HTMLSourceElementBinding, HTMLSpanElementBinding, - HTMLStyleElementBinding, HTMLTableCaptionElementBinding, HTMLTableCellElementBinding, - HTMLTableColElementBinding, HTMLTableElementBinding, HTMLTableRowElementBinding, - HTMLTableSectionElementBinding, HTMLTemplateElementBinding, HTMLTextAreaElementBinding, - HTMLTimeElementBinding, HTMLTitleElementBinding, HTMLTrackElementBinding, - HTMLUListElementBinding, HTMLVideoElementBinding, + HTMLSelectElementBinding, HTMLSlotElementBinding, HTMLSourceElementBinding, + HTMLSpanElementBinding, HTMLStyleElementBinding, HTMLTableCaptionElementBinding, + HTMLTableCellElementBinding, HTMLTableColElementBinding, HTMLTableElementBinding, + HTMLTableRowElementBinding, HTMLTableSectionElementBinding, HTMLTemplateElementBinding, + HTMLTextAreaElementBinding, HTMLTimeElementBinding, HTMLTitleElementBinding, + HTMLTrackElementBinding, HTMLUListElementBinding, HTMLVideoElementBinding, }; use crate::dom::bindings::codegen::PrototypeList; use crate::dom::bindings::conversions::DerivedFrom; @@ -54,7 +54,7 @@ use crate::dom::window::Window; use crate::script_runtime::{CanGc, JSContext, JSContext as SafeJSContext}; use crate::script_thread::ScriptThread; -// https://html.spec.whatwg.org/multipage/#htmlconstructor +/// unsafe fn html_constructor( cx: JSContext, global: &GlobalScope, @@ -67,7 +67,7 @@ unsafe fn html_constructor( let window = global.downcast::().unwrap(); let document = window.Document(); - // Step 1 + // Step 1. Let registry be current global object's custom element registry. let registry = window.CustomElements(); // Step 2 https://html.spec.whatwg.org/multipage/#htmlconstructor @@ -95,7 +95,8 @@ unsafe fn html_constructor( return Err(()); } - // Step 3 + // Step 3. Let definition be the item in registry's custom element definition set with constructor + // equal to NewTarget. If there is no such item, then throw a TypeError. rooted!(in(*cx) let new_target = call_args.new_target().to_object()); let definition = match registry.lookup_definition_by_constructor(new_target.handle()) { Some(definition) => definition, @@ -110,6 +111,9 @@ unsafe fn html_constructor( }, }; + // Step 4. Let isValue be null. + let mut is_value = None; + rooted!(in(*cx) let callee = UnwrapObjectStatic(call_args.callee())); if callee.is_null() { throw_dom_exception(cx, global, Error::Security, can_gc); @@ -121,24 +125,28 @@ unsafe fn html_constructor( rooted!(in(*cx) let mut constructor = ptr::null_mut::()); rooted!(in(*cx) let global_object = CurrentGlobalOrNull(*cx)); + // Step 5. If definition's local name is equal to definition's name + // (i.e., definition is for an autonomous custom element): if definition.is_autonomous() { - // Step 4 // Since this element is autonomous, its active function object must be the HTMLElement - // Retrieve the constructor object for HTMLElement HTMLElementBinding::GetConstructorObject( cx, global_object.handle(), constructor.handle_mut(), ); - } else { - // Step 5 + } + // Step 6. Otherwise (i.e., if definition is for a customized built-in element): + else { get_constructor_object_from_local_name( definition.local_name.clone(), cx, global_object.handle(), constructor.handle_mut(), ); + + // Step 6.3 Set isValue to definition's name. + is_value = Some(definition.name.clone()); } // Callee must be the same as the element interface's constructor object. if constructor.get() != callee.get() { @@ -158,9 +166,9 @@ unsafe fn html_constructor( let entry = definition.construction_stack.borrow().last().cloned(); let result = match entry { - // Step 8 + // Step 7. If definition's construction stack is empty: None => { - // Step 8.1 + // Step 7.1 let name = QualName::new(None, ns!(html), definition.local_name.clone()); // Any prototype used to create these elements will be overwritten before returning // from this function, so we don't bother overwriting the defaults here. @@ -176,19 +184,24 @@ unsafe fn html_constructor( ) }; - // Step 8.2 is performed in the generated caller code. + // Step 7.2-7.5 are performed in the generated caller code. - // Step 8.3 + // Step 7.6 Set element's custom element state to "custom". element.set_custom_element_state(CustomElementState::Custom); - // Step 8.4 + // Step 7.7 Set element's custom element definition to definition. element.set_custom_element_definition(definition.clone()); - // Step 8.5 + // Step 7.8 Set element's is value to isValue. + if let Some(is_value) = is_value { + element.set_is(is_value); + } + if !check_type(&element) { throw_dom_exception(cx, global, Error::InvalidState, can_gc); return Err(()); } else { + // Step 7.9 Return element. element } }, @@ -339,6 +352,7 @@ fn get_constructor_object_from_local_name( local_name!("script") => HTMLScriptElementBinding::GetConstructorObject, local_name!("section") => HTMLElementBinding::GetConstructorObject, local_name!("select") => HTMLSelectElementBinding::GetConstructorObject, + local_name!("slot") => HTMLSlotElementBinding::GetConstructorObject, local_name!("small") => HTMLElementBinding::GetConstructorObject, local_name!("source") => HTMLSourceElementBinding::GetConstructorObject, local_name!("span") => HTMLSpanElementBinding::GetConstructorObject, diff --git a/components/script/dom/create.rs b/components/script/dom/create.rs index 14774549b27..c2e038eb66b 100644 --- a/components/script/dom/create.rs +++ b/components/script/dom/create.rs @@ -119,7 +119,7 @@ fn create_svg_element( } } -// https://dom.spec.whatwg.org/#concept-create-element +/// #[allow(unsafe_code)] #[allow(clippy::too_many_arguments)] fn create_html_element( diff --git a/components/script/dom/customelementregistry.rs b/components/script/dom/customelementregistry.rs index 5e570743325..a9e787068f1 100644 --- a/components/script/dom/customelementregistry.rs +++ b/components/script/dom/customelementregistry.rs @@ -664,25 +664,34 @@ pub(crate) enum ConstructionStackEntry { /// #[derive(Clone, JSTraceable, MallocSizeOf)] pub(crate) struct CustomElementDefinition { + /// #[no_trace] pub(crate) name: LocalName, + /// #[no_trace] pub(crate) local_name: LocalName, + /// #[ignore_malloc_size_of = "Rc"] pub(crate) constructor: Rc, + /// pub(crate) observed_attributes: Vec, + /// pub(crate) callbacks: LifecycleCallbacks, + /// pub(crate) construction_stack: DomRefCell>, + /// pub(crate) form_associated: bool, + /// pub(crate) disable_internals: bool, + /// pub(crate) disable_shadow: bool, } @@ -797,19 +806,21 @@ pub(crate) fn upgrade_element( element: &Element, can_gc: CanGc, ) { - // Step 1 + // Step 1. If element's custom element state is not "undefined" or "uncustomized", then return. let state = element.get_custom_element_state(); if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized { return; } - // Step 2 + // Step 2. Set element's custom element definition to definition. element.set_custom_element_definition(Rc::clone(&definition)); - // Step 3 + // Step 3. Set element's custom element state to "failed". element.set_custom_element_state(CustomElementState::Failed); - // Step 4 + // Step 4. For each attribute in element's attribute list, in order, enqueue a custom element callback reaction + // with element, callback name "attributeChangedCallback", and « attribute's local name, null, attribute's value, + // attribute's namespace ». for attr in element.attrs().iter() { let local_name = attr.local_name().clone(); let value = DOMString::from(&**attr.value()); @@ -821,7 +832,8 @@ pub(crate) fn upgrade_element( ); } - // Step 5 + // Step 5. If element is connected, then enqueue a custom element callback reaction with element, + // callback name "connectedCallback", and « ». if element.is_connected() { ScriptThread::enqueue_callback_reaction( element, @@ -830,7 +842,7 @@ pub(crate) fn upgrade_element( ); } - // Step 6 + // Step 6. Add element to the end of definition's construction stack. definition .construction_stack .borrow_mut() @@ -976,7 +988,8 @@ fn run_upgrade_constructor( /// pub(crate) fn try_upgrade_element(element: &Element) { - // Step 1 + // Step 1. Let definition be the result of looking up a custom element definition given element's node document, + // element's namespace, element's local name, and element's is value. let document = element.owner_document(); let namespace = element.namespace(); let local_name = element.local_name(); @@ -984,7 +997,8 @@ pub(crate) fn try_upgrade_element(element: &Element) { if let Some(definition) = document.lookup_custom_element_definition(namespace, local_name, is.as_ref()) { - // Step 2 + // Step 2. If definition is not null, then enqueue a custom element upgrade reaction given + // element and definition. ScriptThread::enqueue_upgrade_reaction(element, definition); } } @@ -1242,9 +1256,11 @@ impl CustomElementReactionStack { element: &Element, definition: Rc, ) { - // Step 1 + // Step 1. Add a new upgrade reaction to element's custom element reaction queue, + // with custom element definition definition. element.push_upgrade_reaction(definition); - // Step 2 + + // Step 2. Enqueue an element on the appropriate element queue given element. self.enqueue_element(element); } } @@ -1449,6 +1465,7 @@ fn is_extendable_element_interface(element: &str) -> bool { element == "script" || element == "section" || element == "select" || + element == "slot" || element == "small" || element == "source" || element == "span" || diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 79b783cbfb4..35ccd0d89e2 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -174,6 +174,7 @@ pub struct Element { attrs: DomRefCell>>, #[no_trace] id_attribute: DomRefCell>, + /// #[no_trace] is: DomRefCell>, #[ignore_malloc_size_of = "Arc"] @@ -344,6 +345,7 @@ impl Element { *self.is.borrow_mut() = Some(is); } + /// pub(crate) fn get_is(&self) -> Option { self.is.borrow().clone() } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 4a4c9f53462..2837e25c08a 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -3261,20 +3261,24 @@ impl NodeMethods for Node { } /// - fn CloneNode(&self, deep: bool, can_gc: CanGc) -> Fallible> { + fn CloneNode(&self, subtree: bool, can_gc: CanGc) -> Fallible> { + // Step 1. If this is a shadow root, then throw a "NotSupportedError" DOMException. if self.is::() { return Err(Error::NotSupported); } - Ok(Node::clone( + + // Step 2. Return the result of cloning a node given this with subtree set to subtree. + let result = Node::clone( self, None, - if deep { + if subtree { CloneChildrenFlag::CloneChildren } else { CloneChildrenFlag::DoNotCloneChildren }, can_gc, - )) + ); + Ok(result) } /// diff --git a/tests/wpt/meta/custom-elements/builtin-coverage.html.ini b/tests/wpt/meta/custom-elements/builtin-coverage.html.ini deleted file mode 100644 index a10dd340315..00000000000 --- a/tests/wpt/meta/custom-elements/builtin-coverage.html.ini +++ /dev/null @@ -1,333 +0,0 @@ -[builtin-coverage.html] - [a: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [abbr: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [address: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [area: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [article: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [aside: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [audio: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [b: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [base: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [bdi: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [bdo: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [blockquote: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [body: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [br: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [button: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [canvas: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [caption: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [cite: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [code: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [col: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [colgroup: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [data: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [dd: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [del: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [details: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [dfn: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [div: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [dl: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [dt: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [em: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [embed: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [fieldset: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [figcaption: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [figure: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [footer: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [form: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [h1: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [h2: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [h3: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [h4: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [h5: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [h6: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [header: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [hgroup: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [hr: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [html: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [i: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [iframe: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [img: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [input: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [ins: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [kbd: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [label: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [legend: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [li: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [link: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [main: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [map: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [mark: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [menu: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [meta: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [meter: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [nav: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [noscript: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [object: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [ol: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [optgroup: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [option: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [output: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [p: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [param: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [picture: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [pre: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [progress: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [q: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [rp: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [rt: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [ruby: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [s: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [samp: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [script: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [section: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [select: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [small: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [source: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [span: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [strong: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [style: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [sub: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [summary: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [sup: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [table: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [tbody: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [td: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [template: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [textarea: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [tfoot: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [th: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [thead: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [time: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [title: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [tr: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [track: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [u: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [ul: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [var: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [video: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [wbr: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [datalist: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [dialog: Operator 'new' should instantiate a customized built-in element] - expected: FAIL - - [slot: Define a customized built-in element] - expected: FAIL diff --git a/tests/wpt/meta/custom-elements/reactions/customized-builtins/HTMLSlotElement.html.ini b/tests/wpt/meta/custom-elements/reactions/customized-builtins/HTMLSlotElement.html.ini deleted file mode 100644 index c6223bd8ced..00000000000 --- a/tests/wpt/meta/custom-elements/reactions/customized-builtins/HTMLSlotElement.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[HTMLSlotElement.html] - [name on HTMLSlotElement must enqueue an attributeChanged reaction when adding name content attribute] - expected: FAIL - - [name on HTMLSlotElement must enqueue an attributeChanged reaction when replacing an existing attribute] - expected: FAIL