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