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