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