Set is value when constructing custom elements with the new operator (#35930)

* Add doc comments throughout the code

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Set is-value for elements constructed with the "new" operator

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Allow custom elements to extend <slot>

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Update WPT expectations

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-03-12 18:53:27 +01:00 committed by GitHub
parent bb3d28bca7
commit b10c53ba6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 71 additions and 373 deletions

View file

@ -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
/// <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::<Window>().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::<JSObject>());
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,

View file

@ -119,7 +119,7 @@ fn create_svg_element(
}
}
// https://dom.spec.whatwg.org/#concept-create-element
/// <https://dom.spec.whatwg.org/#concept-create-element>
#[allow(unsafe_code)]
#[allow(clippy::too_many_arguments)]
fn create_html_element(

View file

@ -664,25 +664,34 @@ pub(crate) enum ConstructionStackEntry {
/// <https://html.spec.whatwg.org/multipage/#custom-element-definition>
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub(crate) struct CustomElementDefinition {
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-name>
#[no_trace]
pub(crate) name: LocalName,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-local-name>
#[no_trace]
pub(crate) local_name: LocalName,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-constructor>
#[ignore_malloc_size_of = "Rc"]
pub(crate) constructor: Rc<CustomElementConstructor>,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-observed-attributes>
pub(crate) observed_attributes: Vec<DOMString>,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-lifecycle-callbacks>
pub(crate) callbacks: LifecycleCallbacks,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-construction-stack>
pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-form-associated>
pub(crate) form_associated: bool,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-internals>
pub(crate) disable_internals: bool,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-shadow>
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(
/// <https://html.spec.whatwg.org/multipage/#concept-try-upgrade>
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<CustomElementDefinition>,
) {
// 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" ||

View file

@ -174,6 +174,7 @@ pub struct Element {
attrs: DomRefCell<Vec<Dom<Attr>>>,
#[no_trace]
id_attribute: DomRefCell<Option<Atom>>,
/// <https://dom.spec.whatwg.org/#concept-element-is-value>
#[no_trace]
is: DomRefCell<Option<LocalName>>,
#[ignore_malloc_size_of = "Arc"]
@ -344,6 +345,7 @@ impl Element {
*self.is.borrow_mut() = Some(is);
}
/// <https://dom.spec.whatwg.org/#concept-element-is-value>
pub(crate) fn get_is(&self) -> Option<LocalName> {
self.is.borrow().clone()
}

View file

@ -3261,20 +3261,24 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
}
/// <https://dom.spec.whatwg.org/#dom-node-clonenode>
fn CloneNode(&self, deep: bool, can_gc: CanGc) -> Fallible<DomRoot<Node>> {
fn CloneNode(&self, subtree: bool, can_gc: CanGc) -> Fallible<DomRoot<Node>> {
// Step 1. If this is a shadow root, then throw a "NotSupportedError" DOMException.
if self.is::<ShadowRoot>() {
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)
}
/// <https://dom.spec.whatwg.org/#dom-node-isequalnode>

View file

@ -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

View file

@ -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