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) => {
615                definition
616                    .constructor
617                    .safe_to_jsval(cx, retval, CanGc::deprecated_note())
618            },
619            None => retval.set(UndefinedValue()),
620        }
621    }
622
623    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-getname>
624    fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
625        self.definitions
626            .borrow()
627            .0
628            .values()
629            .find(|definition| definition.constructor == constructor)
630            .map(|definition| DOMString::from(definition.name.to_string()))
631    }
632
633    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-whendefined>
634    fn WhenDefined(&self, name: DOMString, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
635        let name = LocalName::from(name);
636
637        // Step 1
638        if !is_valid_custom_element_name(&name) {
639            let promise = Promise::new_in_current_realm(comp, can_gc);
640            promise.reject_native(
641                &DOMException::new(
642                    self.window.as_global_scope(),
643                    DOMErrorName::SyntaxError,
644                    can_gc,
645                ),
646                can_gc,
647            );
648            return promise;
649        }
650
651        // Step 2
652        if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
653            let cx = GlobalScope::get_cx();
654            rooted!(in(*cx) let mut constructor = UndefinedValue());
655            definition
656                .constructor
657                .safe_to_jsval(cx, constructor.handle_mut(), can_gc);
658            let promise = Promise::new_in_current_realm(comp, can_gc);
659            promise.resolve_native(&constructor.get(), can_gc);
660            return promise;
661        }
662
663        // Steps 3, 4, 5, 6
664        let existing_promise = self.when_defined.borrow().get(&name).cloned();
665        existing_promise.unwrap_or_else(|| {
666            let promise = Promise::new_in_current_realm(comp, can_gc);
667            self.when_defined.borrow_mut().insert(name, promise.clone());
668            promise
669        })
670    }
671    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-upgrade>
672    fn Upgrade(&self, node: &Node) {
673        // Spec says to make a list first and then iterate the list, but
674        // try-to-upgrade only queues upgrade reactions and doesn't itself
675        // modify the tree, so that's not an observable distinction.
676        node.traverse_preorder(ShadowIncluding::Yes).for_each(|n| {
677            if let Some(element) = n.downcast::<Element>() {
678                try_upgrade_element(element);
679            }
680        });
681    }
682}
683
684#[derive(Clone, JSTraceable, MallocSizeOf)]
685pub(crate) struct LifecycleCallbacks {
686    #[conditional_malloc_size_of]
687    connected_callback: Option<Rc<Function>>,
688
689    #[conditional_malloc_size_of]
690    connected_move_callback: Option<Rc<Function>>,
691
692    #[conditional_malloc_size_of]
693    disconnected_callback: Option<Rc<Function>>,
694
695    #[conditional_malloc_size_of]
696    adopted_callback: Option<Rc<Function>>,
697
698    #[conditional_malloc_size_of]
699    attribute_changed_callback: Option<Rc<Function>>,
700
701    #[conditional_malloc_size_of]
702    form_associated_callback: Option<Rc<Function>>,
703
704    #[conditional_malloc_size_of]
705    form_reset_callback: Option<Rc<Function>>,
706
707    #[conditional_malloc_size_of]
708    form_disabled_callback: Option<Rc<Function>>,
709
710    #[conditional_malloc_size_of]
711    form_state_restore_callback: Option<Rc<Function>>,
712}
713
714#[derive(Clone, JSTraceable, MallocSizeOf)]
715pub(crate) enum ConstructionStackEntry {
716    Element(DomRoot<Element>),
717    AlreadyConstructedMarker,
718}
719
720/// <https://html.spec.whatwg.org/multipage/#custom-element-definition>
721#[derive(Clone, JSTraceable, MallocSizeOf)]
722pub(crate) struct CustomElementDefinition {
723    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-name>
724    #[no_trace]
725    pub(crate) name: LocalName,
726
727    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-local-name>
728    #[no_trace]
729    pub(crate) local_name: LocalName,
730
731    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-constructor>
732    #[conditional_malloc_size_of]
733    pub(crate) constructor: Rc<CustomElementConstructor>,
734
735    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-observed-attributes>
736    pub(crate) observed_attributes: Vec<DOMString>,
737
738    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-lifecycle-callbacks>
739    pub(crate) callbacks: LifecycleCallbacks,
740
741    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-construction-stack>
742    pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
743
744    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-form-associated>
745    pub(crate) form_associated: bool,
746
747    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-internals>
748    pub(crate) disable_internals: bool,
749
750    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-shadow>
751    pub(crate) disable_shadow: bool,
752}
753
754impl CustomElementDefinition {
755    #[expect(clippy::too_many_arguments)]
756    fn new(
757        name: LocalName,
758        local_name: LocalName,
759        constructor: Rc<CustomElementConstructor>,
760        observed_attributes: Vec<DOMString>,
761        callbacks: LifecycleCallbacks,
762        form_associated: bool,
763        disable_internals: bool,
764        disable_shadow: bool,
765    ) -> CustomElementDefinition {
766        CustomElementDefinition {
767            name,
768            local_name,
769            constructor,
770            observed_attributes,
771            callbacks,
772            construction_stack: Default::default(),
773            form_associated,
774            disable_internals,
775            disable_shadow,
776        }
777    }
778
779    /// <https://html.spec.whatwg.org/multipage/#autonomous-custom-element>
780    pub(crate) fn is_autonomous(&self) -> bool {
781        self.name == self.local_name
782    }
783
784    /// <https://dom.spec.whatwg.org/#concept-create-element> Step 5.1
785    #[expect(unsafe_code)]
786    pub(crate) fn create_element(
787        &self,
788        document: &Document,
789        prefix: Option<Prefix>,
790        registry: Option<DomRoot<CustomElementRegistry>>,
791        // This function can cause GC through AutoEntryScript::Drop, but we can't pass a CanGc there
792        can_gc: CanGc,
793    ) -> Fallible<DomRoot<Element>> {
794        let window = document.window();
795        let cx = GlobalScope::get_cx();
796        // Step 5.1.1. Let C be definition’s constructor.
797        rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback()));
798        rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>());
799        {
800            // Go into the constructor's realm
801            let _ac = JSAutoRealm::new(*cx, self.constructor.callback());
802            // Step 5.3.1. Set result to the result of constructing C, with no arguments.
803            // https://webidl.spec.whatwg.org/#construct-a-callback-function
804            run_a_script::<DomTypeHolder, _>(window.upcast(), || {
805                run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
806                    let args = HandleValueArray::empty();
807                    if unsafe {
808                        !Construct1(*cx, constructor.handle(), &args, element.handle_mut())
809                    } {
810                        Err(Error::JSFailed)
811                    } else {
812                        Ok(())
813                    }
814                })
815            })?;
816        }
817
818        rooted!(in(*cx) let element_val = ObjectValue(element.get()));
819        let element: DomRoot<Element> =
820            match SafeFromJSValConvertible::safe_from_jsval(cx, element_val.handle(), (), can_gc) {
821                Ok(ConversionResult::Success(element)) => element,
822                Ok(ConversionResult::Failure(..)) => {
823                    return Err(Error::Type(
824                        c"Constructor did not return a DOM node".to_owned(),
825                    ));
826                },
827                _ => return Err(Error::JSFailed),
828            };
829
830        // Step 5.1.3.2 Assert: result’s custom element state and custom element definition are initialized.
831        // Step 5.1.3.3 Assert: result’s namespace is the HTML namespace.
832        // Note: IDL enforces that result is an HTMLElement object, which all use the HTML namespace.
833        // Note: the custom element definition is initialized by the caller if
834        // this method returns a success value.
835        assert!(element.is::<HTMLElement>());
836
837        // Step 5.1.3.4. If result’s attribute list is not empty, then throw a "NotSupportedError" DOMException.
838        // Step 5.1.3.5. If result has children, then throw a "NotSupportedError" DOMException.
839        // Step 5.1.3.6. If result’s parent is not null, then throw a "NotSupportedError" DOMException.
840        // Step 5.1.3.7. If result’s node document is not document, then throw a "NotSupportedError" DOMException.
841        // Step 5.1.3.8. If result’s local name is not equal to localName then throw a "NotSupportedError" DOMException.
842        if element.HasAttributes() ||
843            element.upcast::<Node>().children_count() > 0 ||
844            element.upcast::<Node>().has_parent() ||
845            &*element.upcast::<Node>().owner_doc() != document ||
846            *element.namespace() != ns!(html) ||
847            *element.local_name() != self.local_name
848        {
849            return Err(Error::NotSupported(None));
850        }
851
852        // Step 5.1.3.9. Set result’s namespace prefix to prefix.
853        element.set_prefix(prefix);
854
855        // Step 5.1.3.10. Set result’s is value to null.
856        // Element's `is` is None by default
857
858        // Step 5.1.3.11. Set result’s custom element registry to registry.
859        element.set_custom_element_registry(registry);
860
861        Ok(element)
862    }
863}
864
865/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element>
866pub(crate) fn upgrade_element(
867    cx: &mut js::context::JSContext,
868    definition: Rc<CustomElementDefinition>,
869    element: &Element,
870) {
871    // Step 1. If element's custom element state is not "undefined" or "uncustomized", then return.
872    let state = element.get_custom_element_state();
873    if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
874        return;
875    }
876
877    // Step 2. Set element's custom element definition to definition.
878    element.set_custom_element_definition(Rc::clone(&definition));
879
880    // Step 3. Set element's custom element state to "failed".
881    element.set_custom_element_state(CustomElementState::Failed);
882
883    // Step 4. For each attribute in element's attribute list, in order, enqueue a custom element callback reaction
884    // with element, callback name "attributeChangedCallback", and « attribute's local name, null, attribute's value,
885    // attribute's namespace ».
886    let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
887    for attr in element.attrs().iter() {
888        let local_name = attr.local_name().clone();
889        let value = DOMString::from(&**attr.value());
890        let namespace = attr.namespace().clone();
891        custom_element_reaction_stack.enqueue_callback_reaction(
892            element,
893            CallbackReaction::AttributeChanged(local_name, None, Some(value), namespace),
894            Some(definition.clone()),
895        );
896    }
897
898    // Step 5. If element is connected, then enqueue a custom element callback reaction with element,
899    // callback name "connectedCallback", and « ».
900    if element.is_connected() {
901        ScriptThread::enqueue_callback_reaction(
902            element,
903            CallbackReaction::Connected,
904            Some(definition.clone()),
905        );
906    }
907
908    // Step 6. Add element to the end of definition's construction stack.
909    definition
910        .construction_stack
911        .borrow_mut()
912        .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
913
914    // Steps 7-8, successful case
915    let result = run_upgrade_constructor(cx, &definition, element);
916
917    // "regardless of whether the above steps threw an exception" step
918    definition.construction_stack.borrow_mut().pop();
919
920    // Step 8 exception handling
921    if let Err(error) = result {
922        // Step 8.exception.1
923        element.clear_custom_element_definition();
924
925        // Step 8.exception.2
926        element.clear_reaction_queue();
927
928        // Step 8.exception.3
929        let global = GlobalScope::current().expect("No current global");
930
931        let mut realm = enter_auto_realm(cx, &*global);
932        let cx = &mut realm.current_realm();
933
934        let in_realm_proof = cx.into();
935        let in_realm = InRealm::Already(&in_realm_proof);
936
937        throw_dom_exception(cx.into(), &global, error, CanGc::from_cx(cx));
938        report_pending_exception(cx.into(), in_realm, CanGc::from_cx(cx));
939
940        return;
941    }
942
943    // Step 9: handle with form-associated custom element
944    if let Some(html_element) = element.downcast::<HTMLElement>() {
945        if html_element.is_form_associated_custom_element() {
946            // We know this element is is form-associated, so we can use the implementation of
947            // `FormControl` for HTMLElement, which makes that assumption.
948            // Step 9.1: Reset the form owner of element
949            html_element.reset_form_owner(CanGc::from_cx(cx));
950            if let Some(form) = html_element.form_owner() {
951                // Even though the tree hasn't structurally mutated,
952                // HTMLCollections need to be invalidated.
953                form.upcast::<Node>().rev_version();
954                // The spec tells us specifically to enqueue a formAssociated reaction
955                // here, but it also says to do that for resetting form owner in general,
956                // and we don't need two reactions.
957            }
958
959            // Either enabled_state or disabled_state needs to be set,
960            // and the possibility of a disabled fieldset ancestor needs
961            // to be accounted for. (In the spec, being disabled is
962            // a fact that's true or false about a node at a given time,
963            // not a flag that belongs to the node and is updated,
964            // so it doesn't describe this check as an action.)
965            element.check_disabled_attribute();
966            element.check_ancestors_disabled_state_for_form_control();
967            element.update_read_write_state_from_readonly_attribute();
968
969            // Step 9.2: If element is disabled, then enqueue a custom element callback reaction
970            // with element.
971            if element.disabled_state() {
972                ScriptThread::enqueue_callback_reaction(
973                    element,
974                    CallbackReaction::FormDisabled(true),
975                    Some(definition),
976                )
977            }
978        }
979    }
980
981    // Step 10
982    element.set_custom_element_state(CustomElementState::Custom);
983}
984
985/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element>
986/// Steps 9.1-9.4
987#[expect(unsafe_code)]
988fn run_upgrade_constructor(
989    cx: &mut js::context::JSContext,
990    definition: &CustomElementDefinition,
991    element: &Element,
992) -> ErrorResult {
993    let constructor = &definition.constructor;
994    let window = element.owner_window();
995    rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
996    rooted!(&in(cx) let mut element_val = UndefinedValue());
997    element.safe_to_jsval(cx.into(), element_val.handle_mut(), CanGc::from_cx(cx));
998    rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
999    {
1000        // Step 9.1. If definition's disable shadow is true and element's shadow root is non-null,
1001        // then throw a "NotSupportedError" DOMException.
1002        if definition.disable_shadow && element.is_shadow_host() {
1003            return Err(Error::NotSupported(None));
1004        }
1005
1006        // Go into the constructor's realm
1007        let mut realm = AutoRealm::new(cx, std::ptr::NonNull::new(constructor.callback()).unwrap());
1008        let cx = &mut *realm;
1009
1010        let args = HandleValueArray::empty();
1011        // Step 8.2. Set element's custom element state to "precustomized".
1012        element.set_custom_element_state(CustomElementState::Precustomized);
1013
1014        // Step 9.3. Let constructResult be the result of constructing C, with no arguments.
1015        // https://webidl.spec.whatwg.org/#construct-a-callback-function
1016        run_a_script::<DomTypeHolder, _>(window.upcast(), || {
1017            run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
1018                if unsafe {
1019                    !Construct1(
1020                        cx.raw_cx(),
1021                        constructor_val.handle(),
1022                        &args,
1023                        construct_result.handle_mut(),
1024                    )
1025                } {
1026                    Err(Error::JSFailed)
1027                } else {
1028                    Ok(())
1029                }
1030            })
1031        })?;
1032
1033        let mut same = false;
1034        rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
1035
1036        // Step 9.4. If SameValue(constructResult, element) is false, then throw a TypeError.
1037        if unsafe {
1038            !SameValue(
1039                cx,
1040                construct_result_val.handle(),
1041                element_val.handle(),
1042                &mut same,
1043            )
1044        } {
1045            return Err(Error::JSFailed);
1046        }
1047        if !same {
1048            return Err(Error::Type(
1049                c"Returned element is not SameValue as the upgraded element".to_owned(),
1050            ));
1051        }
1052    }
1053    Ok(())
1054}
1055
1056/// <https://html.spec.whatwg.org/multipage/#concept-try-upgrade>
1057pub(crate) fn try_upgrade_element(element: &Element) {
1058    // Step 1. Let definition be the result of looking up a custom element definition given element's node document,
1059    // element's namespace, element's local name, and element's is value.
1060    let document = element.owner_document();
1061    let namespace = element.namespace();
1062    let local_name = element.local_name();
1063    let is = element.get_is();
1064    if let Some(definition) =
1065        document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
1066    {
1067        // Step 2. If definition is not null, then enqueue a custom element upgrade reaction given
1068        // element and definition.
1069        ScriptThread::enqueue_upgrade_reaction(element, definition);
1070    }
1071}
1072
1073#[derive(JSTraceable, MallocSizeOf)]
1074#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1075pub(crate) enum CustomElementReaction {
1076    Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1077    Callback(
1078        #[conditional_malloc_size_of] Rc<Function>,
1079        #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1080    ),
1081}
1082
1083impl CustomElementReaction {
1084    /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions>
1085    pub(crate) fn invoke(&self, cx: &mut js::context::JSContext, element: &Element) {
1086        // Step 2.1
1087        match *self {
1088            CustomElementReaction::Upgrade(ref definition) => {
1089                upgrade_element(cx, definition.clone(), element)
1090            },
1091            CustomElementReaction::Callback(ref callback, ref arguments) => {
1092                // We're rooted, so it's safe to hand out a handle to objects in Heap
1093                let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1094                rooted!(&in(cx) let mut value: JSVal);
1095                let _ = callback.Call_(
1096                    element,
1097                    arguments,
1098                    value.handle_mut(),
1099                    ExceptionHandling::Report,
1100                    CanGc::from_cx(cx),
1101                );
1102            },
1103        }
1104    }
1105}
1106
1107pub(crate) enum CallbackReaction {
1108    Connected,
1109    Disconnected,
1110    Adopted(DomRoot<Document>, DomRoot<Document>),
1111    AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace),
1112    FormAssociated(Option<DomRoot<HTMLFormElement>>),
1113    FormDisabled(bool),
1114    FormReset,
1115    ConnectedMove,
1116}
1117
1118/// <https://html.spec.whatwg.org/multipage/#processing-the-backup-element-queue>
1119#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1120enum BackupElementQueueFlag {
1121    Processing,
1122    NotProcessing,
1123}
1124
1125/// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack>
1126/// # Safety
1127/// This can be shared inside an Rc because one of those Rc copies lives
1128/// inside ScriptThread, so the GC can always reach this structure.
1129#[derive(JSTraceable, MallocSizeOf)]
1130#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1131#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1132pub(crate) struct CustomElementReactionStack {
1133    stack: DomRefCell<Vec<ElementQueue>>,
1134    backup_queue: ElementQueue,
1135    processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1136}
1137
1138impl CustomElementReactionStack {
1139    pub(crate) fn new() -> CustomElementReactionStack {
1140        CustomElementReactionStack {
1141            stack: DomRefCell::new(Vec::new()),
1142            backup_queue: ElementQueue::new(),
1143            processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1144        }
1145    }
1146
1147    pub(crate) fn push_new_element_queue(&self) {
1148        self.stack.borrow_mut().push(ElementQueue::new());
1149    }
1150
1151    pub(crate) fn pop_current_element_queue(&self, cx: &mut js::context::JSContext) {
1152        rooted_vec!(let mut stack);
1153        mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1154
1155        if let Some(current_queue) = stack.last() {
1156            current_queue.invoke_reactions(cx);
1157        }
1158        stack.pop();
1159
1160        mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1161        self.stack.borrow_mut().append(&mut *stack);
1162    }
1163
1164    /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue>
1165    /// Step 4
1166    pub(crate) fn invoke_backup_element_queue(&self, cx: &mut js::context::JSContext) {
1167        // Step 4.1
1168        self.backup_queue.invoke_reactions(cx);
1169
1170        // Step 4.2
1171        self.processing_backup_element_queue
1172            .set(BackupElementQueueFlag::NotProcessing);
1173    }
1174
1175    /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue>
1176    pub(crate) fn enqueue_element(&self, element: &Element) {
1177        if let Some(current_queue) = self.stack.borrow().last() {
1178            // Step 2
1179            current_queue.append_element(element);
1180        } else {
1181            // Step 1.1
1182            self.backup_queue.append_element(element);
1183
1184            // Step 1.2
1185            if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1186                return;
1187            }
1188
1189            // Step 1.3
1190            self.processing_backup_element_queue
1191                .set(BackupElementQueueFlag::Processing);
1192
1193            // Step 4
1194            ScriptThread::enqueue_microtask(Microtask::CustomElementReaction);
1195        }
1196    }
1197
1198    /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-callback-reaction>
1199    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1200    pub(crate) fn enqueue_callback_reaction(
1201        &self,
1202        element: &Element,
1203        reaction: CallbackReaction,
1204        definition: Option<Rc<CustomElementDefinition>>,
1205    ) {
1206        // Step 1. Let definition be element's custom element definition.
1207        let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1208            Some(definition) => definition,
1209            None => return,
1210        };
1211
1212        // Step 2. Let callback be the value of the entry in definition's lifecycle callbacks with
1213        // key callbackName.
1214        let (callback, args) = match reaction {
1215            CallbackReaction::Connected => {
1216                (definition.callbacks.connected_callback.clone(), Vec::new())
1217            },
1218            CallbackReaction::Disconnected => (
1219                definition.callbacks.disconnected_callback.clone(),
1220                Vec::new(),
1221            ),
1222            CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1223                let args = vec![Heap::default(), Heap::default()];
1224                args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1225                args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1226                (definition.callbacks.adopted_callback.clone(), args)
1227            },
1228            CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1229                // Step 5.
1230                if !definition
1231                    .observed_attributes
1232                    .iter()
1233                    .any(|attr| *attr == *local_name)
1234                {
1235                    return;
1236                }
1237
1238                let cx = GlobalScope::get_cx();
1239                // We might be here during HTML parsing, rather than
1240                // during Javscript execution, and so we typically aren't
1241                // already in a realm here.
1242                let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get());
1243
1244                let local_name = DOMString::from(&*local_name);
1245                rooted!(in(*cx) let mut name_value = UndefinedValue());
1246                local_name.safe_to_jsval(cx, name_value.handle_mut(), CanGc::deprecated_note());
1247
1248                rooted!(in(*cx) let mut old_value = NullValue());
1249                if let Some(old_val) = old_val {
1250                    old_val.safe_to_jsval(cx, old_value.handle_mut(), CanGc::deprecated_note());
1251                }
1252
1253                rooted!(in(*cx) let mut value = NullValue());
1254                if let Some(val) = val {
1255                    val.safe_to_jsval(cx, value.handle_mut(), CanGc::deprecated_note());
1256                }
1257
1258                rooted!(in(*cx) let mut namespace_value = NullValue());
1259                if namespace != ns!() {
1260                    let namespace = DOMString::from(&*namespace);
1261                    namespace.safe_to_jsval(
1262                        cx,
1263                        namespace_value.handle_mut(),
1264                        CanGc::deprecated_note(),
1265                    );
1266                }
1267
1268                let args = vec![
1269                    Heap::default(),
1270                    Heap::default(),
1271                    Heap::default(),
1272                    Heap::default(),
1273                ];
1274                args[0].set(name_value.get());
1275                args[1].set(old_value.get());
1276                args[2].set(value.get());
1277                args[3].set(namespace_value.get());
1278
1279                (
1280                    definition.callbacks.attribute_changed_callback.clone(),
1281                    args,
1282                )
1283            },
1284            CallbackReaction::FormAssociated(form) => {
1285                let args = vec![Heap::default()];
1286                if let Some(form) = form {
1287                    args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1288                } else {
1289                    args[0].set(NullValue());
1290                }
1291                (definition.callbacks.form_associated_callback.clone(), args)
1292            },
1293            CallbackReaction::FormDisabled(disabled) => {
1294                let cx = GlobalScope::get_cx();
1295                rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
1296                let args = vec![Heap::default()];
1297                args[0].set(disabled_value.get());
1298                (definition.callbacks.form_disabled_callback.clone(), args)
1299            },
1300            CallbackReaction::FormReset => {
1301                (definition.callbacks.form_reset_callback.clone(), Vec::new())
1302            },
1303            CallbackReaction::ConnectedMove => {
1304                let callback = definition.callbacks.connected_move_callback.clone();
1305                // Step 3. If callbackName is "connectedMoveCallback" and callback is null:
1306                if callback.is_none() {
1307                    // Step 3.1. Let disconnectedCallback be the value of the entry in
1308                    // definition's lifecycle callbacks with key "disconnectedCallback".
1309                    let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1310
1311                    // Step 3.2. Let connectedCallback be the value of the entry in
1312                    // definition's lifecycle callbacks with key "connectedCallback".
1313                    let connected_callback = definition.callbacks.connected_callback.clone();
1314
1315                    // Step 3.3. If connectedCallback and disconnectedCallback are null,
1316                    // then return.
1317                    if disconnected_callback.is_none() && connected_callback.is_none() {
1318                        return;
1319                    }
1320
1321                    // Step 3.4. Set callback to the following steps:
1322                    // Step 3.4.1. If disconnectedCallback is not null, then call
1323                    // disconnectedCallback with no arguments.
1324                    if let Some(disconnected_callback) = disconnected_callback {
1325                        element.push_callback_reaction(disconnected_callback, Box::new([]));
1326                    }
1327                    // Step 3.4.2. If connectedCallback is not null, then call
1328                    // connectedCallback with no arguments.
1329                    if let Some(connected_callback) = connected_callback {
1330                        element.push_callback_reaction(connected_callback, Box::new([]));
1331                    }
1332
1333                    self.enqueue_element(element);
1334                    return;
1335                }
1336
1337                (callback, Vec::new())
1338            },
1339        };
1340
1341        // Step 4. If callback is null, then return.
1342        let callback = match callback {
1343            Some(callback) => callback,
1344            None => return,
1345        };
1346
1347        // Step 6. Add a new callback reaction to element's custom element reaction queue, with
1348        // callback function callback and arguments args.
1349        element.push_callback_reaction(callback, args.into_boxed_slice());
1350
1351        // Step 7. Enqueue an element on the appropriate element queue given element.
1352        self.enqueue_element(element);
1353    }
1354
1355    /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-upgrade-reaction>
1356    pub(crate) fn enqueue_upgrade_reaction(
1357        &self,
1358        element: &Element,
1359        definition: Rc<CustomElementDefinition>,
1360    ) {
1361        // Step 1. Add a new upgrade reaction to element's custom element reaction queue,
1362        // with custom element definition definition.
1363        element.push_upgrade_reaction(definition);
1364
1365        // Step 2. Enqueue an element on the appropriate element queue given element.
1366        self.enqueue_element(element);
1367    }
1368}
1369
1370/// <https://html.spec.whatwg.org/multipage/#element-queue>
1371#[derive(JSTraceable, MallocSizeOf)]
1372#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1373struct ElementQueue {
1374    queue: DomRefCell<VecDeque<Dom<Element>>>,
1375}
1376
1377impl ElementQueue {
1378    fn new() -> ElementQueue {
1379        ElementQueue {
1380            queue: Default::default(),
1381        }
1382    }
1383
1384    /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions>
1385    fn invoke_reactions(&self, cx: &mut js::context::JSContext) {
1386        // Steps 1-2
1387        while let Some(element) = self.next_element() {
1388            element.invoke_reactions(cx)
1389        }
1390        self.queue.borrow_mut().clear();
1391    }
1392
1393    fn next_element(&self) -> Option<DomRoot<Element>> {
1394        self.queue
1395            .borrow_mut()
1396            .pop_front()
1397            .as_deref()
1398            .map(DomRoot::from_ref)
1399    }
1400
1401    fn append_element(&self, element: &Element) {
1402        self.queue.borrow_mut().push_back(Dom::from_ref(element));
1403    }
1404}
1405
1406/// <https://html.spec.whatwg.org/multipage/#valid-custom-element-name>
1407pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1408    // Custom elment names must match:
1409    // PotentialCustomElementName ::= [a-z] (PCENChar)* '-' (PCENChar)*
1410    let mut chars = name.chars();
1411    if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1412        return false;
1413    }
1414
1415    let mut has_dash = false;
1416
1417    for c in chars {
1418        if c == '-' {
1419            has_dash = true;
1420            continue;
1421        }
1422
1423        if !is_potential_custom_element_char(c) {
1424            return false;
1425        }
1426    }
1427
1428    if !has_dash {
1429        return false;
1430    }
1431
1432    if name == "annotation-xml" ||
1433        name == "color-profile" ||
1434        name == "font-face" ||
1435        name == "font-face-src" ||
1436        name == "font-face-uri" ||
1437        name == "font-face-format" ||
1438        name == "font-face-name" ||
1439        name == "missing-glyph"
1440    {
1441        return false;
1442    }
1443
1444    true
1445}
1446
1447/// Check if this character is a PCENChar
1448/// <https://html.spec.whatwg.org/multipage/#prod-pcenchar>
1449fn is_potential_custom_element_char(c: char) -> bool {
1450    c == '-' ||
1451        c == '.' ||
1452        c == '_' ||
1453        c == '\u{B7}' ||
1454        c.is_ascii_digit() ||
1455        c.is_ascii_lowercase() ||
1456        ('\u{C0}'..='\u{D6}').contains(&c) ||
1457        ('\u{D8}'..='\u{F6}').contains(&c) ||
1458        ('\u{F8}'..='\u{37D}').contains(&c) ||
1459        ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1460        ('\u{200C}'..='\u{200D}').contains(&c) ||
1461        ('\u{203F}'..='\u{2040}').contains(&c) ||
1462        ('\u{2070}'..='\u{218F}').contains(&c) ||
1463        ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1464        ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1465        ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1466        ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1467        ('\u{10000}'..='\u{EFFFF}').contains(&c)
1468}
1469
1470fn is_extendable_element_interface(element: &str) -> bool {
1471    element == "a" ||
1472        element == "abbr" ||
1473        element == "acronym" ||
1474        element == "address" ||
1475        element == "area" ||
1476        element == "article" ||
1477        element == "aside" ||
1478        element == "audio" ||
1479        element == "b" ||
1480        element == "base" ||
1481        element == "bdi" ||
1482        element == "bdo" ||
1483        element == "big" ||
1484        element == "blockquote" ||
1485        element == "body" ||
1486        element == "br" ||
1487        element == "button" ||
1488        element == "canvas" ||
1489        element == "caption" ||
1490        element == "center" ||
1491        element == "cite" ||
1492        element == "code" ||
1493        element == "col" ||
1494        element == "colgroup" ||
1495        element == "data" ||
1496        element == "datalist" ||
1497        element == "dd" ||
1498        element == "del" ||
1499        element == "details" ||
1500        element == "dfn" ||
1501        element == "dialog" ||
1502        element == "dir" ||
1503        element == "div" ||
1504        element == "dl" ||
1505        element == "dt" ||
1506        element == "em" ||
1507        element == "embed" ||
1508        element == "fieldset" ||
1509        element == "figcaption" ||
1510        element == "figure" ||
1511        element == "font" ||
1512        element == "footer" ||
1513        element == "form" ||
1514        element == "frame" ||
1515        element == "frameset" ||
1516        element == "h1" ||
1517        element == "h2" ||
1518        element == "h3" ||
1519        element == "h4" ||
1520        element == "h5" ||
1521        element == "h6" ||
1522        element == "head" ||
1523        element == "header" ||
1524        element == "hgroup" ||
1525        element == "hr" ||
1526        element == "html" ||
1527        element == "i" ||
1528        element == "iframe" ||
1529        element == "img" ||
1530        element == "input" ||
1531        element == "ins" ||
1532        element == "kbd" ||
1533        element == "label" ||
1534        element == "legend" ||
1535        element == "li" ||
1536        element == "link" ||
1537        element == "listing" ||
1538        element == "main" ||
1539        element == "map" ||
1540        element == "mark" ||
1541        element == "marquee" ||
1542        element == "menu" ||
1543        element == "meta" ||
1544        element == "meter" ||
1545        element == "nav" ||
1546        element == "nobr" ||
1547        element == "noframes" ||
1548        element == "noscript" ||
1549        element == "object" ||
1550        element == "ol" ||
1551        element == "optgroup" ||
1552        element == "option" ||
1553        element == "output" ||
1554        element == "p" ||
1555        element == "param" ||
1556        element == "picture" ||
1557        element == "plaintext" ||
1558        element == "pre" ||
1559        element == "progress" ||
1560        element == "q" ||
1561        element == "rp" ||
1562        element == "rt" ||
1563        element == "ruby" ||
1564        element == "s" ||
1565        element == "samp" ||
1566        element == "script" ||
1567        element == "section" ||
1568        element == "select" ||
1569        element == "slot" ||
1570        element == "small" ||
1571        element == "source" ||
1572        element == "span" ||
1573        element == "strike" ||
1574        element == "strong" ||
1575        element == "style" ||
1576        element == "sub" ||
1577        element == "summary" ||
1578        element == "sup" ||
1579        element == "table" ||
1580        element == "tbody" ||
1581        element == "td" ||
1582        element == "template" ||
1583        element == "textarea" ||
1584        element == "tfoot" ||
1585        element == "th" ||
1586        element == "thead" ||
1587        element == "time" ||
1588        element == "title" ||
1589        element == "tr" ||
1590        element == "tt" ||
1591        element == "track" ||
1592        element == "u" ||
1593        element == "ul" ||
1594        element == "var" ||
1595        element == "video" ||
1596        element == "wbr" ||
1597        element == "xmp"
1598}