1use std::cell::Cell;
6use std::collections::VecDeque;
7use std::ffi::CStr;
8use std::rc::Rc;
9use std::{mem, ptr};
10
11use dom_struct::dom_struct;
12use html5ever::{LocalName, Namespace, Prefix, ns};
13use js::glue::UnwrapObjectStatic;
14use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSAutoRealm, JSObject};
15use js::jsval::{BooleanValue, JSVal, NullValue, ObjectValue, UndefinedValue};
16use js::rust::wrappers::{Construct1, JS_GetProperty, SameValue};
17use js::rust::{HandleObject, MutableHandleValue};
18use rustc_hash::FxBuildHasher;
19use script_bindings::conversions::{SafeFromJSValConvertible, SafeToJSValConvertible};
20use script_bindings::settings_stack::run_a_script;
21
22use super::bindings::trace::HashMapTracedValues;
23use crate::DomTypeHolder;
24use crate::dom::bindings::callback::{CallbackContainer, ExceptionHandling};
25use crate::dom::bindings::cell::DomRefCell;
26use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::{
27 CustomElementConstructor, CustomElementRegistryMethods, ElementDefinitionOptions,
28};
29use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
30use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
31use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
32use crate::dom::bindings::conversions::{ConversionResult, StringificationBehavior};
33use crate::dom::bindings::error::{
34 Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception,
35};
36use crate::dom::bindings::inheritance::{Castable, NodeTypeId};
37use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector, reflect_dom_object};
38use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot};
39use crate::dom::bindings::settings_stack::AutoIncumbentScript;
40use crate::dom::bindings::str::DOMString;
41use crate::dom::document::Document;
42use crate::dom::domexception::{DOMErrorName, DOMException};
43use crate::dom::element::Element;
44use crate::dom::globalscope::GlobalScope;
45use crate::dom::html::htmlelement::HTMLElement;
46use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
47use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
48use crate::dom::promise::Promise;
49use crate::dom::window::Window;
50use crate::microtask::Microtask;
51use crate::realms::{InRealm, enter_realm};
52use crate::script_runtime::{CanGc, JSContext};
53use crate::script_thread::ScriptThread;
54
55#[derive(Clone, Copy, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
57pub(crate) enum CustomElementState {
58 Undefined,
59 Failed,
60 #[default]
61 Uncustomized,
62 Precustomized,
63 Custom,
64}
65
66#[dom_struct]
68pub(crate) struct CustomElementRegistry {
69 reflector_: Reflector,
70
71 window: Dom<Window>,
72
73 #[conditional_malloc_size_of]
74 when_defined: DomRefCell<HashMapTracedValues<LocalName, Rc<Promise>, FxBuildHasher>>,
77
78 element_definition_is_running: Cell<bool>,
79
80 #[conditional_malloc_size_of]
81 definitions:
82 DomRefCell<HashMapTracedValues<LocalName, Rc<CustomElementDefinition>, FxBuildHasher>>,
83}
84
85impl CustomElementRegistry {
86 fn new_inherited(window: &Window) -> CustomElementRegistry {
87 CustomElementRegistry {
88 reflector_: Reflector::new(),
89 window: Dom::from_ref(window),
90 when_defined: DomRefCell::new(HashMapTracedValues::new_fx()),
91 element_definition_is_running: Cell::new(false),
92 definitions: DomRefCell::new(HashMapTracedValues::new_fx()),
93 }
94 }
95
96 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<CustomElementRegistry> {
97 reflect_dom_object(
98 Box::new(CustomElementRegistry::new_inherited(window)),
99 window,
100 can_gc,
101 )
102 }
103
104 pub(crate) fn teardown(&self) {
107 self.when_defined.borrow_mut().0.clear()
108 }
109
110 pub(crate) fn lookup_definition(
112 &self,
113 local_name: &LocalName,
114 is: Option<&LocalName>,
115 ) -> Option<Rc<CustomElementDefinition>> {
116 self.definitions
117 .borrow()
118 .0
119 .values()
120 .find(|definition| {
121 definition.local_name == *local_name &&
123 (definition.name == *local_name || Some(&definition.name) == is)
124 })
125 .cloned()
126 }
127
128 pub(crate) fn lookup_definition_by_constructor(
129 &self,
130 constructor: HandleObject,
131 ) -> Option<Rc<CustomElementDefinition>> {
132 self.definitions
133 .borrow()
134 .0
135 .values()
136 .find(|definition| definition.constructor.callback() == constructor.get())
137 .cloned()
138 }
139
140 pub(crate) fn lookup_a_custom_element_registry(
142 node: &Node,
143 ) -> Option<DomRoot<CustomElementRegistry>> {
144 match node.type_id() {
145 NodeTypeId::Element(_) => node
147 .downcast::<Element>()
148 .expect("Nodes with element type must be an element")
149 .custom_element_registry(),
150 NodeTypeId::Document(_) => Some(
154 node.downcast::<Document>()
155 .expect("Nodes with document type must be a document")
156 .custom_element_registry(),
157 ),
158 _ => None,
160 }
161 }
162
163 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
165 registry.is_some()
169 }
170
171 #[expect(unsafe_code)]
174 fn check_prototype(
175 &self,
176 constructor: HandleObject,
177 mut prototype: MutableHandleValue,
178 ) -> ErrorResult {
179 unsafe {
180 if !JS_GetProperty(
182 *GlobalScope::get_cx(),
183 constructor,
184 c"prototype".as_ptr(),
185 prototype.reborrow(),
186 ) {
187 return Err(Error::JSFailed);
188 }
189
190 if !prototype.is_object() {
192 return Err(Error::Type(
193 c"constructor.prototype is not an object".to_owned(),
194 ));
195 }
196 }
197 Ok(())
198 }
199
200 #[expect(unsafe_code)]
204 unsafe fn get_callbacks(&self, prototype: HandleObject) -> Fallible<LifecycleCallbacks> {
205 let cx = GlobalScope::get_cx();
206
207 Ok(LifecycleCallbacks {
209 connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
210 disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
211 adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
212 attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
213
214 form_associated_callback: None,
215 form_disabled_callback: None,
216 form_reset_callback: None,
217 form_state_restore_callback: None,
218 })
219 }
220
221 #[expect(unsafe_code)]
224 unsafe fn add_form_associated_callbacks(
225 &self,
226 prototype: HandleObject,
227 callbacks: &mut LifecycleCallbacks,
228 ) -> ErrorResult {
229 let cx = self.window.get_cx();
230
231 callbacks.form_associated_callback =
232 get_callback(cx, prototype, c"formAssociatedCallback")?;
233 callbacks.form_reset_callback = get_callback(cx, prototype, c"formResetCallback")?;
234 callbacks.form_disabled_callback = get_callback(cx, prototype, c"formDisabledCallback")?;
235 callbacks.form_state_restore_callback =
236 get_callback(cx, prototype, c"formStateRestoreCallback")?;
237
238 Ok(())
239 }
240
241 #[expect(unsafe_code)]
242 fn get_observed_attributes(
243 &self,
244 constructor: HandleObject,
245 can_gc: CanGc,
246 ) -> Fallible<Vec<DOMString>> {
247 let cx = GlobalScope::get_cx();
248 rooted!(in(*cx) let mut observed_attributes = UndefinedValue());
249 if unsafe {
250 !JS_GetProperty(
251 *cx,
252 constructor,
253 c"observedAttributes".as_ptr(),
254 observed_attributes.handle_mut(),
255 )
256 } {
257 return Err(Error::JSFailed);
258 }
259
260 if observed_attributes.is_undefined() {
261 return Ok(Vec::new());
262 }
263
264 let conversion = SafeFromJSValConvertible::safe_from_jsval(
265 cx,
266 observed_attributes.handle(),
267 StringificationBehavior::Default,
268 can_gc,
269 );
270 match conversion {
271 Ok(ConversionResult::Success(attributes)) => Ok(attributes),
272 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
273 _ => Err(Error::JSFailed),
274 }
275 }
276
277 #[expect(unsafe_code)]
280 fn get_form_associated_value(
281 &self,
282 constructor: HandleObject,
283 can_gc: CanGc,
284 ) -> Fallible<bool> {
285 let cx = self.window.get_cx();
286 rooted!(in(*cx) let mut form_associated_value = UndefinedValue());
287 if unsafe {
288 !JS_GetProperty(
289 *cx,
290 constructor,
291 c"formAssociated".as_ptr(),
292 form_associated_value.handle_mut(),
293 )
294 } {
295 return Err(Error::JSFailed);
296 }
297
298 if form_associated_value.is_undefined() {
299 return Ok(false);
300 }
301
302 let conversion = SafeFromJSValConvertible::safe_from_jsval(
303 cx,
304 form_associated_value.handle(),
305 (),
306 can_gc,
307 );
308 match conversion {
309 Ok(ConversionResult::Success(flag)) => Ok(flag),
310 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
311 _ => Err(Error::JSFailed),
312 }
313 }
314
315 #[expect(unsafe_code)]
318 fn get_disabled_features(
319 &self,
320 constructor: HandleObject,
321 can_gc: CanGc,
322 ) -> Fallible<Vec<DOMString>> {
323 let cx = self.window.get_cx();
324 rooted!(in(*cx) let mut disabled_features = UndefinedValue());
325 if unsafe {
326 !JS_GetProperty(
327 *cx,
328 constructor,
329 c"disabledFeatures".as_ptr(),
330 disabled_features.handle_mut(),
331 )
332 } {
333 return Err(Error::JSFailed);
334 }
335
336 if disabled_features.is_undefined() {
337 return Ok(Vec::new());
338 }
339
340 let conversion = SafeFromJSValConvertible::safe_from_jsval(
341 cx,
342 disabled_features.handle(),
343 StringificationBehavior::Default,
344 can_gc,
345 );
346 match conversion {
347 Ok(ConversionResult::Success(attributes)) => Ok(attributes),
348 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
349 _ => Err(Error::JSFailed),
350 }
351 }
352}
353
354#[expect(unsafe_code)]
357fn get_callback(
358 cx: JSContext,
359 prototype: HandleObject,
360 name: &CStr,
361) -> Fallible<Option<Rc<Function>>> {
362 rooted!(in(*cx) let mut callback = UndefinedValue());
363 unsafe {
364 if !JS_GetProperty(
366 *cx,
367 prototype,
368 name.as_ptr() as *const _,
369 callback.handle_mut(),
370 ) {
371 return Err(Error::JSFailed);
372 }
373
374 if !callback.is_undefined() {
376 if !callback.is_object() || !IsCallable(callback.to_object()) {
377 return Err(Error::Type(
378 c"Lifecycle callback is not callable".to_owned(),
379 ));
380 }
381 Ok(Some(Function::new(cx, callback.to_object())))
382 } else {
383 Ok(None)
384 }
385 }
386}
387
388impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
389 #[expect(unsafe_code)]
390 fn Define(
392 &self,
393 name: DOMString,
394 constructor_: Rc<CustomElementConstructor>,
395 options: &ElementDefinitionOptions,
396 can_gc: CanGc,
397 ) -> ErrorResult {
398 let cx = GlobalScope::get_cx();
399 rooted!(in(*cx) let constructor = constructor_.callback());
400 let name = LocalName::from(name);
401
402 rooted!(in(*cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
405
406 if unwrapped_constructor.is_null() {
407 return Err(Error::Security(None));
409 }
410
411 if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
412 return Err(Error::Type(
413 c"Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
414 ));
415 }
416
417 if !is_valid_custom_element_name(&name) {
419 return Err(Error::Syntax(None));
420 }
421
422 if self.definitions.borrow().contains_key(&name) {
425 return Err(Error::NotSupported(None));
426 }
427
428 if self
431 .definitions
432 .borrow()
433 .iter()
434 .any(|(_, def)| def.constructor == constructor_)
435 {
436 return Err(Error::NotSupported(None));
437 }
438
439 let extends = &options.extends;
441
442 let local_name = if let Some(ref extended_name) = *extends {
444 if is_valid_custom_element_name(&extended_name.str()) {
448 return Err(Error::NotSupported(None));
449 }
450
451 if !is_extendable_element_interface(&extended_name.str()) {
455 return Err(Error::NotSupported(None));
456 }
457
458 LocalName::from(extended_name)
460 } else {
461 name.clone()
463 };
464
465 if self.element_definition_is_running.get() {
467 return Err(Error::NotSupported(None));
468 }
469
470 self.element_definition_is_running.set(true);
472
473 rooted!(in(*cx) let mut prototype = UndefinedValue());
478 {
479 let _ac = JSAutoRealm::new(*cx, constructor.get());
480 if let Err(error) = self.check_prototype(constructor.handle(), prototype.handle_mut()) {
481 self.element_definition_is_running.set(false);
482 return Err(error);
483 }
484 };
485
486 rooted!(in(*cx) let proto_object = prototype.to_object());
492 let mut callbacks = {
493 let _ac = JSAutoRealm::new(*cx, proto_object.get());
494 let callbacks = unsafe { self.get_callbacks(proto_object.handle()) };
495 match callbacks {
496 Ok(callbacks) => callbacks,
497 Err(error) => {
498 self.element_definition_is_running.set(false);
499 return Err(error);
500 },
501 }
502 };
503
504 let observed_attributes = if callbacks.attribute_changed_callback.is_some() {
507 let _ac = JSAutoRealm::new(*cx, constructor.get());
508 match self.get_observed_attributes(constructor.handle(), can_gc) {
509 Ok(attributes) => attributes,
510 Err(error) => {
511 self.element_definition_is_running.set(false);
512 return Err(error);
513 },
514 }
515 } else {
516 Vec::new()
517 };
518
519 let (disable_internals, disable_shadow) = {
521 let _ac = JSAutoRealm::new(*cx, constructor.get());
522 match self.get_disabled_features(constructor.handle(), can_gc) {
523 Ok(sequence) => (
524 sequence.iter().any(|s| *s == "internals"),
525 sequence.iter().any(|s| *s == "shadow"),
526 ),
527 Err(error) => {
528 self.element_definition_is_running.set(false);
529 return Err(error);
530 },
531 }
532 };
533
534 let form_associated = {
536 let _ac = JSAutoRealm::new(*cx, constructor.get());
537 match self.get_form_associated_value(constructor.handle(), can_gc) {
538 Ok(flag) => flag,
539 Err(error) => {
540 self.element_definition_is_running.set(false);
541 return Err(error);
542 },
543 }
544 };
545
546 if form_associated {
548 let _ac = JSAutoRealm::new(*cx, proto_object.get());
549 unsafe {
550 if let Err(error) =
551 self.add_form_associated_callbacks(proto_object.handle(), &mut callbacks)
552 {
553 self.element_definition_is_running.set(false);
554 return Err(error);
555 }
556 }
557 }
558
559 self.element_definition_is_running.set(false);
560
561 let definition = Rc::new(CustomElementDefinition::new(
563 name.clone(),
564 local_name.clone(),
565 constructor_,
566 observed_attributes,
567 callbacks,
568 form_associated,
569 disable_internals,
570 disable_shadow,
571 ));
572
573 self.definitions
575 .borrow_mut()
576 .insert(name.clone(), definition.clone());
577
578 let document = self.window.Document();
581
582 for candidate in document
584 .upcast::<Node>()
585 .traverse_preorder(ShadowIncluding::Yes)
586 .filter_map(DomRoot::downcast::<Element>)
587 {
588 let is = candidate.get_is();
589 if *candidate.local_name() == local_name &&
590 *candidate.namespace() == ns!(html) &&
591 (extends.is_none() || is.as_ref() == Some(&name))
592 {
593 ScriptThread::enqueue_upgrade_reaction(&candidate, definition.clone());
594 }
595 }
596
597 let promise = self.when_defined.borrow_mut().remove(&name);
599 if let Some(promise) = promise {
600 rooted!(in(*cx) let mut constructor = UndefinedValue());
601 definition
602 .constructor
603 .safe_to_jsval(cx, constructor.handle_mut(), can_gc);
604 promise.resolve_native(&constructor.get(), can_gc);
605 }
606 Ok(())
607 }
608
609 fn Get(&self, cx: JSContext, name: DOMString, mut retval: MutableHandleValue) {
611 match self.definitions.borrow().get(&LocalName::from(name)) {
612 Some(definition) => definition
613 .constructor
614 .safe_to_jsval(cx, retval, CanGc::note()),
615 None => retval.set(UndefinedValue()),
616 }
617 }
618
619 fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
621 self.definitions
622 .borrow()
623 .0
624 .values()
625 .find(|definition| definition.constructor == constructor)
626 .map(|definition| DOMString::from(definition.name.to_string()))
627 }
628
629 fn WhenDefined(&self, name: DOMString, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
631 let name = LocalName::from(name);
632
633 if !is_valid_custom_element_name(&name) {
635 let promise = Promise::new_in_current_realm(comp, can_gc);
636 promise.reject_native(
637 &DOMException::new(
638 self.window.as_global_scope(),
639 DOMErrorName::SyntaxError,
640 can_gc,
641 ),
642 can_gc,
643 );
644 return promise;
645 }
646
647 if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
649 let cx = GlobalScope::get_cx();
650 rooted!(in(*cx) let mut constructor = UndefinedValue());
651 definition
652 .constructor
653 .safe_to_jsval(cx, constructor.handle_mut(), can_gc);
654 let promise = Promise::new_in_current_realm(comp, can_gc);
655 promise.resolve_native(&constructor.get(), can_gc);
656 return promise;
657 }
658
659 let existing_promise = self.when_defined.borrow().get(&name).cloned();
661 existing_promise.unwrap_or_else(|| {
662 let promise = Promise::new_in_current_realm(comp, can_gc);
663 self.when_defined.borrow_mut().insert(name, promise.clone());
664 promise
665 })
666 }
667 fn Upgrade(&self, node: &Node) {
669 node.traverse_preorder(ShadowIncluding::Yes).for_each(|n| {
673 if let Some(element) = n.downcast::<Element>() {
674 try_upgrade_element(element);
675 }
676 });
677 }
678}
679
680#[derive(Clone, JSTraceable, MallocSizeOf)]
681pub(crate) struct LifecycleCallbacks {
682 #[conditional_malloc_size_of]
683 connected_callback: Option<Rc<Function>>,
684
685 #[conditional_malloc_size_of]
686 disconnected_callback: Option<Rc<Function>>,
687
688 #[conditional_malloc_size_of]
689 adopted_callback: Option<Rc<Function>>,
690
691 #[conditional_malloc_size_of]
692 attribute_changed_callback: Option<Rc<Function>>,
693
694 #[conditional_malloc_size_of]
695 form_associated_callback: Option<Rc<Function>>,
696
697 #[conditional_malloc_size_of]
698 form_reset_callback: Option<Rc<Function>>,
699
700 #[conditional_malloc_size_of]
701 form_disabled_callback: Option<Rc<Function>>,
702
703 #[conditional_malloc_size_of]
704 form_state_restore_callback: Option<Rc<Function>>,
705}
706
707#[derive(Clone, JSTraceable, MallocSizeOf)]
708pub(crate) enum ConstructionStackEntry {
709 Element(DomRoot<Element>),
710 AlreadyConstructedMarker,
711}
712
713#[derive(Clone, JSTraceable, MallocSizeOf)]
715pub(crate) struct CustomElementDefinition {
716 #[no_trace]
718 pub(crate) name: LocalName,
719
720 #[no_trace]
722 pub(crate) local_name: LocalName,
723
724 #[conditional_malloc_size_of]
726 pub(crate) constructor: Rc<CustomElementConstructor>,
727
728 pub(crate) observed_attributes: Vec<DOMString>,
730
731 pub(crate) callbacks: LifecycleCallbacks,
733
734 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
736
737 pub(crate) form_associated: bool,
739
740 pub(crate) disable_internals: bool,
742
743 pub(crate) disable_shadow: bool,
745}
746
747impl CustomElementDefinition {
748 #[expect(clippy::too_many_arguments)]
749 fn new(
750 name: LocalName,
751 local_name: LocalName,
752 constructor: Rc<CustomElementConstructor>,
753 observed_attributes: Vec<DOMString>,
754 callbacks: LifecycleCallbacks,
755 form_associated: bool,
756 disable_internals: bool,
757 disable_shadow: bool,
758 ) -> CustomElementDefinition {
759 CustomElementDefinition {
760 name,
761 local_name,
762 constructor,
763 observed_attributes,
764 callbacks,
765 construction_stack: Default::default(),
766 form_associated,
767 disable_internals,
768 disable_shadow,
769 }
770 }
771
772 pub(crate) fn is_autonomous(&self) -> bool {
774 self.name == self.local_name
775 }
776
777 #[expect(unsafe_code)]
779 pub(crate) fn create_element(
780 &self,
781 document: &Document,
782 prefix: Option<Prefix>,
783 registry: Option<DomRoot<CustomElementRegistry>>,
784 can_gc: CanGc,
786 ) -> Fallible<DomRoot<Element>> {
787 let window = document.window();
788 let cx = GlobalScope::get_cx();
789 rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback()));
791 rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>());
792 {
793 let _ac = JSAutoRealm::new(*cx, self.constructor.callback());
795 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
798 let _callback_guard = AutoIncumbentScript::new(window.upcast());
799 let args = HandleValueArray::empty();
800 if unsafe { !Construct1(*cx, constructor.handle(), &args, element.handle_mut()) } {
801 Err(Error::JSFailed)
802 } else {
803 Ok(())
804 }
805 })?;
806 }
807
808 rooted!(in(*cx) let element_val = ObjectValue(element.get()));
809 let element: DomRoot<Element> =
810 match SafeFromJSValConvertible::safe_from_jsval(cx, element_val.handle(), (), can_gc) {
811 Ok(ConversionResult::Success(element)) => element,
812 Ok(ConversionResult::Failure(..)) => {
813 return Err(Error::Type(
814 c"Constructor did not return a DOM node".to_owned(),
815 ));
816 },
817 _ => return Err(Error::JSFailed),
818 };
819
820 assert!(element.is::<HTMLElement>());
826
827 if element.HasAttributes() ||
833 element.upcast::<Node>().children_count() > 0 ||
834 element.upcast::<Node>().has_parent() ||
835 &*element.upcast::<Node>().owner_doc() != document ||
836 *element.namespace() != ns!(html) ||
837 *element.local_name() != self.local_name
838 {
839 return Err(Error::NotSupported(None));
840 }
841
842 element.set_prefix(prefix);
844
845 element.set_custom_element_registry(registry);
850
851 Ok(element)
852 }
853}
854
855pub(crate) fn upgrade_element(
857 definition: Rc<CustomElementDefinition>,
858 element: &Element,
859 can_gc: CanGc,
860) {
861 let state = element.get_custom_element_state();
863 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
864 return;
865 }
866
867 element.set_custom_element_definition(Rc::clone(&definition));
869
870 element.set_custom_element_state(CustomElementState::Failed);
872
873 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
877 for attr in element.attrs().iter() {
878 let local_name = attr.local_name().clone();
879 let value = DOMString::from(&**attr.value());
880 let namespace = attr.namespace().clone();
881 custom_element_reaction_stack.enqueue_callback_reaction(
882 element,
883 CallbackReaction::AttributeChanged(local_name, None, Some(value), namespace),
884 Some(definition.clone()),
885 );
886 }
887
888 if element.is_connected() {
891 ScriptThread::enqueue_callback_reaction(
892 element,
893 CallbackReaction::Connected,
894 Some(definition.clone()),
895 );
896 }
897
898 definition
900 .construction_stack
901 .borrow_mut()
902 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
903
904 let result = run_upgrade_constructor(&definition, element, can_gc);
906
907 definition.construction_stack.borrow_mut().pop();
909
910 if let Err(error) = result {
912 element.clear_custom_element_definition();
914
915 element.clear_reaction_queue();
917
918 let global = GlobalScope::current().expect("No current global");
920 let cx = GlobalScope::get_cx();
921 let ar = enter_realm(&*global);
922 throw_dom_exception(cx, &global, error, can_gc);
923 report_pending_exception(cx, true, InRealm::Entered(&ar), can_gc);
924
925 return;
926 }
927
928 if let Some(html_element) = element.downcast::<HTMLElement>() {
930 if html_element.is_form_associated_custom_element() {
931 html_element.reset_form_owner(can_gc);
935 if let Some(form) = html_element.form_owner() {
936 form.upcast::<Node>().rev_version();
939 }
943
944 element.check_disabled_attribute();
951 element.check_ancestors_disabled_state_for_form_control();
952 element.update_read_write_state_from_readonly_attribute();
953
954 if element.disabled_state() {
957 ScriptThread::enqueue_callback_reaction(
958 element,
959 CallbackReaction::FormDisabled(true),
960 Some(definition.clone()),
961 )
962 }
963 }
964 }
965
966 element.set_custom_element_state(CustomElementState::Custom);
968}
969
970#[expect(unsafe_code)]
973fn run_upgrade_constructor(
974 definition: &CustomElementDefinition,
975 element: &Element,
976 can_gc: CanGc,
978) -> ErrorResult {
979 let constructor = &definition.constructor;
980 let window = element.owner_window();
981 let cx = GlobalScope::get_cx();
982 rooted!(in(*cx) let constructor_val = ObjectValue(constructor.callback()));
983 rooted!(in(*cx) let mut element_val = UndefinedValue());
984 element.safe_to_jsval(cx, element_val.handle_mut(), can_gc);
985 rooted!(in(*cx) let mut construct_result = ptr::null_mut::<JSObject>());
986 {
987 if definition.disable_shadow && element.is_shadow_host() {
990 return Err(Error::NotSupported(None));
991 }
992
993 let _ac = JSAutoRealm::new(*cx, constructor.callback());
995 let args = HandleValueArray::empty();
996 element.set_custom_element_state(CustomElementState::Precustomized);
998
999 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
1002 let _callback_guard = AutoIncumbentScript::new(window.upcast());
1003 if unsafe {
1004 !Construct1(
1005 *cx,
1006 constructor_val.handle(),
1007 &args,
1008 construct_result.handle_mut(),
1009 )
1010 } {
1011 Err(Error::JSFailed)
1012 } else {
1013 Ok(())
1014 }
1015 })?;
1016
1017 let mut same = false;
1018 rooted!(in(*cx) let construct_result_val = ObjectValue(construct_result.get()));
1019
1020 if unsafe {
1022 !SameValue(
1023 *cx,
1024 construct_result_val.handle(),
1025 element_val.handle(),
1026 &mut same,
1027 )
1028 } {
1029 return Err(Error::JSFailed);
1030 }
1031 if !same {
1032 return Err(Error::Type(
1033 c"Returned element is not SameValue as the upgraded element".to_owned(),
1034 ));
1035 }
1036 }
1037 Ok(())
1038}
1039
1040pub(crate) fn try_upgrade_element(element: &Element) {
1042 let document = element.owner_document();
1045 let namespace = element.namespace();
1046 let local_name = element.local_name();
1047 let is = element.get_is();
1048 if let Some(definition) =
1049 document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
1050 {
1051 ScriptThread::enqueue_upgrade_reaction(element, definition);
1054 }
1055}
1056
1057#[derive(JSTraceable, MallocSizeOf)]
1058#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1059pub(crate) enum CustomElementReaction {
1060 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1061 Callback(
1062 #[conditional_malloc_size_of] Rc<Function>,
1063 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1064 ),
1065}
1066
1067impl CustomElementReaction {
1068 pub(crate) fn invoke(&self, element: &Element, can_gc: CanGc) {
1070 match *self {
1072 CustomElementReaction::Upgrade(ref definition) => {
1073 upgrade_element(definition.clone(), element, can_gc)
1074 },
1075 CustomElementReaction::Callback(ref callback, ref arguments) => {
1076 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1078 rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
1079 let _ = callback.Call_(
1080 element,
1081 arguments,
1082 value.handle_mut(),
1083 ExceptionHandling::Report,
1084 can_gc,
1085 );
1086 },
1087 }
1088 }
1089}
1090
1091pub(crate) enum CallbackReaction {
1092 Connected,
1093 Disconnected,
1094 Adopted(DomRoot<Document>, DomRoot<Document>),
1095 AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace),
1096 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1097 FormDisabled(bool),
1098 FormReset,
1099}
1100
1101#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1103enum BackupElementQueueFlag {
1104 Processing,
1105 NotProcessing,
1106}
1107
1108#[derive(JSTraceable, MallocSizeOf)]
1113#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1114#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1115pub(crate) struct CustomElementReactionStack {
1116 stack: DomRefCell<Vec<ElementQueue>>,
1117 backup_queue: ElementQueue,
1118 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1119}
1120
1121impl CustomElementReactionStack {
1122 pub(crate) fn new() -> CustomElementReactionStack {
1123 CustomElementReactionStack {
1124 stack: DomRefCell::new(Vec::new()),
1125 backup_queue: ElementQueue::new(),
1126 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1127 }
1128 }
1129
1130 pub(crate) fn push_new_element_queue(&self) {
1131 self.stack.borrow_mut().push(ElementQueue::new());
1132 }
1133
1134 pub(crate) fn pop_current_element_queue(&self, can_gc: CanGc) {
1135 rooted_vec!(let mut stack);
1136 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1137
1138 if let Some(current_queue) = stack.last() {
1139 current_queue.invoke_reactions(can_gc);
1140 }
1141 stack.pop();
1142
1143 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1144 self.stack.borrow_mut().append(&mut *stack);
1145 }
1146
1147 pub(crate) fn invoke_backup_element_queue(&self, can_gc: CanGc) {
1150 self.backup_queue.invoke_reactions(can_gc);
1152
1153 self.processing_backup_element_queue
1155 .set(BackupElementQueueFlag::NotProcessing);
1156 }
1157
1158 pub(crate) fn enqueue_element(&self, element: &Element) {
1160 if let Some(current_queue) = self.stack.borrow().last() {
1161 current_queue.append_element(element);
1163 } else {
1164 self.backup_queue.append_element(element);
1166
1167 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1169 return;
1170 }
1171
1172 self.processing_backup_element_queue
1174 .set(BackupElementQueueFlag::Processing);
1175
1176 ScriptThread::enqueue_microtask(Microtask::CustomElementReaction);
1178 }
1179 }
1180
1181 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1183 pub(crate) fn enqueue_callback_reaction(
1184 &self,
1185 element: &Element,
1186 reaction: CallbackReaction,
1187 definition: Option<Rc<CustomElementDefinition>>,
1188 ) {
1189 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1191 Some(definition) => definition,
1192 None => return,
1193 };
1194
1195 let (callback, args) = match reaction {
1197 CallbackReaction::Connected => {
1198 (definition.callbacks.connected_callback.clone(), Vec::new())
1199 },
1200 CallbackReaction::Disconnected => (
1201 definition.callbacks.disconnected_callback.clone(),
1202 Vec::new(),
1203 ),
1204 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1205 let args = vec![Heap::default(), Heap::default()];
1206 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1207 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1208 (definition.callbacks.adopted_callback.clone(), args)
1209 },
1210 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1211 if !definition
1213 .observed_attributes
1214 .iter()
1215 .any(|attr| *attr == *local_name)
1216 {
1217 return;
1218 }
1219
1220 let cx = GlobalScope::get_cx();
1221 let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get());
1225
1226 let local_name = DOMString::from(&*local_name);
1227 rooted!(in(*cx) let mut name_value = UndefinedValue());
1228 local_name.safe_to_jsval(cx, name_value.handle_mut(), CanGc::note());
1229
1230 rooted!(in(*cx) let mut old_value = NullValue());
1231 if let Some(old_val) = old_val {
1232 old_val.safe_to_jsval(cx, old_value.handle_mut(), CanGc::note());
1233 }
1234
1235 rooted!(in(*cx) let mut value = NullValue());
1236 if let Some(val) = val {
1237 val.safe_to_jsval(cx, value.handle_mut(), CanGc::note());
1238 }
1239
1240 rooted!(in(*cx) let mut namespace_value = NullValue());
1241 if namespace != ns!() {
1242 let namespace = DOMString::from(&*namespace);
1243 namespace.safe_to_jsval(cx, namespace_value.handle_mut(), CanGc::note());
1244 }
1245
1246 let args = vec![
1247 Heap::default(),
1248 Heap::default(),
1249 Heap::default(),
1250 Heap::default(),
1251 ];
1252 args[0].set(name_value.get());
1253 args[1].set(old_value.get());
1254 args[2].set(value.get());
1255 args[3].set(namespace_value.get());
1256
1257 (
1258 definition.callbacks.attribute_changed_callback.clone(),
1259 args,
1260 )
1261 },
1262 CallbackReaction::FormAssociated(form) => {
1263 let args = vec![Heap::default()];
1264 if let Some(form) = form {
1265 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1266 } else {
1267 args[0].set(NullValue());
1268 }
1269 (definition.callbacks.form_associated_callback.clone(), args)
1270 },
1271 CallbackReaction::FormDisabled(disabled) => {
1272 let cx = GlobalScope::get_cx();
1273 rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
1274 let args = vec![Heap::default()];
1275 args[0].set(disabled_value.get());
1276 (definition.callbacks.form_disabled_callback.clone(), args)
1277 },
1278 CallbackReaction::FormReset => {
1279 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1280 },
1281 };
1282
1283 let callback = match callback {
1285 Some(callback) => callback,
1286 None => return,
1287 };
1288
1289 element.push_callback_reaction(callback, args.into_boxed_slice());
1291
1292 self.enqueue_element(element);
1294 }
1295
1296 pub(crate) fn enqueue_upgrade_reaction(
1298 &self,
1299 element: &Element,
1300 definition: Rc<CustomElementDefinition>,
1301 ) {
1302 element.push_upgrade_reaction(definition);
1305
1306 self.enqueue_element(element);
1308 }
1309}
1310
1311#[derive(JSTraceable, MallocSizeOf)]
1313#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1314struct ElementQueue {
1315 queue: DomRefCell<VecDeque<Dom<Element>>>,
1316}
1317
1318impl ElementQueue {
1319 fn new() -> ElementQueue {
1320 ElementQueue {
1321 queue: Default::default(),
1322 }
1323 }
1324
1325 fn invoke_reactions(&self, can_gc: CanGc) {
1327 while let Some(element) = self.next_element() {
1329 element.invoke_reactions(can_gc)
1330 }
1331 self.queue.borrow_mut().clear();
1332 }
1333
1334 fn next_element(&self) -> Option<DomRoot<Element>> {
1335 self.queue
1336 .borrow_mut()
1337 .pop_front()
1338 .as_deref()
1339 .map(DomRoot::from_ref)
1340 }
1341
1342 fn append_element(&self, element: &Element) {
1343 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1344 }
1345}
1346
1347pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1349 let mut chars = name.chars();
1352 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1353 return false;
1354 }
1355
1356 let mut has_dash = false;
1357
1358 for c in chars {
1359 if c == '-' {
1360 has_dash = true;
1361 continue;
1362 }
1363
1364 if !is_potential_custom_element_char(c) {
1365 return false;
1366 }
1367 }
1368
1369 if !has_dash {
1370 return false;
1371 }
1372
1373 if name == "annotation-xml" ||
1374 name == "color-profile" ||
1375 name == "font-face" ||
1376 name == "font-face-src" ||
1377 name == "font-face-uri" ||
1378 name == "font-face-format" ||
1379 name == "font-face-name" ||
1380 name == "missing-glyph"
1381 {
1382 return false;
1383 }
1384
1385 true
1386}
1387
1388fn is_potential_custom_element_char(c: char) -> bool {
1391 c == '-' ||
1392 c == '.' ||
1393 c == '_' ||
1394 c == '\u{B7}' ||
1395 c.is_ascii_digit() ||
1396 c.is_ascii_lowercase() ||
1397 ('\u{C0}'..='\u{D6}').contains(&c) ||
1398 ('\u{D8}'..='\u{F6}').contains(&c) ||
1399 ('\u{F8}'..='\u{37D}').contains(&c) ||
1400 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1401 ('\u{200C}'..='\u{200D}').contains(&c) ||
1402 ('\u{203F}'..='\u{2040}').contains(&c) ||
1403 ('\u{2070}'..='\u{218F}').contains(&c) ||
1404 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1405 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1406 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1407 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1408 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1409}
1410
1411fn is_extendable_element_interface(element: &str) -> bool {
1412 element == "a" ||
1413 element == "abbr" ||
1414 element == "acronym" ||
1415 element == "address" ||
1416 element == "area" ||
1417 element == "article" ||
1418 element == "aside" ||
1419 element == "audio" ||
1420 element == "b" ||
1421 element == "base" ||
1422 element == "bdi" ||
1423 element == "bdo" ||
1424 element == "big" ||
1425 element == "blockquote" ||
1426 element == "body" ||
1427 element == "br" ||
1428 element == "button" ||
1429 element == "canvas" ||
1430 element == "caption" ||
1431 element == "center" ||
1432 element == "cite" ||
1433 element == "code" ||
1434 element == "col" ||
1435 element == "colgroup" ||
1436 element == "data" ||
1437 element == "datalist" ||
1438 element == "dd" ||
1439 element == "del" ||
1440 element == "details" ||
1441 element == "dfn" ||
1442 element == "dialog" ||
1443 element == "dir" ||
1444 element == "div" ||
1445 element == "dl" ||
1446 element == "dt" ||
1447 element == "em" ||
1448 element == "embed" ||
1449 element == "fieldset" ||
1450 element == "figcaption" ||
1451 element == "figure" ||
1452 element == "font" ||
1453 element == "footer" ||
1454 element == "form" ||
1455 element == "frame" ||
1456 element == "frameset" ||
1457 element == "h1" ||
1458 element == "h2" ||
1459 element == "h3" ||
1460 element == "h4" ||
1461 element == "h5" ||
1462 element == "h6" ||
1463 element == "head" ||
1464 element == "header" ||
1465 element == "hgroup" ||
1466 element == "hr" ||
1467 element == "html" ||
1468 element == "i" ||
1469 element == "iframe" ||
1470 element == "img" ||
1471 element == "input" ||
1472 element == "ins" ||
1473 element == "kbd" ||
1474 element == "label" ||
1475 element == "legend" ||
1476 element == "li" ||
1477 element == "link" ||
1478 element == "listing" ||
1479 element == "main" ||
1480 element == "map" ||
1481 element == "mark" ||
1482 element == "marquee" ||
1483 element == "menu" ||
1484 element == "meta" ||
1485 element == "meter" ||
1486 element == "nav" ||
1487 element == "nobr" ||
1488 element == "noframes" ||
1489 element == "noscript" ||
1490 element == "object" ||
1491 element == "ol" ||
1492 element == "optgroup" ||
1493 element == "option" ||
1494 element == "output" ||
1495 element == "p" ||
1496 element == "param" ||
1497 element == "picture" ||
1498 element == "plaintext" ||
1499 element == "pre" ||
1500 element == "progress" ||
1501 element == "q" ||
1502 element == "rp" ||
1503 element == "rt" ||
1504 element == "ruby" ||
1505 element == "s" ||
1506 element == "samp" ||
1507 element == "script" ||
1508 element == "section" ||
1509 element == "select" ||
1510 element == "slot" ||
1511 element == "small" ||
1512 element == "source" ||
1513 element == "span" ||
1514 element == "strike" ||
1515 element == "strong" ||
1516 element == "style" ||
1517 element == "sub" ||
1518 element == "summary" ||
1519 element == "sup" ||
1520 element == "table" ||
1521 element == "tbody" ||
1522 element == "td" ||
1523 element == "template" ||
1524 element == "textarea" ||
1525 element == "tfoot" ||
1526 element == "th" ||
1527 element == "thead" ||
1528 element == "time" ||
1529 element == "title" ||
1530 element == "tr" ||
1531 element == "tt" ||
1532 element == "track" ||
1533 element == "u" ||
1534 element == "ul" ||
1535 element == "var" ||
1536 element == "video" ||
1537 element == "wbr" ||
1538 element == "xmp"
1539}