Skip to main content

script/dom/
customelementregistry.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::collections::VecDeque;
7use std::ffi::CStr;
8use std::ptr::NonNull;
9use std::rc::Rc;
10use std::{mem, ptr};
11
12use dom_struct::dom_struct;
13use html5ever::{LocalName, Namespace, Prefix, ns};
14use js::context::JSContext;
15use js::conversions::FromJSValConvertible;
16use js::glue::UnwrapObjectStatic;
17use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSObject};
18use js::jsval::{BooleanValue, JSVal, NullValue, ObjectValue, UndefinedValue};
19use js::realm::{AutoRealm, CurrentRealm};
20use js::rust::wrappers2::{Construct1, JS_GetProperty, SameValue};
21use js::rust::{HandleObject, MutableHandleValue};
22use rustc_hash::FxBuildHasher;
23use script_bindings::cell::DomRefCell;
24use script_bindings::conversions::SafeToJSValConvertible;
25use script_bindings::reflector::{DomObject, Reflector, reflect_dom_object_with_proto_and_cx};
26use script_bindings::settings_stack::{run_a_callback, run_a_script};
27use style::attr::AttrValue;
28
29use super::bindings::trace::HashMapTracedValues;
30use crate::DomTypeHolder;
31use crate::dom::bindings::callback::{CallbackContainer, ExceptionHandling};
32use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::{
33    CustomElementConstructor, CustomElementRegistryMethods, ElementDefinitionOptions,
34};
35use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
36use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
37use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
38use crate::dom::bindings::conversions::{ConversionResult, StringificationBehavior, get_property};
39use crate::dom::bindings::error::{
40    Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception,
41};
42use crate::dom::bindings::inheritance::{Castable, DocumentFragmentTypeId, NodeTypeId};
43use crate::dom::bindings::reflector::DomGlobal;
44use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot, UnrootedDom};
45use crate::dom::bindings::str::DOMString;
46use crate::dom::document::Document;
47use crate::dom::domexception::{DOMErrorName, DOMException};
48use crate::dom::element::Element;
49use crate::dom::globalscope::GlobalScope;
50use crate::dom::html::htmlelement::HTMLElement;
51use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
52use crate::dom::iterators::ShadowIncluding;
53use crate::dom::node::{Node, NodeTraits};
54use crate::dom::promise::Promise;
55use crate::dom::shadowroot::ShadowRoot;
56use crate::dom::window::Window;
57use crate::microtask::Microtask;
58use crate::realms::enter_auto_realm;
59use crate::script_thread::ScriptThread;
60
61/// <https://dom.spec.whatwg.org/#concept-element-custom-element-state>
62#[derive(Clone, Copy, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
63pub(crate) enum CustomElementState {
64    Undefined,
65    Failed,
66    #[default]
67    Uncustomized,
68    Precustomized,
69    Custom,
70}
71
72/// <https://html.spec.whatwg.org/multipage/#customelementregistry>
73#[dom_struct]
74pub(crate) struct CustomElementRegistry {
75    reflector_: Reflector,
76
77    window: Dom<Window>,
78
79    #[conditional_malloc_size_of]
80    /// It is safe to use FxBuildHasher here as `LocalName` is an `Atom` in the string_cache.
81    /// These get a u32 hashed instead of a string.
82    /// <https://html.spec.whatwg.org/multipage/#when-defined-promise-map>
83    when_defined: DomRefCell<HashMapTracedValues<LocalName, Rc<Promise>, FxBuildHasher>>,
84
85    /// <https://html.spec.whatwg.org/multipage/#element-definition-is-running>
86    element_definition_is_running: Cell<bool>,
87
88    /// <https://html.spec.whatwg.org/multipage/#is-scoped>
89    is_scoped: Cell<bool>,
90
91    /// <https://html.spec.whatwg.org/multipage/#scoped-document-set>
92    scoped_document_set: DomRefCell<Vec<Dom<Document>>>,
93
94    #[conditional_malloc_size_of]
95    /// <https://html.spec.whatwg.org/multipage/#custom-element-definition-set>
96    definitions:
97        DomRefCell<HashMapTracedValues<LocalName, Rc<CustomElementDefinition>, FxBuildHasher>>,
98}
99
100impl CustomElementRegistry {
101    fn new_inherited(window: &Window) -> CustomElementRegistry {
102        CustomElementRegistry {
103            reflector_: Reflector::new(),
104            window: Dom::from_ref(window),
105            when_defined: DomRefCell::new(HashMapTracedValues::new_fx()),
106            element_definition_is_running: Cell::new(false),
107            is_scoped: Cell::new(false),
108            scoped_document_set: DomRefCell::new(Vec::new()),
109            definitions: DomRefCell::new(HashMapTracedValues::new_fx()),
110        }
111    }
112
113    pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<CustomElementRegistry> {
114        CustomElementRegistry::new_with_proto(cx, window, None)
115    }
116
117    fn new_with_proto(
118        cx: &mut JSContext,
119        window: &Window,
120        proto: Option<HandleObject>,
121    ) -> DomRoot<CustomElementRegistry> {
122        reflect_dom_object_with_proto_and_cx(
123            Box::new(CustomElementRegistry::new_inherited(window)),
124            window,
125            proto,
126            cx,
127        )
128    }
129
130    /// <https://html.spec.whatwg.org/multipage/#is-scoped>
131    pub(crate) fn is_scoped(&self) -> bool {
132        self.is_scoped.get()
133    }
134
135    /// Cleans up any active promises
136    /// <https://github.com/servo/servo/issues/15318>
137    pub(crate) fn teardown(&self) {
138        self.when_defined.borrow_mut().0.clear()
139    }
140
141    /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-definition>
142    pub(crate) fn lookup_definition(
143        &self,
144        local_name: &LocalName,
145        is: Option<&LocalName>,
146    ) -> Option<Rc<CustomElementDefinition>> {
147        self.definitions
148            .borrow()
149            .0
150            .values()
151            .find(|definition| {
152                // Step 4-5
153                definition.local_name == *local_name &&
154                    (definition.name == *local_name || Some(&definition.name) == is)
155            })
156            .cloned()
157    }
158
159    pub(crate) fn lookup_definition_by_constructor(
160        &self,
161        constructor: HandleObject,
162    ) -> Option<Rc<CustomElementDefinition>> {
163        self.definitions
164            .borrow()
165            .0
166            .values()
167            .find(|definition| definition.constructor.callback() == constructor.get())
168            .cloned()
169    }
170
171    /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-registry>
172    pub(crate) fn lookup_a_custom_element_registry(
173        node: &Node,
174    ) -> Option<DomRoot<CustomElementRegistry>> {
175        match node.type_id() {
176            // Step 1. If node is an Element object, then return node's custom element registry.
177            NodeTypeId::Element(_) => node
178                .downcast::<Element>()
179                .expect("Nodes with element type must be an element")
180                .custom_element_registry(),
181            // Step 2. If node is a ShadowRoot object, then return node's custom element registry.
182            NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) => node
183                .downcast::<ShadowRoot>()
184                .expect("Nodes with ShadowRoot type must be a ShadowRoot")
185                .custom_element_registry(),
186            // Step 3. If node is a Document object, then return node's custom element registry.
187            NodeTypeId::Document(_) => node
188                .downcast::<Document>()
189                .expect("Nodes with document type must be a document")
190                .custom_element_registry(),
191            // Step 4. Return null.
192            _ => None,
193        }
194    }
195
196    /// <https://dom.spec.whatwg.org/#is-a-global-custom-element-registry>
197    pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
198        // Null or a CustomElementRegistry object registry is a global custom element registry
199        // if registry is non-null and registry's is scoped is false.
200        registry.is_some_and(|r| !r.is_scoped())
201    }
202
203    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
204    /// Steps 10.1, 10.2
205    #[expect(unsafe_code)]
206    fn check_prototype(
207        &self,
208        cx: &mut JSContext,
209        constructor: HandleObject,
210        mut prototype: MutableHandleValue,
211    ) -> ErrorResult {
212        unsafe {
213            // Step 10.1
214            if !JS_GetProperty(cx, constructor, c"prototype".as_ptr(), prototype.reborrow()) {
215                return Err(Error::JSFailed);
216            }
217
218            // Step 10.2
219            if !prototype.is_object() {
220                return Err(Error::Type(
221                    c"constructor.prototype is not an object".to_owned(),
222                ));
223            }
224        }
225        Ok(())
226    }
227
228    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
229    /// This function includes both steps 14.3 and 14.4 which add the callbacks to a map and
230    /// process them.
231    fn get_callbacks(
232        &self,
233        cx: &mut JSContext,
234        prototype: HandleObject,
235    ) -> Fallible<LifecycleCallbacks> {
236        // Step 4
237        Ok(LifecycleCallbacks {
238            connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
239            disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
240            connected_move_callback: get_callback(cx, prototype, c"connectedMoveCallback")?,
241            adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
242            attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
243
244            form_associated_callback: None,
245            form_disabled_callback: None,
246            form_reset_callback: None,
247            form_state_restore_callback: None,
248        })
249    }
250
251    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
252    /// Step 14.13: Add form associated callbacks to LifecycleCallbacks
253    #[expect(unsafe_code)]
254    unsafe fn add_form_associated_callbacks(
255        &self,
256        cx: &mut JSContext,
257        prototype: HandleObject,
258        callbacks: &mut LifecycleCallbacks,
259    ) -> ErrorResult {
260        callbacks.form_associated_callback =
261            get_callback(cx, prototype, c"formAssociatedCallback")?;
262        callbacks.form_reset_callback = get_callback(cx, prototype, c"formResetCallback")?;
263        callbacks.form_disabled_callback = get_callback(cx, prototype, c"formDisabledCallback")?;
264        callbacks.form_state_restore_callback =
265            get_callback(cx, prototype, c"formStateRestoreCallback")?;
266
267        Ok(())
268    }
269
270    /// <https://html.spec.whatwg.org/multipage/#upgrade-particular-elements-within-a-document>
271    fn upgrade_particular_elements_within_a_document(
272        &self,
273        cx: &JSContext,
274        document: &Document,
275        definition: &Rc<CustomElementDefinition>,
276        local_name: &LocalName,
277        name: &LocalName,
278    ) {
279        // Step 1. Let upgradeCandidates be all elements that are shadow-including
280        // descendants of document, whose custom element registry is registry, whose
281        // namespace is the HTML namespace, and whose local name is localName,
282        // in shadow-including tree order. Additionally, if name is not localName,
283        // only include elements whose is value is equal to name.
284        for candidate in document
285            .upcast::<Node>()
286            .traverse_preorder_non_rooting(cx, ShadowIncluding::Yes)
287            .filter_map(UnrootedDom::downcast::<Element>)
288        {
289            // Note: If the registry is scoped, only include elements whose custom
290            // element registry is explicitly set to this registry. Otherwise,
291            // include elements with no explicit registry (they inherit the
292            // document's global registry) as well.
293            let registry_matches = if self.is_scoped.get() {
294                candidate
295                    .custom_element_registry()
296                    .is_some_and(|registry| *registry == *self)
297            } else {
298                candidate
299                    .custom_element_registry()
300                    .is_none_or(|registry| *registry == *self)
301            };
302            if *candidate.local_name() == *local_name &&
303                *candidate.namespace() == ns!(html) &&
304                registry_matches &&
305                (*name == *local_name || candidate.get_is().as_ref() == Some(name))
306            {
307                // Step 2. For each element element of upgradeCandidates: enqueue a
308                // custom element upgrade reaction given element and definition.
309                ScriptThread::enqueue_upgrade_reaction(cx, &candidate, definition.clone());
310            }
311        }
312    }
313}
314
315/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
316/// Step 14.4: Get `callbackValue` for all `callbackName` in `lifecycleCallbacks`.
317#[expect(unsafe_code)]
318fn get_callback(
319    cx: &mut JSContext,
320    prototype: HandleObject,
321    name: &CStr,
322) -> Fallible<Option<Rc<Function>>> {
323    rooted!(&in(cx) let mut callback = UndefinedValue());
324    unsafe {
325        // Step 10.4.1
326        if !JS_GetProperty(cx, prototype, name.as_ptr(), callback.handle_mut()) {
327            return Err(Error::JSFailed);
328        }
329
330        // Step 10.4.2
331        if !callback.is_undefined() {
332            if !callback.is_object() || !IsCallable(callback.to_object()) {
333                return Err(Error::Type(
334                    c"Lifecycle callback is not callable".to_owned(),
335                ));
336            }
337            Ok(Some(Function::new(cx, callback.to_object())))
338        } else {
339            Ok(None)
340        }
341    }
342}
343
344impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
345    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry>
346    fn Constructor(
347        cx: &mut JSContext,
348        window: &Window,
349        proto: Option<HandleObject>,
350    ) -> DomRoot<CustomElementRegistry> {
351        let registry = CustomElementRegistry::new_with_proto(cx, window, proto);
352
353        // Step 1: Set this's is scoped to true.
354        registry.is_scoped.set(true);
355        registry
356    }
357
358    #[expect(unsafe_code)]
359    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
360    fn Define(
361        &self,
362        cx: &mut JSContext,
363        name: DOMString,
364        constructor_: Rc<CustomElementConstructor>,
365        options: &ElementDefinitionOptions,
366    ) -> ErrorResult {
367        rooted!(&in(cx) let constructor = constructor_.callback());
368        let name = LocalName::from(name);
369
370        // Step 1. If IsConstructor(constructor) is false, then throw a TypeError.
371        // We must unwrap the constructor as all wrappers are constructable if they are callable.
372        rooted!(&in(cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
373
374        if unwrapped_constructor.is_null() {
375            // We do not have permission to access the unwrapped constructor.
376            return Err(Error::Security(None));
377        }
378
379        if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
380            return Err(Error::Type(
381                c"Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
382            ));
383        }
384
385        // Step 2. If name is not a valid custom element name, then throw a "SyntaxError" DOMException.
386        if !is_valid_custom_element_name(&name) {
387            return Err(Error::Syntax(Some(format!(
388                "{} name is not a valid custom element name",
389                name
390            ))));
391        }
392
393        // Step 3. If this's custom element definition set contains an item with name name,
394        // then throw a "NotSupportedError" DOMException.
395        if self.definitions.borrow().contains_key(&name) {
396            return Err(Error::NotSupported(Some(format!(
397                "{} has already been defined as a custom element",
398                name
399            ))));
400        }
401
402        // Step 4. If this's custom element definition set contains an
403        // item with constructor constructor, then throw a "NotSupportedError" DOMException.
404        if self
405            .definitions
406            .borrow()
407            .iter()
408            .any(|(_, def)| def.constructor == constructor_)
409        {
410            return Err(Error::NotSupported(None));
411        }
412
413        // Step 6. Let extends be options["extends"] if it exists; otherwise null.
414        let extends = &options.extends;
415
416        // Steps 5, 7
417        let local_name = if let Some(ref extended_name) = *extends {
418            // Step 7.1 If this's is scoped is true, then throw a "NotSupportedError" DOMException.
419            if self.is_scoped.get() {
420                return Err(Error::NotSupported(Some(
421                    "Scoped custom element registries cannot define customized built-in elements"
422                        .to_owned(),
423                )));
424            }
425
426            // Step 7.2 If extends is a valid custom element name, then throw a "NotSupportedError" DOMException.
427            if is_valid_custom_element_name(&extended_name.str()) {
428                return Err(Error::NotSupported(None));
429            }
430
431            // Step 7.3 If the element interface for extends and the HTML namespace is HTMLUnknownElement
432            // (e.g., if extends does not indicate an element definition in this specification)
433            // then throw a "NotSupportedError" DOMException.
434            if !is_extendable_element_interface(&extended_name.str()) {
435                return Err(Error::NotSupported(None));
436            }
437
438            // Step 7.4 Set localName to extends.
439            LocalName::from(extended_name)
440        } else {
441            // Step 5. Let localName be name.
442            name.clone()
443        };
444
445        // Step 8
446        if self.element_definition_is_running.get() {
447            return Err(Error::NotSupported(None));
448        }
449
450        // Step 9
451        self.element_definition_is_running.set(true);
452
453        // Steps 10-13: Initialize `formAssociated`, `disableInternals`, `disableShadow`, and
454        // `observedAttributes` with default values, but this is done later.
455
456        // Steps 14.1 - 14.2: Get the value of the prototype.
457        rooted!(&in(cx) let mut prototype = UndefinedValue());
458        {
459            let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
460            if let Err(error) =
461                self.check_prototype(&mut realm, constructor.handle(), prototype.handle_mut())
462            {
463                self.element_definition_is_running.set(false);
464                return Err(error);
465            }
466        };
467
468        // Steps 10.3 - 10.4
469        // It would be easier to get all the callbacks in one pass after
470        // we know whether this definition is going to be form-associated,
471        // but the order of operations is specified and it's observable
472        // if one of the callback getters throws an exception.
473        rooted!(&in(cx) let proto_object = prototype.to_object());
474        let mut callbacks = {
475            let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
476            match self.get_callbacks(&mut realm, proto_object.handle()) {
477                Ok(callbacks) => callbacks,
478                Err(error) => {
479                    self.element_definition_is_running.set(false);
480                    return Err(error);
481                },
482            }
483        };
484
485        // Step 14.5: Handle the case where with `attributeChangedCallback` on `lifecycleCallbacks`
486        // is not null.
487        let observed_attributes: Vec<DOMString> = if callbacks.attribute_changed_callback.is_some()
488        {
489            let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
490            match get_property(
491                &mut realm,
492                constructor.handle(),
493                c"observedAttributes",
494                StringificationBehavior::Default,
495            ) {
496                Ok(attributes) => attributes.unwrap_or_default(),
497                Err(error) => {
498                    self.element_definition_is_running.set(false);
499                    return Err(error);
500                },
501            }
502        } else {
503            Vec::new()
504        };
505
506        // Steps 14.6 - 14.10: Handle `disabledFeatures`.
507        let (disable_internals, disable_shadow) = {
508            let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
509            match get_property::<Vec<DOMString>>(
510                &mut realm,
511                constructor.handle(),
512                c"disabledFeatures",
513                StringificationBehavior::Default,
514            ) {
515                Ok(sequence) => {
516                    let sequence = sequence.unwrap_or_default();
517                    (
518                        sequence.iter().any(|s| *s == "internals"),
519                        sequence.iter().any(|s| *s == "shadow"),
520                    )
521                },
522                Err(error) => {
523                    self.element_definition_is_running.set(false);
524                    return Err(error);
525                },
526            }
527        };
528
529        // Step 14.11 - 14.12: Handle `formAssociated`.
530        let form_associated: bool = {
531            let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
532            match get_property(&mut realm, constructor.handle(), c"formAssociated", ()) {
533                Ok(flag) => flag.unwrap_or_default(),
534                Err(error) => {
535                    self.element_definition_is_running.set(false);
536                    return Err(error);
537                },
538            }
539        };
540
541        // Steps 14.13: Add the `formAssociated` callbacks.
542        if form_associated {
543            let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
544            unsafe {
545                if let Err(error) = self.add_form_associated_callbacks(
546                    &mut realm,
547                    proto_object.handle(),
548                    &mut callbacks,
549                ) {
550                    self.element_definition_is_running.set(false);
551                    return Err(error);
552                }
553            }
554        }
555
556        self.element_definition_is_running.set(false);
557
558        // Step 15: Let definition be a new custom element definition with name name,
559        // local name localName, constructor constructor, observed attributes
560        // observedAttributes, lifecycle callbacks lifecycleCallbacks,
561        // form-associated formAssociated, disable internals disableInternals,
562        // and disable shadow disableShadow.
563        let definition = Rc::new(CustomElementDefinition::new(
564            name.clone(),
565            local_name.clone(),
566            constructor_,
567            observed_attributes,
568            callbacks,
569            form_associated,
570            disable_internals,
571            disable_shadow,
572        ));
573
574        // Step 16: Append definition to this's custom element definition set.
575        self.definitions
576            .borrow_mut()
577            .insert(name.clone(), definition.clone());
578
579        // Step 17: If this's is scoped is true, then for each document of
580        // this's scoped document set: upgrade particular elements within a
581        // document given this, document, definition, and localName.
582        if self.is_scoped.get() {
583            for document in self.scoped_document_set.borrow().iter() {
584                self.upgrade_particular_elements_within_a_document(
585                    cx,
586                    document,
587                    &definition,
588                    &local_name,
589                    &local_name,
590                );
591            }
592        } else {
593            // Step 18: Otherwise, upgrade particular elements within a document given
594            // this, this's relevant global object's associated Document, definition,
595            // localName, and name.
596            self.upgrade_particular_elements_within_a_document(
597                cx,
598                &self.window.Document(),
599                &definition,
600                &local_name,
601                &name,
602            );
603        }
604
605        // Step 19: If this's when-defined promise map[name] exists:
606        // Step 19.2: Remove this's when-defined promise map[name].
607        let promise = self.when_defined.borrow_mut().remove(&name);
608        if let Some(promise) = promise {
609            rooted!(&in(cx) let mut constructor = UndefinedValue());
610            definition
611                .constructor
612                .safe_to_jsval(cx, constructor.handle_mut());
613            // Step 19.1: Resolve this's when-defined promise map[name] with constructor.
614            promise.resolve_native(cx, &constructor.get());
615        }
616        Ok(())
617    }
618
619    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-get>
620    fn Get(&self, cx: &mut JSContext, name: DOMString, mut retval: MutableHandleValue) {
621        match self.definitions.borrow().get(&LocalName::from(name)) {
622            Some(definition) => definition.constructor.safe_to_jsval(cx, retval),
623            None => retval.set(UndefinedValue()),
624        }
625    }
626
627    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-getname>
628    fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
629        self.definitions
630            .borrow()
631            .0
632            .values()
633            .find(|definition| definition.constructor == constructor)
634            .map(|definition| DOMString::from(definition.name.to_string()))
635    }
636
637    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-whendefined>
638    fn WhenDefined(&self, realm: &mut CurrentRealm, name: DOMString) -> Rc<Promise> {
639        let name = LocalName::from(name);
640
641        // Step 1
642        if !is_valid_custom_element_name(&name) {
643            let promise = Promise::new_in_realm(realm);
644            let error = DOMException::new(
645                realm,
646                self.window.as_global_scope(),
647                DOMErrorName::SyntaxError,
648            );
649            promise.reject_native(realm, &error);
650            return promise;
651        }
652
653        // Step 2
654        if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
655            rooted!(&in(*realm) let mut constructor = UndefinedValue());
656            definition
657                .constructor
658                .safe_to_jsval(realm, constructor.handle_mut());
659            let promise = Promise::new_in_realm(realm);
660            promise.resolve_native(realm, &constructor.get());
661            return promise;
662        }
663
664        // Steps 3, 4, 5, 6
665        let existing_promise = self.when_defined.borrow().get(&name).cloned();
666        existing_promise.unwrap_or_else(|| {
667            let promise = Promise::new_in_realm(realm);
668            self.when_defined.borrow_mut().insert(name, promise.clone());
669            promise
670        })
671    }
672
673    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-upgrade>
674    fn Upgrade(&self, cx: &JSContext, node: &Node) {
675        // Step 1. For each shadow-including inclusive descendant candidate of
676        // root, in shadow-including tree order:
677        for node in node.traverse_preorder_non_rooting(cx, ShadowIncluding::Yes) {
678            // Step 1.1. If candidate is not an Element node, then continue.
679            let Some(element) = node.downcast::<Element>() else {
680                continue;
681            };
682            // Step 1.2. If candidate's custom element registry is not this,
683            // then continue.
684            if element
685                .custom_element_registry()
686                .is_some_and(|registry| *registry != *self)
687            {
688                continue;
689            }
690            // Step 1.3. Try to upgrade candidate.
691            try_upgrade_element(cx, element);
692        }
693    }
694
695    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-initialize>
696    fn Initialize(&self, cx: &JSContext, root: &Node) -> ErrorResult {
697        // Step 1. If this's is scoped is false and either root is a Document node
698        // or root's node document's custom element registry is not this, then
699        // throw a "NotSupportedError" DOMException.
700        if !self.is_scoped.get() {
701            let is_document = root.is::<Document>();
702            let registry_mismatch = root
703                .owner_doc()
704                .custom_element_registry()
705                .is_some_and(|registry| *registry != *self);
706            if is_document || registry_mismatch {
707                return Err(Error::NotSupported(Some(
708                    "Initialize is not allowed on a non-scoped registry for this root".to_owned(),
709                )));
710            }
711        }
712
713        // Step 2. If root is a Document node whose custom element registry is null,
714        // then set root's custom element registry to this.
715        if let Some(document) = root.downcast::<Document>() {
716            if document.custom_element_registry().is_none() {
717                document.set_custom_element_registry(self);
718            }
719        }
720        // Step 3. Otherwise, if root is a ShadowRoot node whose custom element registry
721        // is null, then set root's custom element registry to this.
722        else if let Some(shadow_root) = root.downcast::<ShadowRoot>() &&
723            shadow_root.custom_element_registry().is_none()
724        {
725            shadow_root.set_custom_element_registry(self);
726        }
727
728        // Step 4. For each inclusive descendant inclusiveDescendant of root, in tree order:
729        for node in root.traverse_preorder(ShadowIncluding::No) {
730            // Step 4.1. If inclusiveDescendant is not an Element node, then continue.
731            let Some(element) = node.downcast::<Element>() else {
732                continue;
733            };
734
735            // Step 4.2. If inclusiveDescendant's custom element registry is null:
736            if element.custom_element_registry().is_none() {
737                // Step 4.2.1. Set inclusiveDescendant's custom element registry to this.
738                element.set_custom_element_registry(Some(self));
739
740                // Step 4.2.2. If this's is scoped is true, then append
741                // inclusiveDescendant's node document to this's scoped document set.
742                if self.is_scoped.get() {
743                    let document = element.upcast::<Node>().owner_doc();
744                    self.scoped_document_set
745                        .borrow_mut()
746                        .push(Dom::from_ref(&document));
747                }
748            // Step 4.3. If inclusiveDescendant's custom element registry is not this, then continue.
749            } else if element
750                .custom_element_registry()
751                .is_none_or(|registry| *registry != *self)
752            {
753                continue;
754            }
755
756            // Step 4.4. Try to upgrade inclusiveDescendant.
757            try_upgrade_element(cx, element);
758        }
759        Ok(())
760    }
761}
762
763#[derive(Clone, JSTraceable, MallocSizeOf)]
764pub(crate) struct LifecycleCallbacks {
765    #[conditional_malloc_size_of]
766    connected_callback: Option<Rc<Function>>,
767
768    #[conditional_malloc_size_of]
769    connected_move_callback: Option<Rc<Function>>,
770
771    #[conditional_malloc_size_of]
772    disconnected_callback: Option<Rc<Function>>,
773
774    #[conditional_malloc_size_of]
775    adopted_callback: Option<Rc<Function>>,
776
777    #[conditional_malloc_size_of]
778    attribute_changed_callback: Option<Rc<Function>>,
779
780    #[conditional_malloc_size_of]
781    form_associated_callback: Option<Rc<Function>>,
782
783    #[conditional_malloc_size_of]
784    form_reset_callback: Option<Rc<Function>>,
785
786    #[conditional_malloc_size_of]
787    form_disabled_callback: Option<Rc<Function>>,
788
789    #[conditional_malloc_size_of]
790    form_state_restore_callback: Option<Rc<Function>>,
791}
792
793#[derive(Clone, JSTraceable, MallocSizeOf)]
794pub(crate) enum ConstructionStackEntry {
795    Element(DomRoot<Element>),
796    AlreadyConstructedMarker,
797}
798
799/// <https://html.spec.whatwg.org/multipage/#custom-element-definition>
800#[derive(Clone, JSTraceable, MallocSizeOf)]
801pub(crate) struct CustomElementDefinition {
802    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-name>
803    #[no_trace]
804    pub(crate) name: LocalName,
805
806    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-local-name>
807    #[no_trace]
808    pub(crate) local_name: LocalName,
809
810    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-constructor>
811    #[conditional_malloc_size_of]
812    pub(crate) constructor: Rc<CustomElementConstructor>,
813
814    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-observed-attributes>
815    pub(crate) observed_attributes: Vec<DOMString>,
816
817    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-lifecycle-callbacks>
818    pub(crate) callbacks: LifecycleCallbacks,
819
820    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-construction-stack>
821    pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
822
823    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-form-associated>
824    pub(crate) form_associated: bool,
825
826    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-internals>
827    pub(crate) disable_internals: bool,
828
829    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-shadow>
830    pub(crate) disable_shadow: bool,
831}
832
833impl CustomElementDefinition {
834    #[expect(clippy::too_many_arguments)]
835    fn new(
836        name: LocalName,
837        local_name: LocalName,
838        constructor: Rc<CustomElementConstructor>,
839        observed_attributes: Vec<DOMString>,
840        callbacks: LifecycleCallbacks,
841        form_associated: bool,
842        disable_internals: bool,
843        disable_shadow: bool,
844    ) -> CustomElementDefinition {
845        CustomElementDefinition {
846            name,
847            local_name,
848            constructor,
849            observed_attributes,
850            callbacks,
851            construction_stack: Default::default(),
852            form_associated,
853            disable_internals,
854            disable_shadow,
855        }
856    }
857
858    /// <https://html.spec.whatwg.org/multipage/#autonomous-custom-element>
859    pub(crate) fn is_autonomous(&self) -> bool {
860        self.name == self.local_name
861    }
862
863    /// <https://dom.spec.whatwg.org/#concept-create-element> Step 5.1
864    #[expect(unsafe_code)]
865    pub(crate) fn create_element(
866        &self,
867        cx: &mut JSContext,
868        document: &Document,
869        prefix: Option<Prefix>,
870        registry: Option<&CustomElementRegistry>,
871    ) -> Fallible<DomRoot<Element>> {
872        let window = document.window();
873
874        // Step 5.1.1. Let C be definition’s constructor.
875        rooted!(&in(cx) let constructor = ObjectValue(self.constructor.callback()));
876        rooted!(&in(cx) let mut element = ptr::null_mut::<JSObject>());
877        {
878            // Go into the constructor's realm
879            let mut realm = AutoRealm::new(cx, NonNull::new(self.constructor.callback()).unwrap());
880            let cx = &mut realm;
881
882            // Step 5.3.1. Set result to the result of constructing C, with no arguments.
883            // https://webidl.spec.whatwg.org/#construct-a-callback-function
884            run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
885                run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
886                    let args = HandleValueArray::empty();
887                    if unsafe { !Construct1(cx, constructor.handle(), &args, element.handle_mut()) }
888                    {
889                        Err(Error::JSFailed)
890                    } else {
891                        Ok(())
892                    }
893                })
894            })?;
895        }
896
897        rooted!(&in(cx) let element_val = ObjectValue(element.get()));
898        let element: DomRoot<Element> =
899            match FromJSValConvertible::safe_from_jsval(cx, element_val.handle(), ()) {
900                Ok(ConversionResult::Success(element)) => element,
901                Ok(ConversionResult::Failure(..)) => {
902                    return Err(Error::Type(
903                        c"Constructor did not return a DOM node".to_owned(),
904                    ));
905                },
906                _ => return Err(Error::JSFailed),
907            };
908
909        // Step 5.1.3.2 Assert: result’s custom element state and custom element definition are initialized.
910        // Step 5.1.3.3 Assert: result’s namespace is the HTML namespace.
911        // Note: IDL enforces that result is an HTMLElement object, which all use the HTML namespace.
912        // Note: the custom element definition is initialized by the caller if
913        // this method returns a success value.
914        assert!(element.is::<HTMLElement>());
915
916        // Step 5.1.3.4. If result’s attribute list is not empty, then throw a "NotSupportedError" DOMException.
917        // Step 5.1.3.5. If result has children, then throw a "NotSupportedError" DOMException.
918        // Step 5.1.3.6. If result’s parent is not null, then throw a "NotSupportedError" DOMException.
919        // Step 5.1.3.7. If result’s node document is not document, then throw a "NotSupportedError" DOMException.
920        // Step 5.1.3.8. If result’s local name is not equal to localName then throw a "NotSupportedError" DOMException.
921        if element.HasAttributes() ||
922            element.upcast::<Node>().children_count() > 0 ||
923            element.upcast::<Node>().has_parent() ||
924            &*element.upcast::<Node>().owner_doc() != document ||
925            *element.namespace() != ns!(html) ||
926            *element.local_name() != self.local_name
927        {
928            return Err(Error::NotSupported(None));
929        }
930
931        // Step 5.1.3.9. Set result’s namespace prefix to prefix.
932        element.set_prefix(prefix);
933
934        // Step 5.1.3.10. Set result’s is value to null.
935        // Element's `is` is None by default
936
937        // Step 5.1.3.11. Set result’s custom element registry to registry.
938        element.set_custom_element_registry(registry);
939
940        Ok(element)
941    }
942
943    pub(crate) fn has_attribute_changed_callback(&self) -> bool {
944        self.callbacks.attribute_changed_callback.is_some()
945    }
946}
947
948/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element>
949pub(crate) fn upgrade_element(
950    cx: &mut JSContext,
951    definition: Rc<CustomElementDefinition>,
952    element: &Element,
953) {
954    // Step 1. If element's custom element state is not "undefined" or "uncustomized", then return.
955    let state = element.get_custom_element_state();
956    if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
957        return;
958    }
959
960    // Step 2. Set element's custom element definition to definition.
961    element.set_custom_element_definition(Rc::clone(&definition));
962
963    // Step 3. Set element's custom element state to "failed".
964    element.set_custom_element_state(CustomElementState::Failed);
965
966    // Step 4. For each attribute in element's attribute list, in order, enqueue a custom element callback reaction
967    // with element, callback name "attributeChangedCallback", and « attribute's local name, null, attribute's value,
968    // attribute's namespace ».
969    let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
970    for attr in element.attrs().borrow().iter() {
971        let local_name = attr.local_name().clone();
972        let namespace = attr.namespace().clone();
973        custom_element_reaction_stack.enqueue_callback_reaction(
974            cx,
975            element,
976            CallbackReaction::AttributeChanged(local_name, None, Some(&*attr.value()), namespace),
977            Some(definition.clone()),
978        );
979    }
980
981    // Step 5. If element is connected, then enqueue a custom element callback reaction with element,
982    // callback name "connectedCallback", and « ».
983    if element.is_connected() {
984        custom_element_reaction_stack.enqueue_callback_reaction(
985            cx,
986            element,
987            CallbackReaction::Connected,
988            Some(definition.clone()),
989        );
990    }
991
992    // Step 6. Add element to the end of definition's construction stack.
993    definition
994        .construction_stack
995        .borrow_mut()
996        .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
997
998    // Steps 7-8, successful case
999    let result = run_upgrade_constructor(cx, &definition, element);
1000
1001    // "regardless of whether the above steps threw an exception" step
1002    definition.construction_stack.borrow_mut().pop();
1003
1004    // Step 8 exception handling
1005    if let Err(error) = result {
1006        // Step 8.exception.1
1007        element.clear_custom_element_definition();
1008
1009        // Step 8.exception.2
1010        element.clear_reaction_queue();
1011
1012        // Step 8.exception.3
1013        let global = GlobalScope::current().expect("No current global");
1014
1015        let mut realm = enter_auto_realm(cx, &*global);
1016        let cx = &mut realm.current_realm();
1017
1018        throw_dom_exception(cx, &global, error);
1019        report_pending_exception(cx);
1020
1021        return;
1022    }
1023
1024    // Step 9: handle with form-associated custom element
1025    if let Some(html_element) = element.downcast::<HTMLElement>() &&
1026        html_element.is_form_associated_custom_element()
1027    {
1028        // We know this element is is form-associated, so we can use the implementation of
1029        // `FormControl` for HTMLElement, which makes that assumption.
1030        // Step 9.1: Reset the form owner of element
1031        html_element.reset_form_owner(cx);
1032        if let Some(form) = html_element.form_owner() {
1033            // Even though the tree hasn't structurally mutated,
1034            // HTMLCollections need to be invalidated.
1035            form.upcast::<Node>().rev_version();
1036            // The spec tells us specifically to enqueue a formAssociated reaction
1037            // here, but it also says to do that for resetting form owner in general,
1038            // and we don't need two reactions.
1039        }
1040
1041        // Either enabled_state or disabled_state needs to be set,
1042        // and the possibility of a disabled fieldset ancestor needs
1043        // to be accounted for. (In the spec, being disabled is
1044        // a fact that's true or false about a node at a given time,
1045        // not a flag that belongs to the node and is updated,
1046        // so it doesn't describe this check as an action.)
1047        element.check_disabled_attribute();
1048        element.check_ancestors_disabled_state_for_form_control();
1049        element.update_read_write_state_from_readonly_attribute();
1050
1051        // Step 9.2: If element is disabled, then enqueue a custom element callback reaction
1052        // with element.
1053        if element.disabled_state() {
1054            custom_element_reaction_stack.enqueue_callback_reaction(
1055                cx,
1056                element,
1057                CallbackReaction::FormDisabled(true),
1058                Some(definition),
1059            )
1060        }
1061    }
1062
1063    // Step 10
1064    element.set_custom_element_state(CustomElementState::Custom);
1065}
1066
1067/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element>
1068/// Steps 9.1-9.4
1069#[expect(unsafe_code)]
1070fn run_upgrade_constructor(
1071    cx: &mut JSContext,
1072    definition: &CustomElementDefinition,
1073    element: &Element,
1074) -> ErrorResult {
1075    let constructor = &definition.constructor;
1076    let window = element.owner_window();
1077    rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
1078    rooted!(&in(cx) let mut element_val = UndefinedValue());
1079    element.safe_to_jsval(cx, element_val.handle_mut());
1080    rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
1081    {
1082        // Step 9.1. If definition's disable shadow is true and element's shadow root is non-null,
1083        // then throw a "NotSupportedError" DOMException.
1084        if definition.disable_shadow && element.is_shadow_host() {
1085            return Err(Error::NotSupported(None));
1086        }
1087
1088        // Go into the constructor's realm
1089        let mut realm = AutoRealm::new(cx, NonNull::new(constructor.callback()).unwrap());
1090        let cx = &mut *realm;
1091
1092        let args = HandleValueArray::empty();
1093        // Step 8.2. Set element's custom element state to "precustomized".
1094        element.set_custom_element_state(CustomElementState::Precustomized);
1095
1096        // Step 9.3. Let constructResult be the result of constructing C, with no arguments.
1097        // https://webidl.spec.whatwg.org/#construct-a-callback-function
1098        run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
1099            run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
1100                if unsafe {
1101                    !Construct1(
1102                        cx,
1103                        constructor_val.handle(),
1104                        &args,
1105                        construct_result.handle_mut(),
1106                    )
1107                } {
1108                    Err(Error::JSFailed)
1109                } else {
1110                    Ok(())
1111                }
1112            })
1113        })?;
1114
1115        let mut same = false;
1116        rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
1117
1118        // Step 9.4. If SameValue(constructResult, element) is false, then throw a TypeError.
1119        if unsafe {
1120            !SameValue(
1121                cx,
1122                construct_result_val.handle(),
1123                element_val.handle(),
1124                &mut same,
1125            )
1126        } {
1127            return Err(Error::JSFailed);
1128        }
1129        if !same {
1130            return Err(Error::Type(
1131                c"Returned element is not SameValue as the upgraded element".to_owned(),
1132            ));
1133        }
1134    }
1135    Ok(())
1136}
1137
1138/// <https://html.spec.whatwg.org/multipage/#concept-try-upgrade>
1139pub(crate) fn try_upgrade_element(cx: &JSContext, element: &Element) {
1140    // Step 1. Let definition be the result of looking up a custom element definition given element's node document,
1141    // element's namespace, element's local name, and element's is value.
1142    let document = element.owner_document();
1143    let namespace = element.namespace();
1144    let local_name = element.local_name();
1145    let is = element.get_is();
1146    if let Some(definition) =
1147        document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
1148    {
1149        // Step 2. If definition is not null, then enqueue a custom element upgrade reaction given
1150        // element and definition.
1151        ScriptThread::enqueue_upgrade_reaction(cx, element, definition);
1152    }
1153}
1154
1155#[derive(JSTraceable, MallocSizeOf)]
1156#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1157pub(crate) enum CustomElementReaction {
1158    Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1159    Callback(
1160        #[conditional_malloc_size_of] Rc<Function>,
1161        #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1162    ),
1163}
1164
1165impl CustomElementReaction {
1166    /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions>
1167    pub(crate) fn invoke(&self, cx: &mut JSContext, element: &Element) {
1168        // Step 2.1
1169        match *self {
1170            CustomElementReaction::Upgrade(ref definition) => {
1171                upgrade_element(cx, definition.clone(), element)
1172            },
1173            CustomElementReaction::Callback(ref callback, ref arguments) => {
1174                // We're rooted, so it's safe to hand out a handle to objects in Heap
1175                let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1176                rooted!(&in(cx) let mut value: JSVal);
1177                let _ = callback.Call_(
1178                    cx,
1179                    element,
1180                    arguments,
1181                    value.handle_mut(),
1182                    ExceptionHandling::Report,
1183                );
1184            },
1185        }
1186    }
1187}
1188
1189pub(crate) enum CallbackReaction<'a> {
1190    Connected,
1191    Disconnected,
1192    Adopted(DomRoot<Document>, DomRoot<Document>),
1193    AttributeChanged(
1194        LocalName,
1195        Option<&'a AttrValue>,
1196        Option<&'a AttrValue>,
1197        Namespace,
1198    ),
1199    FormAssociated(Option<DomRoot<HTMLFormElement>>),
1200    FormDisabled(bool),
1201    FormReset,
1202    ConnectedMove,
1203}
1204
1205/// <https://html.spec.whatwg.org/multipage/#processing-the-backup-element-queue>
1206#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1207enum BackupElementQueueFlag {
1208    Processing,
1209    NotProcessing,
1210}
1211
1212/// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack>
1213/// # Safety
1214/// This can be shared inside an Rc because one of those Rc copies lives
1215/// inside ScriptThread, so the GC can always reach this structure.
1216#[derive(JSTraceable, MallocSizeOf)]
1217#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1218#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1219pub(crate) struct CustomElementReactionStack {
1220    stack: DomRefCell<Vec<ElementQueue>>,
1221    backup_queue: ElementQueue,
1222    processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1223}
1224
1225impl CustomElementReactionStack {
1226    pub(crate) fn new() -> CustomElementReactionStack {
1227        CustomElementReactionStack {
1228            stack: DomRefCell::new(Vec::new()),
1229            backup_queue: ElementQueue::new(),
1230            processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1231        }
1232    }
1233
1234    pub(crate) fn push_new_element_queue(&self) {
1235        self.stack.borrow_mut().push(ElementQueue::new());
1236    }
1237
1238    pub(crate) fn pop_current_element_queue(&self, cx: &mut JSContext) {
1239        rooted_vec!(let mut stack);
1240        mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1241
1242        if let Some(current_queue) = stack.last() {
1243            current_queue.invoke_reactions(cx);
1244        }
1245        stack.pop();
1246
1247        mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1248        self.stack.borrow_mut().append(&mut *stack);
1249    }
1250
1251    /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue>
1252    /// Step 4
1253    pub(crate) fn invoke_backup_element_queue(&self, cx: &mut JSContext) {
1254        // Step 4.1
1255        self.backup_queue.invoke_reactions(cx);
1256
1257        // Step 4.2
1258        self.processing_backup_element_queue
1259            .set(BackupElementQueueFlag::NotProcessing);
1260    }
1261
1262    /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue>
1263    pub(crate) fn enqueue_element(&self, cx: &JSContext, element: &Element) {
1264        if let Some(current_queue) = self.stack.borrow().last() {
1265            // Step 2
1266            current_queue.append_element(element);
1267        } else {
1268            // Step 1.1
1269            self.backup_queue.append_element(element);
1270
1271            // Step 1.2
1272            if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1273                return;
1274            }
1275
1276            // Step 1.3
1277            self.processing_backup_element_queue
1278                .set(BackupElementQueueFlag::Processing);
1279
1280            // Step 4
1281            ScriptThread::enqueue_microtask(cx, Microtask::CustomElementReaction);
1282        }
1283    }
1284
1285    /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-callback-reaction>
1286    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1287    pub(crate) fn enqueue_callback_reaction(
1288        &self,
1289        cx: &mut JSContext,
1290        element: &Element,
1291        reaction: CallbackReaction,
1292        definition: Option<Rc<CustomElementDefinition>>,
1293    ) {
1294        // Step 1. Let definition be element's custom element definition.
1295        let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1296            Some(definition) => definition,
1297            None => return,
1298        };
1299
1300        // Step 2. Let callback be the value of the entry in definition's lifecycle callbacks with
1301        // key callbackName.
1302        let (callback, args) = match reaction {
1303            CallbackReaction::Connected => {
1304                (definition.callbacks.connected_callback.clone(), Vec::new())
1305            },
1306            CallbackReaction::Disconnected => (
1307                definition.callbacks.disconnected_callback.clone(),
1308                Vec::new(),
1309            ),
1310            CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1311                let args = vec![Heap::default(), Heap::default()];
1312                args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1313                args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1314                (definition.callbacks.adopted_callback.clone(), args)
1315            },
1316            CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1317                // Step 5.
1318                if !definition
1319                    .observed_attributes
1320                    .iter()
1321                    .any(|attr| *attr == *local_name)
1322                {
1323                    return;
1324                }
1325
1326                // We might be here during HTML parsing, rather than
1327                // during Javscript execution, and so we typically aren't
1328                // already in a realm here.
1329                let mut realm = enter_auto_realm(cx, &*element.global());
1330                let cx = &mut realm;
1331
1332                let local_name = DOMString::from(&*local_name);
1333                rooted!(&in(cx) let mut name_value = UndefinedValue());
1334                local_name.safe_to_jsval(cx, name_value.handle_mut());
1335
1336                rooted!(&in(cx) let mut old_value = NullValue());
1337                if let Some(old_val) = old_val {
1338                    old_val.safe_to_jsval(cx, old_value.handle_mut());
1339                }
1340
1341                rooted!(&in(cx) let mut value = NullValue());
1342                if let Some(val) = val {
1343                    val.safe_to_jsval(cx, value.handle_mut());
1344                }
1345
1346                rooted!(&in(cx) let mut namespace_value = NullValue());
1347                if namespace != ns!() {
1348                    let namespace = DOMString::from(&*namespace);
1349                    namespace.safe_to_jsval(cx, namespace_value.handle_mut());
1350                }
1351
1352                let args = vec![
1353                    Heap::default(),
1354                    Heap::default(),
1355                    Heap::default(),
1356                    Heap::default(),
1357                ];
1358                args[0].set(name_value.get());
1359                args[1].set(old_value.get());
1360                args[2].set(value.get());
1361                args[3].set(namespace_value.get());
1362
1363                (
1364                    definition.callbacks.attribute_changed_callback.clone(),
1365                    args,
1366                )
1367            },
1368            CallbackReaction::FormAssociated(form) => {
1369                let args = vec![Heap::default()];
1370                if let Some(form) = form {
1371                    args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1372                } else {
1373                    args[0].set(NullValue());
1374                }
1375                (definition.callbacks.form_associated_callback.clone(), args)
1376            },
1377            CallbackReaction::FormDisabled(disabled) => {
1378                rooted!(&in(cx) let disabled_value = BooleanValue(disabled));
1379                let args = vec![Heap::default()];
1380                args[0].set(disabled_value.get());
1381                (definition.callbacks.form_disabled_callback.clone(), args)
1382            },
1383            CallbackReaction::FormReset => {
1384                (definition.callbacks.form_reset_callback.clone(), Vec::new())
1385            },
1386            CallbackReaction::ConnectedMove => {
1387                let callback = definition.callbacks.connected_move_callback.clone();
1388                // Step 3. If callbackName is "connectedMoveCallback" and callback is null:
1389                if callback.is_none() {
1390                    // Step 3.1. Let disconnectedCallback be the value of the entry in
1391                    // definition's lifecycle callbacks with key "disconnectedCallback".
1392                    let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1393
1394                    // Step 3.2. Let connectedCallback be the value of the entry in
1395                    // definition's lifecycle callbacks with key "connectedCallback".
1396                    let connected_callback = definition.callbacks.connected_callback.clone();
1397
1398                    // Step 3.3. If connectedCallback and disconnectedCallback are null,
1399                    // then return.
1400                    if disconnected_callback.is_none() && connected_callback.is_none() {
1401                        return;
1402                    }
1403
1404                    // Step 3.4. Set callback to the following steps:
1405                    // Step 3.4.1. If disconnectedCallback is not null, then call
1406                    // disconnectedCallback with no arguments.
1407                    if let Some(disconnected_callback) = disconnected_callback {
1408                        element.push_callback_reaction(disconnected_callback, Box::new([]));
1409                    }
1410                    // Step 3.4.2. If connectedCallback is not null, then call
1411                    // connectedCallback with no arguments.
1412                    if let Some(connected_callback) = connected_callback {
1413                        element.push_callback_reaction(connected_callback, Box::new([]));
1414                    }
1415
1416                    self.enqueue_element(cx, element);
1417                    return;
1418                }
1419
1420                (callback, Vec::new())
1421            },
1422        };
1423
1424        // Step 4. If callback is null, then return.
1425        let callback = match callback {
1426            Some(callback) => callback,
1427            None => return,
1428        };
1429
1430        // Step 6. Add a new callback reaction to element's custom element reaction queue, with
1431        // callback function callback and arguments args.
1432        element.push_callback_reaction(callback, args.into_boxed_slice());
1433
1434        // Step 7. Enqueue an element on the appropriate element queue given element.
1435        self.enqueue_element(cx, element);
1436    }
1437
1438    /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-upgrade-reaction>
1439    pub(crate) fn enqueue_upgrade_reaction(
1440        &self,
1441        cx: &JSContext,
1442        element: &Element,
1443        definition: Rc<CustomElementDefinition>,
1444    ) {
1445        // Step 1. Add a new upgrade reaction to element's custom element reaction queue,
1446        // with custom element definition definition.
1447        element.push_upgrade_reaction(definition);
1448
1449        // Step 2. Enqueue an element on the appropriate element queue given element.
1450        self.enqueue_element(cx, element);
1451    }
1452}
1453
1454/// <https://html.spec.whatwg.org/multipage/#element-queue>
1455#[derive(JSTraceable, MallocSizeOf)]
1456#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1457struct ElementQueue {
1458    queue: DomRefCell<VecDeque<Dom<Element>>>,
1459}
1460
1461impl ElementQueue {
1462    fn new() -> ElementQueue {
1463        ElementQueue {
1464            queue: Default::default(),
1465        }
1466    }
1467
1468    /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions>
1469    fn invoke_reactions(&self, cx: &mut JSContext) {
1470        // Steps 1-2
1471        while let Some(element) = self.next_element() {
1472            element.invoke_reactions(cx)
1473        }
1474        self.queue.borrow_mut().clear();
1475    }
1476
1477    fn next_element(&self) -> Option<DomRoot<Element>> {
1478        self.queue
1479            .borrow_mut()
1480            .pop_front()
1481            .as_deref()
1482            .map(DomRoot::from_ref)
1483    }
1484
1485    fn append_element(&self, element: &Element) {
1486        self.queue.borrow_mut().push_back(Dom::from_ref(element));
1487    }
1488}
1489
1490/// <https://html.spec.whatwg.org/multipage/#valid-custom-element-name>
1491pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1492    // Custom elment names must match:
1493    // PotentialCustomElementName ::= [a-z] (PCENChar)* '-' (PCENChar)*
1494    let mut chars = name.chars();
1495    if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1496        return false;
1497    }
1498
1499    let mut has_dash = false;
1500
1501    for c in chars {
1502        if c == '-' {
1503            has_dash = true;
1504            continue;
1505        }
1506
1507        if !is_potential_custom_element_char(c) {
1508            return false;
1509        }
1510    }
1511
1512    if !has_dash {
1513        return false;
1514    }
1515
1516    if name == "annotation-xml" ||
1517        name == "color-profile" ||
1518        name == "font-face" ||
1519        name == "font-face-src" ||
1520        name == "font-face-uri" ||
1521        name == "font-face-format" ||
1522        name == "font-face-name" ||
1523        name == "missing-glyph"
1524    {
1525        return false;
1526    }
1527
1528    true
1529}
1530
1531/// Check if this character is a PCENChar
1532/// <https://html.spec.whatwg.org/multipage/#prod-pcenchar>
1533fn is_potential_custom_element_char(c: char) -> bool {
1534    c == '-' ||
1535        c == '.' ||
1536        c == '_' ||
1537        c == '\u{B7}' ||
1538        c.is_ascii_digit() ||
1539        c.is_ascii_lowercase() ||
1540        ('\u{C0}'..='\u{D6}').contains(&c) ||
1541        ('\u{D8}'..='\u{F6}').contains(&c) ||
1542        ('\u{F8}'..='\u{37D}').contains(&c) ||
1543        ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1544        ('\u{200C}'..='\u{200D}').contains(&c) ||
1545        ('\u{203F}'..='\u{2040}').contains(&c) ||
1546        ('\u{2070}'..='\u{218F}').contains(&c) ||
1547        ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1548        ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1549        ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1550        ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1551        ('\u{10000}'..='\u{EFFFF}').contains(&c)
1552}
1553
1554fn is_extendable_element_interface(element: &str) -> bool {
1555    element == "a" ||
1556        element == "abbr" ||
1557        element == "acronym" ||
1558        element == "address" ||
1559        element == "area" ||
1560        element == "article" ||
1561        element == "aside" ||
1562        element == "audio" ||
1563        element == "b" ||
1564        element == "base" ||
1565        element == "bdi" ||
1566        element == "bdo" ||
1567        element == "big" ||
1568        element == "blockquote" ||
1569        element == "body" ||
1570        element == "br" ||
1571        element == "button" ||
1572        element == "canvas" ||
1573        element == "caption" ||
1574        element == "center" ||
1575        element == "cite" ||
1576        element == "code" ||
1577        element == "col" ||
1578        element == "colgroup" ||
1579        element == "data" ||
1580        element == "datalist" ||
1581        element == "dd" ||
1582        element == "del" ||
1583        element == "details" ||
1584        element == "dfn" ||
1585        element == "dialog" ||
1586        element == "dir" ||
1587        element == "div" ||
1588        element == "dl" ||
1589        element == "dt" ||
1590        element == "em" ||
1591        element == "embed" ||
1592        element == "fieldset" ||
1593        element == "figcaption" ||
1594        element == "figure" ||
1595        element == "font" ||
1596        element == "footer" ||
1597        element == "form" ||
1598        element == "frame" ||
1599        element == "frameset" ||
1600        element == "h1" ||
1601        element == "h2" ||
1602        element == "h3" ||
1603        element == "h4" ||
1604        element == "h5" ||
1605        element == "h6" ||
1606        element == "head" ||
1607        element == "header" ||
1608        element == "hgroup" ||
1609        element == "hr" ||
1610        element == "html" ||
1611        element == "i" ||
1612        element == "iframe" ||
1613        element == "img" ||
1614        element == "input" ||
1615        element == "ins" ||
1616        element == "kbd" ||
1617        element == "label" ||
1618        element == "legend" ||
1619        element == "li" ||
1620        element == "link" ||
1621        element == "listing" ||
1622        element == "main" ||
1623        element == "map" ||
1624        element == "mark" ||
1625        element == "marquee" ||
1626        element == "menu" ||
1627        element == "meta" ||
1628        element == "meter" ||
1629        element == "nav" ||
1630        element == "nobr" ||
1631        element == "noframes" ||
1632        element == "noscript" ||
1633        element == "object" ||
1634        element == "ol" ||
1635        element == "optgroup" ||
1636        element == "option" ||
1637        element == "output" ||
1638        element == "p" ||
1639        element == "param" ||
1640        element == "picture" ||
1641        element == "plaintext" ||
1642        element == "pre" ||
1643        element == "progress" ||
1644        element == "q" ||
1645        element == "rp" ||
1646        element == "rt" ||
1647        element == "ruby" ||
1648        element == "s" ||
1649        element == "samp" ||
1650        element == "script" ||
1651        element == "section" ||
1652        element == "select" ||
1653        element == "slot" ||
1654        element == "small" ||
1655        element == "source" ||
1656        element == "span" ||
1657        element == "strike" ||
1658        element == "strong" ||
1659        element == "style" ||
1660        element == "sub" ||
1661        element == "summary" ||
1662        element == "sup" ||
1663        element == "table" ||
1664        element == "tbody" ||
1665        element == "td" ||
1666        element == "template" ||
1667        element == "textarea" ||
1668        element == "tfoot" ||
1669        element == "th" ||
1670        element == "thead" ||
1671        element == "time" ||
1672        element == "title" ||
1673        element == "tr" ||
1674        element == "tt" ||
1675        element == "track" ||
1676        element == "u" ||
1677        element == "ul" ||
1678        element == "var" ||
1679        element == "video" ||
1680        element == "wbr" ||
1681        element == "xmp"
1682}