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