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, ToJSValConvertible};
16use js::glue::UnwrapObjectStatic;
17use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSObject};
18use js::jsval::{BooleanValue, JSVal, NullValue, ObjectValue, UndefinedValue};
19use js::realm::{AutoRealm, CurrentRealm};
20use js::rust::wrappers2::{Construct1, JS_GetProperty, SameValue};
21use js::rust::{HandleObject, MutableHandleValue};
22use rustc_hash::FxBuildHasher;
23use script_bindings::cell::DomRefCell;
24use script_bindings::reflector::{DomObject, Reflector, reflect_dom_object_with_proto};
25use script_bindings::settings_stack::{run_a_callback, run_a_script};
26use style::attr::AttrValue;
27
28use super::bindings::trace::HashMapTracedValues;
29use crate::DomTypeHolder;
30use crate::dom::bindings::callback::{CallbackContainer, ExceptionHandling};
31use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::{
32 CustomElementConstructor, CustomElementRegistryMethods, ElementDefinitionOptions,
33};
34use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
35use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
36use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
37use crate::dom::bindings::conversions::{ConversionResult, StringificationBehavior, get_property};
38use crate::dom::bindings::error::{
39 Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception,
40};
41use crate::dom::bindings::inheritance::{Castable, DocumentFragmentTypeId, NodeTypeId};
42use crate::dom::bindings::reflector::DomGlobal;
43use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot, UnrootedDom};
44use crate::dom::bindings::str::DOMString;
45use crate::dom::document::Document;
46use crate::dom::domexception::{DOMErrorName, DOMException};
47use crate::dom::element::Element;
48use crate::dom::globalscope::GlobalScope;
49use crate::dom::html::htmlelement::HTMLElement;
50use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
51use crate::dom::iterators::ShadowIncluding;
52use crate::dom::node::{Node, NodeTraits};
53use crate::dom::promise::Promise;
54use crate::dom::shadowroot::ShadowRoot;
55use crate::dom::window::Window;
56use crate::event_loop::script_thread::ScriptThread;
57use crate::microtask::CustomElementReactionMicrotask;
58use crate::realms::enter_auto_realm;
59
60#[derive(Clone, Copy, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
62pub(crate) enum CustomElementState {
63 Undefined,
64 Failed,
65 #[default]
66 Uncustomized,
67 Precustomized,
68 Custom,
69}
70
71#[dom_struct]
73pub(crate) struct CustomElementRegistry {
74 reflector_: Reflector,
75
76 window: Dom<Window>,
77
78 #[conditional_malloc_size_of]
79 when_defined: DomRefCell<HashMapTracedValues<LocalName, Rc<Promise>, FxBuildHasher>>,
83
84 element_definition_is_running: Cell<bool>,
86
87 is_scoped: Cell<bool>,
89
90 scoped_document_set: DomRefCell<Vec<Dom<Document>>>,
92
93 #[conditional_malloc_size_of]
94 definitions:
96 DomRefCell<HashMapTracedValues<LocalName, Rc<CustomElementDefinition>, FxBuildHasher>>,
97}
98
99impl CustomElementRegistry {
100 fn new_inherited(window: &Window) -> CustomElementRegistry {
101 CustomElementRegistry {
102 reflector_: Reflector::new(),
103 window: Dom::from_ref(window),
104 when_defined: DomRefCell::new(HashMapTracedValues::new_fx()),
105 element_definition_is_running: Cell::new(false),
106 is_scoped: Cell::new(false),
107 scoped_document_set: DomRefCell::new(Vec::new()),
108 definitions: DomRefCell::new(HashMapTracedValues::new_fx()),
109 }
110 }
111
112 pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<CustomElementRegistry> {
113 CustomElementRegistry::new_with_proto(cx, window, None)
114 }
115
116 fn new_with_proto(
117 cx: &mut JSContext,
118 window: &Window,
119 proto: Option<HandleObject>,
120 ) -> DomRoot<CustomElementRegistry> {
121 reflect_dom_object_with_proto(
122 cx,
123 Box::new(CustomElementRegistry::new_inherited(window)),
124 window,
125 proto,
126 )
127 }
128
129 pub(crate) fn is_scoped(&self) -> bool {
131 self.is_scoped.get()
132 }
133
134 pub(crate) fn teardown(&self) {
137 self.when_defined.borrow_mut().0.clear()
138 }
139
140 pub(crate) fn lookup_definition_by_constructor(
144 &self,
145 constructor: HandleObject,
146 ) -> Option<Rc<CustomElementDefinition>> {
147 self.definitions
148 .borrow()
149 .0
150 .values()
151 .find(|definition| definition.constructor.callback() == constructor.get())
152 .cloned()
153 }
154
155 pub(crate) fn lookup_a_custom_element_registry(
157 node: &Node,
158 ) -> Option<DomRoot<CustomElementRegistry>> {
159 match node.type_id() {
160 NodeTypeId::Element(_) => node
162 .downcast::<Element>()
163 .expect("Nodes with element type must be an element")
164 .custom_element_registry(),
165 NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) => node
167 .downcast::<ShadowRoot>()
168 .expect("Nodes with ShadowRoot type must be a ShadowRoot")
169 .custom_element_registry(),
170 NodeTypeId::Document(_) => node
172 .downcast::<Document>()
173 .expect("Nodes with document type must be a document")
174 .custom_element_registry(),
175 _ => None,
177 }
178 }
179
180 pub(crate) fn lookup_custom_element_definition(
182 registry: Option<&CustomElementRegistry>,
183 namespace: &Namespace,
184 local_name: &LocalName,
185 is: Option<&LocalName>,
186 ) -> Option<Rc<CustomElementDefinition>> {
187 let registry = registry?;
189
190 if *namespace != ns!(html) {
192 return None;
193 }
194
195 registry
203 .definitions
204 .borrow()
205 .0
206 .values()
207 .find(|definition| {
208 definition.local_name == *local_name &&
209 (definition.name == *local_name || Some(&definition.name) == is)
210 })
211 .cloned()
212 }
213
214 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
216 registry.is_some_and(|r| !r.is_scoped())
219 }
220
221 #[expect(unsafe_code)]
224 fn check_prototype(
225 &self,
226 cx: &mut JSContext,
227 constructor: HandleObject,
228 mut prototype: MutableHandleValue,
229 ) -> ErrorResult {
230 unsafe {
231 if !JS_GetProperty(cx, constructor, c"prototype".as_ptr(), prototype.reborrow()) {
233 return Err(Error::JSFailed);
234 }
235
236 if !prototype.is_object() {
238 return Err(Error::Type(
239 c"constructor.prototype is not an object".to_owned(),
240 ));
241 }
242 }
243 Ok(())
244 }
245
246 fn get_callbacks(
250 &self,
251 cx: &mut JSContext,
252 prototype: HandleObject,
253 ) -> Fallible<LifecycleCallbacks> {
254 Ok(LifecycleCallbacks {
256 connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
257 disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
258 connected_move_callback: get_callback(cx, prototype, c"connectedMoveCallback")?,
259 adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
260 attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
261
262 form_associated_callback: None,
263 form_disabled_callback: None,
264 form_reset_callback: None,
265 form_state_restore_callback: None,
266 })
267 }
268
269 #[expect(unsafe_code)]
272 unsafe fn add_form_associated_callbacks(
273 &self,
274 cx: &mut JSContext,
275 prototype: HandleObject,
276 callbacks: &mut LifecycleCallbacks,
277 ) -> ErrorResult {
278 callbacks.form_associated_callback =
279 get_callback(cx, prototype, c"formAssociatedCallback")?;
280 callbacks.form_reset_callback = get_callback(cx, prototype, c"formResetCallback")?;
281 callbacks.form_disabled_callback = get_callback(cx, prototype, c"formDisabledCallback")?;
282 callbacks.form_state_restore_callback =
283 get_callback(cx, prototype, c"formStateRestoreCallback")?;
284
285 Ok(())
286 }
287
288 fn upgrade_particular_elements_within_a_document(
290 &self,
291 cx: &JSContext,
292 document: &Document,
293 definition: &Rc<CustomElementDefinition>,
294 local_name: &LocalName,
295 name: &LocalName,
296 ) {
297 for candidate in document
303 .upcast::<Node>()
304 .traverse_preorder_non_rooting(cx, ShadowIncluding::Yes)
305 .filter_map(UnrootedDom::downcast::<Element>)
306 {
307 let registry_matches = if self.is_scoped.get() {
312 candidate
313 .custom_element_registry()
314 .is_some_and(|registry| *registry == *self)
315 } else {
316 candidate
317 .custom_element_registry()
318 .is_none_or(|registry| *registry == *self)
319 };
320 if *candidate.local_name() == *local_name &&
321 *candidate.namespace() == ns!(html) &&
322 registry_matches &&
323 (*name == *local_name || candidate.get_is().as_ref() == Some(name))
324 {
325 ScriptThread::enqueue_upgrade_reaction(cx, &candidate, definition.clone());
328 }
329 }
330 }
331
332 pub(crate) fn add_scoped_document(&self, document: &Document) {
333 self.scoped_document_set
334 .borrow_mut()
335 .push(Dom::from_ref(document));
336 }
337}
338
339#[expect(unsafe_code)]
342fn get_callback(
343 cx: &mut JSContext,
344 prototype: HandleObject,
345 name: &CStr,
346) -> Fallible<Option<Rc<Function>>> {
347 rooted!(&in(cx) let mut callback = UndefinedValue());
348 unsafe {
349 if !JS_GetProperty(cx, prototype, name.as_ptr(), callback.handle_mut()) {
351 return Err(Error::JSFailed);
352 }
353
354 if !callback.is_undefined() {
356 if !callback.is_object() || !IsCallable(callback.to_object()) {
357 return Err(Error::Type(
358 c"Lifecycle callback is not callable".to_owned(),
359 ));
360 }
361 Ok(Some(Function::new(cx, callback.to_object())))
362 } else {
363 Ok(None)
364 }
365 }
366}
367
368impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
369 fn Constructor(
371 cx: &mut JSContext,
372 window: &Window,
373 proto: Option<HandleObject>,
374 ) -> DomRoot<CustomElementRegistry> {
375 let registry = CustomElementRegistry::new_with_proto(cx, window, proto);
376
377 registry.is_scoped.set(true);
379 registry
380 }
381
382 #[expect(unsafe_code)]
383 fn Define(
385 &self,
386 cx: &mut JSContext,
387 name: DOMString,
388 constructor_: Rc<CustomElementConstructor>,
389 options: &ElementDefinitionOptions,
390 ) -> ErrorResult {
391 rooted!(&in(cx) let constructor = constructor_.callback());
392 let name = LocalName::from(name);
393
394 rooted!(&in(cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
397
398 if unwrapped_constructor.is_null() {
399 return Err(Error::Security(None));
401 }
402
403 if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
404 return Err(Error::Type(
405 c"Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
406 ));
407 }
408
409 if !is_valid_custom_element_name(&name) {
411 return Err(Error::Syntax(Some(format!(
412 "{} name is not a valid custom element name",
413 name
414 ))));
415 }
416
417 if self.definitions.borrow().contains_key(&name) {
420 return Err(Error::NotSupported(Some(format!(
421 "{} has already been defined as a custom element",
422 name
423 ))));
424 }
425
426 if self
429 .definitions
430 .borrow()
431 .iter()
432 .any(|(_, def)| def.constructor == constructor_)
433 {
434 return Err(Error::NotSupported(None));
435 }
436
437 let extends = &options.extends;
439
440 let local_name = if let Some(ref extended_name) = *extends {
442 if self.is_scoped.get() {
444 return Err(Error::NotSupported(Some(
445 "Scoped custom element registries cannot define customized built-in elements"
446 .to_owned(),
447 )));
448 }
449
450 if is_valid_custom_element_name(&extended_name.str()) {
452 return Err(Error::NotSupported(None));
453 }
454
455 if !is_extendable_element_interface(&extended_name.str()) {
459 return Err(Error::NotSupported(None));
460 }
461
462 LocalName::from(extended_name)
464 } else {
465 name.clone()
467 };
468
469 if self.element_definition_is_running.get() {
471 return Err(Error::NotSupported(None));
472 }
473
474 self.element_definition_is_running.set(true);
476
477 rooted!(&in(cx) let mut prototype = UndefinedValue());
482 {
483 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
484 if let Err(error) =
485 self.check_prototype(&mut realm, constructor.handle(), prototype.handle_mut())
486 {
487 self.element_definition_is_running.set(false);
488 return Err(error);
489 }
490 };
491
492 rooted!(&in(cx) let proto_object = prototype.to_object());
498 let mut callbacks = {
499 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
500 match self.get_callbacks(&mut realm, proto_object.handle()) {
501 Ok(callbacks) => callbacks,
502 Err(error) => {
503 self.element_definition_is_running.set(false);
504 return Err(error);
505 },
506 }
507 };
508
509 let observed_attributes: Vec<DOMString> = if callbacks.attribute_changed_callback.is_some()
512 {
513 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
514 match get_property(
515 &mut realm,
516 constructor.handle(),
517 c"observedAttributes",
518 StringificationBehavior::Default,
519 ) {
520 Ok(attributes) => attributes.unwrap_or_default(),
521 Err(error) => {
522 self.element_definition_is_running.set(false);
523 return Err(error);
524 },
525 }
526 } else {
527 Vec::new()
528 };
529
530 let (disable_internals, disable_shadow) = {
532 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
533 match get_property::<Vec<DOMString>>(
534 &mut realm,
535 constructor.handle(),
536 c"disabledFeatures",
537 StringificationBehavior::Default,
538 ) {
539 Ok(sequence) => {
540 let sequence = sequence.unwrap_or_default();
541 (
542 sequence.iter().any(|s| *s == "internals"),
543 sequence.iter().any(|s| *s == "shadow"),
544 )
545 },
546 Err(error) => {
547 self.element_definition_is_running.set(false);
548 return Err(error);
549 },
550 }
551 };
552
553 let form_associated: bool = {
555 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
556 match get_property(&mut realm, constructor.handle(), c"formAssociated", ()) {
557 Ok(flag) => flag.unwrap_or_default(),
558 Err(error) => {
559 self.element_definition_is_running.set(false);
560 return Err(error);
561 },
562 }
563 };
564
565 if form_associated {
567 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
568 unsafe {
569 if let Err(error) = self.add_form_associated_callbacks(
570 &mut realm,
571 proto_object.handle(),
572 &mut callbacks,
573 ) {
574 self.element_definition_is_running.set(false);
575 return Err(error);
576 }
577 }
578 }
579
580 self.element_definition_is_running.set(false);
581
582 let definition = Rc::new(CustomElementDefinition::new(
588 name.clone(),
589 local_name.clone(),
590 constructor_,
591 observed_attributes,
592 callbacks,
593 form_associated,
594 disable_internals,
595 disable_shadow,
596 ));
597
598 self.definitions
600 .borrow_mut()
601 .insert(name.clone(), definition.clone());
602
603 if self.is_scoped.get() {
607 for document in self.scoped_document_set.borrow().iter() {
608 self.upgrade_particular_elements_within_a_document(
609 cx,
610 document,
611 &definition,
612 &local_name,
613 &local_name,
614 );
615 }
616 } else {
617 self.upgrade_particular_elements_within_a_document(
621 cx,
622 &self.window.Document(),
623 &definition,
624 &local_name,
625 &name,
626 );
627 }
628
629 let promise = self.when_defined.borrow_mut().remove(&name);
632 if let Some(promise) = promise {
633 rooted!(&in(cx) let mut constructor = UndefinedValue());
634 definition
635 .constructor
636 .safe_to_jsval(cx, constructor.handle_mut());
637 promise.resolve_native(cx, &constructor.get());
639 }
640 Ok(())
641 }
642
643 fn Get(&self, cx: &mut JSContext, name: DOMString, mut retval: MutableHandleValue) {
645 match self.definitions.borrow().get(&LocalName::from(name)) {
646 Some(definition) => definition.constructor.safe_to_jsval(cx, retval),
647 None => retval.set(UndefinedValue()),
648 }
649 }
650
651 fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
653 self.definitions
654 .borrow()
655 .0
656 .values()
657 .find(|definition| definition.constructor == constructor)
658 .map(|definition| DOMString::from(definition.name.to_string()))
659 }
660
661 fn WhenDefined(&self, realm: &mut CurrentRealm, name: DOMString) -> Rc<Promise> {
663 let name = LocalName::from(name);
664
665 if !is_valid_custom_element_name(&name) {
667 let promise = Promise::new_in_realm(realm);
668 let error = DOMException::new(
669 realm,
670 self.window.as_global_scope(),
671 DOMErrorName::SyntaxError,
672 );
673 promise.reject_native(realm, &error);
674 return promise;
675 }
676
677 if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
679 rooted!(&in(*realm) let mut constructor = UndefinedValue());
680 definition
681 .constructor
682 .safe_to_jsval(realm, constructor.handle_mut());
683 let promise = Promise::new_in_realm(realm);
684 promise.resolve_native(realm, &constructor.get());
685 return promise;
686 }
687
688 let existing_promise = self.when_defined.borrow().get(&name).cloned();
690 existing_promise.unwrap_or_else(|| {
691 let promise = Promise::new_in_realm(realm);
692 self.when_defined.borrow_mut().insert(name, promise.clone());
693 promise
694 })
695 }
696
697 fn Upgrade(&self, cx: &JSContext, node: &Node) {
699 for node in node.traverse_preorder_non_rooting(cx, ShadowIncluding::Yes) {
702 let Some(element) = node.downcast::<Element>() else {
704 continue;
705 };
706 if element
709 .custom_element_registry()
710 .is_some_and(|registry| *registry != *self)
711 {
712 continue;
713 }
714 try_upgrade_element(cx, element);
716 }
717 }
718
719 fn Initialize(&self, cx: &JSContext, root: &Node) -> ErrorResult {
721 if !self.is_scoped.get() {
725 let is_document = root.is::<Document>();
726 let registry_mismatch = root
727 .owner_doc()
728 .custom_element_registry()
729 .is_some_and(|registry| *registry != *self);
730 if is_document || registry_mismatch {
731 return Err(Error::NotSupported(Some(
732 "Initialize is not allowed on a non-scoped registry for this root".to_owned(),
733 )));
734 }
735 }
736
737 if let Some(document) = root.downcast::<Document>() {
740 if document.custom_element_registry().is_none() {
741 document.set_custom_element_registry(self);
742 }
743 }
744 else if let Some(shadow_root) = root.downcast::<ShadowRoot>() &&
747 shadow_root.custom_element_registry().is_none()
748 {
749 shadow_root.set_custom_element_registry(self);
750 }
751
752 for node in root.traverse_preorder(ShadowIncluding::No) {
754 let Some(element) = node.downcast::<Element>() else {
756 continue;
757 };
758
759 if element.custom_element_registry().is_none() {
761 element.set_custom_element_registry(Some(self), cx.no_gc());
763
764 if self.is_scoped.get() {
767 let document = element.upcast::<Node>().owner_doc();
768 self.scoped_document_set
769 .borrow_mut()
770 .push(Dom::from_ref(&document));
771 }
772 } else if element
774 .custom_element_registry()
775 .is_none_or(|registry| *registry != *self)
776 {
777 continue;
778 }
779
780 try_upgrade_element(cx, element);
782 }
783 Ok(())
784 }
785}
786
787#[derive(Clone, JSTraceable, MallocSizeOf)]
788pub(crate) struct LifecycleCallbacks {
789 #[conditional_malloc_size_of]
790 connected_callback: Option<Rc<Function>>,
791
792 #[conditional_malloc_size_of]
793 connected_move_callback: Option<Rc<Function>>,
794
795 #[conditional_malloc_size_of]
796 disconnected_callback: Option<Rc<Function>>,
797
798 #[conditional_malloc_size_of]
799 adopted_callback: Option<Rc<Function>>,
800
801 #[conditional_malloc_size_of]
802 attribute_changed_callback: Option<Rc<Function>>,
803
804 #[conditional_malloc_size_of]
805 form_associated_callback: Option<Rc<Function>>,
806
807 #[conditional_malloc_size_of]
808 form_reset_callback: Option<Rc<Function>>,
809
810 #[conditional_malloc_size_of]
811 form_disabled_callback: Option<Rc<Function>>,
812
813 #[conditional_malloc_size_of]
814 form_state_restore_callback: Option<Rc<Function>>,
815}
816
817#[derive(Clone, JSTraceable, MallocSizeOf)]
818pub(crate) enum ConstructionStackEntry {
819 Element(DomRoot<Element>),
820 AlreadyConstructedMarker,
821}
822
823#[derive(Clone, JSTraceable, MallocSizeOf)]
825pub(crate) struct CustomElementDefinition {
826 #[no_trace]
828 pub(crate) name: LocalName,
829
830 #[no_trace]
832 pub(crate) local_name: LocalName,
833
834 #[conditional_malloc_size_of]
836 pub(crate) constructor: Rc<CustomElementConstructor>,
837
838 pub(crate) observed_attributes: Vec<DOMString>,
840
841 pub(crate) callbacks: LifecycleCallbacks,
843
844 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
846
847 pub(crate) form_associated: bool,
849
850 pub(crate) disable_internals: bool,
852
853 pub(crate) disable_shadow: bool,
855}
856
857impl CustomElementDefinition {
858 #[expect(clippy::too_many_arguments)]
859 fn new(
860 name: LocalName,
861 local_name: LocalName,
862 constructor: Rc<CustomElementConstructor>,
863 observed_attributes: Vec<DOMString>,
864 callbacks: LifecycleCallbacks,
865 form_associated: bool,
866 disable_internals: bool,
867 disable_shadow: bool,
868 ) -> CustomElementDefinition {
869 CustomElementDefinition {
870 name,
871 local_name,
872 constructor,
873 observed_attributes,
874 callbacks,
875 construction_stack: Default::default(),
876 form_associated,
877 disable_internals,
878 disable_shadow,
879 }
880 }
881
882 pub(crate) fn is_autonomous(&self) -> bool {
884 self.name == self.local_name
885 }
886
887 #[expect(unsafe_code)]
889 pub(crate) fn create_element(
890 &self,
891 cx: &mut JSContext,
892 document: &Document,
893 prefix: Option<Prefix>,
894 registry: Option<&CustomElementRegistry>,
895 ) -> Fallible<DomRoot<Element>> {
896 let window = document.window();
897
898 rooted!(&in(cx) let constructor = ObjectValue(self.constructor.callback()));
900 rooted!(&in(cx) let mut element = ptr::null_mut::<JSObject>());
901 {
902 let mut realm = AutoRealm::new(cx, NonNull::new(self.constructor.callback()).unwrap());
904 let cx = &mut realm;
905
906 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
909 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
910 let args = HandleValueArray::empty();
911 if unsafe { !Construct1(cx, constructor.handle(), &args, element.handle_mut()) }
912 {
913 Err(Error::JSFailed)
914 } else {
915 Ok(())
916 }
917 })
918 })?;
919 }
920
921 rooted!(&in(cx) let element_val = ObjectValue(element.get()));
922 let element: DomRoot<Element> =
923 match FromJSValConvertible::safe_from_jsval(cx, element_val.handle(), ()) {
924 Ok(ConversionResult::Success(element)) => element,
925 Ok(ConversionResult::Failure(..)) => {
926 return Err(Error::Type(
927 c"Constructor did not return a DOM node".to_owned(),
928 ));
929 },
930 _ => return Err(Error::JSFailed),
931 };
932
933 assert!(element.is::<HTMLElement>());
939
940 if element.HasAttributes() ||
946 element.upcast::<Node>().children_count() > 0 ||
947 element.upcast::<Node>().has_parent() ||
948 &*element.upcast::<Node>().owner_doc() != document ||
949 *element.namespace() != ns!(html) ||
950 *element.local_name() != self.local_name
951 {
952 return Err(Error::NotSupported(None));
953 }
954
955 element.set_prefix(prefix);
957
958 element.set_custom_element_registry(registry, cx.no_gc());
963
964 Ok(element)
965 }
966
967 pub(crate) fn has_attribute_changed_callback(&self) -> bool {
968 self.callbacks.attribute_changed_callback.is_some()
969 }
970}
971
972pub(crate) fn upgrade_element(
974 cx: &mut JSContext,
975 definition: Rc<CustomElementDefinition>,
976 element: &Element,
977) {
978 let state = element.get_custom_element_state();
980 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
981 return;
982 }
983
984 element.set_custom_element_definition(Rc::clone(&definition), cx.no_gc());
986
987 element.set_custom_element_state(CustomElementState::Failed, cx.no_gc());
989
990 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
994 for attr in element.attrs().borrow().iter() {
995 let local_name = attr.local_name().clone();
996 let namespace = attr.namespace().clone();
997 custom_element_reaction_stack.enqueue_callback_reaction(
998 cx,
999 element,
1000 CallbackReaction::AttributeChanged(local_name, None, Some(&*attr.value()), namespace),
1001 Some(definition.clone()),
1002 );
1003 }
1004
1005 if element.is_connected() {
1008 custom_element_reaction_stack.enqueue_callback_reaction(
1009 cx,
1010 element,
1011 CallbackReaction::Connected,
1012 Some(definition.clone()),
1013 );
1014 }
1015
1016 definition
1018 .construction_stack
1019 .borrow_mut()
1020 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
1021
1022 let result = run_upgrade_constructor(cx, &definition, element);
1024
1025 definition.construction_stack.borrow_mut().pop();
1027
1028 if let Err(error) = result {
1030 element.clear_custom_element_definition(cx.no_gc());
1032
1033 element.clear_reaction_queue();
1035
1036 let global = GlobalScope::current().expect("No current global");
1038
1039 let mut realm = enter_auto_realm(cx, &*global);
1040 let cx = &mut realm.current_realm();
1041
1042 throw_dom_exception(cx, &global, error);
1043 report_pending_exception(cx);
1044
1045 return;
1046 }
1047
1048 if let Some(html_element) = element.downcast::<HTMLElement>() &&
1050 html_element.is_form_associated_custom_element()
1051 {
1052 html_element.reset_form_owner(cx);
1056 if let Some(form) = html_element.form_owner() {
1057 form.upcast::<Node>().rev_version(cx.no_gc());
1060 }
1064
1065 element.check_disabled_attribute();
1072 element.check_ancestors_disabled_state_for_form_control();
1073 element.update_read_write_state_from_readonly_attribute();
1074
1075 if element.disabled_state() {
1078 custom_element_reaction_stack.enqueue_callback_reaction(
1079 cx,
1080 element,
1081 CallbackReaction::FormDisabled(true),
1082 Some(definition),
1083 )
1084 }
1085 }
1086
1087 element.set_custom_element_state(CustomElementState::Custom, cx.no_gc());
1089}
1090
1091#[expect(unsafe_code)]
1094fn run_upgrade_constructor(
1095 cx: &mut JSContext,
1096 definition: &CustomElementDefinition,
1097 element: &Element,
1098) -> ErrorResult {
1099 let constructor = &definition.constructor;
1100 let window = element.owner_window();
1101 rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
1102 rooted!(&in(cx) let mut element_val = UndefinedValue());
1103 element.safe_to_jsval(cx, element_val.handle_mut());
1104 rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
1105 {
1106 if definition.disable_shadow && element.is_shadow_host() {
1109 return Err(Error::NotSupported(None));
1110 }
1111
1112 let mut realm = AutoRealm::new(cx, NonNull::new(constructor.callback()).unwrap());
1114 let cx = &mut *realm;
1115
1116 let args = HandleValueArray::empty();
1117 element.set_custom_element_state(CustomElementState::Precustomized, cx.no_gc());
1119
1120 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
1123 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
1124 if unsafe {
1125 !Construct1(
1126 cx,
1127 constructor_val.handle(),
1128 &args,
1129 construct_result.handle_mut(),
1130 )
1131 } {
1132 Err(Error::JSFailed)
1133 } else {
1134 Ok(())
1135 }
1136 })
1137 })?;
1138
1139 let mut same = false;
1140 rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
1141
1142 if unsafe {
1144 !SameValue(
1145 cx,
1146 construct_result_val.handle(),
1147 element_val.handle(),
1148 &mut same,
1149 )
1150 } {
1151 return Err(Error::JSFailed);
1152 }
1153 if !same {
1154 return Err(Error::Type(
1155 c"Returned element is not SameValue as the upgraded element".to_owned(),
1156 ));
1157 }
1158 }
1159 Ok(())
1160}
1161
1162pub(crate) fn try_upgrade_element(cx: &JSContext, element: &Element) {
1164 let lookup_registry = {
1168 let registry = element.custom_element_registry();
1171 if registry
1172 .as_ref()
1173 .is_some_and(|registry| registry.is_scoped())
1174 {
1175 registry
1176 } else {
1177 element.owner_document().custom_element_registry()
1178 }
1179 };
1180 if let Some(definition) = CustomElementRegistry::lookup_custom_element_definition(
1181 lookup_registry.as_deref(),
1182 element.namespace(),
1183 element.local_name(),
1184 element.get_is().as_ref(),
1185 ) {
1186 ScriptThread::enqueue_upgrade_reaction(cx, element, definition);
1189 }
1190}
1191
1192#[derive(JSTraceable, MallocSizeOf)]
1193#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1194pub(crate) enum CustomElementReaction {
1195 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1196 Callback(
1197 #[conditional_malloc_size_of] Rc<Function>,
1198 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1199 ),
1200}
1201
1202impl CustomElementReaction {
1203 pub(crate) fn invoke(&self, cx: &mut JSContext, element: &Element) {
1205 match *self {
1207 CustomElementReaction::Upgrade(ref definition) => {
1208 upgrade_element(cx, definition.clone(), element)
1209 },
1210 CustomElementReaction::Callback(ref callback, ref arguments) => {
1211 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1213 rooted!(&in(cx) let mut value: JSVal);
1214 let _ = callback.Call_(
1215 cx,
1216 element,
1217 arguments,
1218 value.handle_mut(),
1219 ExceptionHandling::Report,
1220 );
1221 },
1222 }
1223 }
1224}
1225
1226pub(crate) enum CallbackReaction<'a> {
1227 Connected,
1228 Disconnected,
1229 Adopted(DomRoot<Document>, DomRoot<Document>),
1230 AttributeChanged(
1231 LocalName,
1232 Option<&'a AttrValue>,
1233 Option<&'a AttrValue>,
1234 Namespace,
1235 ),
1236 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1237 FormDisabled(bool),
1238 FormReset,
1239 ConnectedMove,
1240}
1241
1242#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1244enum BackupElementQueueFlag {
1245 Processing,
1246 NotProcessing,
1247}
1248
1249#[derive(JSTraceable, MallocSizeOf)]
1254#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1255#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1256pub(crate) struct CustomElementReactionStack {
1257 stack: DomRefCell<Vec<ElementQueue>>,
1258 backup_queue: ElementQueue,
1259 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1260}
1261
1262impl CustomElementReactionStack {
1263 pub(crate) fn new() -> CustomElementReactionStack {
1264 CustomElementReactionStack {
1265 stack: DomRefCell::new(Vec::new()),
1266 backup_queue: ElementQueue::new(),
1267 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1268 }
1269 }
1270
1271 pub(crate) fn push_new_element_queue(&self) {
1272 self.stack.borrow_mut().push(ElementQueue::new());
1273 }
1274
1275 pub(crate) fn pop_current_element_queue(&self, cx: &mut JSContext) {
1276 rooted_vec!(let mut stack);
1277 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1278
1279 if let Some(current_queue) = stack.last() {
1280 current_queue.invoke_reactions(cx);
1281 }
1282 stack.pop();
1283
1284 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1285 self.stack.borrow_mut().append(&mut *stack);
1286 }
1287
1288 pub(crate) fn invoke_backup_element_queue(&self, cx: &mut JSContext) {
1291 self.backup_queue.invoke_reactions(cx);
1293
1294 self.processing_backup_element_queue
1296 .set(BackupElementQueueFlag::NotProcessing);
1297 }
1298
1299 pub(crate) fn enqueue_element(&self, cx: &JSContext, element: &Element) {
1301 if let Some(current_queue) = self.stack.borrow().last() {
1302 current_queue.append_element(element);
1304 } else {
1305 self.backup_queue.append_element(element);
1307
1308 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1310 return;
1311 }
1312
1313 self.processing_backup_element_queue
1315 .set(BackupElementQueueFlag::Processing);
1316
1317 ScriptThread::enqueue_microtask(cx, Box::new(CustomElementReactionMicrotask::new()));
1319 }
1320 }
1321
1322 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1324 pub(crate) fn enqueue_callback_reaction(
1325 &self,
1326 cx: &mut JSContext,
1327 element: &Element,
1328 reaction: CallbackReaction,
1329 definition: Option<Rc<CustomElementDefinition>>,
1330 ) {
1331 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1333 Some(definition) => definition,
1334 None => return,
1335 };
1336
1337 let (callback, args) = match reaction {
1340 CallbackReaction::Connected => {
1341 (definition.callbacks.connected_callback.clone(), Vec::new())
1342 },
1343 CallbackReaction::Disconnected => (
1344 definition.callbacks.disconnected_callback.clone(),
1345 Vec::new(),
1346 ),
1347 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1348 let args = vec![Heap::default(), Heap::default()];
1349 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1350 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1351 (definition.callbacks.adopted_callback.clone(), args)
1352 },
1353 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1354 if !definition
1356 .observed_attributes
1357 .iter()
1358 .any(|attr| *attr == *local_name)
1359 {
1360 return;
1361 }
1362
1363 let mut realm = enter_auto_realm(cx, &*element.global());
1367 let cx = &mut realm;
1368
1369 let local_name = DOMString::from(&*local_name);
1370 rooted!(&in(cx) let mut name_value = UndefinedValue());
1371 local_name.safe_to_jsval(cx, name_value.handle_mut());
1372
1373 rooted!(&in(cx) let mut old_value = NullValue());
1374 if let Some(old_val) = old_val {
1375 old_val.safe_to_jsval(cx, old_value.handle_mut());
1376 }
1377
1378 rooted!(&in(cx) let mut value = NullValue());
1379 if let Some(val) = val {
1380 val.safe_to_jsval(cx, value.handle_mut());
1381 }
1382
1383 rooted!(&in(cx) let mut namespace_value = NullValue());
1384 if namespace != ns!() {
1385 let namespace = DOMString::from(&*namespace);
1386 namespace.safe_to_jsval(cx, namespace_value.handle_mut());
1387 }
1388
1389 let args = vec![
1390 Heap::default(),
1391 Heap::default(),
1392 Heap::default(),
1393 Heap::default(),
1394 ];
1395 args[0].set(name_value.get());
1396 args[1].set(old_value.get());
1397 args[2].set(value.get());
1398 args[3].set(namespace_value.get());
1399
1400 (
1401 definition.callbacks.attribute_changed_callback.clone(),
1402 args,
1403 )
1404 },
1405 CallbackReaction::FormAssociated(form) => {
1406 let args = vec![Heap::default()];
1407 if let Some(form) = form {
1408 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1409 } else {
1410 args[0].set(NullValue());
1411 }
1412 (definition.callbacks.form_associated_callback.clone(), args)
1413 },
1414 CallbackReaction::FormDisabled(disabled) => {
1415 rooted!(&in(cx) let disabled_value = BooleanValue(disabled));
1416 let args = vec![Heap::default()];
1417 args[0].set(disabled_value.get());
1418 (definition.callbacks.form_disabled_callback.clone(), args)
1419 },
1420 CallbackReaction::FormReset => {
1421 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1422 },
1423 CallbackReaction::ConnectedMove => {
1424 let callback = definition.callbacks.connected_move_callback.clone();
1425 if callback.is_none() {
1427 let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1430
1431 let connected_callback = definition.callbacks.connected_callback.clone();
1434
1435 if disconnected_callback.is_none() && connected_callback.is_none() {
1438 return;
1439 }
1440
1441 if let Some(disconnected_callback) = disconnected_callback {
1445 element.push_callback_reaction(
1446 disconnected_callback,
1447 Box::new([]),
1448 cx.no_gc(),
1449 );
1450 }
1451 if let Some(connected_callback) = connected_callback {
1454 element.push_callback_reaction(
1455 connected_callback,
1456 Box::new([]),
1457 cx.no_gc(),
1458 );
1459 }
1460
1461 self.enqueue_element(cx, element);
1462 return;
1463 }
1464
1465 (callback, Vec::new())
1466 },
1467 };
1468
1469 let callback = match callback {
1471 Some(callback) => callback,
1472 None => return,
1473 };
1474
1475 element.push_callback_reaction(callback, args.into_boxed_slice(), cx.no_gc());
1478
1479 self.enqueue_element(cx, element);
1481 }
1482
1483 pub(crate) fn enqueue_upgrade_reaction(
1485 &self,
1486 cx: &JSContext,
1487 element: &Element,
1488 definition: Rc<CustomElementDefinition>,
1489 ) {
1490 element.push_upgrade_reaction(definition, cx.no_gc());
1493
1494 self.enqueue_element(cx, element);
1496 }
1497}
1498
1499#[derive(JSTraceable, MallocSizeOf)]
1501#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1502struct ElementQueue {
1503 queue: DomRefCell<VecDeque<Dom<Element>>>,
1504}
1505
1506impl ElementQueue {
1507 fn new() -> ElementQueue {
1508 ElementQueue {
1509 queue: Default::default(),
1510 }
1511 }
1512
1513 fn invoke_reactions(&self, cx: &mut JSContext) {
1515 while let Some(element) = self.next_element() {
1517 element.invoke_reactions(cx)
1518 }
1519 self.queue.borrow_mut().clear();
1520 }
1521
1522 fn next_element(&self) -> Option<DomRoot<Element>> {
1523 self.queue
1524 .borrow_mut()
1525 .pop_front()
1526 .as_deref()
1527 .map(DomRoot::from_ref)
1528 }
1529
1530 fn append_element(&self, element: &Element) {
1531 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1532 }
1533}
1534
1535pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1537 let mut chars = name.chars();
1540 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1541 return false;
1542 }
1543
1544 let mut has_dash = false;
1545
1546 for c in chars {
1547 if c == '-' {
1548 has_dash = true;
1549 continue;
1550 }
1551
1552 if !is_potential_custom_element_char(c) {
1553 return false;
1554 }
1555 }
1556
1557 if !has_dash {
1558 return false;
1559 }
1560
1561 if name == "annotation-xml" ||
1562 name == "color-profile" ||
1563 name == "font-face" ||
1564 name == "font-face-src" ||
1565 name == "font-face-uri" ||
1566 name == "font-face-format" ||
1567 name == "font-face-name" ||
1568 name == "missing-glyph"
1569 {
1570 return false;
1571 }
1572
1573 true
1574}
1575
1576fn is_potential_custom_element_char(c: char) -> bool {
1579 c == '-' ||
1580 c == '.' ||
1581 c == '_' ||
1582 c == '\u{B7}' ||
1583 c.is_ascii_digit() ||
1584 c.is_ascii_lowercase() ||
1585 ('\u{C0}'..='\u{D6}').contains(&c) ||
1586 ('\u{D8}'..='\u{F6}').contains(&c) ||
1587 ('\u{F8}'..='\u{37D}').contains(&c) ||
1588 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1589 ('\u{200C}'..='\u{200D}').contains(&c) ||
1590 ('\u{203F}'..='\u{2040}').contains(&c) ||
1591 ('\u{2070}'..='\u{218F}').contains(&c) ||
1592 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1593 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1594 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1595 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1596 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1597}
1598
1599fn is_extendable_element_interface(element: &str) -> bool {
1600 element == "a" ||
1601 element == "abbr" ||
1602 element == "acronym" ||
1603 element == "address" ||
1604 element == "area" ||
1605 element == "article" ||
1606 element == "aside" ||
1607 element == "audio" ||
1608 element == "b" ||
1609 element == "base" ||
1610 element == "bdi" ||
1611 element == "bdo" ||
1612 element == "big" ||
1613 element == "blockquote" ||
1614 element == "body" ||
1615 element == "br" ||
1616 element == "button" ||
1617 element == "canvas" ||
1618 element == "caption" ||
1619 element == "center" ||
1620 element == "cite" ||
1621 element == "code" ||
1622 element == "col" ||
1623 element == "colgroup" ||
1624 element == "data" ||
1625 element == "datalist" ||
1626 element == "dd" ||
1627 element == "del" ||
1628 element == "details" ||
1629 element == "dfn" ||
1630 element == "dialog" ||
1631 element == "dir" ||
1632 element == "div" ||
1633 element == "dl" ||
1634 element == "dt" ||
1635 element == "em" ||
1636 element == "embed" ||
1637 element == "fieldset" ||
1638 element == "figcaption" ||
1639 element == "figure" ||
1640 element == "font" ||
1641 element == "footer" ||
1642 element == "form" ||
1643 element == "frame" ||
1644 element == "frameset" ||
1645 element == "h1" ||
1646 element == "h2" ||
1647 element == "h3" ||
1648 element == "h4" ||
1649 element == "h5" ||
1650 element == "h6" ||
1651 element == "head" ||
1652 element == "header" ||
1653 element == "hgroup" ||
1654 element == "hr" ||
1655 element == "html" ||
1656 element == "i" ||
1657 element == "iframe" ||
1658 element == "img" ||
1659 element == "input" ||
1660 element == "ins" ||
1661 element == "kbd" ||
1662 element == "label" ||
1663 element == "legend" ||
1664 element == "li" ||
1665 element == "link" ||
1666 element == "listing" ||
1667 element == "main" ||
1668 element == "map" ||
1669 element == "mark" ||
1670 element == "marquee" ||
1671 element == "menu" ||
1672 element == "meta" ||
1673 element == "meter" ||
1674 element == "nav" ||
1675 element == "nobr" ||
1676 element == "noframes" ||
1677 element == "noscript" ||
1678 element == "object" ||
1679 element == "ol" ||
1680 element == "optgroup" ||
1681 element == "option" ||
1682 element == "output" ||
1683 element == "p" ||
1684 element == "param" ||
1685 element == "picture" ||
1686 element == "plaintext" ||
1687 element == "pre" ||
1688 element == "progress" ||
1689 element == "q" ||
1690 element == "rp" ||
1691 element == "rt" ||
1692 element == "ruby" ||
1693 element == "s" ||
1694 element == "samp" ||
1695 element == "script" ||
1696 element == "section" ||
1697 element == "select" ||
1698 element == "slot" ||
1699 element == "small" ||
1700 element == "source" ||
1701 element == "span" ||
1702 element == "strike" ||
1703 element == "strong" ||
1704 element == "style" ||
1705 element == "sub" ||
1706 element == "summary" ||
1707 element == "sup" ||
1708 element == "table" ||
1709 element == "tbody" ||
1710 element == "td" ||
1711 element == "template" ||
1712 element == "textarea" ||
1713 element == "tfoot" ||
1714 element == "th" ||
1715 element == "thead" ||
1716 element == "time" ||
1717 element == "title" ||
1718 element == "tr" ||
1719 element == "tt" ||
1720 element == "track" ||
1721 element == "u" ||
1722 element == "ul" ||
1723 element == "var" ||
1724 element == "video" ||
1725 element == "wbr" ||
1726 element == "xmp"
1727}