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_and_cx};
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::Microtask;
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_and_cx(
123 Box::new(CustomElementRegistry::new_inherited(window)),
124 window,
125 proto,
126 cx,
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(
143 &self,
144 local_name: &LocalName,
145 is: Option<&LocalName>,
146 ) -> Option<Rc<CustomElementDefinition>> {
147 self.definitions
148 .borrow()
149 .0
150 .values()
151 .find(|definition| {
152 definition.local_name == *local_name &&
154 (definition.name == *local_name || Some(&definition.name) == is)
155 })
156 .cloned()
157 }
158
159 pub(crate) fn lookup_definition_by_constructor(
160 &self,
161 constructor: HandleObject,
162 ) -> Option<Rc<CustomElementDefinition>> {
163 self.definitions
164 .borrow()
165 .0
166 .values()
167 .find(|definition| definition.constructor.callback() == constructor.get())
168 .cloned()
169 }
170
171 pub(crate) fn lookup_a_custom_element_registry(
173 node: &Node,
174 ) -> Option<DomRoot<CustomElementRegistry>> {
175 match node.type_id() {
176 NodeTypeId::Element(_) => node
178 .downcast::<Element>()
179 .expect("Nodes with element type must be an element")
180 .custom_element_registry(),
181 NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) => node
183 .downcast::<ShadowRoot>()
184 .expect("Nodes with ShadowRoot type must be a ShadowRoot")
185 .custom_element_registry(),
186 NodeTypeId::Document(_) => node
188 .downcast::<Document>()
189 .expect("Nodes with document type must be a document")
190 .custom_element_registry(),
191 _ => None,
193 }
194 }
195
196 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
198 registry.is_some_and(|r| !r.is_scoped())
201 }
202
203 #[expect(unsafe_code)]
206 fn check_prototype(
207 &self,
208 cx: &mut JSContext,
209 constructor: HandleObject,
210 mut prototype: MutableHandleValue,
211 ) -> ErrorResult {
212 unsafe {
213 if !JS_GetProperty(cx, constructor, c"prototype".as_ptr(), prototype.reborrow()) {
215 return Err(Error::JSFailed);
216 }
217
218 if !prototype.is_object() {
220 return Err(Error::Type(
221 c"constructor.prototype is not an object".to_owned(),
222 ));
223 }
224 }
225 Ok(())
226 }
227
228 fn get_callbacks(
232 &self,
233 cx: &mut JSContext,
234 prototype: HandleObject,
235 ) -> Fallible<LifecycleCallbacks> {
236 Ok(LifecycleCallbacks {
238 connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
239 disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
240 connected_move_callback: get_callback(cx, prototype, c"connectedMoveCallback")?,
241 adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
242 attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
243
244 form_associated_callback: None,
245 form_disabled_callback: None,
246 form_reset_callback: None,
247 form_state_restore_callback: None,
248 })
249 }
250
251 #[expect(unsafe_code)]
254 unsafe fn add_form_associated_callbacks(
255 &self,
256 cx: &mut JSContext,
257 prototype: HandleObject,
258 callbacks: &mut LifecycleCallbacks,
259 ) -> ErrorResult {
260 callbacks.form_associated_callback =
261 get_callback(cx, prototype, c"formAssociatedCallback")?;
262 callbacks.form_reset_callback = get_callback(cx, prototype, c"formResetCallback")?;
263 callbacks.form_disabled_callback = get_callback(cx, prototype, c"formDisabledCallback")?;
264 callbacks.form_state_restore_callback =
265 get_callback(cx, prototype, c"formStateRestoreCallback")?;
266
267 Ok(())
268 }
269
270 fn upgrade_particular_elements_within_a_document(
272 &self,
273 cx: &JSContext,
274 document: &Document,
275 definition: &Rc<CustomElementDefinition>,
276 local_name: &LocalName,
277 name: &LocalName,
278 ) {
279 for candidate in document
285 .upcast::<Node>()
286 .traverse_preorder_non_rooting(cx, ShadowIncluding::Yes)
287 .filter_map(UnrootedDom::downcast::<Element>)
288 {
289 let registry_matches = if self.is_scoped.get() {
294 candidate
295 .custom_element_registry()
296 .is_some_and(|registry| *registry == *self)
297 } else {
298 candidate
299 .custom_element_registry()
300 .is_none_or(|registry| *registry == *self)
301 };
302 if *candidate.local_name() == *local_name &&
303 *candidate.namespace() == ns!(html) &&
304 registry_matches &&
305 (*name == *local_name || candidate.get_is().as_ref() == Some(name))
306 {
307 ScriptThread::enqueue_upgrade_reaction(cx, &candidate, definition.clone());
310 }
311 }
312 }
313}
314
315#[expect(unsafe_code)]
318fn get_callback(
319 cx: &mut JSContext,
320 prototype: HandleObject,
321 name: &CStr,
322) -> Fallible<Option<Rc<Function>>> {
323 rooted!(&in(cx) let mut callback = UndefinedValue());
324 unsafe {
325 if !JS_GetProperty(cx, prototype, name.as_ptr(), callback.handle_mut()) {
327 return Err(Error::JSFailed);
328 }
329
330 if !callback.is_undefined() {
332 if !callback.is_object() || !IsCallable(callback.to_object()) {
333 return Err(Error::Type(
334 c"Lifecycle callback is not callable".to_owned(),
335 ));
336 }
337 Ok(Some(Function::new(cx, callback.to_object())))
338 } else {
339 Ok(None)
340 }
341 }
342}
343
344impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
345 fn Constructor(
347 cx: &mut JSContext,
348 window: &Window,
349 proto: Option<HandleObject>,
350 ) -> DomRoot<CustomElementRegistry> {
351 let registry = CustomElementRegistry::new_with_proto(cx, window, proto);
352
353 registry.is_scoped.set(true);
355 registry
356 }
357
358 #[expect(unsafe_code)]
359 fn Define(
361 &self,
362 cx: &mut JSContext,
363 name: DOMString,
364 constructor_: Rc<CustomElementConstructor>,
365 options: &ElementDefinitionOptions,
366 ) -> ErrorResult {
367 rooted!(&in(cx) let constructor = constructor_.callback());
368 let name = LocalName::from(name);
369
370 rooted!(&in(cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
373
374 if unwrapped_constructor.is_null() {
375 return Err(Error::Security(None));
377 }
378
379 if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
380 return Err(Error::Type(
381 c"Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
382 ));
383 }
384
385 if !is_valid_custom_element_name(&name) {
387 return Err(Error::Syntax(Some(format!(
388 "{} name is not a valid custom element name",
389 name
390 ))));
391 }
392
393 if self.definitions.borrow().contains_key(&name) {
396 return Err(Error::NotSupported(Some(format!(
397 "{} has already been defined as a custom element",
398 name
399 ))));
400 }
401
402 if self
405 .definitions
406 .borrow()
407 .iter()
408 .any(|(_, def)| def.constructor == constructor_)
409 {
410 return Err(Error::NotSupported(None));
411 }
412
413 let extends = &options.extends;
415
416 let local_name = if let Some(ref extended_name) = *extends {
418 if self.is_scoped.get() {
420 return Err(Error::NotSupported(Some(
421 "Scoped custom element registries cannot define customized built-in elements"
422 .to_owned(),
423 )));
424 }
425
426 if is_valid_custom_element_name(&extended_name.str()) {
428 return Err(Error::NotSupported(None));
429 }
430
431 if !is_extendable_element_interface(&extended_name.str()) {
435 return Err(Error::NotSupported(None));
436 }
437
438 LocalName::from(extended_name)
440 } else {
441 name.clone()
443 };
444
445 if self.element_definition_is_running.get() {
447 return Err(Error::NotSupported(None));
448 }
449
450 self.element_definition_is_running.set(true);
452
453 rooted!(&in(cx) let mut prototype = UndefinedValue());
458 {
459 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
460 if let Err(error) =
461 self.check_prototype(&mut realm, constructor.handle(), prototype.handle_mut())
462 {
463 self.element_definition_is_running.set(false);
464 return Err(error);
465 }
466 };
467
468 rooted!(&in(cx) let proto_object = prototype.to_object());
474 let mut callbacks = {
475 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
476 match self.get_callbacks(&mut realm, proto_object.handle()) {
477 Ok(callbacks) => callbacks,
478 Err(error) => {
479 self.element_definition_is_running.set(false);
480 return Err(error);
481 },
482 }
483 };
484
485 let observed_attributes: Vec<DOMString> = if callbacks.attribute_changed_callback.is_some()
488 {
489 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
490 match get_property(
491 &mut realm,
492 constructor.handle(),
493 c"observedAttributes",
494 StringificationBehavior::Default,
495 ) {
496 Ok(attributes) => attributes.unwrap_or_default(),
497 Err(error) => {
498 self.element_definition_is_running.set(false);
499 return Err(error);
500 },
501 }
502 } else {
503 Vec::new()
504 };
505
506 let (disable_internals, disable_shadow) = {
508 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
509 match get_property::<Vec<DOMString>>(
510 &mut realm,
511 constructor.handle(),
512 c"disabledFeatures",
513 StringificationBehavior::Default,
514 ) {
515 Ok(sequence) => {
516 let sequence = sequence.unwrap_or_default();
517 (
518 sequence.iter().any(|s| *s == "internals"),
519 sequence.iter().any(|s| *s == "shadow"),
520 )
521 },
522 Err(error) => {
523 self.element_definition_is_running.set(false);
524 return Err(error);
525 },
526 }
527 };
528
529 let form_associated: bool = {
531 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
532 match get_property(&mut realm, constructor.handle(), c"formAssociated", ()) {
533 Ok(flag) => flag.unwrap_or_default(),
534 Err(error) => {
535 self.element_definition_is_running.set(false);
536 return Err(error);
537 },
538 }
539 };
540
541 if form_associated {
543 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
544 unsafe {
545 if let Err(error) = self.add_form_associated_callbacks(
546 &mut realm,
547 proto_object.handle(),
548 &mut callbacks,
549 ) {
550 self.element_definition_is_running.set(false);
551 return Err(error);
552 }
553 }
554 }
555
556 self.element_definition_is_running.set(false);
557
558 let definition = Rc::new(CustomElementDefinition::new(
564 name.clone(),
565 local_name.clone(),
566 constructor_,
567 observed_attributes,
568 callbacks,
569 form_associated,
570 disable_internals,
571 disable_shadow,
572 ));
573
574 self.definitions
576 .borrow_mut()
577 .insert(name.clone(), definition.clone());
578
579 if self.is_scoped.get() {
583 for document in self.scoped_document_set.borrow().iter() {
584 self.upgrade_particular_elements_within_a_document(
585 cx,
586 document,
587 &definition,
588 &local_name,
589 &local_name,
590 );
591 }
592 } else {
593 self.upgrade_particular_elements_within_a_document(
597 cx,
598 &self.window.Document(),
599 &definition,
600 &local_name,
601 &name,
602 );
603 }
604
605 let promise = self.when_defined.borrow_mut().remove(&name);
608 if let Some(promise) = promise {
609 rooted!(&in(cx) let mut constructor = UndefinedValue());
610 definition
611 .constructor
612 .safe_to_jsval(cx, constructor.handle_mut());
613 promise.resolve_native(cx, &constructor.get());
615 }
616 Ok(())
617 }
618
619 fn Get(&self, cx: &mut JSContext, name: DOMString, mut retval: MutableHandleValue) {
621 match self.definitions.borrow().get(&LocalName::from(name)) {
622 Some(definition) => definition.constructor.safe_to_jsval(cx, retval),
623 None => retval.set(UndefinedValue()),
624 }
625 }
626
627 fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
629 self.definitions
630 .borrow()
631 .0
632 .values()
633 .find(|definition| definition.constructor == constructor)
634 .map(|definition| DOMString::from(definition.name.to_string()))
635 }
636
637 fn WhenDefined(&self, realm: &mut CurrentRealm, name: DOMString) -> Rc<Promise> {
639 let name = LocalName::from(name);
640
641 if !is_valid_custom_element_name(&name) {
643 let promise = Promise::new_in_realm(realm);
644 let error = DOMException::new(
645 realm,
646 self.window.as_global_scope(),
647 DOMErrorName::SyntaxError,
648 );
649 promise.reject_native(realm, &error);
650 return promise;
651 }
652
653 if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
655 rooted!(&in(*realm) let mut constructor = UndefinedValue());
656 definition
657 .constructor
658 .safe_to_jsval(realm, constructor.handle_mut());
659 let promise = Promise::new_in_realm(realm);
660 promise.resolve_native(realm, &constructor.get());
661 return promise;
662 }
663
664 let existing_promise = self.when_defined.borrow().get(&name).cloned();
666 existing_promise.unwrap_or_else(|| {
667 let promise = Promise::new_in_realm(realm);
668 self.when_defined.borrow_mut().insert(name, promise.clone());
669 promise
670 })
671 }
672
673 fn Upgrade(&self, cx: &JSContext, node: &Node) {
675 for node in node.traverse_preorder_non_rooting(cx, ShadowIncluding::Yes) {
678 let Some(element) = node.downcast::<Element>() else {
680 continue;
681 };
682 if element
685 .custom_element_registry()
686 .is_some_and(|registry| *registry != *self)
687 {
688 continue;
689 }
690 try_upgrade_element(cx, element);
692 }
693 }
694
695 fn Initialize(&self, cx: &JSContext, root: &Node) -> ErrorResult {
697 if !self.is_scoped.get() {
701 let is_document = root.is::<Document>();
702 let registry_mismatch = root
703 .owner_doc()
704 .custom_element_registry()
705 .is_some_and(|registry| *registry != *self);
706 if is_document || registry_mismatch {
707 return Err(Error::NotSupported(Some(
708 "Initialize is not allowed on a non-scoped registry for this root".to_owned(),
709 )));
710 }
711 }
712
713 if let Some(document) = root.downcast::<Document>() {
716 if document.custom_element_registry().is_none() {
717 document.set_custom_element_registry(self);
718 }
719 }
720 else if let Some(shadow_root) = root.downcast::<ShadowRoot>() &&
723 shadow_root.custom_element_registry().is_none()
724 {
725 shadow_root.set_custom_element_registry(self);
726 }
727
728 for node in root.traverse_preorder(ShadowIncluding::No) {
730 let Some(element) = node.downcast::<Element>() else {
732 continue;
733 };
734
735 if element.custom_element_registry().is_none() {
737 element.set_custom_element_registry(Some(self));
739
740 if self.is_scoped.get() {
743 let document = element.upcast::<Node>().owner_doc();
744 self.scoped_document_set
745 .borrow_mut()
746 .push(Dom::from_ref(&document));
747 }
748 } else if element
750 .custom_element_registry()
751 .is_none_or(|registry| *registry != *self)
752 {
753 continue;
754 }
755
756 try_upgrade_element(cx, element);
758 }
759 Ok(())
760 }
761}
762
763#[derive(Clone, JSTraceable, MallocSizeOf)]
764pub(crate) struct LifecycleCallbacks {
765 #[conditional_malloc_size_of]
766 connected_callback: Option<Rc<Function>>,
767
768 #[conditional_malloc_size_of]
769 connected_move_callback: Option<Rc<Function>>,
770
771 #[conditional_malloc_size_of]
772 disconnected_callback: Option<Rc<Function>>,
773
774 #[conditional_malloc_size_of]
775 adopted_callback: Option<Rc<Function>>,
776
777 #[conditional_malloc_size_of]
778 attribute_changed_callback: Option<Rc<Function>>,
779
780 #[conditional_malloc_size_of]
781 form_associated_callback: Option<Rc<Function>>,
782
783 #[conditional_malloc_size_of]
784 form_reset_callback: Option<Rc<Function>>,
785
786 #[conditional_malloc_size_of]
787 form_disabled_callback: Option<Rc<Function>>,
788
789 #[conditional_malloc_size_of]
790 form_state_restore_callback: Option<Rc<Function>>,
791}
792
793#[derive(Clone, JSTraceable, MallocSizeOf)]
794pub(crate) enum ConstructionStackEntry {
795 Element(DomRoot<Element>),
796 AlreadyConstructedMarker,
797}
798
799#[derive(Clone, JSTraceable, MallocSizeOf)]
801pub(crate) struct CustomElementDefinition {
802 #[no_trace]
804 pub(crate) name: LocalName,
805
806 #[no_trace]
808 pub(crate) local_name: LocalName,
809
810 #[conditional_malloc_size_of]
812 pub(crate) constructor: Rc<CustomElementConstructor>,
813
814 pub(crate) observed_attributes: Vec<DOMString>,
816
817 pub(crate) callbacks: LifecycleCallbacks,
819
820 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
822
823 pub(crate) form_associated: bool,
825
826 pub(crate) disable_internals: bool,
828
829 pub(crate) disable_shadow: bool,
831}
832
833impl CustomElementDefinition {
834 #[expect(clippy::too_many_arguments)]
835 fn new(
836 name: LocalName,
837 local_name: LocalName,
838 constructor: Rc<CustomElementConstructor>,
839 observed_attributes: Vec<DOMString>,
840 callbacks: LifecycleCallbacks,
841 form_associated: bool,
842 disable_internals: bool,
843 disable_shadow: bool,
844 ) -> CustomElementDefinition {
845 CustomElementDefinition {
846 name,
847 local_name,
848 constructor,
849 observed_attributes,
850 callbacks,
851 construction_stack: Default::default(),
852 form_associated,
853 disable_internals,
854 disable_shadow,
855 }
856 }
857
858 pub(crate) fn is_autonomous(&self) -> bool {
860 self.name == self.local_name
861 }
862
863 #[expect(unsafe_code)]
865 pub(crate) fn create_element(
866 &self,
867 cx: &mut JSContext,
868 document: &Document,
869 prefix: Option<Prefix>,
870 registry: Option<&CustomElementRegistry>,
871 ) -> Fallible<DomRoot<Element>> {
872 let window = document.window();
873
874 rooted!(&in(cx) let constructor = ObjectValue(self.constructor.callback()));
876 rooted!(&in(cx) let mut element = ptr::null_mut::<JSObject>());
877 {
878 let mut realm = AutoRealm::new(cx, NonNull::new(self.constructor.callback()).unwrap());
880 let cx = &mut realm;
881
882 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
885 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
886 let args = HandleValueArray::empty();
887 if unsafe { !Construct1(cx, constructor.handle(), &args, element.handle_mut()) }
888 {
889 Err(Error::JSFailed)
890 } else {
891 Ok(())
892 }
893 })
894 })?;
895 }
896
897 rooted!(&in(cx) let element_val = ObjectValue(element.get()));
898 let element: DomRoot<Element> =
899 match FromJSValConvertible::safe_from_jsval(cx, element_val.handle(), ()) {
900 Ok(ConversionResult::Success(element)) => element,
901 Ok(ConversionResult::Failure(..)) => {
902 return Err(Error::Type(
903 c"Constructor did not return a DOM node".to_owned(),
904 ));
905 },
906 _ => return Err(Error::JSFailed),
907 };
908
909 assert!(element.is::<HTMLElement>());
915
916 if element.HasAttributes() ||
922 element.upcast::<Node>().children_count() > 0 ||
923 element.upcast::<Node>().has_parent() ||
924 &*element.upcast::<Node>().owner_doc() != document ||
925 *element.namespace() != ns!(html) ||
926 *element.local_name() != self.local_name
927 {
928 return Err(Error::NotSupported(None));
929 }
930
931 element.set_prefix(prefix);
933
934 element.set_custom_element_registry(registry);
939
940 Ok(element)
941 }
942
943 pub(crate) fn has_attribute_changed_callback(&self) -> bool {
944 self.callbacks.attribute_changed_callback.is_some()
945 }
946}
947
948pub(crate) fn upgrade_element(
950 cx: &mut JSContext,
951 definition: Rc<CustomElementDefinition>,
952 element: &Element,
953) {
954 let state = element.get_custom_element_state();
956 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
957 return;
958 }
959
960 element.set_custom_element_definition(Rc::clone(&definition));
962
963 element.set_custom_element_state(CustomElementState::Failed);
965
966 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
970 for attr in element.attrs().borrow().iter() {
971 let local_name = attr.local_name().clone();
972 let namespace = attr.namespace().clone();
973 custom_element_reaction_stack.enqueue_callback_reaction(
974 cx,
975 element,
976 CallbackReaction::AttributeChanged(local_name, None, Some(&*attr.value()), namespace),
977 Some(definition.clone()),
978 );
979 }
980
981 if element.is_connected() {
984 custom_element_reaction_stack.enqueue_callback_reaction(
985 cx,
986 element,
987 CallbackReaction::Connected,
988 Some(definition.clone()),
989 );
990 }
991
992 definition
994 .construction_stack
995 .borrow_mut()
996 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
997
998 let result = run_upgrade_constructor(cx, &definition, element);
1000
1001 definition.construction_stack.borrow_mut().pop();
1003
1004 if let Err(error) = result {
1006 element.clear_custom_element_definition();
1008
1009 element.clear_reaction_queue();
1011
1012 let global = GlobalScope::current().expect("No current global");
1014
1015 let mut realm = enter_auto_realm(cx, &*global);
1016 let cx = &mut realm.current_realm();
1017
1018 throw_dom_exception(cx, &global, error);
1019 report_pending_exception(cx);
1020
1021 return;
1022 }
1023
1024 if let Some(html_element) = element.downcast::<HTMLElement>() &&
1026 html_element.is_form_associated_custom_element()
1027 {
1028 html_element.reset_form_owner(cx);
1032 if let Some(form) = html_element.form_owner() {
1033 form.upcast::<Node>().rev_version();
1036 }
1040
1041 element.check_disabled_attribute();
1048 element.check_ancestors_disabled_state_for_form_control();
1049 element.update_read_write_state_from_readonly_attribute();
1050
1051 if element.disabled_state() {
1054 custom_element_reaction_stack.enqueue_callback_reaction(
1055 cx,
1056 element,
1057 CallbackReaction::FormDisabled(true),
1058 Some(definition),
1059 )
1060 }
1061 }
1062
1063 element.set_custom_element_state(CustomElementState::Custom);
1065}
1066
1067#[expect(unsafe_code)]
1070fn run_upgrade_constructor(
1071 cx: &mut JSContext,
1072 definition: &CustomElementDefinition,
1073 element: &Element,
1074) -> ErrorResult {
1075 let constructor = &definition.constructor;
1076 let window = element.owner_window();
1077 rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
1078 rooted!(&in(cx) let mut element_val = UndefinedValue());
1079 element.safe_to_jsval(cx, element_val.handle_mut());
1080 rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
1081 {
1082 if definition.disable_shadow && element.is_shadow_host() {
1085 return Err(Error::NotSupported(None));
1086 }
1087
1088 let mut realm = AutoRealm::new(cx, NonNull::new(constructor.callback()).unwrap());
1090 let cx = &mut *realm;
1091
1092 let args = HandleValueArray::empty();
1093 element.set_custom_element_state(CustomElementState::Precustomized);
1095
1096 run_a_script::<DomTypeHolder, _, _>(cx, window.upcast(), |cx| {
1099 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
1100 if unsafe {
1101 !Construct1(
1102 cx,
1103 constructor_val.handle(),
1104 &args,
1105 construct_result.handle_mut(),
1106 )
1107 } {
1108 Err(Error::JSFailed)
1109 } else {
1110 Ok(())
1111 }
1112 })
1113 })?;
1114
1115 let mut same = false;
1116 rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
1117
1118 if unsafe {
1120 !SameValue(
1121 cx,
1122 construct_result_val.handle(),
1123 element_val.handle(),
1124 &mut same,
1125 )
1126 } {
1127 return Err(Error::JSFailed);
1128 }
1129 if !same {
1130 return Err(Error::Type(
1131 c"Returned element is not SameValue as the upgraded element".to_owned(),
1132 ));
1133 }
1134 }
1135 Ok(())
1136}
1137
1138pub(crate) fn try_upgrade_element(cx: &JSContext, element: &Element) {
1140 let document = element.owner_document();
1143 let namespace = element.namespace();
1144 let local_name = element.local_name();
1145 let is = element.get_is();
1146 if let Some(definition) =
1147 document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
1148 {
1149 ScriptThread::enqueue_upgrade_reaction(cx, element, definition);
1152 }
1153}
1154
1155#[derive(JSTraceable, MallocSizeOf)]
1156#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1157pub(crate) enum CustomElementReaction {
1158 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1159 Callback(
1160 #[conditional_malloc_size_of] Rc<Function>,
1161 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1162 ),
1163}
1164
1165impl CustomElementReaction {
1166 pub(crate) fn invoke(&self, cx: &mut JSContext, element: &Element) {
1168 match *self {
1170 CustomElementReaction::Upgrade(ref definition) => {
1171 upgrade_element(cx, definition.clone(), element)
1172 },
1173 CustomElementReaction::Callback(ref callback, ref arguments) => {
1174 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1176 rooted!(&in(cx) let mut value: JSVal);
1177 let _ = callback.Call_(
1178 cx,
1179 element,
1180 arguments,
1181 value.handle_mut(),
1182 ExceptionHandling::Report,
1183 );
1184 },
1185 }
1186 }
1187}
1188
1189pub(crate) enum CallbackReaction<'a> {
1190 Connected,
1191 Disconnected,
1192 Adopted(DomRoot<Document>, DomRoot<Document>),
1193 AttributeChanged(
1194 LocalName,
1195 Option<&'a AttrValue>,
1196 Option<&'a AttrValue>,
1197 Namespace,
1198 ),
1199 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1200 FormDisabled(bool),
1201 FormReset,
1202 ConnectedMove,
1203}
1204
1205#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1207enum BackupElementQueueFlag {
1208 Processing,
1209 NotProcessing,
1210}
1211
1212#[derive(JSTraceable, MallocSizeOf)]
1217#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1218#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1219pub(crate) struct CustomElementReactionStack {
1220 stack: DomRefCell<Vec<ElementQueue>>,
1221 backup_queue: ElementQueue,
1222 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1223}
1224
1225impl CustomElementReactionStack {
1226 pub(crate) fn new() -> CustomElementReactionStack {
1227 CustomElementReactionStack {
1228 stack: DomRefCell::new(Vec::new()),
1229 backup_queue: ElementQueue::new(),
1230 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1231 }
1232 }
1233
1234 pub(crate) fn push_new_element_queue(&self) {
1235 self.stack.borrow_mut().push(ElementQueue::new());
1236 }
1237
1238 pub(crate) fn pop_current_element_queue(&self, cx: &mut JSContext) {
1239 rooted_vec!(let mut stack);
1240 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1241
1242 if let Some(current_queue) = stack.last() {
1243 current_queue.invoke_reactions(cx);
1244 }
1245 stack.pop();
1246
1247 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1248 self.stack.borrow_mut().append(&mut *stack);
1249 }
1250
1251 pub(crate) fn invoke_backup_element_queue(&self, cx: &mut JSContext) {
1254 self.backup_queue.invoke_reactions(cx);
1256
1257 self.processing_backup_element_queue
1259 .set(BackupElementQueueFlag::NotProcessing);
1260 }
1261
1262 pub(crate) fn enqueue_element(&self, cx: &JSContext, element: &Element) {
1264 if let Some(current_queue) = self.stack.borrow().last() {
1265 current_queue.append_element(element);
1267 } else {
1268 self.backup_queue.append_element(element);
1270
1271 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1273 return;
1274 }
1275
1276 self.processing_backup_element_queue
1278 .set(BackupElementQueueFlag::Processing);
1279
1280 ScriptThread::enqueue_microtask(cx, Microtask::CustomElementReaction);
1282 }
1283 }
1284
1285 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1287 pub(crate) fn enqueue_callback_reaction(
1288 &self,
1289 cx: &mut JSContext,
1290 element: &Element,
1291 reaction: CallbackReaction,
1292 definition: Option<Rc<CustomElementDefinition>>,
1293 ) {
1294 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1296 Some(definition) => definition,
1297 None => return,
1298 };
1299
1300 let (callback, args) = match reaction {
1303 CallbackReaction::Connected => {
1304 (definition.callbacks.connected_callback.clone(), Vec::new())
1305 },
1306 CallbackReaction::Disconnected => (
1307 definition.callbacks.disconnected_callback.clone(),
1308 Vec::new(),
1309 ),
1310 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1311 let args = vec![Heap::default(), Heap::default()];
1312 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1313 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1314 (definition.callbacks.adopted_callback.clone(), args)
1315 },
1316 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1317 if !definition
1319 .observed_attributes
1320 .iter()
1321 .any(|attr| *attr == *local_name)
1322 {
1323 return;
1324 }
1325
1326 let mut realm = enter_auto_realm(cx, &*element.global());
1330 let cx = &mut realm;
1331
1332 let local_name = DOMString::from(&*local_name);
1333 rooted!(&in(cx) let mut name_value = UndefinedValue());
1334 local_name.safe_to_jsval(cx, name_value.handle_mut());
1335
1336 rooted!(&in(cx) let mut old_value = NullValue());
1337 if let Some(old_val) = old_val {
1338 old_val.safe_to_jsval(cx, old_value.handle_mut());
1339 }
1340
1341 rooted!(&in(cx) let mut value = NullValue());
1342 if let Some(val) = val {
1343 val.safe_to_jsval(cx, value.handle_mut());
1344 }
1345
1346 rooted!(&in(cx) let mut namespace_value = NullValue());
1347 if namespace != ns!() {
1348 let namespace = DOMString::from(&*namespace);
1349 namespace.safe_to_jsval(cx, namespace_value.handle_mut());
1350 }
1351
1352 let args = vec![
1353 Heap::default(),
1354 Heap::default(),
1355 Heap::default(),
1356 Heap::default(),
1357 ];
1358 args[0].set(name_value.get());
1359 args[1].set(old_value.get());
1360 args[2].set(value.get());
1361 args[3].set(namespace_value.get());
1362
1363 (
1364 definition.callbacks.attribute_changed_callback.clone(),
1365 args,
1366 )
1367 },
1368 CallbackReaction::FormAssociated(form) => {
1369 let args = vec![Heap::default()];
1370 if let Some(form) = form {
1371 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1372 } else {
1373 args[0].set(NullValue());
1374 }
1375 (definition.callbacks.form_associated_callback.clone(), args)
1376 },
1377 CallbackReaction::FormDisabled(disabled) => {
1378 rooted!(&in(cx) let disabled_value = BooleanValue(disabled));
1379 let args = vec![Heap::default()];
1380 args[0].set(disabled_value.get());
1381 (definition.callbacks.form_disabled_callback.clone(), args)
1382 },
1383 CallbackReaction::FormReset => {
1384 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1385 },
1386 CallbackReaction::ConnectedMove => {
1387 let callback = definition.callbacks.connected_move_callback.clone();
1388 if callback.is_none() {
1390 let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1393
1394 let connected_callback = definition.callbacks.connected_callback.clone();
1397
1398 if disconnected_callback.is_none() && connected_callback.is_none() {
1401 return;
1402 }
1403
1404 if let Some(disconnected_callback) = disconnected_callback {
1408 element.push_callback_reaction(disconnected_callback, Box::new([]));
1409 }
1410 if let Some(connected_callback) = connected_callback {
1413 element.push_callback_reaction(connected_callback, Box::new([]));
1414 }
1415
1416 self.enqueue_element(cx, element);
1417 return;
1418 }
1419
1420 (callback, Vec::new())
1421 },
1422 };
1423
1424 let callback = match callback {
1426 Some(callback) => callback,
1427 None => return,
1428 };
1429
1430 element.push_callback_reaction(callback, args.into_boxed_slice());
1433
1434 self.enqueue_element(cx, element);
1436 }
1437
1438 pub(crate) fn enqueue_upgrade_reaction(
1440 &self,
1441 cx: &JSContext,
1442 element: &Element,
1443 definition: Rc<CustomElementDefinition>,
1444 ) {
1445 element.push_upgrade_reaction(definition);
1448
1449 self.enqueue_element(cx, element);
1451 }
1452}
1453
1454#[derive(JSTraceable, MallocSizeOf)]
1456#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1457struct ElementQueue {
1458 queue: DomRefCell<VecDeque<Dom<Element>>>,
1459}
1460
1461impl ElementQueue {
1462 fn new() -> ElementQueue {
1463 ElementQueue {
1464 queue: Default::default(),
1465 }
1466 }
1467
1468 fn invoke_reactions(&self, cx: &mut JSContext) {
1470 while let Some(element) = self.next_element() {
1472 element.invoke_reactions(cx)
1473 }
1474 self.queue.borrow_mut().clear();
1475 }
1476
1477 fn next_element(&self) -> Option<DomRoot<Element>> {
1478 self.queue
1479 .borrow_mut()
1480 .pop_front()
1481 .as_deref()
1482 .map(DomRoot::from_ref)
1483 }
1484
1485 fn append_element(&self, element: &Element) {
1486 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1487 }
1488}
1489
1490pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1492 let mut chars = name.chars();
1495 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1496 return false;
1497 }
1498
1499 let mut has_dash = false;
1500
1501 for c in chars {
1502 if c == '-' {
1503 has_dash = true;
1504 continue;
1505 }
1506
1507 if !is_potential_custom_element_char(c) {
1508 return false;
1509 }
1510 }
1511
1512 if !has_dash {
1513 return false;
1514 }
1515
1516 if name == "annotation-xml" ||
1517 name == "color-profile" ||
1518 name == "font-face" ||
1519 name == "font-face-src" ||
1520 name == "font-face-uri" ||
1521 name == "font-face-format" ||
1522 name == "font-face-name" ||
1523 name == "missing-glyph"
1524 {
1525 return false;
1526 }
1527
1528 true
1529}
1530
1531fn is_potential_custom_element_char(c: char) -> bool {
1534 c == '-' ||
1535 c == '.' ||
1536 c == '_' ||
1537 c == '\u{B7}' ||
1538 c.is_ascii_digit() ||
1539 c.is_ascii_lowercase() ||
1540 ('\u{C0}'..='\u{D6}').contains(&c) ||
1541 ('\u{D8}'..='\u{F6}').contains(&c) ||
1542 ('\u{F8}'..='\u{37D}').contains(&c) ||
1543 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1544 ('\u{200C}'..='\u{200D}').contains(&c) ||
1545 ('\u{203F}'..='\u{2040}').contains(&c) ||
1546 ('\u{2070}'..='\u{218F}').contains(&c) ||
1547 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1548 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1549 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1550 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1551 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1552}
1553
1554fn is_extendable_element_interface(element: &str) -> bool {
1555 element == "a" ||
1556 element == "abbr" ||
1557 element == "acronym" ||
1558 element == "address" ||
1559 element == "area" ||
1560 element == "article" ||
1561 element == "aside" ||
1562 element == "audio" ||
1563 element == "b" ||
1564 element == "base" ||
1565 element == "bdi" ||
1566 element == "bdo" ||
1567 element == "big" ||
1568 element == "blockquote" ||
1569 element == "body" ||
1570 element == "br" ||
1571 element == "button" ||
1572 element == "canvas" ||
1573 element == "caption" ||
1574 element == "center" ||
1575 element == "cite" ||
1576 element == "code" ||
1577 element == "col" ||
1578 element == "colgroup" ||
1579 element == "data" ||
1580 element == "datalist" ||
1581 element == "dd" ||
1582 element == "del" ||
1583 element == "details" ||
1584 element == "dfn" ||
1585 element == "dialog" ||
1586 element == "dir" ||
1587 element == "div" ||
1588 element == "dl" ||
1589 element == "dt" ||
1590 element == "em" ||
1591 element == "embed" ||
1592 element == "fieldset" ||
1593 element == "figcaption" ||
1594 element == "figure" ||
1595 element == "font" ||
1596 element == "footer" ||
1597 element == "form" ||
1598 element == "frame" ||
1599 element == "frameset" ||
1600 element == "h1" ||
1601 element == "h2" ||
1602 element == "h3" ||
1603 element == "h4" ||
1604 element == "h5" ||
1605 element == "h6" ||
1606 element == "head" ||
1607 element == "header" ||
1608 element == "hgroup" ||
1609 element == "hr" ||
1610 element == "html" ||
1611 element == "i" ||
1612 element == "iframe" ||
1613 element == "img" ||
1614 element == "input" ||
1615 element == "ins" ||
1616 element == "kbd" ||
1617 element == "label" ||
1618 element == "legend" ||
1619 element == "li" ||
1620 element == "link" ||
1621 element == "listing" ||
1622 element == "main" ||
1623 element == "map" ||
1624 element == "mark" ||
1625 element == "marquee" ||
1626 element == "menu" ||
1627 element == "meta" ||
1628 element == "meter" ||
1629 element == "nav" ||
1630 element == "nobr" ||
1631 element == "noframes" ||
1632 element == "noscript" ||
1633 element == "object" ||
1634 element == "ol" ||
1635 element == "optgroup" ||
1636 element == "option" ||
1637 element == "output" ||
1638 element == "p" ||
1639 element == "param" ||
1640 element == "picture" ||
1641 element == "plaintext" ||
1642 element == "pre" ||
1643 element == "progress" ||
1644 element == "q" ||
1645 element == "rp" ||
1646 element == "rt" ||
1647 element == "ruby" ||
1648 element == "s" ||
1649 element == "samp" ||
1650 element == "script" ||
1651 element == "section" ||
1652 element == "select" ||
1653 element == "slot" ||
1654 element == "small" ||
1655 element == "source" ||
1656 element == "span" ||
1657 element == "strike" ||
1658 element == "strong" ||
1659 element == "style" ||
1660 element == "sub" ||
1661 element == "summary" ||
1662 element == "sup" ||
1663 element == "table" ||
1664 element == "tbody" ||
1665 element == "td" ||
1666 element == "template" ||
1667 element == "textarea" ||
1668 element == "tfoot" ||
1669 element == "th" ||
1670 element == "thead" ||
1671 element == "time" ||
1672 element == "title" ||
1673 element == "tr" ||
1674 element == "tt" ||
1675 element == "track" ||
1676 element == "u" ||
1677 element == "ul" ||
1678 element == "var" ||
1679 element == "video" ||
1680 element == "wbr" ||
1681 element == "xmp"
1682}