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