1use 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;
16use js::glue::UnwrapObjectStatic;
17use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSObject};
18use js::jsval::{BooleanValue, JSVal, NullValue, ObjectValue, UndefinedValue};
19use js::realm::{AutoRealm, CurrentRealm};
20use js::rust::wrappers2::{Construct1, JS_GetProperty, SameValue};
21use js::rust::{HandleObject, MutableHandleValue};
22use rustc_hash::FxBuildHasher;
23use script_bindings::cell::DomRefCell;
24use script_bindings::conversions::SafeToJSValConvertible;
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 super::bindings::trace::HashMapTracedValues;
30use crate::DomTypeHolder;
31use crate::dom::bindings::callback::{CallbackContainer, ExceptionHandling};
32use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::{
33 CustomElementConstructor, CustomElementRegistryMethods, ElementDefinitionOptions,
34};
35use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
36use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
37use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
38use crate::dom::bindings::conversions::{ConversionResult, StringificationBehavior, get_property};
39use crate::dom::bindings::error::{
40 Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception,
41};
42use crate::dom::bindings::inheritance::{Castable, DocumentFragmentTypeId, NodeTypeId};
43use crate::dom::bindings::reflector::DomGlobal;
44use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot, UnrootedDom};
45use crate::dom::bindings::str::DOMString;
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;
56use crate::dom::window::Window;
57use crate::microtask::CustomElementReactionMicrotask;
58use crate::realms::enter_auto_realm;
59use crate::script_thread::ScriptThread;
60
61#[derive(Clone, Copy, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
63pub(crate) enum CustomElementState {
64 Undefined,
65 Failed,
66 #[default]
67 Uncustomized,
68 Precustomized,
69 Custom,
70}
71
72#[dom_struct]
74pub(crate) struct CustomElementRegistry {
75 reflector_: Reflector,
76
77 window: Dom<Window>,
78
79 #[conditional_malloc_size_of]
80 when_defined: DomRefCell<HashMapTracedValues<LocalName, Rc<Promise>, FxBuildHasher>>,
84
85 element_definition_is_running: Cell<bool>,
87
88 is_scoped: Cell<bool>,
90
91 scoped_document_set: DomRefCell<Vec<Dom<Document>>>,
93
94 #[conditional_malloc_size_of]
95 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: DomRefCell::new(Vec::new()),
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 pub(crate) fn is_scoped(&self) -> bool {
132 self.is_scoped.get()
133 }
134
135 pub(crate) fn teardown(&self) {
138 self.when_defined.borrow_mut().0.clear()
139 }
140
141 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 pub(crate) fn lookup_a_custom_element_registry(
158 node: &Node,
159 ) -> Option<DomRoot<CustomElementRegistry>> {
160 match node.type_id() {
161 NodeTypeId::Element(_) => node
163 .downcast::<Element>()
164 .expect("Nodes with element type must be an element")
165 .custom_element_registry(),
166 NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) => node
168 .downcast::<ShadowRoot>()
169 .expect("Nodes with ShadowRoot type must be a ShadowRoot")
170 .custom_element_registry(),
171 NodeTypeId::Document(_) => node
173 .downcast::<Document>()
174 .expect("Nodes with document type must be a document")
175 .custom_element_registry(),
176 _ => None,
178 }
179 }
180
181 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 let registry = registry?;
190
191 if *namespace != ns!(html) {
193 return None;
194 }
195
196 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 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
217 registry.is_some_and(|r| !r.is_scoped())
220 }
221
222 #[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 if !JS_GetProperty(cx, constructor, c"prototype".as_ptr(), prototype.reborrow()) {
234 return Err(Error::JSFailed);
235 }
236
237 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 fn get_callbacks(
251 &self,
252 cx: &mut JSContext,
253 prototype: HandleObject,
254 ) -> Fallible<LifecycleCallbacks> {
255 Ok(LifecycleCallbacks {
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 #[expect(unsafe_code)]
273 unsafe fn add_form_associated_callbacks(
274 &self,
275 cx: &mut JSContext,
276 prototype: HandleObject,
277 callbacks: &mut LifecycleCallbacks,
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 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 for candidate in document
304 .upcast::<Node>()
305 .traverse_preorder_non_rooting(cx, ShadowIncluding::Yes)
306 .filter_map(UnrootedDom::downcast::<Element>)
307 {
308 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 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 .push(Dom::from_ref(document));
337 }
338}
339
340#[expect(unsafe_code)]
343fn get_callback(
344 cx: &mut JSContext,
345 prototype: HandleObject,
346 name: &CStr,
347) -> Fallible<Option<Rc<Function>>> {
348 rooted!(&in(cx) let mut callback = UndefinedValue());
349 unsafe {
350 if !JS_GetProperty(cx, prototype, name.as_ptr(), callback.handle_mut()) {
352 return Err(Error::JSFailed);
353 }
354
355 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 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 registry.is_scoped.set(true);
380 registry
381 }
382
383 #[expect(unsafe_code)]
384 fn Define(
386 &self,
387 cx: &mut JSContext,
388 name: DOMString,
389 constructor_: Rc<CustomElementConstructor>,
390 options: &ElementDefinitionOptions,
391 ) -> ErrorResult {
392 rooted!(&in(cx) let constructor = constructor_.callback());
393 let name = LocalName::from(name);
394
395 rooted!(&in(cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
398
399 if unwrapped_constructor.is_null() {
400 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 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 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 if self
430 .definitions
431 .borrow()
432 .iter()
433 .any(|(_, def)| def.constructor == constructor_)
434 {
435 return Err(Error::NotSupported(None));
436 }
437
438 let extends = &options.extends;
440
441 let local_name = if let Some(ref extended_name) = *extends {
443 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 if is_valid_custom_element_name(&extended_name.str()) {
453 return Err(Error::NotSupported(None));
454 }
455
456 if !is_extendable_element_interface(&extended_name.str()) {
460 return Err(Error::NotSupported(None));
461 }
462
463 LocalName::from(extended_name)
465 } else {
466 name.clone()
468 };
469
470 if self.element_definition_is_running.get() {
472 return Err(Error::NotSupported(None));
473 }
474
475 self.element_definition_is_running.set(true);
477
478 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 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 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 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 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 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 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 self.definitions
601 .borrow_mut()
602 .insert(name.clone(), definition.clone());
603
604 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 self.upgrade_particular_elements_within_a_document(
622 cx,
623 &self.window.Document(),
624 &definition,
625 &local_name,
626 &name,
627 );
628 }
629
630 let promise = self.when_defined.borrow_mut().remove(&name);
633 if let Some(promise) = promise {
634 rooted!(&in(cx) let mut constructor = UndefinedValue());
635 definition
636 .constructor
637 .safe_to_jsval(cx, constructor.handle_mut());
638 promise.resolve_native(cx, &constructor.get());
640 }
641 Ok(())
642 }
643
644 fn Get(&self, cx: &mut JSContext, name: DOMString, mut retval: MutableHandleValue) {
646 match self.definitions.borrow().get(&LocalName::from(name)) {
647 Some(definition) => definition.constructor.safe_to_jsval(cx, retval),
648 None => retval.set(UndefinedValue()),
649 }
650 }
651
652 fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
654 self.definitions
655 .borrow()
656 .0
657 .values()
658 .find(|definition| definition.constructor == constructor)
659 .map(|definition| DOMString::from(definition.name.to_string()))
660 }
661
662 fn WhenDefined(&self, realm: &mut CurrentRealm, name: DOMString) -> Rc<Promise> {
664 let name = LocalName::from(name);
665
666 if !is_valid_custom_element_name(&name) {
668 let promise = Promise::new_in_realm(realm);
669 let error = DOMException::new(
670 realm,
671 self.window.as_global_scope(),
672 DOMErrorName::SyntaxError,
673 );
674 promise.reject_native(realm, &error);
675 return promise;
676 }
677
678 if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
680 rooted!(&in(*realm) let mut constructor = UndefinedValue());
681 definition
682 .constructor
683 .safe_to_jsval(realm, constructor.handle_mut());
684 let promise = Promise::new_in_realm(realm);
685 promise.resolve_native(realm, &constructor.get());
686 return promise;
687 }
688
689 let existing_promise = self.when_defined.borrow().get(&name).cloned();
691 existing_promise.unwrap_or_else(|| {
692 let promise = Promise::new_in_realm(realm);
693 self.when_defined.borrow_mut().insert(name, promise.clone());
694 promise
695 })
696 }
697
698 fn Upgrade(&self, cx: &JSContext, node: &Node) {
700 for node in node.traverse_preorder_non_rooting(cx, ShadowIncluding::Yes) {
703 let Some(element) = node.downcast::<Element>() else {
705 continue;
706 };
707 if element
710 .custom_element_registry()
711 .is_some_and(|registry| *registry != *self)
712 {
713 continue;
714 }
715 try_upgrade_element(cx, element);
717 }
718 }
719
720 fn Initialize(&self, cx: &JSContext, root: &Node) -> ErrorResult {
722 if !self.is_scoped.get() {
726 let is_document = root.is::<Document>();
727 let registry_mismatch = root
728 .owner_doc()
729 .custom_element_registry()
730 .is_some_and(|registry| *registry != *self);
731 if is_document || registry_mismatch {
732 return Err(Error::NotSupported(Some(
733 "Initialize is not allowed on a non-scoped registry for this root".to_owned(),
734 )));
735 }
736 }
737
738 if let Some(document) = root.downcast::<Document>() {
741 if document.custom_element_registry().is_none() {
742 document.set_custom_element_registry(self);
743 }
744 }
745 else if let Some(shadow_root) = root.downcast::<ShadowRoot>() &&
748 shadow_root.custom_element_registry().is_none()
749 {
750 shadow_root.set_custom_element_registry(self);
751 }
752
753 for node in root.traverse_preorder(ShadowIncluding::No) {
755 let Some(element) = node.downcast::<Element>() else {
757 continue;
758 };
759
760 if element.custom_element_registry().is_none() {
762 element.set_custom_element_registry(Some(self));
764
765 if self.is_scoped.get() {
768 let document = element.upcast::<Node>().owner_doc();
769 self.scoped_document_set
770 .borrow_mut()
771 .push(Dom::from_ref(&document));
772 }
773 } else if element
775 .custom_element_registry()
776 .is_none_or(|registry| *registry != *self)
777 {
778 continue;
779 }
780
781 try_upgrade_element(cx, element);
783 }
784 Ok(())
785 }
786}
787
788#[derive(Clone, JSTraceable, MallocSizeOf)]
789pub(crate) struct LifecycleCallbacks {
790 #[conditional_malloc_size_of]
791 connected_callback: Option<Rc<Function>>,
792
793 #[conditional_malloc_size_of]
794 connected_move_callback: Option<Rc<Function>>,
795
796 #[conditional_malloc_size_of]
797 disconnected_callback: Option<Rc<Function>>,
798
799 #[conditional_malloc_size_of]
800 adopted_callback: Option<Rc<Function>>,
801
802 #[conditional_malloc_size_of]
803 attribute_changed_callback: Option<Rc<Function>>,
804
805 #[conditional_malloc_size_of]
806 form_associated_callback: Option<Rc<Function>>,
807
808 #[conditional_malloc_size_of]
809 form_reset_callback: Option<Rc<Function>>,
810
811 #[conditional_malloc_size_of]
812 form_disabled_callback: Option<Rc<Function>>,
813
814 #[conditional_malloc_size_of]
815 form_state_restore_callback: Option<Rc<Function>>,
816}
817
818#[derive(Clone, JSTraceable, MallocSizeOf)]
819pub(crate) enum ConstructionStackEntry {
820 Element(DomRoot<Element>),
821 AlreadyConstructedMarker,
822}
823
824#[derive(Clone, JSTraceable, MallocSizeOf)]
826pub(crate) struct CustomElementDefinition {
827 #[no_trace]
829 pub(crate) name: LocalName,
830
831 #[no_trace]
833 pub(crate) local_name: LocalName,
834
835 #[conditional_malloc_size_of]
837 pub(crate) constructor: Rc<CustomElementConstructor>,
838
839 pub(crate) observed_attributes: Vec<DOMString>,
841
842 pub(crate) callbacks: LifecycleCallbacks,
844
845 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
847
848 pub(crate) form_associated: bool,
850
851 pub(crate) disable_internals: bool,
853
854 pub(crate) disable_shadow: bool,
856}
857
858impl CustomElementDefinition {
859 #[expect(clippy::too_many_arguments)]
860 fn new(
861 name: LocalName,
862 local_name: LocalName,
863 constructor: Rc<CustomElementConstructor>,
864 observed_attributes: Vec<DOMString>,
865 callbacks: LifecycleCallbacks,
866 form_associated: bool,
867 disable_internals: bool,
868 disable_shadow: bool,
869 ) -> CustomElementDefinition {
870 CustomElementDefinition {
871 name,
872 local_name,
873 constructor,
874 observed_attributes,
875 callbacks,
876 construction_stack: Default::default(),
877 form_associated,
878 disable_internals,
879 disable_shadow,
880 }
881 }
882
883 pub(crate) fn is_autonomous(&self) -> bool {
885 self.name == self.local_name
886 }
887
888 #[expect(unsafe_code)]
890 pub(crate) fn create_element(
891 &self,
892 cx: &mut JSContext,
893 document: &Document,
894 prefix: Option<Prefix>,
895 registry: Option<&CustomElementRegistry>,
896 ) -> Fallible<DomRoot<Element>> {
897 let window = document.window();
898
899 rooted!(&in(cx) let constructor = ObjectValue(self.constructor.callback()));
901 rooted!(&in(cx) let mut element = ptr::null_mut::<JSObject>());
902 {
903 let mut realm = AutoRealm::new(cx, NonNull::new(self.constructor.callback()).unwrap());
905 let cx = &mut realm;
906
907 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
910 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
911 let args = HandleValueArray::empty();
912 if unsafe { !Construct1(cx, constructor.handle(), &args, element.handle_mut()) }
913 {
914 Err(Error::JSFailed)
915 } else {
916 Ok(())
917 }
918 })
919 })?;
920 }
921
922 rooted!(&in(cx) let element_val = ObjectValue(element.get()));
923 let element: DomRoot<Element> =
924 match FromJSValConvertible::safe_from_jsval(cx, element_val.handle(), ()) {
925 Ok(ConversionResult::Success(element)) => element,
926 Ok(ConversionResult::Failure(..)) => {
927 return Err(Error::Type(
928 c"Constructor did not return a DOM node".to_owned(),
929 ));
930 },
931 _ => return Err(Error::JSFailed),
932 };
933
934 assert!(element.is::<HTMLElement>());
940
941 if element.HasAttributes() ||
947 element.upcast::<Node>().children_count() > 0 ||
948 element.upcast::<Node>().has_parent() ||
949 &*element.upcast::<Node>().owner_doc() != document ||
950 *element.namespace() != ns!(html) ||
951 *element.local_name() != self.local_name
952 {
953 return Err(Error::NotSupported(None));
954 }
955
956 element.set_prefix(prefix);
958
959 element.set_custom_element_registry(registry);
964
965 Ok(element)
966 }
967
968 pub(crate) fn has_attribute_changed_callback(&self) -> bool {
969 self.callbacks.attribute_changed_callback.is_some()
970 }
971}
972
973pub(crate) fn upgrade_element(
975 cx: &mut JSContext,
976 definition: Rc<CustomElementDefinition>,
977 element: &Element,
978) {
979 let state = element.get_custom_element_state();
981 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
982 return;
983 }
984
985 element.set_custom_element_definition(Rc::clone(&definition));
987
988 element.set_custom_element_state(CustomElementState::Failed);
990
991 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
995 for attr in element.attrs().borrow().iter() {
996 let local_name = attr.local_name().clone();
997 let namespace = attr.namespace().clone();
998 custom_element_reaction_stack.enqueue_callback_reaction(
999 cx,
1000 element,
1001 CallbackReaction::AttributeChanged(local_name, None, Some(&*attr.value()), namespace),
1002 Some(definition.clone()),
1003 );
1004 }
1005
1006 if element.is_connected() {
1009 custom_element_reaction_stack.enqueue_callback_reaction(
1010 cx,
1011 element,
1012 CallbackReaction::Connected,
1013 Some(definition.clone()),
1014 );
1015 }
1016
1017 definition
1019 .construction_stack
1020 .borrow_mut()
1021 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
1022
1023 let result = run_upgrade_constructor(cx, &definition, element);
1025
1026 definition.construction_stack.borrow_mut().pop();
1028
1029 if let Err(error) = result {
1031 element.clear_custom_element_definition();
1033
1034 element.clear_reaction_queue();
1036
1037 let global = GlobalScope::current().expect("No current global");
1039
1040 let mut realm = enter_auto_realm(cx, &*global);
1041 let cx = &mut realm.current_realm();
1042
1043 throw_dom_exception(cx, &global, error);
1044 report_pending_exception(cx);
1045
1046 return;
1047 }
1048
1049 if let Some(html_element) = element.downcast::<HTMLElement>() &&
1051 html_element.is_form_associated_custom_element()
1052 {
1053 html_element.reset_form_owner(cx);
1057 if let Some(form) = html_element.form_owner() {
1058 form.upcast::<Node>().rev_version();
1061 }
1065
1066 element.check_disabled_attribute();
1073 element.check_ancestors_disabled_state_for_form_control();
1074 element.update_read_write_state_from_readonly_attribute();
1075
1076 if element.disabled_state() {
1079 custom_element_reaction_stack.enqueue_callback_reaction(
1080 cx,
1081 element,
1082 CallbackReaction::FormDisabled(true),
1083 Some(definition),
1084 )
1085 }
1086 }
1087
1088 element.set_custom_element_state(CustomElementState::Custom);
1090}
1091
1092#[expect(unsafe_code)]
1095fn run_upgrade_constructor(
1096 cx: &mut JSContext,
1097 definition: &CustomElementDefinition,
1098 element: &Element,
1099) -> ErrorResult {
1100 let constructor = &definition.constructor;
1101 let window = element.owner_window();
1102 rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
1103 rooted!(&in(cx) let mut element_val = UndefinedValue());
1104 element.safe_to_jsval(cx, element_val.handle_mut());
1105 rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
1106 {
1107 if definition.disable_shadow && element.is_shadow_host() {
1110 return Err(Error::NotSupported(None));
1111 }
1112
1113 let mut realm = AutoRealm::new(cx, NonNull::new(constructor.callback()).unwrap());
1115 let cx = &mut *realm;
1116
1117 let args = HandleValueArray::empty();
1118 element.set_custom_element_state(CustomElementState::Precustomized);
1120
1121 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
1124 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
1125 if unsafe {
1126 !Construct1(
1127 cx,
1128 constructor_val.handle(),
1129 &args,
1130 construct_result.handle_mut(),
1131 )
1132 } {
1133 Err(Error::JSFailed)
1134 } else {
1135 Ok(())
1136 }
1137 })
1138 })?;
1139
1140 let mut same = false;
1141 rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
1142
1143 if unsafe {
1145 !SameValue(
1146 cx,
1147 construct_result_val.handle(),
1148 element_val.handle(),
1149 &mut same,
1150 )
1151 } {
1152 return Err(Error::JSFailed);
1153 }
1154 if !same {
1155 return Err(Error::Type(
1156 c"Returned element is not SameValue as the upgraded element".to_owned(),
1157 ));
1158 }
1159 }
1160 Ok(())
1161}
1162
1163pub(crate) fn try_upgrade_element(cx: &JSContext, element: &Element) {
1165 let lookup_registry = {
1169 let registry = element.custom_element_registry();
1172 if registry
1173 .as_ref()
1174 .is_some_and(|registry| registry.is_scoped())
1175 {
1176 registry
1177 } else {
1178 element.owner_document().custom_element_registry()
1179 }
1180 };
1181 if let Some(definition) = CustomElementRegistry::lookup_custom_element_definition(
1182 lookup_registry.as_deref(),
1183 element.namespace(),
1184 element.local_name(),
1185 element.get_is().as_ref(),
1186 ) {
1187 ScriptThread::enqueue_upgrade_reaction(cx, element, definition);
1190 }
1191}
1192
1193#[derive(JSTraceable, MallocSizeOf)]
1194#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1195pub(crate) enum CustomElementReaction {
1196 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1197 Callback(
1198 #[conditional_malloc_size_of] Rc<Function>,
1199 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1200 ),
1201}
1202
1203impl CustomElementReaction {
1204 pub(crate) fn invoke(&self, cx: &mut JSContext, element: &Element) {
1206 match *self {
1208 CustomElementReaction::Upgrade(ref definition) => {
1209 upgrade_element(cx, definition.clone(), element)
1210 },
1211 CustomElementReaction::Callback(ref callback, ref arguments) => {
1212 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1214 rooted!(&in(cx) let mut value: JSVal);
1215 let _ = callback.Call_(
1216 cx,
1217 element,
1218 arguments,
1219 value.handle_mut(),
1220 ExceptionHandling::Report,
1221 );
1222 },
1223 }
1224 }
1225}
1226
1227pub(crate) enum CallbackReaction<'a> {
1228 Connected,
1229 Disconnected,
1230 Adopted(DomRoot<Document>, DomRoot<Document>),
1231 AttributeChanged(
1232 LocalName,
1233 Option<&'a AttrValue>,
1234 Option<&'a AttrValue>,
1235 Namespace,
1236 ),
1237 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1238 FormDisabled(bool),
1239 FormReset,
1240 ConnectedMove,
1241}
1242
1243#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1245enum BackupElementQueueFlag {
1246 Processing,
1247 NotProcessing,
1248}
1249
1250#[derive(JSTraceable, MallocSizeOf)]
1255#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1256#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1257pub(crate) struct CustomElementReactionStack {
1258 stack: DomRefCell<Vec<ElementQueue>>,
1259 backup_queue: ElementQueue,
1260 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1261}
1262
1263impl CustomElementReactionStack {
1264 pub(crate) fn new() -> CustomElementReactionStack {
1265 CustomElementReactionStack {
1266 stack: DomRefCell::new(Vec::new()),
1267 backup_queue: ElementQueue::new(),
1268 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1269 }
1270 }
1271
1272 pub(crate) fn push_new_element_queue(&self) {
1273 self.stack.borrow_mut().push(ElementQueue::new());
1274 }
1275
1276 pub(crate) fn pop_current_element_queue(&self, cx: &mut JSContext) {
1277 rooted_vec!(let mut stack);
1278 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1279
1280 if let Some(current_queue) = stack.last() {
1281 current_queue.invoke_reactions(cx);
1282 }
1283 stack.pop();
1284
1285 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1286 self.stack.borrow_mut().append(&mut *stack);
1287 }
1288
1289 pub(crate) fn invoke_backup_element_queue(&self, cx: &mut JSContext) {
1292 self.backup_queue.invoke_reactions(cx);
1294
1295 self.processing_backup_element_queue
1297 .set(BackupElementQueueFlag::NotProcessing);
1298 }
1299
1300 pub(crate) fn enqueue_element(&self, cx: &JSContext, element: &Element) {
1302 if let Some(current_queue) = self.stack.borrow().last() {
1303 current_queue.append_element(element);
1305 } else {
1306 self.backup_queue.append_element(element);
1308
1309 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1311 return;
1312 }
1313
1314 self.processing_backup_element_queue
1316 .set(BackupElementQueueFlag::Processing);
1317
1318 ScriptThread::enqueue_microtask(cx, Box::new(CustomElementReactionMicrotask::new()));
1320 }
1321 }
1322
1323 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1325 pub(crate) fn enqueue_callback_reaction(
1326 &self,
1327 cx: &mut JSContext,
1328 element: &Element,
1329 reaction: CallbackReaction,
1330 definition: Option<Rc<CustomElementDefinition>>,
1331 ) {
1332 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1334 Some(definition) => definition,
1335 None => return,
1336 };
1337
1338 let (callback, args) = match reaction {
1341 CallbackReaction::Connected => {
1342 (definition.callbacks.connected_callback.clone(), Vec::new())
1343 },
1344 CallbackReaction::Disconnected => (
1345 definition.callbacks.disconnected_callback.clone(),
1346 Vec::new(),
1347 ),
1348 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1349 let args = vec![Heap::default(), Heap::default()];
1350 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1351 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1352 (definition.callbacks.adopted_callback.clone(), args)
1353 },
1354 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1355 if !definition
1357 .observed_attributes
1358 .iter()
1359 .any(|attr| *attr == *local_name)
1360 {
1361 return;
1362 }
1363
1364 let mut realm = enter_auto_realm(cx, &*element.global());
1368 let cx = &mut realm;
1369
1370 let local_name = DOMString::from(&*local_name);
1371 rooted!(&in(cx) let mut name_value = UndefinedValue());
1372 local_name.safe_to_jsval(cx, name_value.handle_mut());
1373
1374 rooted!(&in(cx) let mut old_value = NullValue());
1375 if let Some(old_val) = old_val {
1376 old_val.safe_to_jsval(cx, old_value.handle_mut());
1377 }
1378
1379 rooted!(&in(cx) let mut value = NullValue());
1380 if let Some(val) = val {
1381 val.safe_to_jsval(cx, value.handle_mut());
1382 }
1383
1384 rooted!(&in(cx) let mut namespace_value = NullValue());
1385 if namespace != ns!() {
1386 let namespace = DOMString::from(&*namespace);
1387 namespace.safe_to_jsval(cx, namespace_value.handle_mut());
1388 }
1389
1390 let args = vec![
1391 Heap::default(),
1392 Heap::default(),
1393 Heap::default(),
1394 Heap::default(),
1395 ];
1396 args[0].set(name_value.get());
1397 args[1].set(old_value.get());
1398 args[2].set(value.get());
1399 args[3].set(namespace_value.get());
1400
1401 (
1402 definition.callbacks.attribute_changed_callback.clone(),
1403 args,
1404 )
1405 },
1406 CallbackReaction::FormAssociated(form) => {
1407 let args = vec![Heap::default()];
1408 if let Some(form) = form {
1409 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1410 } else {
1411 args[0].set(NullValue());
1412 }
1413 (definition.callbacks.form_associated_callback.clone(), args)
1414 },
1415 CallbackReaction::FormDisabled(disabled) => {
1416 rooted!(&in(cx) let disabled_value = BooleanValue(disabled));
1417 let args = vec![Heap::default()];
1418 args[0].set(disabled_value.get());
1419 (definition.callbacks.form_disabled_callback.clone(), args)
1420 },
1421 CallbackReaction::FormReset => {
1422 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1423 },
1424 CallbackReaction::ConnectedMove => {
1425 let callback = definition.callbacks.connected_move_callback.clone();
1426 if callback.is_none() {
1428 let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1431
1432 let connected_callback = definition.callbacks.connected_callback.clone();
1435
1436 if disconnected_callback.is_none() && connected_callback.is_none() {
1439 return;
1440 }
1441
1442 if let Some(disconnected_callback) = disconnected_callback {
1446 element.push_callback_reaction(disconnected_callback, Box::new([]));
1447 }
1448 if let Some(connected_callback) = connected_callback {
1451 element.push_callback_reaction(connected_callback, Box::new([]));
1452 }
1453
1454 self.enqueue_element(cx, element);
1455 return;
1456 }
1457
1458 (callback, Vec::new())
1459 },
1460 };
1461
1462 let callback = match callback {
1464 Some(callback) => callback,
1465 None => return,
1466 };
1467
1468 element.push_callback_reaction(callback, args.into_boxed_slice());
1471
1472 self.enqueue_element(cx, element);
1474 }
1475
1476 pub(crate) fn enqueue_upgrade_reaction(
1478 &self,
1479 cx: &JSContext,
1480 element: &Element,
1481 definition: Rc<CustomElementDefinition>,
1482 ) {
1483 element.push_upgrade_reaction(definition);
1486
1487 self.enqueue_element(cx, element);
1489 }
1490}
1491
1492#[derive(JSTraceable, MallocSizeOf)]
1494#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1495struct ElementQueue {
1496 queue: DomRefCell<VecDeque<Dom<Element>>>,
1497}
1498
1499impl ElementQueue {
1500 fn new() -> ElementQueue {
1501 ElementQueue {
1502 queue: Default::default(),
1503 }
1504 }
1505
1506 fn invoke_reactions(&self, cx: &mut JSContext) {
1508 while let Some(element) = self.next_element() {
1510 element.invoke_reactions(cx)
1511 }
1512 self.queue.borrow_mut().clear();
1513 }
1514
1515 fn next_element(&self) -> Option<DomRoot<Element>> {
1516 self.queue
1517 .borrow_mut()
1518 .pop_front()
1519 .as_deref()
1520 .map(DomRoot::from_ref)
1521 }
1522
1523 fn append_element(&self, element: &Element) {
1524 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1525 }
1526}
1527
1528pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1530 let mut chars = name.chars();
1533 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1534 return false;
1535 }
1536
1537 let mut has_dash = false;
1538
1539 for c in chars {
1540 if c == '-' {
1541 has_dash = true;
1542 continue;
1543 }
1544
1545 if !is_potential_custom_element_char(c) {
1546 return false;
1547 }
1548 }
1549
1550 if !has_dash {
1551 return false;
1552 }
1553
1554 if name == "annotation-xml" ||
1555 name == "color-profile" ||
1556 name == "font-face" ||
1557 name == "font-face-src" ||
1558 name == "font-face-uri" ||
1559 name == "font-face-format" ||
1560 name == "font-face-name" ||
1561 name == "missing-glyph"
1562 {
1563 return false;
1564 }
1565
1566 true
1567}
1568
1569fn is_potential_custom_element_char(c: char) -> bool {
1572 c == '-' ||
1573 c == '.' ||
1574 c == '_' ||
1575 c == '\u{B7}' ||
1576 c.is_ascii_digit() ||
1577 c.is_ascii_lowercase() ||
1578 ('\u{C0}'..='\u{D6}').contains(&c) ||
1579 ('\u{D8}'..='\u{F6}').contains(&c) ||
1580 ('\u{F8}'..='\u{37D}').contains(&c) ||
1581 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1582 ('\u{200C}'..='\u{200D}').contains(&c) ||
1583 ('\u{203F}'..='\u{2040}').contains(&c) ||
1584 ('\u{2070}'..='\u{218F}').contains(&c) ||
1585 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1586 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1587 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1588 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1589 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1590}
1591
1592fn is_extendable_element_interface(element: &str) -> bool {
1593 element == "a" ||
1594 element == "abbr" ||
1595 element == "acronym" ||
1596 element == "address" ||
1597 element == "area" ||
1598 element == "article" ||
1599 element == "aside" ||
1600 element == "audio" ||
1601 element == "b" ||
1602 element == "base" ||
1603 element == "bdi" ||
1604 element == "bdo" ||
1605 element == "big" ||
1606 element == "blockquote" ||
1607 element == "body" ||
1608 element == "br" ||
1609 element == "button" ||
1610 element == "canvas" ||
1611 element == "caption" ||
1612 element == "center" ||
1613 element == "cite" ||
1614 element == "code" ||
1615 element == "col" ||
1616 element == "colgroup" ||
1617 element == "data" ||
1618 element == "datalist" ||
1619 element == "dd" ||
1620 element == "del" ||
1621 element == "details" ||
1622 element == "dfn" ||
1623 element == "dialog" ||
1624 element == "dir" ||
1625 element == "div" ||
1626 element == "dl" ||
1627 element == "dt" ||
1628 element == "em" ||
1629 element == "embed" ||
1630 element == "fieldset" ||
1631 element == "figcaption" ||
1632 element == "figure" ||
1633 element == "font" ||
1634 element == "footer" ||
1635 element == "form" ||
1636 element == "frame" ||
1637 element == "frameset" ||
1638 element == "h1" ||
1639 element == "h2" ||
1640 element == "h3" ||
1641 element == "h4" ||
1642 element == "h5" ||
1643 element == "h6" ||
1644 element == "head" ||
1645 element == "header" ||
1646 element == "hgroup" ||
1647 element == "hr" ||
1648 element == "html" ||
1649 element == "i" ||
1650 element == "iframe" ||
1651 element == "img" ||
1652 element == "input" ||
1653 element == "ins" ||
1654 element == "kbd" ||
1655 element == "label" ||
1656 element == "legend" ||
1657 element == "li" ||
1658 element == "link" ||
1659 element == "listing" ||
1660 element == "main" ||
1661 element == "map" ||
1662 element == "mark" ||
1663 element == "marquee" ||
1664 element == "menu" ||
1665 element == "meta" ||
1666 element == "meter" ||
1667 element == "nav" ||
1668 element == "nobr" ||
1669 element == "noframes" ||
1670 element == "noscript" ||
1671 element == "object" ||
1672 element == "ol" ||
1673 element == "optgroup" ||
1674 element == "option" ||
1675 element == "output" ||
1676 element == "p" ||
1677 element == "param" ||
1678 element == "picture" ||
1679 element == "plaintext" ||
1680 element == "pre" ||
1681 element == "progress" ||
1682 element == "q" ||
1683 element == "rp" ||
1684 element == "rt" ||
1685 element == "ruby" ||
1686 element == "s" ||
1687 element == "samp" ||
1688 element == "script" ||
1689 element == "section" ||
1690 element == "select" ||
1691 element == "slot" ||
1692 element == "small" ||
1693 element == "source" ||
1694 element == "span" ||
1695 element == "strike" ||
1696 element == "strong" ||
1697 element == "style" ||
1698 element == "sub" ||
1699 element == "summary" ||
1700 element == "sup" ||
1701 element == "table" ||
1702 element == "tbody" ||
1703 element == "td" ||
1704 element == "template" ||
1705 element == "textarea" ||
1706 element == "tfoot" ||
1707 element == "th" ||
1708 element == "thead" ||
1709 element == "time" ||
1710 element == "title" ||
1711 element == "tr" ||
1712 element == "tt" ||
1713 element == "track" ||
1714 element == "u" ||
1715 element == "ul" ||
1716 element == "var" ||
1717 element == "video" ||
1718 element == "wbr" ||
1719 element == "xmp"
1720}