Skip to main content

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