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, JSAutoRealm, 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};
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, NodeTypeId};
43use crate::dom::bindings::reflector::DomGlobal;
44use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot};
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::window::Window;
56use crate::microtask::Microtask;
57use crate::realms::enter_auto_realm;
58use crate::script_runtime::CanGc;
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>>,
83
84 element_definition_is_running: Cell<bool>,
85
86 #[conditional_malloc_size_of]
87 definitions:
88 DomRefCell<HashMapTracedValues<LocalName, Rc<CustomElementDefinition>, FxBuildHasher>>,
89}
90
91impl CustomElementRegistry {
92 fn new_inherited(window: &Window) -> CustomElementRegistry {
93 CustomElementRegistry {
94 reflector_: Reflector::new(),
95 window: Dom::from_ref(window),
96 when_defined: DomRefCell::new(HashMapTracedValues::new_fx()),
97 element_definition_is_running: Cell::new(false),
98 definitions: DomRefCell::new(HashMapTracedValues::new_fx()),
99 }
100 }
101
102 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<CustomElementRegistry> {
103 reflect_dom_object(
104 Box::new(CustomElementRegistry::new_inherited(window)),
105 window,
106 can_gc,
107 )
108 }
109
110 pub(crate) fn teardown(&self) {
113 self.when_defined.borrow_mut().0.clear()
114 }
115
116 pub(crate) fn lookup_definition(
118 &self,
119 local_name: &LocalName,
120 is: Option<&LocalName>,
121 ) -> Option<Rc<CustomElementDefinition>> {
122 self.definitions
123 .borrow()
124 .0
125 .values()
126 .find(|definition| {
127 definition.local_name == *local_name &&
129 (definition.name == *local_name || Some(&definition.name) == is)
130 })
131 .cloned()
132 }
133
134 pub(crate) fn lookup_definition_by_constructor(
135 &self,
136 constructor: HandleObject,
137 ) -> Option<Rc<CustomElementDefinition>> {
138 self.definitions
139 .borrow()
140 .0
141 .values()
142 .find(|definition| definition.constructor.callback() == constructor.get())
143 .cloned()
144 }
145
146 pub(crate) fn lookup_a_custom_element_registry(
148 node: &Node,
149 ) -> Option<DomRoot<CustomElementRegistry>> {
150 match node.type_id() {
151 NodeTypeId::Element(_) => node
153 .downcast::<Element>()
154 .expect("Nodes with element type must be an element")
155 .custom_element_registry(),
156 NodeTypeId::Document(_) => Some(
160 node.downcast::<Document>()
161 .expect("Nodes with document type must be a document")
162 .custom_element_registry(),
163 ),
164 _ => None,
166 }
167 }
168
169 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
171 registry.is_some()
175 }
176
177 #[expect(unsafe_code)]
180 fn check_prototype(
181 &self,
182 cx: &mut JSContext,
183 constructor: HandleObject,
184 mut prototype: MutableHandleValue,
185 ) -> ErrorResult {
186 unsafe {
187 if !JS_GetProperty(cx, constructor, c"prototype".as_ptr(), prototype.reborrow()) {
189 return Err(Error::JSFailed);
190 }
191
192 if !prototype.is_object() {
194 return Err(Error::Type(
195 c"constructor.prototype is not an object".to_owned(),
196 ));
197 }
198 }
199 Ok(())
200 }
201
202 fn get_callbacks(
206 &self,
207 cx: &mut JSContext,
208 prototype: HandleObject,
209 ) -> Fallible<LifecycleCallbacks> {
210 Ok(LifecycleCallbacks {
212 connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
213 disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
214 connected_move_callback: get_callback(cx, prototype, c"connectedMoveCallback")?,
215 adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
216 attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
217
218 form_associated_callback: None,
219 form_disabled_callback: None,
220 form_reset_callback: None,
221 form_state_restore_callback: None,
222 })
223 }
224
225 #[expect(unsafe_code)]
228 unsafe fn add_form_associated_callbacks(
229 &self,
230 cx: &mut JSContext,
231 prototype: HandleObject,
232 callbacks: &mut LifecycleCallbacks,
233 ) -> ErrorResult {
234 callbacks.form_associated_callback =
235 get_callback(cx, prototype, c"formAssociatedCallback")?;
236 callbacks.form_reset_callback = get_callback(cx, prototype, c"formResetCallback")?;
237 callbacks.form_disabled_callback = get_callback(cx, prototype, c"formDisabledCallback")?;
238 callbacks.form_state_restore_callback =
239 get_callback(cx, prototype, c"formStateRestoreCallback")?;
240
241 Ok(())
242 }
243}
244
245#[expect(unsafe_code)]
248fn get_callback(
249 cx: &mut JSContext,
250 prototype: HandleObject,
251 name: &CStr,
252) -> Fallible<Option<Rc<Function>>> {
253 rooted!(&in(cx) let mut callback = UndefinedValue());
254 unsafe {
255 if !JS_GetProperty(cx, prototype, name.as_ptr(), callback.handle_mut()) {
257 return Err(Error::JSFailed);
258 }
259
260 if !callback.is_undefined() {
262 if !callback.is_object() || !IsCallable(callback.to_object()) {
263 return Err(Error::Type(
264 c"Lifecycle callback is not callable".to_owned(),
265 ));
266 }
267 Ok(Some(Function::new(cx.into(), callback.to_object())))
268 } else {
269 Ok(None)
270 }
271 }
272}
273
274impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
275 #[expect(unsafe_code)]
276 fn Define(
278 &self,
279 cx: &mut JSContext,
280 name: DOMString,
281 constructor_: Rc<CustomElementConstructor>,
282 options: &ElementDefinitionOptions,
283 ) -> ErrorResult {
284 rooted!(&in(cx) let constructor = constructor_.callback());
285 let name = LocalName::from(name);
286
287 rooted!(&in(cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
290
291 if unwrapped_constructor.is_null() {
292 return Err(Error::Security(None));
294 }
295
296 if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
297 return Err(Error::Type(
298 c"Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
299 ));
300 }
301
302 if !is_valid_custom_element_name(&name) {
304 return Err(Error::Syntax(Some(format!(
305 "{} name is not a valid custom element name",
306 name
307 ))));
308 }
309
310 if self.definitions.borrow().contains_key(&name) {
313 return Err(Error::NotSupported(Some(format!(
314 "{} has already been defined as a custom element",
315 name
316 ))));
317 }
318
319 if self
322 .definitions
323 .borrow()
324 .iter()
325 .any(|(_, def)| def.constructor == constructor_)
326 {
327 return Err(Error::NotSupported(None));
328 }
329
330 let extends = &options.extends;
332
333 let local_name = if let Some(ref extended_name) = *extends {
335 if is_valid_custom_element_name(&extended_name.str()) {
339 return Err(Error::NotSupported(None));
340 }
341
342 if !is_extendable_element_interface(&extended_name.str()) {
346 return Err(Error::NotSupported(None));
347 }
348
349 LocalName::from(extended_name)
351 } else {
352 name.clone()
354 };
355
356 if self.element_definition_is_running.get() {
358 return Err(Error::NotSupported(None));
359 }
360
361 self.element_definition_is_running.set(true);
363
364 rooted!(&in(cx) let mut prototype = UndefinedValue());
369 {
370 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
371 if let Err(error) =
372 self.check_prototype(&mut realm, constructor.handle(), prototype.handle_mut())
373 {
374 self.element_definition_is_running.set(false);
375 return Err(error);
376 }
377 };
378
379 rooted!(&in(cx) let proto_object = prototype.to_object());
385 let mut callbacks = {
386 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
387 match self.get_callbacks(&mut realm, proto_object.handle()) {
388 Ok(callbacks) => callbacks,
389 Err(error) => {
390 self.element_definition_is_running.set(false);
391 return Err(error);
392 },
393 }
394 };
395
396 let observed_attributes: Vec<DOMString> = if callbacks.attribute_changed_callback.is_some()
399 {
400 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
401 match get_property(
402 &mut realm,
403 constructor.handle(),
404 c"observedAttributes",
405 StringificationBehavior::Default,
406 ) {
407 Ok(attributes) => attributes.unwrap_or_default(),
408 Err(error) => {
409 self.element_definition_is_running.set(false);
410 return Err(error);
411 },
412 }
413 } else {
414 Vec::new()
415 };
416
417 let (disable_internals, disable_shadow) = {
419 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
420 match get_property::<Vec<DOMString>>(
421 &mut realm,
422 constructor.handle(),
423 c"disabledFeatures",
424 StringificationBehavior::Default,
425 ) {
426 Ok(sequence) => {
427 let sequence = sequence.unwrap_or_default();
428 (
429 sequence.iter().any(|s| *s == "internals"),
430 sequence.iter().any(|s| *s == "shadow"),
431 )
432 },
433 Err(error) => {
434 self.element_definition_is_running.set(false);
435 return Err(error);
436 },
437 }
438 };
439
440 let form_associated: bool = {
442 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
443 match get_property(&mut realm, constructor.handle(), c"formAssociated", ()) {
444 Ok(flag) => flag.unwrap_or_default(),
445 Err(error) => {
446 self.element_definition_is_running.set(false);
447 return Err(error);
448 },
449 }
450 };
451
452 if form_associated {
454 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
455 unsafe {
456 if let Err(error) = self.add_form_associated_callbacks(
457 &mut realm,
458 proto_object.handle(),
459 &mut callbacks,
460 ) {
461 self.element_definition_is_running.set(false);
462 return Err(error);
463 }
464 }
465 }
466
467 self.element_definition_is_running.set(false);
468
469 let definition = Rc::new(CustomElementDefinition::new(
471 name.clone(),
472 local_name.clone(),
473 constructor_,
474 observed_attributes,
475 callbacks,
476 form_associated,
477 disable_internals,
478 disable_shadow,
479 ));
480
481 self.definitions
483 .borrow_mut()
484 .insert(name.clone(), definition.clone());
485
486 let document = self.window.Document();
489
490 for candidate in document
492 .upcast::<Node>()
493 .traverse_preorder(ShadowIncluding::Yes)
494 .filter_map(DomRoot::downcast::<Element>)
495 {
496 let is = candidate.get_is();
497 if *candidate.local_name() == local_name &&
498 *candidate.namespace() == ns!(html) &&
499 (extends.is_none() || is.as_ref() == Some(&name))
500 {
501 ScriptThread::enqueue_upgrade_reaction(&candidate, definition.clone());
502 }
503 }
504
505 let promise = self.when_defined.borrow_mut().remove(&name);
507 if let Some(promise) = promise {
508 rooted!(&in(cx) let mut constructor = UndefinedValue());
509 definition.constructor.safe_to_jsval(
510 cx.into(),
511 constructor.handle_mut(),
512 CanGc::from_cx(cx),
513 );
514 promise.resolve_native_with_cx(cx, &constructor.get());
515 }
516 Ok(())
517 }
518
519 fn Get(&self, cx: &mut JSContext, name: DOMString, mut retval: MutableHandleValue) {
521 match self.definitions.borrow().get(&LocalName::from(name)) {
522 Some(definition) => {
523 definition
524 .constructor
525 .safe_to_jsval(cx.into(), retval, CanGc::from_cx(cx))
526 },
527 None => retval.set(UndefinedValue()),
528 }
529 }
530
531 fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
533 self.definitions
534 .borrow()
535 .0
536 .values()
537 .find(|definition| definition.constructor == constructor)
538 .map(|definition| DOMString::from(definition.name.to_string()))
539 }
540
541 fn WhenDefined(&self, realm: &mut CurrentRealm, name: DOMString) -> Rc<Promise> {
543 let name = LocalName::from(name);
544
545 if !is_valid_custom_element_name(&name) {
547 let promise = Promise::new_in_realm(realm);
548 let error = DOMException::new(
549 self.window.as_global_scope(),
550 DOMErrorName::SyntaxError,
551 CanGc::from_cx(realm),
552 );
553 promise.reject_native_with_cx(realm, &error);
554 return promise;
555 }
556
557 if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
559 rooted!(&in(*realm) let mut constructor = UndefinedValue());
560 definition.constructor.safe_to_jsval(
561 realm.into(),
562 constructor.handle_mut(),
563 CanGc::from_cx(realm),
564 );
565 let promise = Promise::new_in_realm(realm);
566 promise.resolve_native_with_cx(realm, &constructor.get());
567 return promise;
568 }
569
570 let existing_promise = self.when_defined.borrow().get(&name).cloned();
572 existing_promise.unwrap_or_else(|| {
573 let promise = Promise::new_in_realm(realm);
574 self.when_defined.borrow_mut().insert(name, promise.clone());
575 promise
576 })
577 }
578
579 fn Upgrade(&self, node: &Node) {
581 node.traverse_preorder(ShadowIncluding::Yes).for_each(|n| {
585 if let Some(element) = n.downcast::<Element>() {
586 try_upgrade_element(element);
587 }
588 });
589 }
590}
591
592#[derive(Clone, JSTraceable, MallocSizeOf)]
593pub(crate) struct LifecycleCallbacks {
594 #[conditional_malloc_size_of]
595 connected_callback: Option<Rc<Function>>,
596
597 #[conditional_malloc_size_of]
598 connected_move_callback: Option<Rc<Function>>,
599
600 #[conditional_malloc_size_of]
601 disconnected_callback: Option<Rc<Function>>,
602
603 #[conditional_malloc_size_of]
604 adopted_callback: Option<Rc<Function>>,
605
606 #[conditional_malloc_size_of]
607 attribute_changed_callback: Option<Rc<Function>>,
608
609 #[conditional_malloc_size_of]
610 form_associated_callback: Option<Rc<Function>>,
611
612 #[conditional_malloc_size_of]
613 form_reset_callback: Option<Rc<Function>>,
614
615 #[conditional_malloc_size_of]
616 form_disabled_callback: Option<Rc<Function>>,
617
618 #[conditional_malloc_size_of]
619 form_state_restore_callback: Option<Rc<Function>>,
620}
621
622#[derive(Clone, JSTraceable, MallocSizeOf)]
623pub(crate) enum ConstructionStackEntry {
624 Element(DomRoot<Element>),
625 AlreadyConstructedMarker,
626}
627
628#[derive(Clone, JSTraceable, MallocSizeOf)]
630pub(crate) struct CustomElementDefinition {
631 #[no_trace]
633 pub(crate) name: LocalName,
634
635 #[no_trace]
637 pub(crate) local_name: LocalName,
638
639 #[conditional_malloc_size_of]
641 pub(crate) constructor: Rc<CustomElementConstructor>,
642
643 pub(crate) observed_attributes: Vec<DOMString>,
645
646 pub(crate) callbacks: LifecycleCallbacks,
648
649 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
651
652 pub(crate) form_associated: bool,
654
655 pub(crate) disable_internals: bool,
657
658 pub(crate) disable_shadow: bool,
660}
661
662impl CustomElementDefinition {
663 #[expect(clippy::too_many_arguments)]
664 fn new(
665 name: LocalName,
666 local_name: LocalName,
667 constructor: Rc<CustomElementConstructor>,
668 observed_attributes: Vec<DOMString>,
669 callbacks: LifecycleCallbacks,
670 form_associated: bool,
671 disable_internals: bool,
672 disable_shadow: bool,
673 ) -> CustomElementDefinition {
674 CustomElementDefinition {
675 name,
676 local_name,
677 constructor,
678 observed_attributes,
679 callbacks,
680 construction_stack: Default::default(),
681 form_associated,
682 disable_internals,
683 disable_shadow,
684 }
685 }
686
687 pub(crate) fn is_autonomous(&self) -> bool {
689 self.name == self.local_name
690 }
691
692 #[expect(unsafe_code)]
694 pub(crate) fn create_element(
695 &self,
696 cx: &mut JSContext,
697 document: &Document,
698 prefix: Option<Prefix>,
699 registry: Option<DomRoot<CustomElementRegistry>>,
700 ) -> Fallible<DomRoot<Element>> {
701 let window = document.window();
702
703 rooted!(&in(cx) let constructor = ObjectValue(self.constructor.callback()));
705 rooted!(&in(cx) let mut element = ptr::null_mut::<JSObject>());
706 {
707 let mut realm = AutoRealm::new(cx, NonNull::new(self.constructor.callback()).unwrap());
709 let cx = &mut realm;
710
711 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
714 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
715 let args = HandleValueArray::empty();
716 if unsafe { !Construct1(cx, constructor.handle(), &args, element.handle_mut()) }
717 {
718 Err(Error::JSFailed)
719 } else {
720 Ok(())
721 }
722 })
723 })?;
724 }
725
726 rooted!(&in(cx) let element_val = ObjectValue(element.get()));
727 let element: DomRoot<Element> =
728 match FromJSValConvertible::safe_from_jsval(cx, element_val.handle(), ()) {
729 Ok(ConversionResult::Success(element)) => element,
730 Ok(ConversionResult::Failure(..)) => {
731 return Err(Error::Type(
732 c"Constructor did not return a DOM node".to_owned(),
733 ));
734 },
735 _ => return Err(Error::JSFailed),
736 };
737
738 assert!(element.is::<HTMLElement>());
744
745 if element.HasAttributes() ||
751 element.upcast::<Node>().children_count() > 0 ||
752 element.upcast::<Node>().has_parent() ||
753 &*element.upcast::<Node>().owner_doc() != document ||
754 *element.namespace() != ns!(html) ||
755 *element.local_name() != self.local_name
756 {
757 return Err(Error::NotSupported(None));
758 }
759
760 element.set_prefix(prefix);
762
763 element.set_custom_element_registry(registry);
768
769 Ok(element)
770 }
771
772 pub(crate) fn has_attribute_changed_callback(&self) -> bool {
773 self.callbacks.attribute_changed_callback.is_some()
774 }
775}
776
777pub(crate) fn upgrade_element(
779 cx: &mut JSContext,
780 definition: Rc<CustomElementDefinition>,
781 element: &Element,
782) {
783 let state = element.get_custom_element_state();
785 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
786 return;
787 }
788
789 element.set_custom_element_definition(Rc::clone(&definition));
791
792 element.set_custom_element_state(CustomElementState::Failed);
794
795 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
799 for attr in element.attrs().borrow().iter() {
800 let local_name = attr.local_name().clone();
801 let namespace = attr.namespace().clone();
802 custom_element_reaction_stack.enqueue_callback_reaction(
803 element,
804 CallbackReaction::AttributeChanged(local_name, None, Some(&*attr.value()), namespace),
805 Some(definition.clone()),
806 );
807 }
808
809 if element.is_connected() {
812 ScriptThread::enqueue_callback_reaction(
813 element,
814 CallbackReaction::Connected,
815 Some(definition.clone()),
816 );
817 }
818
819 definition
821 .construction_stack
822 .borrow_mut()
823 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
824
825 let result = run_upgrade_constructor(cx, &definition, element);
827
828 definition.construction_stack.borrow_mut().pop();
830
831 if let Err(error) = result {
833 element.clear_custom_element_definition();
835
836 element.clear_reaction_queue();
838
839 let global = GlobalScope::current().expect("No current global");
841
842 let mut realm = enter_auto_realm(cx, &*global);
843 let cx = &mut realm.current_realm();
844
845 throw_dom_exception(cx.into(), &global, error, CanGc::from_cx(cx));
846 report_pending_exception(cx);
847
848 return;
849 }
850
851 if let Some(html_element) = element.downcast::<HTMLElement>() &&
853 html_element.is_form_associated_custom_element()
854 {
855 html_element.reset_form_owner(cx);
859 if let Some(form) = html_element.form_owner() {
860 form.upcast::<Node>().rev_version();
863 }
867
868 element.check_disabled_attribute();
875 element.check_ancestors_disabled_state_for_form_control();
876 element.update_read_write_state_from_readonly_attribute();
877
878 if element.disabled_state() {
881 ScriptThread::enqueue_callback_reaction(
882 element,
883 CallbackReaction::FormDisabled(true),
884 Some(definition),
885 )
886 }
887 }
888
889 element.set_custom_element_state(CustomElementState::Custom);
891}
892
893#[expect(unsafe_code)]
896fn run_upgrade_constructor(
897 cx: &mut JSContext,
898 definition: &CustomElementDefinition,
899 element: &Element,
900) -> ErrorResult {
901 let constructor = &definition.constructor;
902 let window = element.owner_window();
903 rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
904 rooted!(&in(cx) let mut element_val = UndefinedValue());
905 element.safe_to_jsval(cx.into(), element_val.handle_mut(), CanGc::from_cx(cx));
906 rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
907 {
908 if definition.disable_shadow && element.is_shadow_host() {
911 return Err(Error::NotSupported(None));
912 }
913
914 let mut realm = AutoRealm::new(cx, NonNull::new(constructor.callback()).unwrap());
916 let cx = &mut *realm;
917
918 let args = HandleValueArray::empty();
919 element.set_custom_element_state(CustomElementState::Precustomized);
921
922 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
925 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
926 if unsafe {
927 !Construct1(
928 cx,
929 constructor_val.handle(),
930 &args,
931 construct_result.handle_mut(),
932 )
933 } {
934 Err(Error::JSFailed)
935 } else {
936 Ok(())
937 }
938 })
939 })?;
940
941 let mut same = false;
942 rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
943
944 if unsafe {
946 !SameValue(
947 cx,
948 construct_result_val.handle(),
949 element_val.handle(),
950 &mut same,
951 )
952 } {
953 return Err(Error::JSFailed);
954 }
955 if !same {
956 return Err(Error::Type(
957 c"Returned element is not SameValue as the upgraded element".to_owned(),
958 ));
959 }
960 }
961 Ok(())
962}
963
964pub(crate) fn try_upgrade_element(element: &Element) {
966 let document = element.owner_document();
969 let namespace = element.namespace();
970 let local_name = element.local_name();
971 let is = element.get_is();
972 if let Some(definition) =
973 document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
974 {
975 ScriptThread::enqueue_upgrade_reaction(element, definition);
978 }
979}
980
981#[derive(JSTraceable, MallocSizeOf)]
982#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
983pub(crate) enum CustomElementReaction {
984 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
985 Callback(
986 #[conditional_malloc_size_of] Rc<Function>,
987 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
988 ),
989}
990
991impl CustomElementReaction {
992 pub(crate) fn invoke(&self, cx: &mut JSContext, element: &Element) {
994 match *self {
996 CustomElementReaction::Upgrade(ref definition) => {
997 upgrade_element(cx, definition.clone(), element)
998 },
999 CustomElementReaction::Callback(ref callback, ref arguments) => {
1000 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1002 rooted!(&in(cx) let mut value: JSVal);
1003 let _ = callback.Call_(
1004 cx,
1005 element,
1006 arguments,
1007 value.handle_mut(),
1008 ExceptionHandling::Report,
1009 );
1010 },
1011 }
1012 }
1013}
1014
1015pub(crate) enum CallbackReaction<'a> {
1016 Connected,
1017 Disconnected,
1018 Adopted(DomRoot<Document>, DomRoot<Document>),
1019 AttributeChanged(
1020 LocalName,
1021 Option<&'a AttrValue>,
1022 Option<&'a AttrValue>,
1023 Namespace,
1024 ),
1025 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1026 FormDisabled(bool),
1027 FormReset,
1028 ConnectedMove,
1029}
1030
1031#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1033enum BackupElementQueueFlag {
1034 Processing,
1035 NotProcessing,
1036}
1037
1038#[derive(JSTraceable, MallocSizeOf)]
1043#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1044#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1045pub(crate) struct CustomElementReactionStack {
1046 stack: DomRefCell<Vec<ElementQueue>>,
1047 backup_queue: ElementQueue,
1048 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1049}
1050
1051impl CustomElementReactionStack {
1052 pub(crate) fn new() -> CustomElementReactionStack {
1053 CustomElementReactionStack {
1054 stack: DomRefCell::new(Vec::new()),
1055 backup_queue: ElementQueue::new(),
1056 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1057 }
1058 }
1059
1060 pub(crate) fn push_new_element_queue(&self) {
1061 self.stack.borrow_mut().push(ElementQueue::new());
1062 }
1063
1064 pub(crate) fn pop_current_element_queue(&self, cx: &mut JSContext) {
1065 rooted_vec!(let mut stack);
1066 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1067
1068 if let Some(current_queue) = stack.last() {
1069 current_queue.invoke_reactions(cx);
1070 }
1071 stack.pop();
1072
1073 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1074 self.stack.borrow_mut().append(&mut *stack);
1075 }
1076
1077 pub(crate) fn invoke_backup_element_queue(&self, cx: &mut JSContext) {
1080 self.backup_queue.invoke_reactions(cx);
1082
1083 self.processing_backup_element_queue
1085 .set(BackupElementQueueFlag::NotProcessing);
1086 }
1087
1088 pub(crate) fn enqueue_element(&self, element: &Element) {
1090 if let Some(current_queue) = self.stack.borrow().last() {
1091 current_queue.append_element(element);
1093 } else {
1094 self.backup_queue.append_element(element);
1096
1097 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1099 return;
1100 }
1101
1102 self.processing_backup_element_queue
1104 .set(BackupElementQueueFlag::Processing);
1105
1106 ScriptThread::enqueue_microtask(Microtask::CustomElementReaction);
1108 }
1109 }
1110
1111 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1113 pub(crate) fn enqueue_callback_reaction(
1114 &self,
1115 element: &Element,
1116 reaction: CallbackReaction,
1117 definition: Option<Rc<CustomElementDefinition>>,
1118 ) {
1119 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1121 Some(definition) => definition,
1122 None => return,
1123 };
1124
1125 let (callback, args) = match reaction {
1128 CallbackReaction::Connected => {
1129 (definition.callbacks.connected_callback.clone(), Vec::new())
1130 },
1131 CallbackReaction::Disconnected => (
1132 definition.callbacks.disconnected_callback.clone(),
1133 Vec::new(),
1134 ),
1135 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1136 let args = vec![Heap::default(), Heap::default()];
1137 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1138 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1139 (definition.callbacks.adopted_callback.clone(), args)
1140 },
1141 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1142 if !definition
1144 .observed_attributes
1145 .iter()
1146 .any(|attr| *attr == *local_name)
1147 {
1148 return;
1149 }
1150
1151 let cx = GlobalScope::get_cx();
1152 let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get());
1156
1157 let local_name = DOMString::from(&*local_name);
1158 rooted!(in(*cx) let mut name_value = UndefinedValue());
1159 local_name.safe_to_jsval(cx, name_value.handle_mut(), CanGc::deprecated_note());
1160
1161 rooted!(in(*cx) let mut old_value = NullValue());
1162 if let Some(old_val) = old_val {
1163 old_val.safe_to_jsval(cx, old_value.handle_mut(), CanGc::deprecated_note());
1164 }
1165
1166 rooted!(in(*cx) let mut value = NullValue());
1167 if let Some(val) = val {
1168 val.safe_to_jsval(cx, value.handle_mut(), CanGc::deprecated_note());
1169 }
1170
1171 rooted!(in(*cx) let mut namespace_value = NullValue());
1172 if namespace != ns!() {
1173 let namespace = DOMString::from(&*namespace);
1174 namespace.safe_to_jsval(
1175 cx,
1176 namespace_value.handle_mut(),
1177 CanGc::deprecated_note(),
1178 );
1179 }
1180
1181 let args = vec![
1182 Heap::default(),
1183 Heap::default(),
1184 Heap::default(),
1185 Heap::default(),
1186 ];
1187 args[0].set(name_value.get());
1188 args[1].set(old_value.get());
1189 args[2].set(value.get());
1190 args[3].set(namespace_value.get());
1191
1192 (
1193 definition.callbacks.attribute_changed_callback.clone(),
1194 args,
1195 )
1196 },
1197 CallbackReaction::FormAssociated(form) => {
1198 let args = vec![Heap::default()];
1199 if let Some(form) = form {
1200 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1201 } else {
1202 args[0].set(NullValue());
1203 }
1204 (definition.callbacks.form_associated_callback.clone(), args)
1205 },
1206 CallbackReaction::FormDisabled(disabled) => {
1207 let cx = GlobalScope::get_cx();
1208 rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
1209 let args = vec![Heap::default()];
1210 args[0].set(disabled_value.get());
1211 (definition.callbacks.form_disabled_callback.clone(), args)
1212 },
1213 CallbackReaction::FormReset => {
1214 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1215 },
1216 CallbackReaction::ConnectedMove => {
1217 let callback = definition.callbacks.connected_move_callback.clone();
1218 if callback.is_none() {
1220 let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1223
1224 let connected_callback = definition.callbacks.connected_callback.clone();
1227
1228 if disconnected_callback.is_none() && connected_callback.is_none() {
1231 return;
1232 }
1233
1234 if let Some(disconnected_callback) = disconnected_callback {
1238 element.push_callback_reaction(disconnected_callback, Box::new([]));
1239 }
1240 if let Some(connected_callback) = connected_callback {
1243 element.push_callback_reaction(connected_callback, Box::new([]));
1244 }
1245
1246 self.enqueue_element(element);
1247 return;
1248 }
1249
1250 (callback, Vec::new())
1251 },
1252 };
1253
1254 let callback = match callback {
1256 Some(callback) => callback,
1257 None => return,
1258 };
1259
1260 element.push_callback_reaction(callback, args.into_boxed_slice());
1263
1264 self.enqueue_element(element);
1266 }
1267
1268 pub(crate) fn enqueue_upgrade_reaction(
1270 &self,
1271 element: &Element,
1272 definition: Rc<CustomElementDefinition>,
1273 ) {
1274 element.push_upgrade_reaction(definition);
1277
1278 self.enqueue_element(element);
1280 }
1281}
1282
1283#[derive(JSTraceable, MallocSizeOf)]
1285#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1286struct ElementQueue {
1287 queue: DomRefCell<VecDeque<Dom<Element>>>,
1288}
1289
1290impl ElementQueue {
1291 fn new() -> ElementQueue {
1292 ElementQueue {
1293 queue: Default::default(),
1294 }
1295 }
1296
1297 fn invoke_reactions(&self, cx: &mut JSContext) {
1299 while let Some(element) = self.next_element() {
1301 element.invoke_reactions(cx)
1302 }
1303 self.queue.borrow_mut().clear();
1304 }
1305
1306 fn next_element(&self) -> Option<DomRoot<Element>> {
1307 self.queue
1308 .borrow_mut()
1309 .pop_front()
1310 .as_deref()
1311 .map(DomRoot::from_ref)
1312 }
1313
1314 fn append_element(&self, element: &Element) {
1315 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1316 }
1317}
1318
1319pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1321 let mut chars = name.chars();
1324 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1325 return false;
1326 }
1327
1328 let mut has_dash = false;
1329
1330 for c in chars {
1331 if c == '-' {
1332 has_dash = true;
1333 continue;
1334 }
1335
1336 if !is_potential_custom_element_char(c) {
1337 return false;
1338 }
1339 }
1340
1341 if !has_dash {
1342 return false;
1343 }
1344
1345 if name == "annotation-xml" ||
1346 name == "color-profile" ||
1347 name == "font-face" ||
1348 name == "font-face-src" ||
1349 name == "font-face-uri" ||
1350 name == "font-face-format" ||
1351 name == "font-face-name" ||
1352 name == "missing-glyph"
1353 {
1354 return false;
1355 }
1356
1357 true
1358}
1359
1360fn is_potential_custom_element_char(c: char) -> bool {
1363 c == '-' ||
1364 c == '.' ||
1365 c == '_' ||
1366 c == '\u{B7}' ||
1367 c.is_ascii_digit() ||
1368 c.is_ascii_lowercase() ||
1369 ('\u{C0}'..='\u{D6}').contains(&c) ||
1370 ('\u{D8}'..='\u{F6}').contains(&c) ||
1371 ('\u{F8}'..='\u{37D}').contains(&c) ||
1372 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1373 ('\u{200C}'..='\u{200D}').contains(&c) ||
1374 ('\u{203F}'..='\u{2040}').contains(&c) ||
1375 ('\u{2070}'..='\u{218F}').contains(&c) ||
1376 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1377 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1378 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1379 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1380 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1381}
1382
1383fn is_extendable_element_interface(element: &str) -> bool {
1384 element == "a" ||
1385 element == "abbr" ||
1386 element == "acronym" ||
1387 element == "address" ||
1388 element == "area" ||
1389 element == "article" ||
1390 element == "aside" ||
1391 element == "audio" ||
1392 element == "b" ||
1393 element == "base" ||
1394 element == "bdi" ||
1395 element == "bdo" ||
1396 element == "big" ||
1397 element == "blockquote" ||
1398 element == "body" ||
1399 element == "br" ||
1400 element == "button" ||
1401 element == "canvas" ||
1402 element == "caption" ||
1403 element == "center" ||
1404 element == "cite" ||
1405 element == "code" ||
1406 element == "col" ||
1407 element == "colgroup" ||
1408 element == "data" ||
1409 element == "datalist" ||
1410 element == "dd" ||
1411 element == "del" ||
1412 element == "details" ||
1413 element == "dfn" ||
1414 element == "dialog" ||
1415 element == "dir" ||
1416 element == "div" ||
1417 element == "dl" ||
1418 element == "dt" ||
1419 element == "em" ||
1420 element == "embed" ||
1421 element == "fieldset" ||
1422 element == "figcaption" ||
1423 element == "figure" ||
1424 element == "font" ||
1425 element == "footer" ||
1426 element == "form" ||
1427 element == "frame" ||
1428 element == "frameset" ||
1429 element == "h1" ||
1430 element == "h2" ||
1431 element == "h3" ||
1432 element == "h4" ||
1433 element == "h5" ||
1434 element == "h6" ||
1435 element == "head" ||
1436 element == "header" ||
1437 element == "hgroup" ||
1438 element == "hr" ||
1439 element == "html" ||
1440 element == "i" ||
1441 element == "iframe" ||
1442 element == "img" ||
1443 element == "input" ||
1444 element == "ins" ||
1445 element == "kbd" ||
1446 element == "label" ||
1447 element == "legend" ||
1448 element == "li" ||
1449 element == "link" ||
1450 element == "listing" ||
1451 element == "main" ||
1452 element == "map" ||
1453 element == "mark" ||
1454 element == "marquee" ||
1455 element == "menu" ||
1456 element == "meta" ||
1457 element == "meter" ||
1458 element == "nav" ||
1459 element == "nobr" ||
1460 element == "noframes" ||
1461 element == "noscript" ||
1462 element == "object" ||
1463 element == "ol" ||
1464 element == "optgroup" ||
1465 element == "option" ||
1466 element == "output" ||
1467 element == "p" ||
1468 element == "param" ||
1469 element == "picture" ||
1470 element == "plaintext" ||
1471 element == "pre" ||
1472 element == "progress" ||
1473 element == "q" ||
1474 element == "rp" ||
1475 element == "rt" ||
1476 element == "ruby" ||
1477 element == "s" ||
1478 element == "samp" ||
1479 element == "script" ||
1480 element == "section" ||
1481 element == "select" ||
1482 element == "slot" ||
1483 element == "small" ||
1484 element == "source" ||
1485 element == "span" ||
1486 element == "strike" ||
1487 element == "strong" ||
1488 element == "style" ||
1489 element == "sub" ||
1490 element == "summary" ||
1491 element == "sup" ||
1492 element == "table" ||
1493 element == "tbody" ||
1494 element == "td" ||
1495 element == "template" ||
1496 element == "textarea" ||
1497 element == "tfoot" ||
1498 element == "th" ||
1499 element == "thead" ||
1500 element == "time" ||
1501 element == "title" ||
1502 element == "tr" ||
1503 element == "tt" ||
1504 element == "track" ||
1505 element == "u" ||
1506 element == "ul" ||
1507 element == "var" ||
1508 element == "video" ||
1509 element == "wbr" ||
1510 element == "xmp"
1511}