Skip to main content

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