script: Annotate steps for custom element creation. (#35354)

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-02-07 02:53:06 -05:00 committed by GitHub
parent 2ef12cf40f
commit 6393a6c750
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 43 deletions

View file

@ -134,46 +134,70 @@ fn create_html_element(
) -> DomRoot<Element> { ) -> DomRoot<Element> {
assert_eq!(name.ns, ns!(html)); assert_eq!(name.ns, ns!(html));
// Step 4 // Step 2. Let definition be the result of looking up a custom element
// definition given document, namespace, localName, and is.
let definition = document.lookup_custom_element_definition(&name.ns, &name.local, is.as_ref()); let definition = document.lookup_custom_element_definition(&name.ns, &name.local, is.as_ref());
// Step 3. If definition is non-null...
if let Some(definition) = definition { if let Some(definition) = definition {
if definition.is_autonomous() { // ...and definitions name is not equal to its local name
// (i.e., definition represents a customized built-in element):
if !definition.is_autonomous() {
// Step 3.1. Let interface be the element interface for localName and the HTML namespace.
// Step 3.2. Set result to a new element that implements interface, with no attributes,
// namespace set to the HTML namespace, namespace prefix set to prefix,
// local name set to localName, custom element state set to "undefined",
// custom element definition set to null, is value set to is,
// and node document set to document.
let element = create_native_html_element(name, prefix, document, creator, proto);
element.set_is(definition.name.clone());
element.set_custom_element_state(CustomElementState::Undefined);
match mode { match mode {
CustomElementCreationMode::Asynchronous => { // Step 3.3. If synchronousCustomElements is true, then run this step while catching any exceptions:
let result = DomRoot::upcast::<Element>(HTMLElement::new( CustomElementCreationMode::Synchronous => {
name.local.clone(), // Step 3.3.1. Upgrade result using definition.
prefix.clone(), upgrade_element(definition, &element, can_gc);
document, // TODO: "If this step threw an exception exception:" steps.
proto,
can_gc,
));
result.set_custom_element_state(CustomElementState::Undefined);
ScriptThread::enqueue_upgrade_reaction(&result, definition);
return result;
}, },
// Step 3.4. Otherwise, enqueue a custom element upgrade reaction given result and definition.
CustomElementCreationMode::Asynchronous => {
ScriptThread::enqueue_upgrade_reaction(&element, definition)
},
}
return element;
} else {
// Step 4. Otherwise, if definition is non-null:
match mode {
// Step 4.1. If synchronousCustomElements is true, then run these
// steps while catching any exceptions:
CustomElementCreationMode::Synchronous => { CustomElementCreationMode::Synchronous => {
let local_name = name.local.clone(); let local_name = name.local.clone();
//TODO(jdm) Pass proto to create_element? //TODO(jdm) Pass proto to create_element?
// Steps 4.1.1-4.1.11
return match definition.create_element(document, prefix.clone(), can_gc) { return match definition.create_element(document, prefix.clone(), can_gc) {
Ok(element) => { Ok(element) => {
element.set_custom_element_definition(definition.clone()); element.set_custom_element_definition(definition.clone());
element element
}, },
Err(error) => { Err(error) => {
// Step 6. Recovering from exception. // If any of these steps threw an exception exception:
let global = let global =
GlobalScope::current().unwrap_or_else(|| document.global()); GlobalScope::current().unwrap_or_else(|| document.global());
let cx = GlobalScope::get_cx(); let cx = GlobalScope::get_cx();
// Step 6.1.1 // Substep 1. Report exception for definitions constructors corresponding
// JavaScript objects associated realms global object.
unsafe { unsafe {
let ar = enter_realm(&*global); let ar = enter_realm(&*global);
throw_dom_exception(cx, &global, error); throw_dom_exception(cx, &global, error);
report_pending_exception(*cx, true, InRealm::Entered(&ar), can_gc); report_pending_exception(*cx, true, InRealm::Entered(&ar), can_gc);
} }
// Step 6.1.2 // Substep 2. Set result to a new element that implements the HTMLUnknownElement interface,
// with no attributes, namespace set to the HTML namespace, namespace prefix set to prefix,
// local name set to localName, custom element state set to "failed",
// custom element definition set to null, is value set to null,
// and node document set to document.
let element = DomRoot::upcast::<Element>(HTMLUnknownElement::new( let element = DomRoot::upcast::<Element>(HTMLUnknownElement::new(
local_name, prefix, document, proto, can_gc, local_name, prefix, document, proto, can_gc,
)); ));
@ -182,28 +206,38 @@ fn create_html_element(
}, },
}; };
}, },
} // Step 4.2. Otherwise:
} else {
// Steps 5.1-5.2
let element = create_native_html_element(name, prefix, document, creator, proto);
element.set_is(definition.name.clone());
element.set_custom_element_state(CustomElementState::Undefined);
match mode {
// Step 5.3
CustomElementCreationMode::Synchronous => {
upgrade_element(definition, &element, can_gc)
},
// Step 5.4
CustomElementCreationMode::Asynchronous => { CustomElementCreationMode::Asynchronous => {
ScriptThread::enqueue_upgrade_reaction(&element, definition) // Step 4.2.1. Set result to a new element that implements the HTMLElement interface,
// with no attributes, namespace set to the HTML namespace, namespace prefix set to
// prefix, local name set to localName, custom element state set to "undefined",
// custom element definition set to null, is value set to null, and node document
// set to document.
let result = DomRoot::upcast::<Element>(HTMLElement::new(
name.local.clone(),
prefix.clone(),
document,
proto,
can_gc,
));
result.set_custom_element_state(CustomElementState::Undefined);
// Step 4.2.2. Enqueue a custom element upgrade reaction given result and definition.
ScriptThread::enqueue_upgrade_reaction(&result, definition);
return result;
}, },
} }
return element;
} }
} }
// Steps 7.1-7.3 // Step 5. Otherwise:
// Step 5.1. Let interface be the element interface for localName and namespace.
// Step 5.2. Set result to a new element that implements interface, with no attributes,
// namespace set to namespace, namespace prefix set to prefix, local name set to localName,
// custom element state set to "uncustomized", custom element definition set to null,
// is value set to is, and node document set to document.
let result = create_native_html_element(name.clone(), prefix, document, creator, proto); let result = create_native_html_element(name.clone(), prefix, document, creator, proto);
// Step 5.3. If namespace is the HTML namespace, and either localName is a valid custom element name or
// is is non-null, then set results custom element state to "undefined".
match is { match is {
Some(is) => { Some(is) => {
result.set_is(is); result.set_is(is);
@ -218,7 +252,7 @@ fn create_html_element(
}, },
}; };
// Step 8 // Step 6. Return result.
result result
} }

View file

@ -710,7 +710,7 @@ impl CustomElementDefinition {
self.name == self.local_name self.name == self.local_name
} }
/// <https://dom.spec.whatwg.org/#concept-create-element> Step 6.1 /// <https://dom.spec.whatwg.org/#concept-create-element> Step 4.1
#[allow(unsafe_code)] #[allow(unsafe_code)]
pub(crate) fn create_element( pub(crate) fn create_element(
&self, &self,
@ -720,12 +720,13 @@ impl CustomElementDefinition {
) -> Fallible<DomRoot<Element>> { ) -> Fallible<DomRoot<Element>> {
let window = document.window(); let window = document.window();
let cx = GlobalScope::get_cx(); let cx = GlobalScope::get_cx();
// Step 2 // Step 4.1.1. Let C be definitions constructor.
rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback())); rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback()));
rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>()); rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>());
{ {
// Go into the constructor's realm // Go into the constructor's realm
let _ac = JSAutoRealm::new(*cx, self.constructor.callback()); let _ac = JSAutoRealm::new(*cx, self.constructor.callback());
// Step 4.1.2. Set result to the result of constructing C, with no arguments.
let args = HandleValueArray::empty(); let args = HandleValueArray::empty();
if unsafe { !Construct1(*cx, constructor.handle(), &args, element.handle_mut()) } { if unsafe { !Construct1(*cx, constructor.handle(), &args, element.handle_mut()) } {
return Err(Error::JSFailed); return Err(Error::JSFailed);
@ -752,14 +753,18 @@ impl CustomElementDefinition {
_ => return Err(Error::JSFailed), _ => return Err(Error::JSFailed),
}; };
// Step 3 // Step 4.1.3. Assert: results custom element state and custom element definition are initialized.
if !element.is::<HTMLElement>() { // Step 4.1.4. Assert: results namespace is the HTML namespace.
return Err(Error::Type( // Note: IDL enforces that result is an HTMLElement object, which all use the HTML namespace.
"Constructor did not return a DOM node".to_owned(), // Note: the custom element definition is initialized by the caller if
)); // this method returns a success value.
} assert!(element.is::<HTMLElement>());
// Steps 4-9 // Step 4.1.5. If results attribute list is not empty, then throw a "NotSupportedError" DOMException.
// Step 4.1.6. If result has children, then throw a "NotSupportedError" DOMException.
// Step 4.1.7. If results parent is not null, then throw a "NotSupportedError" DOMException.
// Step 4.1.8. If results node document is not document, then throw a "NotSupportedError" DOMException.
// Step 4.1.9. If results local name is not equal to localName then throw a "NotSupportedError" DOMException.
if element.HasAttributes() || if element.HasAttributes() ||
element.upcast::<Node>().children_count() > 0 || element.upcast::<Node>().children_count() > 0 ||
element.upcast::<Node>().has_parent() || element.upcast::<Node>().has_parent() ||
@ -770,10 +775,10 @@ impl CustomElementDefinition {
return Err(Error::NotSupported); return Err(Error::NotSupported);
} }
// Step 10 // Step 4.1.10. Set results namespace prefix to prefix.
element.set_prefix(prefix); element.set_prefix(prefix);
// Step 11 // Step 4.1.11. Set results is value to null.
// Element's `is` is None by default // Element's `is` is None by default
Ok(element) Ok(element)