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