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