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_callback, 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::str::DOMString;
40use crate::dom::document::Document;
41use crate::dom::domexception::{DOMErrorName, DOMException};
42use crate::dom::element::Element;
43use crate::dom::globalscope::GlobalScope;
44use crate::dom::html::htmlelement::HTMLElement;
45use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
46use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
47use crate::dom::promise::Promise;
48use crate::dom::window::Window;
49use crate::microtask::Microtask;
50use crate::realms::{InRealm, enter_realm};
51use crate::script_runtime::{CanGc, JSContext};
52use crate::script_thread::ScriptThread;
53
54#[derive(Clone, Copy, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
56pub(crate) enum CustomElementState {
57 Undefined,
58 Failed,
59 #[default]
60 Uncustomized,
61 Precustomized,
62 Custom,
63}
64
65#[dom_struct]
67pub(crate) struct CustomElementRegistry {
68 reflector_: Reflector,
69
70 window: Dom<Window>,
71
72 #[conditional_malloc_size_of]
73 when_defined: DomRefCell<HashMapTracedValues<LocalName, Rc<Promise>, FxBuildHasher>>,
76
77 element_definition_is_running: Cell<bool>,
78
79 #[conditional_malloc_size_of]
80 definitions:
81 DomRefCell<HashMapTracedValues<LocalName, Rc<CustomElementDefinition>, FxBuildHasher>>,
82}
83
84impl CustomElementRegistry {
85 fn new_inherited(window: &Window) -> CustomElementRegistry {
86 CustomElementRegistry {
87 reflector_: Reflector::new(),
88 window: Dom::from_ref(window),
89 when_defined: DomRefCell::new(HashMapTracedValues::new_fx()),
90 element_definition_is_running: Cell::new(false),
91 definitions: DomRefCell::new(HashMapTracedValues::new_fx()),
92 }
93 }
94
95 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<CustomElementRegistry> {
96 reflect_dom_object(
97 Box::new(CustomElementRegistry::new_inherited(window)),
98 window,
99 can_gc,
100 )
101 }
102
103 pub(crate) fn teardown(&self) {
106 self.when_defined.borrow_mut().0.clear()
107 }
108
109 pub(crate) fn lookup_definition(
111 &self,
112 local_name: &LocalName,
113 is: Option<&LocalName>,
114 ) -> Option<Rc<CustomElementDefinition>> {
115 self.definitions
116 .borrow()
117 .0
118 .values()
119 .find(|definition| {
120 definition.local_name == *local_name &&
122 (definition.name == *local_name || Some(&definition.name) == is)
123 })
124 .cloned()
125 }
126
127 pub(crate) fn lookup_definition_by_constructor(
128 &self,
129 constructor: HandleObject,
130 ) -> Option<Rc<CustomElementDefinition>> {
131 self.definitions
132 .borrow()
133 .0
134 .values()
135 .find(|definition| definition.constructor.callback() == constructor.get())
136 .cloned()
137 }
138
139 pub(crate) fn lookup_a_custom_element_registry(
141 node: &Node,
142 ) -> Option<DomRoot<CustomElementRegistry>> {
143 match node.type_id() {
144 NodeTypeId::Element(_) => node
146 .downcast::<Element>()
147 .expect("Nodes with element type must be an element")
148 .custom_element_registry(),
149 NodeTypeId::Document(_) => Some(
153 node.downcast::<Document>()
154 .expect("Nodes with document type must be a document")
155 .custom_element_registry(),
156 ),
157 _ => None,
159 }
160 }
161
162 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
164 registry.is_some()
168 }
169
170 #[expect(unsafe_code)]
173 fn check_prototype(
174 &self,
175 constructor: HandleObject,
176 mut prototype: MutableHandleValue,
177 ) -> ErrorResult {
178 unsafe {
179 if !JS_GetProperty(
181 *GlobalScope::get_cx(),
182 constructor,
183 c"prototype".as_ptr(),
184 prototype.reborrow(),
185 ) {
186 return Err(Error::JSFailed);
187 }
188
189 if !prototype.is_object() {
191 return Err(Error::Type(
192 c"constructor.prototype is not an object".to_owned(),
193 ));
194 }
195 }
196 Ok(())
197 }
198
199 #[expect(unsafe_code)]
203 unsafe fn get_callbacks(&self, prototype: HandleObject) -> Fallible<LifecycleCallbacks> {
204 let cx = GlobalScope::get_cx();
205
206 Ok(LifecycleCallbacks {
208 connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
209 disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
210 connected_move_callback: get_callback(cx, prototype, c"connectedMoveCallback")?,
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 connected_move_callback: Option<Rc<Function>>,
687
688 #[conditional_malloc_size_of]
689 disconnected_callback: Option<Rc<Function>>,
690
691 #[conditional_malloc_size_of]
692 adopted_callback: Option<Rc<Function>>,
693
694 #[conditional_malloc_size_of]
695 attribute_changed_callback: Option<Rc<Function>>,
696
697 #[conditional_malloc_size_of]
698 form_associated_callback: Option<Rc<Function>>,
699
700 #[conditional_malloc_size_of]
701 form_reset_callback: Option<Rc<Function>>,
702
703 #[conditional_malloc_size_of]
704 form_disabled_callback: Option<Rc<Function>>,
705
706 #[conditional_malloc_size_of]
707 form_state_restore_callback: Option<Rc<Function>>,
708}
709
710#[derive(Clone, JSTraceable, MallocSizeOf)]
711pub(crate) enum ConstructionStackEntry {
712 Element(DomRoot<Element>),
713 AlreadyConstructedMarker,
714}
715
716#[derive(Clone, JSTraceable, MallocSizeOf)]
718pub(crate) struct CustomElementDefinition {
719 #[no_trace]
721 pub(crate) name: LocalName,
722
723 #[no_trace]
725 pub(crate) local_name: LocalName,
726
727 #[conditional_malloc_size_of]
729 pub(crate) constructor: Rc<CustomElementConstructor>,
730
731 pub(crate) observed_attributes: Vec<DOMString>,
733
734 pub(crate) callbacks: LifecycleCallbacks,
736
737 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
739
740 pub(crate) form_associated: bool,
742
743 pub(crate) disable_internals: bool,
745
746 pub(crate) disable_shadow: bool,
748}
749
750impl CustomElementDefinition {
751 #[expect(clippy::too_many_arguments)]
752 fn new(
753 name: LocalName,
754 local_name: LocalName,
755 constructor: Rc<CustomElementConstructor>,
756 observed_attributes: Vec<DOMString>,
757 callbacks: LifecycleCallbacks,
758 form_associated: bool,
759 disable_internals: bool,
760 disable_shadow: bool,
761 ) -> CustomElementDefinition {
762 CustomElementDefinition {
763 name,
764 local_name,
765 constructor,
766 observed_attributes,
767 callbacks,
768 construction_stack: Default::default(),
769 form_associated,
770 disable_internals,
771 disable_shadow,
772 }
773 }
774
775 pub(crate) fn is_autonomous(&self) -> bool {
777 self.name == self.local_name
778 }
779
780 #[expect(unsafe_code)]
782 pub(crate) fn create_element(
783 &self,
784 document: &Document,
785 prefix: Option<Prefix>,
786 registry: Option<DomRoot<CustomElementRegistry>>,
787 can_gc: CanGc,
789 ) -> Fallible<DomRoot<Element>> {
790 let window = document.window();
791 let cx = GlobalScope::get_cx();
792 rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback()));
794 rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>());
795 {
796 let _ac = JSAutoRealm::new(*cx, self.constructor.callback());
798 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
801 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
802 let args = HandleValueArray::empty();
803 if unsafe {
804 !Construct1(*cx, constructor.handle(), &args, element.handle_mut())
805 } {
806 Err(Error::JSFailed)
807 } else {
808 Ok(())
809 }
810 })
811 })?;
812 }
813
814 rooted!(in(*cx) let element_val = ObjectValue(element.get()));
815 let element: DomRoot<Element> =
816 match SafeFromJSValConvertible::safe_from_jsval(cx, element_val.handle(), (), can_gc) {
817 Ok(ConversionResult::Success(element)) => element,
818 Ok(ConversionResult::Failure(..)) => {
819 return Err(Error::Type(
820 c"Constructor did not return a DOM node".to_owned(),
821 ));
822 },
823 _ => return Err(Error::JSFailed),
824 };
825
826 assert!(element.is::<HTMLElement>());
832
833 if element.HasAttributes() ||
839 element.upcast::<Node>().children_count() > 0 ||
840 element.upcast::<Node>().has_parent() ||
841 &*element.upcast::<Node>().owner_doc() != document ||
842 *element.namespace() != ns!(html) ||
843 *element.local_name() != self.local_name
844 {
845 return Err(Error::NotSupported(None));
846 }
847
848 element.set_prefix(prefix);
850
851 element.set_custom_element_registry(registry);
856
857 Ok(element)
858 }
859}
860
861pub(crate) fn upgrade_element(
863 definition: Rc<CustomElementDefinition>,
864 element: &Element,
865 can_gc: CanGc,
866) {
867 let state = element.get_custom_element_state();
869 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
870 return;
871 }
872
873 element.set_custom_element_definition(Rc::clone(&definition));
875
876 element.set_custom_element_state(CustomElementState::Failed);
878
879 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
883 for attr in element.attrs().iter() {
884 let local_name = attr.local_name().clone();
885 let value = DOMString::from(&**attr.value());
886 let namespace = attr.namespace().clone();
887 custom_element_reaction_stack.enqueue_callback_reaction(
888 element,
889 CallbackReaction::AttributeChanged(local_name, None, Some(value), namespace),
890 Some(definition.clone()),
891 );
892 }
893
894 if element.is_connected() {
897 ScriptThread::enqueue_callback_reaction(
898 element,
899 CallbackReaction::Connected,
900 Some(definition.clone()),
901 );
902 }
903
904 definition
906 .construction_stack
907 .borrow_mut()
908 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
909
910 let result = run_upgrade_constructor(&definition, element, can_gc);
912
913 definition.construction_stack.borrow_mut().pop();
915
916 if let Err(error) = result {
918 element.clear_custom_element_definition();
920
921 element.clear_reaction_queue();
923
924 let global = GlobalScope::current().expect("No current global");
926 let cx = GlobalScope::get_cx();
927 let ar = enter_realm(&*global);
928 throw_dom_exception(cx, &global, error, can_gc);
929 report_pending_exception(cx, InRealm::Entered(&ar), can_gc);
930
931 return;
932 }
933
934 if let Some(html_element) = element.downcast::<HTMLElement>() {
936 if html_element.is_form_associated_custom_element() {
937 html_element.reset_form_owner(can_gc);
941 if let Some(form) = html_element.form_owner() {
942 form.upcast::<Node>().rev_version();
945 }
949
950 element.check_disabled_attribute();
957 element.check_ancestors_disabled_state_for_form_control();
958 element.update_read_write_state_from_readonly_attribute();
959
960 if element.disabled_state() {
963 ScriptThread::enqueue_callback_reaction(
964 element,
965 CallbackReaction::FormDisabled(true),
966 Some(definition),
967 )
968 }
969 }
970 }
971
972 element.set_custom_element_state(CustomElementState::Custom);
974}
975
976#[expect(unsafe_code)]
979fn run_upgrade_constructor(
980 definition: &CustomElementDefinition,
981 element: &Element,
982 can_gc: CanGc,
984) -> ErrorResult {
985 let constructor = &definition.constructor;
986 let window = element.owner_window();
987 let cx = GlobalScope::get_cx();
988 rooted!(in(*cx) let constructor_val = ObjectValue(constructor.callback()));
989 rooted!(in(*cx) let mut element_val = UndefinedValue());
990 element.safe_to_jsval(cx, element_val.handle_mut(), can_gc);
991 rooted!(in(*cx) let mut construct_result = ptr::null_mut::<JSObject>());
992 {
993 if definition.disable_shadow && element.is_shadow_host() {
996 return Err(Error::NotSupported(None));
997 }
998
999 let _ac = JSAutoRealm::new(*cx, constructor.callback());
1001 let args = HandleValueArray::empty();
1002 element.set_custom_element_state(CustomElementState::Precustomized);
1004
1005 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
1008 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
1009 if unsafe {
1010 !Construct1(
1011 *cx,
1012 constructor_val.handle(),
1013 &args,
1014 construct_result.handle_mut(),
1015 )
1016 } {
1017 Err(Error::JSFailed)
1018 } else {
1019 Ok(())
1020 }
1021 })
1022 })?;
1023
1024 let mut same = false;
1025 rooted!(in(*cx) let construct_result_val = ObjectValue(construct_result.get()));
1026
1027 if unsafe {
1029 !SameValue(
1030 *cx,
1031 construct_result_val.handle(),
1032 element_val.handle(),
1033 &mut same,
1034 )
1035 } {
1036 return Err(Error::JSFailed);
1037 }
1038 if !same {
1039 return Err(Error::Type(
1040 c"Returned element is not SameValue as the upgraded element".to_owned(),
1041 ));
1042 }
1043 }
1044 Ok(())
1045}
1046
1047pub(crate) fn try_upgrade_element(element: &Element) {
1049 let document = element.owner_document();
1052 let namespace = element.namespace();
1053 let local_name = element.local_name();
1054 let is = element.get_is();
1055 if let Some(definition) =
1056 document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
1057 {
1058 ScriptThread::enqueue_upgrade_reaction(element, definition);
1061 }
1062}
1063
1064#[derive(JSTraceable, MallocSizeOf)]
1065#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1066pub(crate) enum CustomElementReaction {
1067 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1068 Callback(
1069 #[conditional_malloc_size_of] Rc<Function>,
1070 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1071 ),
1072}
1073
1074impl CustomElementReaction {
1075 pub(crate) fn invoke(&self, element: &Element, can_gc: CanGc) {
1077 match *self {
1079 CustomElementReaction::Upgrade(ref definition) => {
1080 upgrade_element(definition.clone(), element, can_gc)
1081 },
1082 CustomElementReaction::Callback(ref callback, ref arguments) => {
1083 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1085 rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
1086 let _ = callback.Call_(
1087 element,
1088 arguments,
1089 value.handle_mut(),
1090 ExceptionHandling::Report,
1091 can_gc,
1092 );
1093 },
1094 }
1095 }
1096}
1097
1098pub(crate) enum CallbackReaction {
1099 Connected,
1100 Disconnected,
1101 Adopted(DomRoot<Document>, DomRoot<Document>),
1102 AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace),
1103 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1104 FormDisabled(bool),
1105 FormReset,
1106 ConnectedMove,
1107}
1108
1109#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1111enum BackupElementQueueFlag {
1112 Processing,
1113 NotProcessing,
1114}
1115
1116#[derive(JSTraceable, MallocSizeOf)]
1121#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1122#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1123pub(crate) struct CustomElementReactionStack {
1124 stack: DomRefCell<Vec<ElementQueue>>,
1125 backup_queue: ElementQueue,
1126 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1127}
1128
1129impl CustomElementReactionStack {
1130 pub(crate) fn new() -> CustomElementReactionStack {
1131 CustomElementReactionStack {
1132 stack: DomRefCell::new(Vec::new()),
1133 backup_queue: ElementQueue::new(),
1134 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1135 }
1136 }
1137
1138 pub(crate) fn push_new_element_queue(&self) {
1139 self.stack.borrow_mut().push(ElementQueue::new());
1140 }
1141
1142 pub(crate) fn pop_current_element_queue(&self, can_gc: CanGc) {
1143 rooted_vec!(let mut stack);
1144 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1145
1146 if let Some(current_queue) = stack.last() {
1147 current_queue.invoke_reactions(can_gc);
1148 }
1149 stack.pop();
1150
1151 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1152 self.stack.borrow_mut().append(&mut *stack);
1153 }
1154
1155 pub(crate) fn invoke_backup_element_queue(&self, can_gc: CanGc) {
1158 self.backup_queue.invoke_reactions(can_gc);
1160
1161 self.processing_backup_element_queue
1163 .set(BackupElementQueueFlag::NotProcessing);
1164 }
1165
1166 pub(crate) fn enqueue_element(&self, element: &Element) {
1168 if let Some(current_queue) = self.stack.borrow().last() {
1169 current_queue.append_element(element);
1171 } else {
1172 self.backup_queue.append_element(element);
1174
1175 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1177 return;
1178 }
1179
1180 self.processing_backup_element_queue
1182 .set(BackupElementQueueFlag::Processing);
1183
1184 ScriptThread::enqueue_microtask(Microtask::CustomElementReaction);
1186 }
1187 }
1188
1189 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1191 pub(crate) fn enqueue_callback_reaction(
1192 &self,
1193 element: &Element,
1194 reaction: CallbackReaction,
1195 definition: Option<Rc<CustomElementDefinition>>,
1196 ) {
1197 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1199 Some(definition) => definition,
1200 None => return,
1201 };
1202
1203 let (callback, args) = match reaction {
1206 CallbackReaction::Connected => {
1207 (definition.callbacks.connected_callback.clone(), Vec::new())
1208 },
1209 CallbackReaction::Disconnected => (
1210 definition.callbacks.disconnected_callback.clone(),
1211 Vec::new(),
1212 ),
1213 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1214 let args = vec![Heap::default(), Heap::default()];
1215 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1216 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1217 (definition.callbacks.adopted_callback.clone(), args)
1218 },
1219 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1220 if !definition
1222 .observed_attributes
1223 .iter()
1224 .any(|attr| *attr == *local_name)
1225 {
1226 return;
1227 }
1228
1229 let cx = GlobalScope::get_cx();
1230 let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get());
1234
1235 let local_name = DOMString::from(&*local_name);
1236 rooted!(in(*cx) let mut name_value = UndefinedValue());
1237 local_name.safe_to_jsval(cx, name_value.handle_mut(), CanGc::note());
1238
1239 rooted!(in(*cx) let mut old_value = NullValue());
1240 if let Some(old_val) = old_val {
1241 old_val.safe_to_jsval(cx, old_value.handle_mut(), CanGc::note());
1242 }
1243
1244 rooted!(in(*cx) let mut value = NullValue());
1245 if let Some(val) = val {
1246 val.safe_to_jsval(cx, value.handle_mut(), CanGc::note());
1247 }
1248
1249 rooted!(in(*cx) let mut namespace_value = NullValue());
1250 if namespace != ns!() {
1251 let namespace = DOMString::from(&*namespace);
1252 namespace.safe_to_jsval(cx, namespace_value.handle_mut(), CanGc::note());
1253 }
1254
1255 let args = vec![
1256 Heap::default(),
1257 Heap::default(),
1258 Heap::default(),
1259 Heap::default(),
1260 ];
1261 args[0].set(name_value.get());
1262 args[1].set(old_value.get());
1263 args[2].set(value.get());
1264 args[3].set(namespace_value.get());
1265
1266 (
1267 definition.callbacks.attribute_changed_callback.clone(),
1268 args,
1269 )
1270 },
1271 CallbackReaction::FormAssociated(form) => {
1272 let args = vec![Heap::default()];
1273 if let Some(form) = form {
1274 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1275 } else {
1276 args[0].set(NullValue());
1277 }
1278 (definition.callbacks.form_associated_callback.clone(), args)
1279 },
1280 CallbackReaction::FormDisabled(disabled) => {
1281 let cx = GlobalScope::get_cx();
1282 rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
1283 let args = vec![Heap::default()];
1284 args[0].set(disabled_value.get());
1285 (definition.callbacks.form_disabled_callback.clone(), args)
1286 },
1287 CallbackReaction::FormReset => {
1288 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1289 },
1290 CallbackReaction::ConnectedMove => {
1291 let callback = definition.callbacks.connected_move_callback.clone();
1292 if callback.is_none() {
1294 let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1297
1298 let connected_callback = definition.callbacks.connected_callback.clone();
1301
1302 if disconnected_callback.is_none() && connected_callback.is_none() {
1305 return;
1306 }
1307
1308 if let Some(disconnected_callback) = disconnected_callback {
1312 element.push_callback_reaction(disconnected_callback, Box::new([]));
1313 }
1314 if let Some(connected_callback) = connected_callback {
1317 element.push_callback_reaction(connected_callback, Box::new([]));
1318 }
1319
1320 self.enqueue_element(element);
1321 return;
1322 }
1323
1324 (callback, Vec::new())
1325 },
1326 };
1327
1328 let callback = match callback {
1330 Some(callback) => callback,
1331 None => return,
1332 };
1333
1334 element.push_callback_reaction(callback, args.into_boxed_slice());
1337
1338 self.enqueue_element(element);
1340 }
1341
1342 pub(crate) fn enqueue_upgrade_reaction(
1344 &self,
1345 element: &Element,
1346 definition: Rc<CustomElementDefinition>,
1347 ) {
1348 element.push_upgrade_reaction(definition);
1351
1352 self.enqueue_element(element);
1354 }
1355}
1356
1357#[derive(JSTraceable, MallocSizeOf)]
1359#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1360struct ElementQueue {
1361 queue: DomRefCell<VecDeque<Dom<Element>>>,
1362}
1363
1364impl ElementQueue {
1365 fn new() -> ElementQueue {
1366 ElementQueue {
1367 queue: Default::default(),
1368 }
1369 }
1370
1371 fn invoke_reactions(&self, can_gc: CanGc) {
1373 while let Some(element) = self.next_element() {
1375 element.invoke_reactions(can_gc)
1376 }
1377 self.queue.borrow_mut().clear();
1378 }
1379
1380 fn next_element(&self) -> Option<DomRoot<Element>> {
1381 self.queue
1382 .borrow_mut()
1383 .pop_front()
1384 .as_deref()
1385 .map(DomRoot::from_ref)
1386 }
1387
1388 fn append_element(&self, element: &Element) {
1389 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1390 }
1391}
1392
1393pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1395 let mut chars = name.chars();
1398 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1399 return false;
1400 }
1401
1402 let mut has_dash = false;
1403
1404 for c in chars {
1405 if c == '-' {
1406 has_dash = true;
1407 continue;
1408 }
1409
1410 if !is_potential_custom_element_char(c) {
1411 return false;
1412 }
1413 }
1414
1415 if !has_dash {
1416 return false;
1417 }
1418
1419 if name == "annotation-xml" ||
1420 name == "color-profile" ||
1421 name == "font-face" ||
1422 name == "font-face-src" ||
1423 name == "font-face-uri" ||
1424 name == "font-face-format" ||
1425 name == "font-face-name" ||
1426 name == "missing-glyph"
1427 {
1428 return false;
1429 }
1430
1431 true
1432}
1433
1434fn is_potential_custom_element_char(c: char) -> bool {
1437 c == '-' ||
1438 c == '.' ||
1439 c == '_' ||
1440 c == '\u{B7}' ||
1441 c.is_ascii_digit() ||
1442 c.is_ascii_lowercase() ||
1443 ('\u{C0}'..='\u{D6}').contains(&c) ||
1444 ('\u{D8}'..='\u{F6}').contains(&c) ||
1445 ('\u{F8}'..='\u{37D}').contains(&c) ||
1446 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1447 ('\u{200C}'..='\u{200D}').contains(&c) ||
1448 ('\u{203F}'..='\u{2040}').contains(&c) ||
1449 ('\u{2070}'..='\u{218F}').contains(&c) ||
1450 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1451 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1452 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1453 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1454 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1455}
1456
1457fn is_extendable_element_interface(element: &str) -> bool {
1458 element == "a" ||
1459 element == "abbr" ||
1460 element == "acronym" ||
1461 element == "address" ||
1462 element == "area" ||
1463 element == "article" ||
1464 element == "aside" ||
1465 element == "audio" ||
1466 element == "b" ||
1467 element == "base" ||
1468 element == "bdi" ||
1469 element == "bdo" ||
1470 element == "big" ||
1471 element == "blockquote" ||
1472 element == "body" ||
1473 element == "br" ||
1474 element == "button" ||
1475 element == "canvas" ||
1476 element == "caption" ||
1477 element == "center" ||
1478 element == "cite" ||
1479 element == "code" ||
1480 element == "col" ||
1481 element == "colgroup" ||
1482 element == "data" ||
1483 element == "datalist" ||
1484 element == "dd" ||
1485 element == "del" ||
1486 element == "details" ||
1487 element == "dfn" ||
1488 element == "dialog" ||
1489 element == "dir" ||
1490 element == "div" ||
1491 element == "dl" ||
1492 element == "dt" ||
1493 element == "em" ||
1494 element == "embed" ||
1495 element == "fieldset" ||
1496 element == "figcaption" ||
1497 element == "figure" ||
1498 element == "font" ||
1499 element == "footer" ||
1500 element == "form" ||
1501 element == "frame" ||
1502 element == "frameset" ||
1503 element == "h1" ||
1504 element == "h2" ||
1505 element == "h3" ||
1506 element == "h4" ||
1507 element == "h5" ||
1508 element == "h6" ||
1509 element == "head" ||
1510 element == "header" ||
1511 element == "hgroup" ||
1512 element == "hr" ||
1513 element == "html" ||
1514 element == "i" ||
1515 element == "iframe" ||
1516 element == "img" ||
1517 element == "input" ||
1518 element == "ins" ||
1519 element == "kbd" ||
1520 element == "label" ||
1521 element == "legend" ||
1522 element == "li" ||
1523 element == "link" ||
1524 element == "listing" ||
1525 element == "main" ||
1526 element == "map" ||
1527 element == "mark" ||
1528 element == "marquee" ||
1529 element == "menu" ||
1530 element == "meta" ||
1531 element == "meter" ||
1532 element == "nav" ||
1533 element == "nobr" ||
1534 element == "noframes" ||
1535 element == "noscript" ||
1536 element == "object" ||
1537 element == "ol" ||
1538 element == "optgroup" ||
1539 element == "option" ||
1540 element == "output" ||
1541 element == "p" ||
1542 element == "param" ||
1543 element == "picture" ||
1544 element == "plaintext" ||
1545 element == "pre" ||
1546 element == "progress" ||
1547 element == "q" ||
1548 element == "rp" ||
1549 element == "rt" ||
1550 element == "ruby" ||
1551 element == "s" ||
1552 element == "samp" ||
1553 element == "script" ||
1554 element == "section" ||
1555 element == "select" ||
1556 element == "slot" ||
1557 element == "small" ||
1558 element == "source" ||
1559 element == "span" ||
1560 element == "strike" ||
1561 element == "strong" ||
1562 element == "style" ||
1563 element == "sub" ||
1564 element == "summary" ||
1565 element == "sup" ||
1566 element == "table" ||
1567 element == "tbody" ||
1568 element == "td" ||
1569 element == "template" ||
1570 element == "textarea" ||
1571 element == "tfoot" ||
1572 element == "th" ||
1573 element == "thead" ||
1574 element == "time" ||
1575 element == "title" ||
1576 element == "tr" ||
1577 element == "tt" ||
1578 element == "track" ||
1579 element == "u" ||
1580 element == "ul" ||
1581 element == "var" ||
1582 element == "video" ||
1583 element == "wbr" ||
1584 element == "xmp"
1585}