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