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