2023-03-29 23:46:18 +01:00
/*
* Copyright ( c ) 2023 , Luke Wilde < lukew @ serenityos . org >
2026-02-27 17:05:47 +00:00
* Copyright ( c ) 2025 , Sam Atkins < sam @ ladybird . org >
2023-03-29 23:46:18 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibJS/Runtime/FunctionObject.h>
2023-07-19 06:54:48 -04:00
# include <LibJS/Runtime/Iterator.h>
2023-10-06 17:54:21 +02:00
# include <LibJS/Runtime/ValueInlines.h>
2023-04-23 17:49:25 -06:00
# include <LibWeb/Bindings/CustomElementRegistryPrototype.h>
2023-03-29 23:46:18 +01:00
# include <LibWeb/DOM/Document.h>
# include <LibWeb/DOM/ElementFactory.h>
# include <LibWeb/DOM/ShadowRoot.h>
# include <LibWeb/HTML/CustomElements/CustomElementName.h>
# include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
# include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
# include <LibWeb/HTML/Scripting/Environments.h>
# include <LibWeb/HTML/Window.h>
# include <LibWeb/Namespace.h>
namespace Web : : HTML {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( CustomElementRegistry ) ;
GC_DEFINE_ALLOCATOR ( CustomElementDefinition ) ;
2023-11-19 19:47:52 +01:00
2026-02-27 17:05:47 +00:00
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry
GC : : Ref < CustomElementRegistry > CustomElementRegistry : : construct_impl ( JS : : Realm & realm )
{
// The new CustomElementRegistry() constructor steps are to set this's is scoped to true.
auto registry = realm . create < CustomElementRegistry > ( realm ) ;
registry - > m_is_scoped = true ;
return registry ;
}
2023-03-29 23:46:18 +01:00
CustomElementRegistry : : CustomElementRegistry ( JS : : Realm & realm )
: Bindings : : PlatformObject ( realm )
{
}
CustomElementRegistry : : ~ CustomElementRegistry ( ) = default ;
2023-08-07 08:41:28 +02:00
void CustomElementRegistry : : initialize ( JS : : Realm & realm )
2023-03-29 23:46:18 +01:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( CustomElementRegistry ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-03-29 23:46:18 +01:00
}
2024-03-11 19:37:21 +01:00
void CustomElementRegistry : : visit_edges ( Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2024-04-15 13:58:21 +02:00
visitor . visit ( m_custom_element_definitions ) ;
visitor . visit ( m_when_defined_promise_map ) ;
2024-03-11 19:37:21 +01:00
}
2023-03-29 23:46:18 +01:00
// https://webidl.spec.whatwg.org/#es-callback-function
2024-11-19 00:38:06 +13:00
// https://github.com/whatwg/html/pull/9893
2024-11-15 04:01:23 +13:00
static JS : : ThrowCompletionOr < GC : : Ref < WebIDL : : CallbackType > > convert_value_to_callback_function ( JS : : VM & vm , JS : : Value value )
2023-03-29 23:46:18 +01:00
{
// FIXME: De-duplicate this from the IDL generator.
// 1. If the result of calling IsCallable(V) is false and the conversion to an IDL value is not being performed due to V being assigned to an attribute whose type is a nullable callback function that is annotated with [LegacyTreatNonObjectAsNull], then throw a TypeError.
if ( ! value . is_function ( ) )
2025-12-05 08:01:44 +01:00
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : NotAFunction , value ) ;
2023-03-29 23:46:18 +01:00
2024-11-19 00:38:06 +13:00
// 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent realm as the callback context.
return vm . heap ( ) . allocate < WebIDL : : CallbackType > ( value . as_object ( ) , HTML : : incumbent_realm ( ) ) ;
2023-03-29 23:46:18 +01:00
}
// https://webidl.spec.whatwg.org/#es-sequence
static JS : : ThrowCompletionOr < Vector < String > > convert_value_to_sequence_of_strings ( JS : : VM & vm , JS : : Value value )
{
// FIXME: De-duplicate this from the IDL generator.
// An ECMAScript value V is converted to an IDL sequence<T> value as follows:
2024-10-31 11:34:22 +00:00
// 1. If V is not an Object, throw a TypeError.
2023-03-29 23:46:18 +01:00
if ( ! value . is_object ( ) )
2025-12-05 08:01:44 +01:00
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : NotAnObject , value ) ;
2023-03-29 23:46:18 +01:00
// 2. Let method be ? GetMethod(V, @@iterator).
2023-04-13 15:41:29 +02:00
auto method = TRY ( value . get_method ( vm , vm . well_known_symbol_iterator ( ) ) ) ;
2023-03-29 23:46:18 +01:00
// 3. If method is undefined, throw a TypeError.
if ( ! method )
2025-12-05 08:01:44 +01:00
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : NotIterable , value ) ;
2023-03-29 23:46:18 +01:00
// 4. Return the result of creating a sequence from V and method.
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable
// To create an IDL value of type sequence<T> given an iterable iterable and an iterator getter method, perform the following steps:
// 1. Let iter be ? GetIterator(iterable, sync, method).
2023-07-18 14:40:02 -04:00
// FIXME: The WebIDL spec is out of date - it should be using GetIteratorFromMethod.
auto iterator = TRY ( JS : : get_iterator_from_method ( vm , value , * method ) ) ;
2023-03-29 23:46:18 +01:00
// 2. Initialize i to be 0.
Vector < String > sequence_of_strings ;
// 3. Repeat
for ( ; ; ) {
// 1. Let next be ? IteratorStep(iter).
2023-04-15 16:23:03 +02:00
auto next = TRY ( JS : : iterator_step ( vm , iterator ) ) ;
2023-03-29 23:46:18 +01:00
// 2. If next is false, then return an IDL sequence value of type sequence<T> of length i, where the value of the element at index j is Sj.
LibJS+LibWeb: Add fast path for builtin iterators in `iterator_step()`
We already have fast path for built-in iterators that skips `next()`
lookup and iteration result object allocation applied for `for..of` and
`for..in` loops. This change extends it to `iterator_step()` to cover
`Array.from()`, `[...arr]` and many other cases.
Makes following function go 2.35x faster on my computer:
```js
(function f() {
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let i = 0; i < 1000000; i++) {
let [a, ...rest] = arr;
}
})();
```
2025-05-12 00:31:51 +03:00
if ( ! next . has < JS : : IterationResult > ( ) )
2023-03-29 23:46:18 +01:00
return sequence_of_strings ;
// 3. Let nextItem be ? IteratorValue(next).
LibJS+LibWeb: Add fast path for builtin iterators in `iterator_step()`
We already have fast path for built-in iterators that skips `next()`
lookup and iteration result object allocation applied for `for..of` and
`for..in` loops. This change extends it to `iterator_step()` to cover
`Array.from()`, `[...arr]` and many other cases.
Makes following function go 2.35x faster on my computer:
```js
(function f() {
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let i = 0; i < 1000000; i++) {
let [a, ...rest] = arr;
}
})();
```
2025-05-12 00:31:51 +03:00
auto next_item = TRY ( next . get < JS : : IterationResult > ( ) . value ) ;
2023-03-29 23:46:18 +01:00
// 4. Initialize Si to the result of converting nextItem to an IDL value of type T.
// https://webidl.spec.whatwg.org/#es-DOMString
// An ECMAScript value V is converted to an IDL DOMString value by running the following algorithm:
// 1. If V is null and the conversion is to an IDL type associated with the [LegacyNullToEmptyString] extended attribute, then return the DOMString value that represents the empty string.
// NOTE: This doesn't apply.
// 2. Let x be ? ToString(V).
// 3. Return the IDL DOMString value that represents the same sequence of code units as the one the ECMAScript String value x represents.
auto string_value = TRY ( next_item . to_string ( vm ) ) ;
sequence_of_strings . append ( move ( string_value ) ) ;
// 5. Set i to i + 1.
}
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define
JS : : ThrowCompletionOr < void > CustomElementRegistry : : define ( String const & name , WebIDL : : CallbackType * constructor , ElementDefinitionOptions options )
{
auto & realm = this - > realm ( ) ;
auto & vm = this - > vm ( ) ;
// 1. If IsConstructor(constructor) is false, then throw a TypeError.
if ( ! JS : : Value ( constructor - > callback ) . is_constructor ( ) )
2025-12-05 08:01:44 +01:00
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : NotAConstructor , JS : : Value ( constructor - > callback ) ) ;
2023-03-29 23:46:18 +01:00
// 2. If name is not a valid custom element name, then throw a "SyntaxError" DOMException.
if ( ! is_valid_custom_element_name ( name ) )
2025-08-07 19:31:52 -04:00
return JS : : throw_completion ( WebIDL : : SyntaxError : : create ( realm , Utf16String : : formatted ( " '{}' is not a valid custom element name " , name ) ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 3. If this's custom element definition set contains an item with name name, then throw a "NotSupportedError"
// DOMException.
2024-03-11 19:37:21 +01:00
auto existing_definition_with_name_iterator = m_custom_element_definitions . find_if ( [ & name ] ( auto const & definition ) {
2023-03-29 23:46:18 +01:00
return definition - > name ( ) = = name ;
} ) ;
if ( existing_definition_with_name_iterator ! = m_custom_element_definitions . end ( ) )
2025-08-07 19:31:52 -04:00
return JS : : throw_completion ( WebIDL : : NotSupportedError : : create ( realm , Utf16String : : formatted ( " A custom element with name '{}' is already defined " , name ) ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 4. If this's custom element definition set contains an item with constructor constructor, then throw a
// "NotSupportedError" DOMException.
2024-03-11 19:37:21 +01:00
auto existing_definition_with_constructor_iterator = m_custom_element_definitions . find_if ( [ & constructor ] ( auto const & definition ) {
2023-03-29 23:46:18 +01:00
return definition - > constructor ( ) . callback = = constructor - > callback ;
} ) ;
if ( existing_definition_with_constructor_iterator ! = m_custom_element_definitions . end ( ) )
2025-08-07 19:31:52 -04:00
return JS : : throw_completion ( WebIDL : : NotSupportedError : : create ( realm , " The given constructor is already in use by another custom element " _utf16 ) ) ;
2023-03-29 23:46:18 +01:00
// 5. Let localName be name.
String local_name = name ;
2024-12-18 15:58:36 +00:00
// 6. Let extends be options["extends"] if it exists; otherwise null.
2023-03-29 23:46:18 +01:00
auto & extends = options . extends ;
2024-12-18 15:58:36 +00:00
// 7. If extends is not null:
2023-03-29 23:46:18 +01:00
if ( extends . has_value ( ) ) {
2026-02-27 17:05:47 +00:00
// 1. If this's is scoped is true, then throw a "NotSupportedError" DOMException.
if ( m_is_scoped )
return JS : : throw_completion ( WebIDL : : NotSupportedError : : create ( realm , " Cannot define a custom element that extends another in a scoped registry " _utf16 ) ) ;
// 2. If extends is a valid custom element name, then throw a "NotSupportedError" DOMException.
2023-03-29 23:46:18 +01:00
if ( is_valid_custom_element_name ( extends . value ( ) ) )
2025-08-07 19:31:52 -04:00
return JS : : throw_completion ( WebIDL : : NotSupportedError : : create ( realm , Utf16String : : formatted ( " '{}' is a custom element name, only non-custom elements can be extended " , extends . value ( ) ) ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 3. If the element interface for extends and the HTML namespace is HTMLUnknownElement (e.g., if extends does
// not indicate an element definition in this specification), then throw a "NotSupportedError" DOMException.
2023-10-01 20:07:44 +13:00
if ( DOM : : is_unknown_html_element ( extends . value ( ) ) )
2025-08-07 19:31:52 -04:00
return JS : : throw_completion ( WebIDL : : NotSupportedError : : create ( realm , Utf16String : : formatted ( " '{}' is an unknown HTML element " , extends . value ( ) ) ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 4. Set localName to extends.
2023-03-29 23:46:18 +01:00
local_name = extends . value ( ) ;
}
2024-12-18 15:58:36 +00:00
// 8. If this's element definition is running is true, then throw a "NotSupportedError" DOMException.
2023-03-29 23:46:18 +01:00
if ( m_element_definition_is_running )
2025-08-07 19:31:52 -04:00
return JS : : throw_completion ( WebIDL : : NotSupportedError : : create ( realm , " Cannot recursively define custom elements " _utf16 ) ) ;
2023-03-29 23:46:18 +01:00
2024-12-18 15:58:36 +00:00
// 9. Set this's element definition is running to true.
2023-03-29 23:46:18 +01:00
m_element_definition_is_running = true ;
// 10. Let formAssociated be false.
bool form_associated = false ;
// 11. Let disableInternals be false.
bool disable_internals = false ;
// 12. Let disableShadow be false.
bool disable_shadow = false ;
// 13. Let observedAttributes be an empty sequence<DOMString>.
Vector < String > observed_attributes ;
2024-12-18 15:58:36 +00:00
// 14. Run the following steps while catching any exceptions:
2026-02-27 17:05:47 +00:00
OrderedHashMap < FlyString , GC : : Root < WebIDL : : CallbackType > > lifecycle_callbacks ;
2023-03-29 23:46:18 +01:00
auto get_definition_attributes_from_constructor = [ & ] ( ) - > JS : : ThrowCompletionOr < void > {
// 1. Let prototype be ? Get(constructor, "prototype").
auto prototype_value = TRY ( constructor - > callback - > get ( vm . names . prototype ) ) ;
2024-10-31 11:34:22 +00:00
// 2. If prototype is not an Object, then throw a TypeError exception.
2026-02-28 12:55:12 +01:00
auto prototype = prototype_value . as_if < JS : : Object > ( ) ;
if ( ! prototype )
2025-12-05 08:01:44 +01:00
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : NotAnObject , prototype_value ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 3. Let lifecycleCallbacks be the ordered map «[ "connectedCallback" → null, "disconnectedCallback" → null,
// "adoptedCallback" → null, "connectedMoveCallback" → null, "attributeChangedCallback" → null ]».
2023-03-29 23:46:18 +01:00
lifecycle_callbacks . set ( CustomElementReactionNames : : connectedCallback , { } ) ;
lifecycle_callbacks . set ( CustomElementReactionNames : : disconnectedCallback , { } ) ;
lifecycle_callbacks . set ( CustomElementReactionNames : : adoptedCallback , { } ) ;
2025-03-08 12:45:26 +13:00
lifecycle_callbacks . set ( CustomElementReactionNames : : connectedMoveCallback , { } ) ;
2023-03-29 23:46:18 +01:00
lifecycle_callbacks . set ( CustomElementReactionNames : : attributeChangedCallback , { } ) ;
2024-12-18 15:58:36 +00:00
// 4. For each callbackName of the keys of lifecycleCallbacks:
2025-03-08 12:45:26 +13:00
for ( auto const & callback_name : { CustomElementReactionNames : : connectedCallback , CustomElementReactionNames : : disconnectedCallback , CustomElementReactionNames : : adoptedCallback , CustomElementReactionNames : : connectedMoveCallback , CustomElementReactionNames : : attributeChangedCallback } ) {
2023-03-29 23:46:18 +01:00
// 1. Let callbackValue be ? Get(prototype, callbackName).
2026-02-28 12:55:12 +01:00
auto callback_value = TRY ( prototype - > get ( Utf16FlyString : : from_utf8 ( callback_name ) ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 2. If callbackValue is not undefined, then set lifecycleCallbacks[callbackName] to the result of
2024-12-18 15:58:36 +00:00
// converting callbackValue to the Web IDL Function callback type.
2023-03-29 23:46:18 +01:00
if ( ! callback_value . is_undefined ( ) ) {
auto callback = TRY ( convert_value_to_callback_function ( vm , callback_value ) ) ;
2024-03-11 19:12:52 +01:00
lifecycle_callbacks . set ( callback_name , callback ) ;
2023-03-29 23:46:18 +01:00
}
}
2024-12-18 15:58:36 +00:00
// 5. If lifecycleCallbacks["attributeChangedCallback"] is not null:
2023-03-29 23:46:18 +01:00
auto attribute_changed_callback_iterator = lifecycle_callbacks . find ( CustomElementReactionNames : : attributeChangedCallback ) ;
VERIFY ( attribute_changed_callback_iterator ! = lifecycle_callbacks . end ( ) ) ;
2024-03-11 19:12:52 +01:00
if ( attribute_changed_callback_iterator - > value ) {
2023-03-29 23:46:18 +01:00
// 1. Let observedAttributesIterable be ? Get(constructor, "observedAttributes").
2024-12-01 01:06:25 +01:00
auto observed_attributes_iterable = TRY ( constructor - > callback - > get ( vm . names . observedAttributes ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 2. If observedAttributesIterable is not undefined, then set observedAttributes to the result of
// converting observedAttributesIterable to a sequence<DOMString>. Rethrow any exceptions from the
// conversion.
2023-03-29 23:46:18 +01:00
if ( ! observed_attributes_iterable . is_undefined ( ) )
observed_attributes = TRY ( convert_value_to_sequence_of_strings ( vm , observed_attributes_iterable ) ) ;
}
// 6. Let disabledFeatures be an empty sequence<DOMString>.
Vector < String > disabled_features ;
// 7. Let disabledFeaturesIterable be ? Get(constructor, "disabledFeatures").
2024-12-01 01:06:25 +01:00
auto disabled_features_iterable = TRY ( constructor - > callback - > get ( vm . names . disabledFeatures ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 8. If disabledFeaturesIterable is not undefined, then set disabledFeatures to the result of converting
// disabledFeaturesIterable to a sequence<DOMString>. Rethrow any exceptions from the conversion.
2023-03-29 23:46:18 +01:00
if ( ! disabled_features_iterable . is_undefined ( ) )
disabled_features = TRY ( convert_value_to_sequence_of_strings ( vm , disabled_features_iterable ) ) ;
2024-12-18 15:58:36 +00:00
// 9. If disabledFeatures contains "internals", then set disableInternals to true.
2023-08-22 19:23:32 +02:00
disable_internals = disabled_features . contains_slow ( " internals " sv ) ;
2023-03-29 23:46:18 +01:00
2024-12-18 15:58:36 +00:00
// 10. If disabledFeatures contains "shadow", then set disableShadow to true.
2023-08-22 19:23:32 +02:00
disable_shadow = disabled_features . contains_slow ( " shadow " sv ) ;
2023-03-29 23:46:18 +01:00
// 11. Let formAssociatedValue be ? Get( constructor, "formAssociated").
2024-12-01 01:06:25 +01:00
auto form_associated_value = TRY ( constructor - > callback - > get ( vm . names . formAssociated ) ) ;
2023-03-29 23:46:18 +01:00
2024-12-18 15:58:36 +00:00
// 12. Set formAssociated to the result of converting formAssociatedValue to a boolean.
2023-03-29 23:46:18 +01:00
form_associated = form_associated_value . to_boolean ( ) ;
2026-02-27 17:05:47 +00:00
// 13. If formAssociated is true, then for each callbackName of « "formAssociatedCallback",
// "formResetCallback", "formDisabledCallback", "formStateRestoreCallback" »:
2023-03-29 23:46:18 +01:00
if ( form_associated ) {
for ( auto const & callback_name : { CustomElementReactionNames : : formAssociatedCallback , CustomElementReactionNames : : formResetCallback , CustomElementReactionNames : : formDisabledCallback , CustomElementReactionNames : : formStateRestoreCallback } ) {
// 1. Let callbackValue be ? Get(prototype, callbackName).
2026-02-28 12:55:12 +01:00
auto callback_value = TRY ( prototype - > get ( Utf16FlyString : : from_utf8 ( callback_name ) ) ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 2. If callbackValue is not undefined, then set lifecycleCallbacks[callbackName] to the result of
// converting callbackValue to the Web IDL Function callback type.
2023-03-29 23:46:18 +01:00
if ( ! callback_value . is_undefined ( ) )
2024-03-11 19:12:52 +01:00
lifecycle_callbacks . set ( callback_name , TRY ( convert_value_to_callback_function ( vm , callback_value ) ) ) ;
2023-03-29 23:46:18 +01:00
}
}
return { } ;
} ;
auto maybe_exception = get_definition_attributes_from_constructor ( ) ;
2026-02-27 17:05:47 +00:00
// Then, regardless of whether the above steps threw an exception or not: set this's element definition is
// running to false.
2023-03-29 23:46:18 +01:00
m_element_definition_is_running = false ;
2024-12-18 15:58:36 +00:00
// Finally, if the steps threw an exception, rethrow that exception.
2023-03-29 23:46:18 +01:00
if ( maybe_exception . is_throw_completion ( ) )
return maybe_exception . release_error ( ) ;
2026-02-27 17:05:47 +00:00
// 15. Let definition be a new custom element definition with name name, local name localName, constructor
// constructor, observed attributes observedAttributes, lifecycle callbacks lifecycleCallbacks, form-associated
// formAssociated, disable internals disableInternals, and disable shadow disableShadow.
2023-03-29 23:46:18 +01:00
auto definition = CustomElementDefinition : : create ( realm , name , local_name , * constructor , move ( observed_attributes ) , move ( lifecycle_callbacks ) , form_associated , disable_internals , disable_shadow ) ;
2024-12-18 15:58:36 +00:00
// 16. Append definition to this's custom element definition set.
2024-03-11 19:37:21 +01:00
m_custom_element_definitions . append ( definition ) ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 17. If this's is scoped is true, then for each document of this's scoped document set:
// upgrade particular elements within a document given this, document, definition, and localName.
if ( m_is_scoped ) {
for ( auto & document : m_scoped_documents )
document . upgrade_particular_elements ( * this , definition , local_name ) ;
}
// 18. Otherwise, upgrade particular elements within a document given this, this's relevant global object's
// associated Document, definition, localName, and name.
else {
auto & document = as < HTML : : Window > ( relevant_global_object ( * this ) ) . associated_document ( ) ;
document . upgrade_particular_elements ( * this , definition , local_name , name ) ;
}
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 19. If this's when-defined promise map[name] exists:
2023-03-29 23:46:18 +01:00
auto promise_when_defined_iterator = m_when_defined_promise_map . find ( name ) ;
if ( promise_when_defined_iterator ! = m_when_defined_promise_map . end ( ) ) {
2024-12-18 15:58:36 +00:00
// 1. Resolve this's when-defined promise map[name] with constructor.
WebIDL : : resolve_promise ( realm , promise_when_defined_iterator - > value , constructor - > callback ) ;
2023-03-29 23:46:18 +01:00
2024-12-18 15:58:36 +00:00
// 2. Remove this's when-defined promise map[name].
2023-03-29 23:46:18 +01:00
m_when_defined_promise_map . remove ( name ) ;
}
return { } ;
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get
2025-01-12 22:50:03 +13:00
Variant < GC : : Root < WebIDL : : CallbackType > , Empty > CustomElementRegistry : : get ( String const & name ) const
2023-03-29 23:46:18 +01:00
{
2024-12-18 15:58:36 +00:00
// 1. If this's custom element definition set contains an item with name name, then return that item's constructor.
2024-03-11 19:37:21 +01:00
auto existing_definition_iterator = m_custom_element_definitions . find_if ( [ & name ] ( auto const & definition ) {
2023-03-29 23:46:18 +01:00
return definition - > name ( ) = = name ;
} ) ;
if ( ! existing_definition_iterator . is_end ( ) )
2024-11-15 04:01:23 +13:00
return GC : : make_root ( ( * existing_definition_iterator ) - > constructor ( ) ) ;
2023-03-29 23:46:18 +01:00
2024-12-18 15:58:36 +00:00
// 2. Return undefined.
2025-01-12 22:50:03 +13:00
return Empty { } ;
2023-03-29 23:46:18 +01:00
}
2024-07-06 15:35:10 +01:00
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-getname
2024-11-15 04:01:23 +13:00
Optional < String > CustomElementRegistry : : get_name ( GC : : Root < WebIDL : : CallbackType > const & constructor ) const
2024-07-06 15:35:10 +01:00
{
2024-12-18 15:58:36 +00:00
// 1. If this's custom element definition set contains an item with constructor constructor, then return that item's name.
2024-07-06 15:35:10 +01:00
auto existing_definition_iterator = m_custom_element_definitions . find_if ( [ & constructor ] ( auto const & definition ) {
return definition - > constructor ( ) . callback = = constructor . cell ( ) - > callback ;
} ) ;
if ( ! existing_definition_iterator . is_end ( ) )
return ( * existing_definition_iterator ) - > name ( ) ;
// 2. Return null.
return { } ;
}
2023-03-29 23:46:18 +01:00
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-whendefined
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : Promise > > CustomElementRegistry : : when_defined ( String const & name )
2023-03-29 23:46:18 +01:00
{
auto & realm = this - > realm ( ) ;
2024-12-18 15:58:36 +00:00
// 1. If name is not a valid custom element name, then return a promise rejected with a "SyntaxError" DOMException.
2024-10-25 12:38:19 -06:00
if ( ! is_valid_custom_element_name ( name ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : create_rejected_promise ( realm , WebIDL : : SyntaxError : : create ( realm , Utf16String : : formatted ( " '{}' is not a valid custom element name " , name ) ) ) ;
2023-03-29 23:46:18 +01:00
2024-12-18 15:58:36 +00:00
// 2. If this's custom element definition set contains an item with name name, then return a promise resolved with that item's constructor.
2026-02-21 12:40:11 +01:00
auto existing_definition_iterator = m_custom_element_definitions . find_if ( [ & name ] ( auto const & definition ) {
2023-03-29 23:46:18 +01:00
return definition - > name ( ) = = name ;
} ) ;
2024-10-25 12:38:19 -06:00
if ( existing_definition_iterator ! = m_custom_element_definitions . end ( ) )
return WebIDL : : create_resolved_promise ( realm , ( * existing_definition_iterator ) - > constructor ( ) . callback ) ;
2023-03-29 23:46:18 +01:00
2024-12-18 15:58:36 +00:00
// 3. If this's when-defined promise map[name] does not exist, then set this's when-defined promise map[name] to a new promise.
2023-03-29 23:46:18 +01:00
auto existing_promise_iterator = m_when_defined_promise_map . find ( name ) ;
2024-12-18 15:58:36 +00:00
GC : : Ptr < WebIDL : : Promise > promise ;
if ( existing_promise_iterator = = m_when_defined_promise_map . end ( ) ) {
2024-10-25 12:38:19 -06:00
promise = WebIDL : : create_promise ( realm ) ;
2024-03-11 19:57:08 +01:00
m_when_defined_promise_map . set ( name , * promise ) ;
2024-12-18 15:58:36 +00:00
} else {
promise = existing_promise_iterator - > value ;
2023-03-29 23:46:18 +01:00
}
2024-12-18 15:58:36 +00:00
// 4. Return this's when-defined promise map[name].
2023-03-29 23:46:18 +01:00
VERIFY ( promise ) ;
2024-11-15 04:01:23 +13:00
return GC : : Ref { * promise } ;
2023-03-29 23:46:18 +01:00
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-upgrade
2024-11-15 04:01:23 +13:00
void CustomElementRegistry : : upgrade ( GC : : Ref < DOM : : Node > root ) const
2023-03-29 23:46:18 +01:00
{
2026-02-27 17:05:47 +00:00
// 1. For each shadow-including inclusive descendant candidate of root, in shadow-including tree order:
root - > for_each_shadow_including_inclusive_descendant ( [ & ] ( DOM : : Node & candidate ) {
// 1. If candidate is not an Element node, then continue.
auto * element = as_if < DOM : : Element > ( candidate ) ;
if ( ! element )
return TraversalDecision : : Continue ;
// 2. If candidate's custom element registry is not this, then continue.
if ( element - > custom_element_registry ( ) ! = this )
return TraversalDecision : : Continue ;
// 3. Try to upgrade candidate.
element - > try_to_upgrade ( ) ;
return TraversalDecision : : Continue ;
} ) ;
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-initialize
WebIDL : : ExceptionOr < void > CustomElementRegistry : : initialize_for_bindings ( GC : : Ref < DOM : : Node > root )
{
// 1. If this's is scoped is false and either root is a Document node or root's node document's custom element
// registry is not this, then throw a "NotSupportedError" DOMException.
if ( ! is_scoped ( ) & & ( root - > is_document ( ) | | root - > document ( ) . custom_element_registry ( ) ! = this ) )
return WebIDL : : NotSupportedError : : create ( realm ( ) , " CustomElementRegistry must either be scoped or the document's custom element registry. " _utf16 ) ;
// 2. If root is a Document node whose custom element registry is null, then set root's custom element registry to
// this.
if ( auto * document = as_if < DOM : : Document > ( * root ) ; document & & ! document - > custom_element_registry ( ) )
document - > set_custom_element_registry ( this ) ;
// 3. Otherwise, if root is a ShadowRoot node whose custom element registry is null, then set root's custom element
// registry to this.
else if ( auto * shadow_root = as_if < DOM : : ShadowRoot > ( * root ) ; shadow_root & & ! shadow_root - > custom_element_registry ( ) )
shadow_root - > set_custom_element_registry ( this ) ;
// 4. For each inclusive descendant inclusiveDescendant of root, in tree order:
root - > for_each_in_inclusive_subtree ( [ this ] ( auto & inclusive_descendant ) {
// 1. If inclusiveDescendant is not an Element node, then continue.
auto * element = as_if < DOM : : Element > ( inclusive_descendant ) ;
if ( ! element )
return TraversalDecision : : Continue ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 2. If inclusiveDescendant's custom element registry is null:
if ( ! element - > custom_element_registry ( ) ) {
// 1. Set inclusiveDescendant's custom element registry to this.
element - > set_custom_element_registry ( this ) ;
// 2. If this's is scoped is true, then append inclusiveDescendant's node document to this's scoped
// document set.
if ( m_is_scoped )
append_scoped_document ( element - > document ( ) ) ;
}
// 3. If inclusiveDescendant's custom element registry is not this, then continue.
if ( element - > custom_element_registry ( ) ! = this )
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2023-03-29 23:46:18 +01:00
2026-02-27 17:05:47 +00:00
// 4. Try to upgrade inclusiveDescendant.
element - > try_to_upgrade ( ) ;
2023-03-29 23:46:18 +01:00
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2023-03-29 23:46:18 +01:00
} ) ;
2026-02-27 17:05:47 +00:00
return { } ;
}
void CustomElementRegistry : : append_scoped_document ( GC : : Ref < DOM : : Document > document )
{
m_scoped_documents . set ( document ) ;
2023-03-29 23:46:18 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < CustomElementDefinition > CustomElementRegistry : : get_definition_with_name_and_local_name ( String const & name , String const & local_name ) const
2023-03-29 23:46:18 +01:00
{
2026-02-21 12:40:11 +01:00
auto definition_iterator = m_custom_element_definitions . find_if ( [ & ] ( auto const & definition ) {
2023-03-29 23:46:18 +01:00
return definition - > name ( ) = = name & & definition - > local_name ( ) = = local_name ;
} ) ;
return definition_iterator . is_end ( ) ? nullptr : definition_iterator - > ptr ( ) ;
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < CustomElementDefinition > CustomElementRegistry : : get_definition_from_new_target ( JS : : FunctionObject const & new_target ) const
2023-03-29 23:46:18 +01:00
{
2026-02-21 12:40:11 +01:00
auto definition_iterator = m_custom_element_definitions . find_if ( [ & ] ( auto const & definition ) {
2023-03-29 23:46:18 +01:00
return definition - > constructor ( ) . callback . ptr ( ) = = & new_target ;
} ) ;
return definition_iterator . is_end ( ) ? nullptr : definition_iterator - > ptr ( ) ;
}
2026-02-27 17:05:47 +00:00
// https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-registry
GC : : Ptr < CustomElementRegistry > look_up_a_custom_element_registry ( DOM : : Node const & node )
{
// To look up a custom element registry, given a Node object node:
// 1. If node is an Element object, then return node's custom element registry.
if ( auto * element = as_if < DOM : : Element > ( node ) )
return element - > custom_element_registry ( ) ;
// 2. If node is a ShadowRoot object, then return node's custom element registry.
if ( auto * shadow_root = as_if < DOM : : ShadowRoot > ( node ) )
return shadow_root - > custom_element_registry ( ) ;
// 3. If node is a Document object, then return node's custom element registry.
if ( auto * document = as_if < DOM : : Document > ( node ) )
return document - > custom_element_registry ( ) ;
// 4. Return null.
return nullptr ;
}
2026-02-24 15:36:55 +00:00
// https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-definition
GC : : Ptr < CustomElementDefinition > look_up_a_custom_element_definition ( GC : : Ptr < CustomElementRegistry > registry , Optional < FlyString > const & namespace_ , FlyString const & local_name , Optional < String > const & is )
{
// 1. If registry is null, then return null.
if ( ! registry )
return nullptr ;
// 2. If namespace is not the HTML namespace, then return null.
if ( namespace_ ! = Namespace : : HTML )
return nullptr ;
// 3. If registry's custom element definition set contains an item with name and local name both equal to
// localName, then return that item.
auto converted_local_name = local_name . to_string ( ) ;
if ( auto maybe_definition = registry - > get_definition_with_name_and_local_name ( converted_local_name , converted_local_name ) )
return maybe_definition ;
// 4. If registry's custom element definition set contains an item with name equal to is and local name equal to
// localName, then return that item.
// 5. Return null.
// NB: If `is` has no value, it can never match as custom element definitions always have a name and localName
// (i.e. not stored as Optional<String>)
if ( ! is . has_value ( ) )
return nullptr ;
return registry - > get_definition_with_name_and_local_name ( is . value ( ) , converted_local_name ) ;
}
2026-02-27 17:05:47 +00:00
// https://dom.spec.whatwg.org/#is-a-global-custom-element-registry
bool is_a_global_custom_element_registry ( GC : : Ptr < CustomElementRegistry > registry )
{
// Null or a CustomElementRegistry object registry is a global custom element registry if registry is non-null and
// registry’ s is scoped is false.
return registry & & ! registry - > is_scoped ( ) ;
}
2023-03-29 23:46:18 +01:00
}