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 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, get_property};
37use crate::dom::bindings::error::{
38 Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception,
39};
40use crate::dom::bindings::inheritance::{Castable, DocumentFragmentTypeId, NodeTypeId};
41use crate::dom::bindings::reflector::DomGlobal;
42use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot, UnrootedDom};
43use crate::dom::bindings::str::DOMString;
44use crate::dom::bindings::trace::HashMapTracedValues;
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::realms::enter_auto_realm;
58use crate::runtime::microtask::CustomElementReactionMicrotask;
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(Some(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
763 .custom_element_registry()
764 .is_none_or(|registry| Self::is_a_global_element_registry(Some(&*registry)))
765 {
766 element.set_custom_element_registry(Some(self), cx.no_gc());
768
769 if self.is_scoped.get() {
772 let document = element.upcast::<Node>().owner_doc();
773 self.scoped_document_set
774 .borrow_mut()
775 .push(Dom::from_ref(&document));
776 }
777 } else if element
779 .custom_element_registry()
780 .is_none_or(|registry| *registry != *self)
781 {
782 continue;
783 }
784
785 try_upgrade_element(cx, element);
787 }
788 Ok(())
789 }
790}
791
792#[derive(Clone, JSTraceable, MallocSizeOf)]
793pub(crate) struct LifecycleCallbacks {
794 #[conditional_malloc_size_of]
795 connected_callback: Option<Rc<Function>>,
796
797 #[conditional_malloc_size_of]
798 connected_move_callback: Option<Rc<Function>>,
799
800 #[conditional_malloc_size_of]
801 disconnected_callback: Option<Rc<Function>>,
802
803 #[conditional_malloc_size_of]
804 adopted_callback: Option<Rc<Function>>,
805
806 #[conditional_malloc_size_of]
807 attribute_changed_callback: Option<Rc<Function>>,
808
809 #[conditional_malloc_size_of]
810 form_associated_callback: Option<Rc<Function>>,
811
812 #[conditional_malloc_size_of]
813 form_reset_callback: Option<Rc<Function>>,
814
815 #[conditional_malloc_size_of]
816 form_disabled_callback: Option<Rc<Function>>,
817
818 #[conditional_malloc_size_of]
819 form_state_restore_callback: Option<Rc<Function>>,
820}
821
822#[derive(Clone, JSTraceable, MallocSizeOf)]
823pub(crate) enum ConstructionStackEntry {
824 Element(DomRoot<Element>),
825 AlreadyConstructedMarker,
826}
827
828#[derive(Clone, JSTraceable, MallocSizeOf)]
830pub(crate) struct CustomElementDefinition {
831 #[no_trace]
833 pub(crate) name: LocalName,
834
835 #[no_trace]
837 pub(crate) local_name: LocalName,
838
839 #[conditional_malloc_size_of]
841 pub(crate) constructor: Rc<CustomElementConstructor>,
842
843 pub(crate) observed_attributes: Vec<DOMString>,
845
846 pub(crate) callbacks: LifecycleCallbacks,
848
849 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
851
852 pub(crate) form_associated: bool,
854
855 pub(crate) disable_internals: bool,
857
858 pub(crate) disable_shadow: bool,
860}
861
862impl CustomElementDefinition {
863 #[expect(clippy::too_many_arguments)]
864 fn new(
865 name: LocalName,
866 local_name: LocalName,
867 constructor: Rc<CustomElementConstructor>,
868 observed_attributes: Vec<DOMString>,
869 callbacks: LifecycleCallbacks,
870 form_associated: bool,
871 disable_internals: bool,
872 disable_shadow: bool,
873 ) -> CustomElementDefinition {
874 CustomElementDefinition {
875 name,
876 local_name,
877 constructor,
878 observed_attributes,
879 callbacks,
880 construction_stack: Default::default(),
881 form_associated,
882 disable_internals,
883 disable_shadow,
884 }
885 }
886
887 pub(crate) fn is_autonomous(&self) -> bool {
889 self.name == self.local_name
890 }
891
892 #[expect(unsafe_code)]
894 pub(crate) fn create_element(
895 &self,
896 cx: &mut JSContext,
897 document: &Document,
898 prefix: Option<Prefix>,
899 registry: Option<&CustomElementRegistry>,
900 ) -> Fallible<DomRoot<Element>> {
901 let window = document.window();
902
903 rooted!(&in(cx) let constructor = ObjectValue(self.constructor.callback()));
905 rooted!(&in(cx) let mut element = ptr::null_mut::<JSObject>());
906 {
907 let mut realm = AutoRealm::new(cx, NonNull::new(self.constructor.callback()).unwrap());
909 let cx = &mut realm;
910
911 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
914 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
915 let args = HandleValueArray::empty();
916 if unsafe { !Construct1(cx, constructor.handle(), &args, element.handle_mut()) }
917 {
918 Err(Error::JSFailed)
919 } else {
920 Ok(())
921 }
922 })
923 })?;
924 }
925
926 rooted!(&in(cx) let element_val = ObjectValue(element.get()));
927 let element: DomRoot<Element> =
928 match FromJSValConvertible::safe_from_jsval(cx, element_val.handle(), ()) {
929 Ok(ConversionResult::Success(element)) => element,
930 Ok(ConversionResult::Failure(..)) => {
931 return Err(Error::Type(
932 c"Constructor did not return a DOM node".to_owned(),
933 ));
934 },
935 _ => return Err(Error::JSFailed),
936 };
937
938 assert!(element.is::<HTMLElement>());
944
945 if element.HasAttributes() ||
951 element.upcast::<Node>().children_count() > 0 ||
952 element.upcast::<Node>().has_parent() ||
953 &*element.upcast::<Node>().owner_doc() != document ||
954 *element.namespace() != ns!(html) ||
955 *element.local_name() != self.local_name
956 {
957 return Err(Error::NotSupported(None));
958 }
959
960 element.set_prefix(prefix);
962
963 element.set_custom_element_registry(registry, cx.no_gc());
968
969 Ok(element)
970 }
971
972 pub(crate) fn has_attribute_changed_callback(&self) -> bool {
973 self.callbacks.attribute_changed_callback.is_some()
974 }
975}
976
977pub(crate) fn upgrade_element(
979 cx: &mut JSContext,
980 definition: Rc<CustomElementDefinition>,
981 element: &Element,
982) {
983 let state = element.get_custom_element_state();
985 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
986 return;
987 }
988
989 element.set_custom_element_definition(Rc::clone(&definition), cx.no_gc());
991
992 element.set_custom_element_state(CustomElementState::Failed, cx.no_gc());
994
995 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
999 for attr in element.attrs().borrow().iter() {
1000 let local_name = attr.local_name().clone();
1001 let namespace = attr.namespace().clone();
1002 custom_element_reaction_stack.enqueue_callback_reaction(
1003 cx,
1004 element,
1005 CallbackReaction::AttributeChanged(local_name, None, Some(&*attr.value()), namespace),
1006 Some(definition.clone()),
1007 );
1008 }
1009
1010 if element.is_connected() {
1013 custom_element_reaction_stack.enqueue_callback_reaction(
1014 cx,
1015 element,
1016 CallbackReaction::Connected,
1017 Some(definition.clone()),
1018 );
1019 }
1020
1021 definition
1023 .construction_stack
1024 .borrow_mut()
1025 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
1026
1027 let result = run_upgrade_constructor(cx, &definition, element);
1029
1030 definition.construction_stack.borrow_mut().pop();
1032
1033 if let Err(error) = result {
1035 element.clear_custom_element_definition(cx.no_gc());
1037
1038 element.clear_reaction_queue();
1040
1041 let global = GlobalScope::current().expect("No current global");
1043
1044 let mut realm = enter_auto_realm(cx, &*global);
1045 let cx = &mut realm.current_realm();
1046
1047 throw_dom_exception(cx, &global, error);
1048 report_pending_exception(cx);
1049
1050 return;
1051 }
1052
1053 if let Some(html_element) = element.downcast::<HTMLElement>() &&
1055 html_element.is_form_associated_custom_element()
1056 {
1057 html_element.reset_form_owner(cx);
1061 if let Some(form) = html_element.form_owner() {
1062 form.upcast::<Node>().rev_version(cx.no_gc());
1065 }
1069
1070 element.check_disabled_attribute();
1077 element.check_ancestors_disabled_state_for_form_control();
1078 element.update_read_write_state_from_readonly_attribute();
1079
1080 if element.disabled_state() {
1083 custom_element_reaction_stack.enqueue_callback_reaction(
1084 cx,
1085 element,
1086 CallbackReaction::FormDisabled(true),
1087 Some(definition),
1088 )
1089 }
1090 }
1091
1092 element.set_custom_element_state(CustomElementState::Custom, cx.no_gc());
1094}
1095
1096#[expect(unsafe_code)]
1099fn run_upgrade_constructor(
1100 cx: &mut JSContext,
1101 definition: &CustomElementDefinition,
1102 element: &Element,
1103) -> ErrorResult {
1104 let constructor = &definition.constructor;
1105 let window = element.owner_window();
1106 rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
1107 rooted!(&in(cx) let mut element_val = UndefinedValue());
1108 element.safe_to_jsval(cx, element_val.handle_mut());
1109 rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
1110 {
1111 if definition.disable_shadow && element.is_shadow_host() {
1114 return Err(Error::NotSupported(None));
1115 }
1116
1117 let mut realm = AutoRealm::new(cx, NonNull::new(constructor.callback()).unwrap());
1119 let cx = &mut *realm;
1120
1121 let args = HandleValueArray::empty();
1122 element.set_custom_element_state(CustomElementState::Precustomized, cx.no_gc());
1124
1125 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
1128 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
1129 if unsafe {
1130 !Construct1(
1131 cx,
1132 constructor_val.handle(),
1133 &args,
1134 construct_result.handle_mut(),
1135 )
1136 } {
1137 Err(Error::JSFailed)
1138 } else {
1139 Ok(())
1140 }
1141 })
1142 })?;
1143
1144 let mut same = false;
1145 rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
1146
1147 if unsafe {
1149 !SameValue(
1150 cx,
1151 construct_result_val.handle(),
1152 element_val.handle(),
1153 &mut same,
1154 )
1155 } {
1156 return Err(Error::JSFailed);
1157 }
1158 if !same {
1159 return Err(Error::Type(
1160 c"Returned element is not SameValue as the upgraded element".to_owned(),
1161 ));
1162 }
1163 }
1164 Ok(())
1165}
1166
1167pub(crate) fn try_upgrade_element(cx: &JSContext, element: &Element) {
1169 let lookup_registry = {
1173 let registry = element.custom_element_registry();
1176 if registry
1177 .as_ref()
1178 .is_some_and(|registry| registry.is_scoped())
1179 {
1180 registry
1181 } else {
1182 element.owner_document().custom_element_registry()
1183 }
1184 };
1185 if let Some(definition) = CustomElementRegistry::lookup_custom_element_definition(
1186 lookup_registry.as_deref(),
1187 element.namespace(),
1188 element.local_name(),
1189 element.get_is().as_ref(),
1190 ) {
1191 ScriptThread::enqueue_upgrade_reaction(cx, element, definition);
1194 }
1195}
1196
1197#[derive(JSTraceable, MallocSizeOf)]
1198#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1199pub(crate) enum CustomElementReaction {
1200 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1201 Callback(
1202 #[conditional_malloc_size_of] Rc<Function>,
1203 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1204 ),
1205}
1206
1207impl CustomElementReaction {
1208 pub(crate) fn invoke(&self, cx: &mut JSContext, element: &Element) {
1210 match *self {
1212 CustomElementReaction::Upgrade(ref definition) => {
1213 upgrade_element(cx, definition.clone(), element)
1214 },
1215 CustomElementReaction::Callback(ref callback, ref arguments) => {
1216 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1218 rooted!(&in(cx) let mut value: JSVal);
1219 let _ = callback.Call_(
1220 cx,
1221 element,
1222 arguments,
1223 value.handle_mut(),
1224 ExceptionHandling::Report,
1225 );
1226 },
1227 }
1228 }
1229}
1230
1231pub(crate) enum CallbackReaction<'a> {
1232 Connected,
1233 Disconnected,
1234 Adopted(DomRoot<Document>, DomRoot<Document>),
1235 AttributeChanged(
1236 LocalName,
1237 Option<&'a AttrValue>,
1238 Option<&'a AttrValue>,
1239 Namespace,
1240 ),
1241 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1242 FormDisabled(bool),
1243 FormReset,
1244 ConnectedMove,
1245}
1246
1247#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1249enum BackupElementQueueFlag {
1250 Processing,
1251 NotProcessing,
1252}
1253
1254#[derive(JSTraceable, MallocSizeOf)]
1259#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1260#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1261pub(crate) struct CustomElementReactionStack {
1262 stack: DomRefCell<Vec<ElementQueue>>,
1263 backup_queue: ElementQueue,
1264 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1265}
1266
1267impl CustomElementReactionStack {
1268 pub(crate) fn new() -> CustomElementReactionStack {
1269 CustomElementReactionStack {
1270 stack: DomRefCell::new(Vec::new()),
1271 backup_queue: ElementQueue::new(),
1272 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1273 }
1274 }
1275
1276 pub(crate) fn push_new_element_queue(&self) {
1277 self.stack.borrow_mut().push(ElementQueue::new());
1278 }
1279
1280 pub(crate) fn pop_current_element_queue(&self, cx: &mut JSContext) {
1281 rooted_vec!(let mut stack);
1282 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1283
1284 if let Some(current_queue) = stack.last() {
1285 current_queue.invoke_reactions(cx);
1286 }
1287 stack.pop();
1288
1289 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1290 self.stack.borrow_mut().append(&mut *stack);
1291 }
1292
1293 pub(crate) fn invoke_backup_element_queue(&self, cx: &mut JSContext) {
1296 self.backup_queue.invoke_reactions(cx);
1298
1299 self.processing_backup_element_queue
1301 .set(BackupElementQueueFlag::NotProcessing);
1302 }
1303
1304 pub(crate) fn enqueue_element(&self, cx: &JSContext, element: &Element) {
1306 if let Some(current_queue) = self.stack.borrow().last() {
1307 current_queue.append_element(element);
1309 } else {
1310 self.backup_queue.append_element(element);
1312
1313 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1315 return;
1316 }
1317
1318 self.processing_backup_element_queue
1320 .set(BackupElementQueueFlag::Processing);
1321
1322 ScriptThread::enqueue_microtask(cx, Box::new(CustomElementReactionMicrotask::new()));
1324 }
1325 }
1326
1327 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1329 pub(crate) fn enqueue_callback_reaction(
1330 &self,
1331 cx: &mut JSContext,
1332 element: &Element,
1333 reaction: CallbackReaction,
1334 definition: Option<Rc<CustomElementDefinition>>,
1335 ) {
1336 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1338 Some(definition) => definition,
1339 None => return,
1340 };
1341
1342 let (callback, args) = match reaction {
1345 CallbackReaction::Connected => {
1346 (definition.callbacks.connected_callback.clone(), Vec::new())
1347 },
1348 CallbackReaction::Disconnected => (
1349 definition.callbacks.disconnected_callback.clone(),
1350 Vec::new(),
1351 ),
1352 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1353 let args = vec![Heap::default(), Heap::default()];
1354 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1355 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1356 (definition.callbacks.adopted_callback.clone(), args)
1357 },
1358 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1359 if !definition
1361 .observed_attributes
1362 .iter()
1363 .any(|attr| *attr == *local_name)
1364 {
1365 return;
1366 }
1367
1368 let mut realm = enter_auto_realm(cx, &*element.global());
1372 let cx = &mut realm;
1373
1374 let local_name = DOMString::from(&*local_name);
1375 rooted!(&in(cx) let mut name_value = UndefinedValue());
1376 local_name.safe_to_jsval(cx, name_value.handle_mut());
1377
1378 rooted!(&in(cx) let mut old_value = NullValue());
1379 if let Some(old_val) = old_val {
1380 old_val.safe_to_jsval(cx, old_value.handle_mut());
1381 }
1382
1383 rooted!(&in(cx) let mut value = NullValue());
1384 if let Some(val) = val {
1385 val.safe_to_jsval(cx, value.handle_mut());
1386 }
1387
1388 rooted!(&in(cx) let mut namespace_value = NullValue());
1389 if namespace != ns!() {
1390 let namespace = DOMString::from(&*namespace);
1391 namespace.safe_to_jsval(cx, namespace_value.handle_mut());
1392 }
1393
1394 let args = vec![
1395 Heap::default(),
1396 Heap::default(),
1397 Heap::default(),
1398 Heap::default(),
1399 ];
1400 args[0].set(name_value.get());
1401 args[1].set(old_value.get());
1402 args[2].set(value.get());
1403 args[3].set(namespace_value.get());
1404
1405 (
1406 definition.callbacks.attribute_changed_callback.clone(),
1407 args,
1408 )
1409 },
1410 CallbackReaction::FormAssociated(form) => {
1411 let args = vec![Heap::default()];
1412 if let Some(form) = form {
1413 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1414 } else {
1415 args[0].set(NullValue());
1416 }
1417 (definition.callbacks.form_associated_callback.clone(), args)
1418 },
1419 CallbackReaction::FormDisabled(disabled) => {
1420 rooted!(&in(cx) let disabled_value = BooleanValue(disabled));
1421 let args = vec![Heap::default()];
1422 args[0].set(disabled_value.get());
1423 (definition.callbacks.form_disabled_callback.clone(), args)
1424 },
1425 CallbackReaction::FormReset => {
1426 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1427 },
1428 CallbackReaction::ConnectedMove => {
1429 let callback = definition.callbacks.connected_move_callback.clone();
1430 if callback.is_none() {
1432 let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1435
1436 let connected_callback = definition.callbacks.connected_callback.clone();
1439
1440 if disconnected_callback.is_none() && connected_callback.is_none() {
1443 return;
1444 }
1445
1446 if let Some(disconnected_callback) = disconnected_callback {
1450 element.push_callback_reaction(
1451 disconnected_callback,
1452 Box::new([]),
1453 cx.no_gc(),
1454 );
1455 }
1456 if let Some(connected_callback) = connected_callback {
1459 element.push_callback_reaction(
1460 connected_callback,
1461 Box::new([]),
1462 cx.no_gc(),
1463 );
1464 }
1465
1466 self.enqueue_element(cx, element);
1467 return;
1468 }
1469
1470 (callback, Vec::new())
1471 },
1472 };
1473
1474 let callback = match callback {
1476 Some(callback) => callback,
1477 None => return,
1478 };
1479
1480 element.push_callback_reaction(callback, args.into_boxed_slice(), cx.no_gc());
1483
1484 self.enqueue_element(cx, element);
1486 }
1487
1488 pub(crate) fn enqueue_upgrade_reaction(
1490 &self,
1491 cx: &JSContext,
1492 element: &Element,
1493 definition: Rc<CustomElementDefinition>,
1494 ) {
1495 element.push_upgrade_reaction(definition, cx.no_gc());
1498
1499 self.enqueue_element(cx, element);
1501 }
1502}
1503
1504#[derive(JSTraceable, MallocSizeOf)]
1506#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1507struct ElementQueue {
1508 queue: DomRefCell<VecDeque<Dom<Element>>>,
1509}
1510
1511impl ElementQueue {
1512 fn new() -> ElementQueue {
1513 ElementQueue {
1514 queue: Default::default(),
1515 }
1516 }
1517
1518 fn invoke_reactions(&self, cx: &mut JSContext) {
1520 while let Some(element) = self.next_element() {
1522 element.invoke_reactions(cx)
1523 }
1524 self.queue.borrow_mut().clear();
1525 }
1526
1527 fn next_element(&self) -> Option<DomRoot<Element>> {
1528 self.queue
1529 .borrow_mut()
1530 .pop_front()
1531 .as_deref()
1532 .map(DomRoot::from_ref)
1533 }
1534
1535 fn append_element(&self, element: &Element) {
1536 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1537 }
1538}
1539
1540pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1542 let mut chars = name.chars();
1545 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1546 return false;
1547 }
1548
1549 let mut has_dash = false;
1550
1551 for c in chars {
1552 if c == '-' {
1553 has_dash = true;
1554 continue;
1555 }
1556
1557 if !is_potential_custom_element_char(c) {
1558 return false;
1559 }
1560 }
1561
1562 if !has_dash {
1563 return false;
1564 }
1565
1566 if name == "annotation-xml" ||
1567 name == "color-profile" ||
1568 name == "font-face" ||
1569 name == "font-face-src" ||
1570 name == "font-face-uri" ||
1571 name == "font-face-format" ||
1572 name == "font-face-name" ||
1573 name == "missing-glyph"
1574 {
1575 return false;
1576 }
1577
1578 true
1579}
1580
1581fn is_potential_custom_element_char(c: char) -> bool {
1584 c == '-' ||
1585 c == '.' ||
1586 c == '_' ||
1587 c == '\u{B7}' ||
1588 c.is_ascii_digit() ||
1589 c.is_ascii_lowercase() ||
1590 ('\u{C0}'..='\u{D6}').contains(&c) ||
1591 ('\u{D8}'..='\u{F6}').contains(&c) ||
1592 ('\u{F8}'..='\u{37D}').contains(&c) ||
1593 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1594 ('\u{200C}'..='\u{200D}').contains(&c) ||
1595 ('\u{203F}'..='\u{2040}').contains(&c) ||
1596 ('\u{2070}'..='\u{218F}').contains(&c) ||
1597 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1598 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1599 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1600 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1601 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1602}
1603
1604fn is_extendable_element_interface(element: &str) -> bool {
1605 element == "a" ||
1606 element == "abbr" ||
1607 element == "acronym" ||
1608 element == "address" ||
1609 element == "area" ||
1610 element == "article" ||
1611 element == "aside" ||
1612 element == "audio" ||
1613 element == "b" ||
1614 element == "base" ||
1615 element == "bdi" ||
1616 element == "bdo" ||
1617 element == "big" ||
1618 element == "blockquote" ||
1619 element == "body" ||
1620 element == "br" ||
1621 element == "button" ||
1622 element == "canvas" ||
1623 element == "caption" ||
1624 element == "center" ||
1625 element == "cite" ||
1626 element == "code" ||
1627 element == "col" ||
1628 element == "colgroup" ||
1629 element == "data" ||
1630 element == "datalist" ||
1631 element == "dd" ||
1632 element == "del" ||
1633 element == "details" ||
1634 element == "dfn" ||
1635 element == "dialog" ||
1636 element == "dir" ||
1637 element == "div" ||
1638 element == "dl" ||
1639 element == "dt" ||
1640 element == "em" ||
1641 element == "embed" ||
1642 element == "fieldset" ||
1643 element == "figcaption" ||
1644 element == "figure" ||
1645 element == "font" ||
1646 element == "footer" ||
1647 element == "form" ||
1648 element == "frame" ||
1649 element == "frameset" ||
1650 element == "h1" ||
1651 element == "h2" ||
1652 element == "h3" ||
1653 element == "h4" ||
1654 element == "h5" ||
1655 element == "h6" ||
1656 element == "head" ||
1657 element == "header" ||
1658 element == "hgroup" ||
1659 element == "hr" ||
1660 element == "html" ||
1661 element == "i" ||
1662 element == "iframe" ||
1663 element == "img" ||
1664 element == "input" ||
1665 element == "ins" ||
1666 element == "kbd" ||
1667 element == "label" ||
1668 element == "legend" ||
1669 element == "li" ||
1670 element == "link" ||
1671 element == "listing" ||
1672 element == "main" ||
1673 element == "map" ||
1674 element == "mark" ||
1675 element == "marquee" ||
1676 element == "menu" ||
1677 element == "meta" ||
1678 element == "meter" ||
1679 element == "nav" ||
1680 element == "nobr" ||
1681 element == "noframes" ||
1682 element == "noscript" ||
1683 element == "object" ||
1684 element == "ol" ||
1685 element == "optgroup" ||
1686 element == "option" ||
1687 element == "output" ||
1688 element == "p" ||
1689 element == "param" ||
1690 element == "picture" ||
1691 element == "plaintext" ||
1692 element == "pre" ||
1693 element == "progress" ||
1694 element == "q" ||
1695 element == "rp" ||
1696 element == "rt" ||
1697 element == "ruby" ||
1698 element == "s" ||
1699 element == "samp" ||
1700 element == "script" ||
1701 element == "section" ||
1702 element == "select" ||
1703 element == "slot" ||
1704 element == "small" ||
1705 element == "source" ||
1706 element == "span" ||
1707 element == "strike" ||
1708 element == "strong" ||
1709 element == "style" ||
1710 element == "sub" ||
1711 element == "summary" ||
1712 element == "sup" ||
1713 element == "table" ||
1714 element == "tbody" ||
1715 element == "td" ||
1716 element == "template" ||
1717 element == "textarea" ||
1718 element == "tfoot" ||
1719 element == "th" ||
1720 element == "thead" ||
1721 element == "time" ||
1722 element == "title" ||
1723 element == "tr" ||
1724 element == "tt" ||
1725 element == "track" ||
1726 element == "u" ||
1727 element == "ul" ||
1728 element == "var" ||
1729 element == "video" ||
1730 element == "wbr" ||
1731 element == "xmp"
1732}