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, NodeTypeId};
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 pub(crate) fn lookup_a_custom_element_registry(
140 node: &Node,
141 ) -> Option<DomRoot<CustomElementRegistry>> {
142 match node.type_id() {
143 NodeTypeId::Element(_) => node
145 .downcast::<Element>()
146 .expect("Nodes with element type must be an element")
147 .custom_element_registry(),
148 NodeTypeId::Document(_) => Some(
152 node.downcast::<Document>()
153 .expect("Nodes with document type must be a document")
154 .custom_element_registry(),
155 ),
156 _ => None,
158 }
159 }
160
161 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
163 registry.is_some()
167 }
168
169 #[expect(unsafe_code)]
172 fn check_prototype(
173 &self,
174 constructor: HandleObject,
175 mut prototype: MutableHandleValue,
176 ) -> ErrorResult {
177 unsafe {
178 if !JS_GetProperty(
180 *GlobalScope::get_cx(),
181 constructor,
182 c"prototype".as_ptr(),
183 prototype.reborrow(),
184 ) {
185 return Err(Error::JSFailed);
186 }
187
188 if !prototype.is_object() {
190 return Err(Error::Type(
191 "constructor.prototype is not an object".to_owned(),
192 ));
193 }
194 }
195 Ok(())
196 }
197
198 #[expect(unsafe_code)]
202 unsafe fn get_callbacks(&self, prototype: HandleObject) -> Fallible<LifecycleCallbacks> {
203 let cx = GlobalScope::get_cx();
204
205 Ok(LifecycleCallbacks {
207 connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
208 disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
209 adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
210 attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
211
212 form_associated_callback: None,
213 form_disabled_callback: None,
214 form_reset_callback: None,
215 form_state_restore_callback: None,
216 })
217 }
218
219 #[expect(unsafe_code)]
222 unsafe fn add_form_associated_callbacks(
223 &self,
224 prototype: HandleObject,
225 callbacks: &mut LifecycleCallbacks,
226 ) -> ErrorResult {
227 let cx = self.window.get_cx();
228
229 callbacks.form_associated_callback =
230 get_callback(cx, prototype, c"formAssociatedCallback")?;
231 callbacks.form_reset_callback = get_callback(cx, prototype, c"formResetCallback")?;
232 callbacks.form_disabled_callback = get_callback(cx, prototype, c"formDisabledCallback")?;
233 callbacks.form_state_restore_callback =
234 get_callback(cx, prototype, c"formStateRestoreCallback")?;
235
236 Ok(())
237 }
238
239 #[expect(unsafe_code)]
240 fn get_observed_attributes(
241 &self,
242 constructor: HandleObject,
243 can_gc: CanGc,
244 ) -> Fallible<Vec<DOMString>> {
245 let cx = GlobalScope::get_cx();
246 rooted!(in(*cx) let mut observed_attributes = UndefinedValue());
247 if unsafe {
248 !JS_GetProperty(
249 *cx,
250 constructor,
251 c"observedAttributes".as_ptr(),
252 observed_attributes.handle_mut(),
253 )
254 } {
255 return Err(Error::JSFailed);
256 }
257
258 if observed_attributes.is_undefined() {
259 return Ok(Vec::new());
260 }
261
262 let conversion = SafeFromJSValConvertible::safe_from_jsval(
263 cx,
264 observed_attributes.handle(),
265 StringificationBehavior::Default,
266 can_gc,
267 );
268 match conversion {
269 Ok(ConversionResult::Success(attributes)) => Ok(attributes),
270 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
271 _ => Err(Error::JSFailed),
272 }
273 }
274
275 #[expect(unsafe_code)]
278 fn get_form_associated_value(
279 &self,
280 constructor: HandleObject,
281 can_gc: CanGc,
282 ) -> Fallible<bool> {
283 let cx = self.window.get_cx();
284 rooted!(in(*cx) let mut form_associated_value = UndefinedValue());
285 if unsafe {
286 !JS_GetProperty(
287 *cx,
288 constructor,
289 c"formAssociated".as_ptr(),
290 form_associated_value.handle_mut(),
291 )
292 } {
293 return Err(Error::JSFailed);
294 }
295
296 if form_associated_value.is_undefined() {
297 return Ok(false);
298 }
299
300 let conversion = SafeFromJSValConvertible::safe_from_jsval(
301 cx,
302 form_associated_value.handle(),
303 (),
304 can_gc,
305 );
306 match conversion {
307 Ok(ConversionResult::Success(flag)) => Ok(flag),
308 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
309 _ => Err(Error::JSFailed),
310 }
311 }
312
313 #[expect(unsafe_code)]
316 fn get_disabled_features(
317 &self,
318 constructor: HandleObject,
319 can_gc: CanGc,
320 ) -> Fallible<Vec<DOMString>> {
321 let cx = self.window.get_cx();
322 rooted!(in(*cx) let mut disabled_features = UndefinedValue());
323 if unsafe {
324 !JS_GetProperty(
325 *cx,
326 constructor,
327 c"disabledFeatures".as_ptr(),
328 disabled_features.handle_mut(),
329 )
330 } {
331 return Err(Error::JSFailed);
332 }
333
334 if disabled_features.is_undefined() {
335 return Ok(Vec::new());
336 }
337
338 let conversion = SafeFromJSValConvertible::safe_from_jsval(
339 cx,
340 disabled_features.handle(),
341 StringificationBehavior::Default,
342 can_gc,
343 );
344 match conversion {
345 Ok(ConversionResult::Success(attributes)) => Ok(attributes),
346 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
347 _ => Err(Error::JSFailed),
348 }
349 }
350}
351
352#[expect(unsafe_code)]
355fn get_callback(
356 cx: JSContext,
357 prototype: HandleObject,
358 name: &CStr,
359) -> Fallible<Option<Rc<Function>>> {
360 rooted!(in(*cx) let mut callback = UndefinedValue());
361 unsafe {
362 if !JS_GetProperty(
364 *cx,
365 prototype,
366 name.as_ptr() as *const _,
367 callback.handle_mut(),
368 ) {
369 return Err(Error::JSFailed);
370 }
371
372 if !callback.is_undefined() {
374 if !callback.is_object() || !IsCallable(callback.to_object()) {
375 return Err(Error::Type("Lifecycle callback is not callable".to_owned()));
376 }
377 Ok(Some(Function::new(cx, callback.to_object())))
378 } else {
379 Ok(None)
380 }
381 }
382}
383
384impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
385 #[expect(unsafe_code)]
386 fn Define(
388 &self,
389 name: DOMString,
390 constructor_: Rc<CustomElementConstructor>,
391 options: &ElementDefinitionOptions,
392 can_gc: CanGc,
393 ) -> ErrorResult {
394 let cx = GlobalScope::get_cx();
395 rooted!(in(*cx) let constructor = constructor_.callback());
396 let name = LocalName::from(name);
397
398 rooted!(in(*cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
401
402 if unwrapped_constructor.is_null() {
403 return Err(Error::Security(None));
405 }
406
407 if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
408 return Err(Error::Type(
409 "Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
410 ));
411 }
412
413 if !is_valid_custom_element_name(&name) {
415 return Err(Error::Syntax(None));
416 }
417
418 if self.definitions.borrow().contains_key(&name) {
421 return Err(Error::NotSupported(None));
422 }
423
424 if self
427 .definitions
428 .borrow()
429 .iter()
430 .any(|(_, def)| def.constructor == constructor_)
431 {
432 return Err(Error::NotSupported(None));
433 }
434
435 let extends = &options.extends;
437
438 let local_name = if let Some(ref extended_name) = *extends {
440 if is_valid_custom_element_name(&extended_name.str()) {
444 return Err(Error::NotSupported(None));
445 }
446
447 if !is_extendable_element_interface(&extended_name.str()) {
451 return Err(Error::NotSupported(None));
452 }
453
454 LocalName::from(extended_name)
456 } else {
457 name.clone()
459 };
460
461 if self.element_definition_is_running.get() {
463 return Err(Error::NotSupported(None));
464 }
465
466 self.element_definition_is_running.set(true);
468
469 rooted!(in(*cx) let mut prototype = UndefinedValue());
474 {
475 let _ac = JSAutoRealm::new(*cx, constructor.get());
476 if let Err(error) = self.check_prototype(constructor.handle(), prototype.handle_mut()) {
477 self.element_definition_is_running.set(false);
478 return Err(error);
479 }
480 };
481
482 rooted!(in(*cx) let proto_object = prototype.to_object());
488 let mut callbacks = {
489 let _ac = JSAutoRealm::new(*cx, proto_object.get());
490 let callbacks = unsafe { self.get_callbacks(proto_object.handle()) };
491 match callbacks {
492 Ok(callbacks) => callbacks,
493 Err(error) => {
494 self.element_definition_is_running.set(false);
495 return Err(error);
496 },
497 }
498 };
499
500 let observed_attributes = if callbacks.attribute_changed_callback.is_some() {
503 let _ac = JSAutoRealm::new(*cx, constructor.get());
504 match self.get_observed_attributes(constructor.handle(), can_gc) {
505 Ok(attributes) => attributes,
506 Err(error) => {
507 self.element_definition_is_running.set(false);
508 return Err(error);
509 },
510 }
511 } else {
512 Vec::new()
513 };
514
515 let (disable_internals, disable_shadow) = {
517 let _ac = JSAutoRealm::new(*cx, constructor.get());
518 match self.get_disabled_features(constructor.handle(), can_gc) {
519 Ok(sequence) => (
520 sequence.iter().any(|s| *s == "internals"),
521 sequence.iter().any(|s| *s == "shadow"),
522 ),
523 Err(error) => {
524 self.element_definition_is_running.set(false);
525 return Err(error);
526 },
527 }
528 };
529
530 let form_associated = {
532 let _ac = JSAutoRealm::new(*cx, constructor.get());
533 match self.get_form_associated_value(constructor.handle(), can_gc) {
534 Ok(flag) => flag,
535 Err(error) => {
536 self.element_definition_is_running.set(false);
537 return Err(error);
538 },
539 }
540 };
541
542 if form_associated {
544 let _ac = JSAutoRealm::new(*cx, proto_object.get());
545 unsafe {
546 if let Err(error) =
547 self.add_form_associated_callbacks(proto_object.handle(), &mut callbacks)
548 {
549 self.element_definition_is_running.set(false);
550 return Err(error);
551 }
552 }
553 }
554
555 self.element_definition_is_running.set(false);
556
557 let definition = Rc::new(CustomElementDefinition::new(
559 name.clone(),
560 local_name.clone(),
561 constructor_,
562 observed_attributes,
563 callbacks,
564 form_associated,
565 disable_internals,
566 disable_shadow,
567 ));
568
569 self.definitions
571 .borrow_mut()
572 .insert(name.clone(), definition.clone());
573
574 let document = self.window.Document();
577
578 for candidate in document
580 .upcast::<Node>()
581 .traverse_preorder(ShadowIncluding::Yes)
582 .filter_map(DomRoot::downcast::<Element>)
583 {
584 let is = candidate.get_is();
585 if *candidate.local_name() == local_name &&
586 *candidate.namespace() == ns!(html) &&
587 (extends.is_none() || is.as_ref() == Some(&name))
588 {
589 ScriptThread::enqueue_upgrade_reaction(&candidate, definition.clone());
590 }
591 }
592
593 let promise = self.when_defined.borrow_mut().remove(&name);
595 if let Some(promise) = promise {
596 rooted!(in(*cx) let mut constructor = UndefinedValue());
597 definition
598 .constructor
599 .safe_to_jsval(cx, constructor.handle_mut(), can_gc);
600 promise.resolve_native(&constructor.get(), can_gc);
601 }
602 Ok(())
603 }
604
605 fn Get(&self, cx: JSContext, name: DOMString, mut retval: MutableHandleValue) {
607 match self.definitions.borrow().get(&LocalName::from(name)) {
608 Some(definition) => definition
609 .constructor
610 .safe_to_jsval(cx, retval, CanGc::note()),
611 None => retval.set(UndefinedValue()),
612 }
613 }
614
615 fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
617 self.definitions
618 .borrow()
619 .0
620 .values()
621 .find(|definition| definition.constructor == constructor)
622 .map(|definition| DOMString::from(definition.name.to_string()))
623 }
624
625 fn WhenDefined(&self, name: DOMString, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
627 let name = LocalName::from(name);
628
629 if !is_valid_custom_element_name(&name) {
631 let promise = Promise::new_in_current_realm(comp, can_gc);
632 promise.reject_native(
633 &DOMException::new(
634 self.window.as_global_scope(),
635 DOMErrorName::SyntaxError,
636 can_gc,
637 ),
638 can_gc,
639 );
640 return promise;
641 }
642
643 if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
645 let cx = GlobalScope::get_cx();
646 rooted!(in(*cx) let mut constructor = UndefinedValue());
647 definition
648 .constructor
649 .safe_to_jsval(cx, constructor.handle_mut(), can_gc);
650 let promise = Promise::new_in_current_realm(comp, can_gc);
651 promise.resolve_native(&constructor.get(), can_gc);
652 return promise;
653 }
654
655 let existing_promise = self.when_defined.borrow().get(&name).cloned();
657 existing_promise.unwrap_or_else(|| {
658 let promise = Promise::new_in_current_realm(comp, can_gc);
659 self.when_defined.borrow_mut().insert(name, promise.clone());
660 promise
661 })
662 }
663 fn Upgrade(&self, node: &Node) {
665 node.traverse_preorder(ShadowIncluding::Yes).for_each(|n| {
669 if let Some(element) = n.downcast::<Element>() {
670 try_upgrade_element(element);
671 }
672 });
673 }
674}
675
676#[derive(Clone, JSTraceable, MallocSizeOf)]
677pub(crate) struct LifecycleCallbacks {
678 #[conditional_malloc_size_of]
679 connected_callback: Option<Rc<Function>>,
680
681 #[conditional_malloc_size_of]
682 disconnected_callback: Option<Rc<Function>>,
683
684 #[conditional_malloc_size_of]
685 adopted_callback: Option<Rc<Function>>,
686
687 #[conditional_malloc_size_of]
688 attribute_changed_callback: Option<Rc<Function>>,
689
690 #[conditional_malloc_size_of]
691 form_associated_callback: Option<Rc<Function>>,
692
693 #[conditional_malloc_size_of]
694 form_reset_callback: Option<Rc<Function>>,
695
696 #[conditional_malloc_size_of]
697 form_disabled_callback: Option<Rc<Function>>,
698
699 #[conditional_malloc_size_of]
700 form_state_restore_callback: Option<Rc<Function>>,
701}
702
703#[derive(Clone, JSTraceable, MallocSizeOf)]
704pub(crate) enum ConstructionStackEntry {
705 Element(DomRoot<Element>),
706 AlreadyConstructedMarker,
707}
708
709#[derive(Clone, JSTraceable, MallocSizeOf)]
711pub(crate) struct CustomElementDefinition {
712 #[no_trace]
714 pub(crate) name: LocalName,
715
716 #[no_trace]
718 pub(crate) local_name: LocalName,
719
720 #[conditional_malloc_size_of]
722 pub(crate) constructor: Rc<CustomElementConstructor>,
723
724 pub(crate) observed_attributes: Vec<DOMString>,
726
727 pub(crate) callbacks: LifecycleCallbacks,
729
730 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
732
733 pub(crate) form_associated: bool,
735
736 pub(crate) disable_internals: bool,
738
739 pub(crate) disable_shadow: bool,
741}
742
743impl CustomElementDefinition {
744 #[expect(clippy::too_many_arguments)]
745 fn new(
746 name: LocalName,
747 local_name: LocalName,
748 constructor: Rc<CustomElementConstructor>,
749 observed_attributes: Vec<DOMString>,
750 callbacks: LifecycleCallbacks,
751 form_associated: bool,
752 disable_internals: bool,
753 disable_shadow: bool,
754 ) -> CustomElementDefinition {
755 CustomElementDefinition {
756 name,
757 local_name,
758 constructor,
759 observed_attributes,
760 callbacks,
761 construction_stack: Default::default(),
762 form_associated,
763 disable_internals,
764 disable_shadow,
765 }
766 }
767
768 pub(crate) fn is_autonomous(&self) -> bool {
770 self.name == self.local_name
771 }
772
773 #[expect(unsafe_code)]
775 pub(crate) fn create_element(
776 &self,
777 document: &Document,
778 prefix: Option<Prefix>,
779 registry: Option<DomRoot<CustomElementRegistry>>,
780 can_gc: CanGc,
782 ) -> Fallible<DomRoot<Element>> {
783 let window = document.window();
784 let cx = GlobalScope::get_cx();
785 rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback()));
787 rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>());
788 {
789 let _ac = JSAutoRealm::new(*cx, self.constructor.callback());
791 let _script_guard = AutoEntryScript::new(window.upcast());
794 let _callback_guard = AutoIncumbentScript::new(window.upcast());
795 let args = HandleValueArray::empty();
796 if unsafe { !Construct1(*cx, constructor.handle(), &args, element.handle_mut()) } {
797 return Err(Error::JSFailed);
798 }
799 }
800
801 rooted!(in(*cx) let element_val = ObjectValue(element.get()));
802 let element: DomRoot<Element> =
803 match SafeFromJSValConvertible::safe_from_jsval(cx, element_val.handle(), (), can_gc) {
804 Ok(ConversionResult::Success(element)) => element,
805 Ok(ConversionResult::Failure(..)) => {
806 return Err(Error::Type(
807 "Constructor did not return a DOM node".to_owned(),
808 ));
809 },
810 _ => return Err(Error::JSFailed),
811 };
812
813 assert!(element.is::<HTMLElement>());
819
820 if element.HasAttributes() ||
826 element.upcast::<Node>().children_count() > 0 ||
827 element.upcast::<Node>().has_parent() ||
828 &*element.upcast::<Node>().owner_doc() != document ||
829 *element.namespace() != ns!(html) ||
830 *element.local_name() != self.local_name
831 {
832 return Err(Error::NotSupported(None));
833 }
834
835 element.set_prefix(prefix);
837
838 element.set_custom_element_registry(registry);
843
844 Ok(element)
845 }
846}
847
848pub(crate) fn upgrade_element(
850 definition: Rc<CustomElementDefinition>,
851 element: &Element,
852 can_gc: CanGc,
853) {
854 let state = element.get_custom_element_state();
856 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
857 return;
858 }
859
860 element.set_custom_element_definition(Rc::clone(&definition));
862
863 element.set_custom_element_state(CustomElementState::Failed);
865
866 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
870 for attr in element.attrs().iter() {
871 let local_name = attr.local_name().clone();
872 let value = DOMString::from(&**attr.value());
873 let namespace = attr.namespace().clone();
874 custom_element_reaction_stack.enqueue_callback_reaction(
875 element,
876 CallbackReaction::AttributeChanged(local_name, None, Some(value), namespace),
877 Some(definition.clone()),
878 );
879 }
880
881 if element.is_connected() {
884 ScriptThread::enqueue_callback_reaction(
885 element,
886 CallbackReaction::Connected,
887 Some(definition.clone()),
888 );
889 }
890
891 definition
893 .construction_stack
894 .borrow_mut()
895 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
896
897 let result = run_upgrade_constructor(&definition, element, can_gc);
899
900 definition.construction_stack.borrow_mut().pop();
902
903 if let Err(error) = result {
905 element.clear_custom_element_definition();
907
908 element.clear_reaction_queue();
910
911 let global = GlobalScope::current().expect("No current global");
913 let cx = GlobalScope::get_cx();
914 let ar = enter_realm(&*global);
915 throw_dom_exception(cx, &global, error, can_gc);
916 report_pending_exception(cx, true, InRealm::Entered(&ar), can_gc);
917
918 return;
919 }
920
921 if let Some(html_element) = element.downcast::<HTMLElement>() {
923 if html_element.is_form_associated_custom_element() {
924 html_element.reset_form_owner(can_gc);
928 if let Some(form) = html_element.form_owner() {
929 form.upcast::<Node>().rev_version();
932 }
936
937 element.check_disabled_attribute();
944 element.check_ancestors_disabled_state_for_form_control();
945 element.update_read_write_state_from_readonly_attribute();
946
947 if element.disabled_state() {
950 ScriptThread::enqueue_callback_reaction(
951 element,
952 CallbackReaction::FormDisabled(true),
953 Some(definition.clone()),
954 )
955 }
956 }
957 }
958
959 element.set_custom_element_state(CustomElementState::Custom);
961}
962
963#[expect(unsafe_code)]
966fn run_upgrade_constructor(
967 definition: &CustomElementDefinition,
968 element: &Element,
969 can_gc: CanGc,
971) -> ErrorResult {
972 let constructor = &definition.constructor;
973 let window = element.owner_window();
974 let cx = GlobalScope::get_cx();
975 rooted!(in(*cx) let constructor_val = ObjectValue(constructor.callback()));
976 rooted!(in(*cx) let mut element_val = UndefinedValue());
977 element.safe_to_jsval(cx, element_val.handle_mut(), can_gc);
978 rooted!(in(*cx) let mut construct_result = ptr::null_mut::<JSObject>());
979 {
980 if definition.disable_shadow && element.is_shadow_host() {
983 return Err(Error::NotSupported(None));
984 }
985
986 let _ac = JSAutoRealm::new(*cx, constructor.callback());
988 let args = HandleValueArray::empty();
989 element.set_custom_element_state(CustomElementState::Precustomized);
991
992 {
995 let _script_guard = AutoEntryScript::new(window.upcast());
996 let _callback_guard = AutoIncumbentScript::new(window.upcast());
997 if unsafe {
998 !Construct1(
999 *cx,
1000 constructor_val.handle(),
1001 &args,
1002 construct_result.handle_mut(),
1003 )
1004 } {
1005 return Err(Error::JSFailed);
1006 }
1007 }
1008
1009 let mut same = false;
1010 rooted!(in(*cx) let construct_result_val = ObjectValue(construct_result.get()));
1011
1012 if unsafe {
1014 !SameValue(
1015 *cx,
1016 construct_result_val.handle(),
1017 element_val.handle(),
1018 &mut same,
1019 )
1020 } {
1021 return Err(Error::JSFailed);
1022 }
1023 if !same {
1024 return Err(Error::Type(
1025 "Returned element is not SameValue as the upgraded element".to_string(),
1026 ));
1027 }
1028 }
1029 Ok(())
1030}
1031
1032pub(crate) fn try_upgrade_element(element: &Element) {
1034 let document = element.owner_document();
1037 let namespace = element.namespace();
1038 let local_name = element.local_name();
1039 let is = element.get_is();
1040 if let Some(definition) =
1041 document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
1042 {
1043 ScriptThread::enqueue_upgrade_reaction(element, definition);
1046 }
1047}
1048
1049#[derive(JSTraceable, MallocSizeOf)]
1050#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1051pub(crate) enum CustomElementReaction {
1052 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1053 Callback(
1054 #[conditional_malloc_size_of] Rc<Function>,
1055 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1056 ),
1057}
1058
1059impl CustomElementReaction {
1060 pub(crate) fn invoke(&self, element: &Element, can_gc: CanGc) {
1062 match *self {
1064 CustomElementReaction::Upgrade(ref definition) => {
1065 upgrade_element(definition.clone(), element, can_gc)
1066 },
1067 CustomElementReaction::Callback(ref callback, ref arguments) => {
1068 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1070 rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
1071 let _ = callback.Call_(
1072 element,
1073 arguments,
1074 value.handle_mut(),
1075 ExceptionHandling::Report,
1076 can_gc,
1077 );
1078 },
1079 }
1080 }
1081}
1082
1083pub(crate) enum CallbackReaction {
1084 Connected,
1085 Disconnected,
1086 Adopted(DomRoot<Document>, DomRoot<Document>),
1087 AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace),
1088 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1089 FormDisabled(bool),
1090 FormReset,
1091}
1092
1093#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1095enum BackupElementQueueFlag {
1096 Processing,
1097 NotProcessing,
1098}
1099
1100#[derive(JSTraceable, MallocSizeOf)]
1105#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1106#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1107pub(crate) struct CustomElementReactionStack {
1108 stack: DomRefCell<Vec<ElementQueue>>,
1109 backup_queue: ElementQueue,
1110 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1111}
1112
1113impl CustomElementReactionStack {
1114 pub(crate) fn new() -> CustomElementReactionStack {
1115 CustomElementReactionStack {
1116 stack: DomRefCell::new(Vec::new()),
1117 backup_queue: ElementQueue::new(),
1118 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1119 }
1120 }
1121
1122 pub(crate) fn push_new_element_queue(&self) {
1123 self.stack.borrow_mut().push(ElementQueue::new());
1124 }
1125
1126 pub(crate) fn pop_current_element_queue(&self, can_gc: CanGc) {
1127 rooted_vec!(let mut stack);
1128 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1129
1130 if let Some(current_queue) = stack.last() {
1131 current_queue.invoke_reactions(can_gc);
1132 }
1133 stack.pop();
1134
1135 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1136 self.stack.borrow_mut().append(&mut *stack);
1137 }
1138
1139 pub(crate) fn invoke_backup_element_queue(&self, can_gc: CanGc) {
1142 self.backup_queue.invoke_reactions(can_gc);
1144
1145 self.processing_backup_element_queue
1147 .set(BackupElementQueueFlag::NotProcessing);
1148 }
1149
1150 pub(crate) fn enqueue_element(&self, element: &Element) {
1152 if let Some(current_queue) = self.stack.borrow().last() {
1153 current_queue.append_element(element);
1155 } else {
1156 self.backup_queue.append_element(element);
1158
1159 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1161 return;
1162 }
1163
1164 self.processing_backup_element_queue
1166 .set(BackupElementQueueFlag::Processing);
1167
1168 ScriptThread::enqueue_microtask(Microtask::CustomElementReaction);
1170 }
1171 }
1172
1173 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1175 pub(crate) fn enqueue_callback_reaction(
1176 &self,
1177 element: &Element,
1178 reaction: CallbackReaction,
1179 definition: Option<Rc<CustomElementDefinition>>,
1180 ) {
1181 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1183 Some(definition) => definition,
1184 None => return,
1185 };
1186
1187 let (callback, args) = match reaction {
1189 CallbackReaction::Connected => {
1190 (definition.callbacks.connected_callback.clone(), Vec::new())
1191 },
1192 CallbackReaction::Disconnected => (
1193 definition.callbacks.disconnected_callback.clone(),
1194 Vec::new(),
1195 ),
1196 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1197 let args = vec![Heap::default(), Heap::default()];
1198 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1199 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1200 (definition.callbacks.adopted_callback.clone(), args)
1201 },
1202 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1203 if !definition
1205 .observed_attributes
1206 .iter()
1207 .any(|attr| *attr == *local_name)
1208 {
1209 return;
1210 }
1211
1212 let cx = GlobalScope::get_cx();
1213 let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get());
1217
1218 let local_name = DOMString::from(&*local_name);
1219 rooted!(in(*cx) let mut name_value = UndefinedValue());
1220 local_name.safe_to_jsval(cx, name_value.handle_mut(), CanGc::note());
1221
1222 rooted!(in(*cx) let mut old_value = NullValue());
1223 if let Some(old_val) = old_val {
1224 old_val.safe_to_jsval(cx, old_value.handle_mut(), CanGc::note());
1225 }
1226
1227 rooted!(in(*cx) let mut value = NullValue());
1228 if let Some(val) = val {
1229 val.safe_to_jsval(cx, value.handle_mut(), CanGc::note());
1230 }
1231
1232 rooted!(in(*cx) let mut namespace_value = NullValue());
1233 if namespace != ns!() {
1234 let namespace = DOMString::from(&*namespace);
1235 namespace.safe_to_jsval(cx, namespace_value.handle_mut(), CanGc::note());
1236 }
1237
1238 let args = vec![
1239 Heap::default(),
1240 Heap::default(),
1241 Heap::default(),
1242 Heap::default(),
1243 ];
1244 args[0].set(name_value.get());
1245 args[1].set(old_value.get());
1246 args[2].set(value.get());
1247 args[3].set(namespace_value.get());
1248
1249 (
1250 definition.callbacks.attribute_changed_callback.clone(),
1251 args,
1252 )
1253 },
1254 CallbackReaction::FormAssociated(form) => {
1255 let args = vec![Heap::default()];
1256 if let Some(form) = form {
1257 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1258 } else {
1259 args[0].set(NullValue());
1260 }
1261 (definition.callbacks.form_associated_callback.clone(), args)
1262 },
1263 CallbackReaction::FormDisabled(disabled) => {
1264 let cx = GlobalScope::get_cx();
1265 rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
1266 let args = vec![Heap::default()];
1267 args[0].set(disabled_value.get());
1268 (definition.callbacks.form_disabled_callback.clone(), args)
1269 },
1270 CallbackReaction::FormReset => {
1271 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1272 },
1273 };
1274
1275 let callback = match callback {
1277 Some(callback) => callback,
1278 None => return,
1279 };
1280
1281 element.push_callback_reaction(callback, args.into_boxed_slice());
1283
1284 self.enqueue_element(element);
1286 }
1287
1288 pub(crate) fn enqueue_upgrade_reaction(
1290 &self,
1291 element: &Element,
1292 definition: Rc<CustomElementDefinition>,
1293 ) {
1294 element.push_upgrade_reaction(definition);
1297
1298 self.enqueue_element(element);
1300 }
1301}
1302
1303#[derive(JSTraceable, MallocSizeOf)]
1305#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1306struct ElementQueue {
1307 queue: DomRefCell<VecDeque<Dom<Element>>>,
1308}
1309
1310impl ElementQueue {
1311 fn new() -> ElementQueue {
1312 ElementQueue {
1313 queue: Default::default(),
1314 }
1315 }
1316
1317 fn invoke_reactions(&self, can_gc: CanGc) {
1319 while let Some(element) = self.next_element() {
1321 element.invoke_reactions(can_gc)
1322 }
1323 self.queue.borrow_mut().clear();
1324 }
1325
1326 fn next_element(&self) -> Option<DomRoot<Element>> {
1327 self.queue
1328 .borrow_mut()
1329 .pop_front()
1330 .as_deref()
1331 .map(DomRoot::from_ref)
1332 }
1333
1334 fn append_element(&self, element: &Element) {
1335 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1336 }
1337}
1338
1339pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1341 let mut chars = name.chars();
1344 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1345 return false;
1346 }
1347
1348 let mut has_dash = false;
1349
1350 for c in chars {
1351 if c == '-' {
1352 has_dash = true;
1353 continue;
1354 }
1355
1356 if !is_potential_custom_element_char(c) {
1357 return false;
1358 }
1359 }
1360
1361 if !has_dash {
1362 return false;
1363 }
1364
1365 if name == "annotation-xml" ||
1366 name == "color-profile" ||
1367 name == "font-face" ||
1368 name == "font-face-src" ||
1369 name == "font-face-uri" ||
1370 name == "font-face-format" ||
1371 name == "font-face-name" ||
1372 name == "missing-glyph"
1373 {
1374 return false;
1375 }
1376
1377 true
1378}
1379
1380fn is_potential_custom_element_char(c: char) -> bool {
1383 c == '-' ||
1384 c == '.' ||
1385 c == '_' ||
1386 c == '\u{B7}' ||
1387 c.is_ascii_digit() ||
1388 c.is_ascii_lowercase() ||
1389 ('\u{C0}'..='\u{D6}').contains(&c) ||
1390 ('\u{D8}'..='\u{F6}').contains(&c) ||
1391 ('\u{F8}'..='\u{37D}').contains(&c) ||
1392 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1393 ('\u{200C}'..='\u{200D}').contains(&c) ||
1394 ('\u{203F}'..='\u{2040}').contains(&c) ||
1395 ('\u{2070}'..='\u{218F}').contains(&c) ||
1396 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1397 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1398 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1399 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1400 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1401}
1402
1403fn is_extendable_element_interface(element: &str) -> bool {
1404 element == "a" ||
1405 element == "abbr" ||
1406 element == "acronym" ||
1407 element == "address" ||
1408 element == "area" ||
1409 element == "article" ||
1410 element == "aside" ||
1411 element == "audio" ||
1412 element == "b" ||
1413 element == "base" ||
1414 element == "bdi" ||
1415 element == "bdo" ||
1416 element == "big" ||
1417 element == "blockquote" ||
1418 element == "body" ||
1419 element == "br" ||
1420 element == "button" ||
1421 element == "canvas" ||
1422 element == "caption" ||
1423 element == "center" ||
1424 element == "cite" ||
1425 element == "code" ||
1426 element == "col" ||
1427 element == "colgroup" ||
1428 element == "data" ||
1429 element == "datalist" ||
1430 element == "dd" ||
1431 element == "del" ||
1432 element == "details" ||
1433 element == "dfn" ||
1434 element == "dialog" ||
1435 element == "dir" ||
1436 element == "div" ||
1437 element == "dl" ||
1438 element == "dt" ||
1439 element == "em" ||
1440 element == "embed" ||
1441 element == "fieldset" ||
1442 element == "figcaption" ||
1443 element == "figure" ||
1444 element == "font" ||
1445 element == "footer" ||
1446 element == "form" ||
1447 element == "frame" ||
1448 element == "frameset" ||
1449 element == "h1" ||
1450 element == "h2" ||
1451 element == "h3" ||
1452 element == "h4" ||
1453 element == "h5" ||
1454 element == "h6" ||
1455 element == "head" ||
1456 element == "header" ||
1457 element == "hgroup" ||
1458 element == "hr" ||
1459 element == "html" ||
1460 element == "i" ||
1461 element == "iframe" ||
1462 element == "img" ||
1463 element == "input" ||
1464 element == "ins" ||
1465 element == "kbd" ||
1466 element == "label" ||
1467 element == "legend" ||
1468 element == "li" ||
1469 element == "link" ||
1470 element == "listing" ||
1471 element == "main" ||
1472 element == "map" ||
1473 element == "mark" ||
1474 element == "marquee" ||
1475 element == "menu" ||
1476 element == "meta" ||
1477 element == "meter" ||
1478 element == "nav" ||
1479 element == "nobr" ||
1480 element == "noframes" ||
1481 element == "noscript" ||
1482 element == "object" ||
1483 element == "ol" ||
1484 element == "optgroup" ||
1485 element == "option" ||
1486 element == "output" ||
1487 element == "p" ||
1488 element == "param" ||
1489 element == "picture" ||
1490 element == "plaintext" ||
1491 element == "pre" ||
1492 element == "progress" ||
1493 element == "q" ||
1494 element == "rp" ||
1495 element == "rt" ||
1496 element == "ruby" ||
1497 element == "s" ||
1498 element == "samp" ||
1499 element == "script" ||
1500 element == "section" ||
1501 element == "select" ||
1502 element == "slot" ||
1503 element == "small" ||
1504 element == "source" ||
1505 element == "span" ||
1506 element == "strike" ||
1507 element == "strong" ||
1508 element == "style" ||
1509 element == "sub" ||
1510 element == "summary" ||
1511 element == "sup" ||
1512 element == "table" ||
1513 element == "tbody" ||
1514 element == "td" ||
1515 element == "template" ||
1516 element == "textarea" ||
1517 element == "tfoot" ||
1518 element == "th" ||
1519 element == "thead" ||
1520 element == "time" ||
1521 element == "title" ||
1522 element == "tr" ||
1523 element == "tt" ||
1524 element == "track" ||
1525 element == "u" ||
1526 element == "ul" ||
1527 element == "var" ||
1528 element == "video" ||
1529 element == "wbr" ||
1530 element == "xmp"
1531}