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::context::JSContext;
14use js::glue::UnwrapObjectStatic;
15use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSAutoRealm, JSObject};
16use js::jsval::{BooleanValue, JSVal, NullValue, ObjectValue, UndefinedValue};
17use js::realm::{AutoRealm, CurrentRealm};
18use js::rust::wrappers::Construct1;
19use js::rust::wrappers2::{JS_GetProperty, SameValue};
20use js::rust::{HandleObject, MutableHandleValue};
21use rustc_hash::FxBuildHasher;
22use script_bindings::cell::DomRefCell;
23use script_bindings::conversions::{SafeFromJSValConvertible, SafeToJSValConvertible};
24use script_bindings::reflector::{DomObject, Reflector, reflect_dom_object};
25use script_bindings::settings_stack::{run_a_callback, run_a_script};
26use style::attr::AttrValue;
27
28use super::bindings::trace::HashMapTracedValues;
29use crate::DomTypeHolder;
30use crate::dom::bindings::callback::{CallbackContainer, ExceptionHandling};
31use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::{
32 CustomElementConstructor, CustomElementRegistryMethods, ElementDefinitionOptions,
33};
34use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
35use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
36use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
37use crate::dom::bindings::conversions::{ConversionResult, StringificationBehavior, get_property};
38use crate::dom::bindings::error::{
39 Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception,
40};
41use crate::dom::bindings::inheritance::{Castable, NodeTypeId};
42use crate::dom::bindings::reflector::DomGlobal;
43use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot};
44use crate::dom::bindings::str::DOMString;
45use crate::dom::document::Document;
46use crate::dom::domexception::{DOMErrorName, DOMException};
47use crate::dom::element::Element;
48use crate::dom::globalscope::GlobalScope;
49use crate::dom::html::htmlelement::HTMLElement;
50use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
51use crate::dom::iterators::ShadowIncluding;
52use crate::dom::node::{Node, NodeTraits};
53use crate::dom::promise::Promise;
54use crate::dom::window::Window;
55use crate::microtask::Microtask;
56use crate::realms::enter_auto_realm;
57use crate::script_runtime::CanGc;
58use crate::script_thread::ScriptThread;
59
60#[derive(Clone, Copy, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
62pub(crate) enum CustomElementState {
63 Undefined,
64 Failed,
65 #[default]
66 Uncustomized,
67 Precustomized,
68 Custom,
69}
70
71#[dom_struct]
73pub(crate) struct CustomElementRegistry {
74 reflector_: Reflector,
75
76 window: Dom<Window>,
77
78 #[conditional_malloc_size_of]
79 when_defined: DomRefCell<HashMapTracedValues<LocalName, Rc<Promise>, FxBuildHasher>>,
82
83 element_definition_is_running: Cell<bool>,
84
85 #[conditional_malloc_size_of]
86 definitions:
87 DomRefCell<HashMapTracedValues<LocalName, Rc<CustomElementDefinition>, FxBuildHasher>>,
88}
89
90impl CustomElementRegistry {
91 fn new_inherited(window: &Window) -> CustomElementRegistry {
92 CustomElementRegistry {
93 reflector_: Reflector::new(),
94 window: Dom::from_ref(window),
95 when_defined: DomRefCell::new(HashMapTracedValues::new_fx()),
96 element_definition_is_running: Cell::new(false),
97 definitions: DomRefCell::new(HashMapTracedValues::new_fx()),
98 }
99 }
100
101 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<CustomElementRegistry> {
102 reflect_dom_object(
103 Box::new(CustomElementRegistry::new_inherited(window)),
104 window,
105 can_gc,
106 )
107 }
108
109 pub(crate) fn teardown(&self) {
112 self.when_defined.borrow_mut().0.clear()
113 }
114
115 pub(crate) fn lookup_definition(
117 &self,
118 local_name: &LocalName,
119 is: Option<&LocalName>,
120 ) -> Option<Rc<CustomElementDefinition>> {
121 self.definitions
122 .borrow()
123 .0
124 .values()
125 .find(|definition| {
126 definition.local_name == *local_name &&
128 (definition.name == *local_name || Some(&definition.name) == is)
129 })
130 .cloned()
131 }
132
133 pub(crate) fn lookup_definition_by_constructor(
134 &self,
135 constructor: HandleObject,
136 ) -> Option<Rc<CustomElementDefinition>> {
137 self.definitions
138 .borrow()
139 .0
140 .values()
141 .find(|definition| definition.constructor.callback() == constructor.get())
142 .cloned()
143 }
144
145 pub(crate) fn lookup_a_custom_element_registry(
147 node: &Node,
148 ) -> Option<DomRoot<CustomElementRegistry>> {
149 match node.type_id() {
150 NodeTypeId::Element(_) => node
152 .downcast::<Element>()
153 .expect("Nodes with element type must be an element")
154 .custom_element_registry(),
155 NodeTypeId::Document(_) => Some(
159 node.downcast::<Document>()
160 .expect("Nodes with document type must be a document")
161 .custom_element_registry(),
162 ),
163 _ => None,
165 }
166 }
167
168 pub(crate) fn is_a_global_element_registry(registry: Option<&CustomElementRegistry>) -> bool {
170 registry.is_some()
174 }
175
176 #[expect(unsafe_code)]
179 fn check_prototype(
180 &self,
181 cx: &mut JSContext,
182 constructor: HandleObject,
183 mut prototype: MutableHandleValue,
184 ) -> ErrorResult {
185 unsafe {
186 if !JS_GetProperty(cx, constructor, c"prototype".as_ptr(), prototype.reborrow()) {
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 fn get_callbacks(
205 &self,
206 cx: &mut JSContext,
207 prototype: HandleObject,
208 ) -> Fallible<LifecycleCallbacks> {
209 Ok(LifecycleCallbacks {
211 connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
212 disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
213 connected_move_callback: get_callback(cx, prototype, c"connectedMoveCallback")?,
214 adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
215 attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
216
217 form_associated_callback: None,
218 form_disabled_callback: None,
219 form_reset_callback: None,
220 form_state_restore_callback: None,
221 })
222 }
223
224 #[expect(unsafe_code)]
227 unsafe fn add_form_associated_callbacks(
228 &self,
229 cx: &mut JSContext,
230 prototype: HandleObject,
231 callbacks: &mut LifecycleCallbacks,
232 ) -> ErrorResult {
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
244#[expect(unsafe_code)]
247fn get_callback(
248 cx: &mut JSContext,
249 prototype: HandleObject,
250 name: &CStr,
251) -> Fallible<Option<Rc<Function>>> {
252 rooted!(&in(cx) let mut callback = UndefinedValue());
253 unsafe {
254 if !JS_GetProperty(cx, prototype, name.as_ptr(), callback.handle_mut()) {
256 return Err(Error::JSFailed);
257 }
258
259 if !callback.is_undefined() {
261 if !callback.is_object() || !IsCallable(callback.to_object()) {
262 return Err(Error::Type(
263 c"Lifecycle callback is not callable".to_owned(),
264 ));
265 }
266 Ok(Some(Function::new(cx.into(), callback.to_object())))
267 } else {
268 Ok(None)
269 }
270 }
271}
272
273impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
274 #[expect(unsafe_code)]
275 fn Define(
277 &self,
278 cx: &mut JSContext,
279 name: DOMString,
280 constructor_: Rc<CustomElementConstructor>,
281 options: &ElementDefinitionOptions,
282 ) -> ErrorResult {
283 rooted!(&in(cx) let constructor = constructor_.callback());
284 let name = LocalName::from(name);
285
286 rooted!(&in(cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
289
290 if unwrapped_constructor.is_null() {
291 return Err(Error::Security(None));
293 }
294
295 if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
296 return Err(Error::Type(
297 c"Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
298 ));
299 }
300
301 if !is_valid_custom_element_name(&name) {
303 return Err(Error::Syntax(Some(format!(
304 "{} name is not a valid custom element name",
305 name
306 ))));
307 }
308
309 if self.definitions.borrow().contains_key(&name) {
312 return Err(Error::NotSupported(Some(format!(
313 "{} has already been defined as a custom element",
314 name
315 ))));
316 }
317
318 if self
321 .definitions
322 .borrow()
323 .iter()
324 .any(|(_, def)| def.constructor == constructor_)
325 {
326 return Err(Error::NotSupported(None));
327 }
328
329 let extends = &options.extends;
331
332 let local_name = if let Some(ref extended_name) = *extends {
334 if is_valid_custom_element_name(&extended_name.str()) {
338 return Err(Error::NotSupported(None));
339 }
340
341 if !is_extendable_element_interface(&extended_name.str()) {
345 return Err(Error::NotSupported(None));
346 }
347
348 LocalName::from(extended_name)
350 } else {
351 name.clone()
353 };
354
355 if self.element_definition_is_running.get() {
357 return Err(Error::NotSupported(None));
358 }
359
360 self.element_definition_is_running.set(true);
362
363 rooted!(&in(cx) let mut prototype = UndefinedValue());
368 {
369 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
370 if let Err(error) =
371 self.check_prototype(&mut realm, constructor.handle(), prototype.handle_mut())
372 {
373 self.element_definition_is_running.set(false);
374 return Err(error);
375 }
376 };
377
378 rooted!(&in(cx) let proto_object = prototype.to_object());
384 let mut callbacks = {
385 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
386 match self.get_callbacks(&mut realm, proto_object.handle()) {
387 Ok(callbacks) => callbacks,
388 Err(error) => {
389 self.element_definition_is_running.set(false);
390 return Err(error);
391 },
392 }
393 };
394
395 let observed_attributes: Vec<DOMString> = if callbacks.attribute_changed_callback.is_some()
398 {
399 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
400 match get_property(
401 &mut realm,
402 constructor.handle(),
403 c"observedAttributes",
404 StringificationBehavior::Default,
405 ) {
406 Ok(attributes) => attributes.unwrap_or_default(),
407 Err(error) => {
408 self.element_definition_is_running.set(false);
409 return Err(error);
410 },
411 }
412 } else {
413 Vec::new()
414 };
415
416 let (disable_internals, disable_shadow) = {
418 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
419 match get_property::<Vec<DOMString>>(
420 &mut realm,
421 constructor.handle(),
422 c"disabledFeatures",
423 StringificationBehavior::Default,
424 ) {
425 Ok(sequence) => {
426 let sequence = sequence.unwrap_or_default();
427 (
428 sequence.iter().any(|s| *s == "internals"),
429 sequence.iter().any(|s| *s == "shadow"),
430 )
431 },
432 Err(error) => {
433 self.element_definition_is_running.set(false);
434 return Err(error);
435 },
436 }
437 };
438
439 let form_associated: bool = {
441 let mut realm = AutoRealm::new_from_handle(cx, constructor.handle());
442 match get_property(&mut realm, constructor.handle(), c"formAssociated", ()) {
443 Ok(flag) => flag.unwrap_or_default(),
444 Err(error) => {
445 self.element_definition_is_running.set(false);
446 return Err(error);
447 },
448 }
449 };
450
451 if form_associated {
453 let mut realm = AutoRealm::new_from_handle(cx, proto_object.handle());
454 unsafe {
455 if let Err(error) = self.add_form_associated_callbacks(
456 &mut realm,
457 proto_object.handle(),
458 &mut callbacks,
459 ) {
460 self.element_definition_is_running.set(false);
461 return Err(error);
462 }
463 }
464 }
465
466 self.element_definition_is_running.set(false);
467
468 let definition = Rc::new(CustomElementDefinition::new(
470 name.clone(),
471 local_name.clone(),
472 constructor_,
473 observed_attributes,
474 callbacks,
475 form_associated,
476 disable_internals,
477 disable_shadow,
478 ));
479
480 self.definitions
482 .borrow_mut()
483 .insert(name.clone(), definition.clone());
484
485 let document = self.window.Document();
488
489 for candidate in document
491 .upcast::<Node>()
492 .traverse_preorder(ShadowIncluding::Yes)
493 .filter_map(DomRoot::downcast::<Element>)
494 {
495 let is = candidate.get_is();
496 if *candidate.local_name() == local_name &&
497 *candidate.namespace() == ns!(html) &&
498 (extends.is_none() || is.as_ref() == Some(&name))
499 {
500 ScriptThread::enqueue_upgrade_reaction(&candidate, definition.clone());
501 }
502 }
503
504 let promise = self.when_defined.borrow_mut().remove(&name);
506 if let Some(promise) = promise {
507 rooted!(&in(cx) let mut constructor = UndefinedValue());
508 definition.constructor.safe_to_jsval(
509 cx.into(),
510 constructor.handle_mut(),
511 CanGc::from_cx(cx),
512 );
513 promise.resolve_native_with_cx(cx, &constructor.get());
514 }
515 Ok(())
516 }
517
518 fn Get(&self, cx: &mut JSContext, name: DOMString, mut retval: MutableHandleValue) {
520 match self.definitions.borrow().get(&LocalName::from(name)) {
521 Some(definition) => {
522 definition
523 .constructor
524 .safe_to_jsval(cx.into(), retval, CanGc::from_cx(cx))
525 },
526 None => retval.set(UndefinedValue()),
527 }
528 }
529
530 fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
532 self.definitions
533 .borrow()
534 .0
535 .values()
536 .find(|definition| definition.constructor == constructor)
537 .map(|definition| DOMString::from(definition.name.to_string()))
538 }
539
540 fn WhenDefined(&self, realm: &mut CurrentRealm, name: DOMString) -> Rc<Promise> {
542 let name = LocalName::from(name);
543
544 if !is_valid_custom_element_name(&name) {
546 let promise = Promise::new_in_realm(realm);
547 let error = DOMException::new(
548 self.window.as_global_scope(),
549 DOMErrorName::SyntaxError,
550 CanGc::from_cx(realm),
551 );
552 promise.reject_native_with_cx(realm, &error);
553 return promise;
554 }
555
556 if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
558 rooted!(&in(*realm) let mut constructor = UndefinedValue());
559 definition.constructor.safe_to_jsval(
560 realm.into(),
561 constructor.handle_mut(),
562 CanGc::from_cx(realm),
563 );
564 let promise = Promise::new_in_realm(realm);
565 promise.resolve_native_with_cx(realm, &constructor.get());
566 return promise;
567 }
568
569 let existing_promise = self.when_defined.borrow().get(&name).cloned();
571 existing_promise.unwrap_or_else(|| {
572 let promise = Promise::new_in_realm(realm);
573 self.when_defined.borrow_mut().insert(name, promise.clone());
574 promise
575 })
576 }
577
578 fn Upgrade(&self, node: &Node) {
580 node.traverse_preorder(ShadowIncluding::Yes).for_each(|n| {
584 if let Some(element) = n.downcast::<Element>() {
585 try_upgrade_element(element);
586 }
587 });
588 }
589}
590
591#[derive(Clone, JSTraceable, MallocSizeOf)]
592pub(crate) struct LifecycleCallbacks {
593 #[conditional_malloc_size_of]
594 connected_callback: Option<Rc<Function>>,
595
596 #[conditional_malloc_size_of]
597 connected_move_callback: Option<Rc<Function>>,
598
599 #[conditional_malloc_size_of]
600 disconnected_callback: Option<Rc<Function>>,
601
602 #[conditional_malloc_size_of]
603 adopted_callback: Option<Rc<Function>>,
604
605 #[conditional_malloc_size_of]
606 attribute_changed_callback: Option<Rc<Function>>,
607
608 #[conditional_malloc_size_of]
609 form_associated_callback: Option<Rc<Function>>,
610
611 #[conditional_malloc_size_of]
612 form_reset_callback: Option<Rc<Function>>,
613
614 #[conditional_malloc_size_of]
615 form_disabled_callback: Option<Rc<Function>>,
616
617 #[conditional_malloc_size_of]
618 form_state_restore_callback: Option<Rc<Function>>,
619}
620
621#[derive(Clone, JSTraceable, MallocSizeOf)]
622pub(crate) enum ConstructionStackEntry {
623 Element(DomRoot<Element>),
624 AlreadyConstructedMarker,
625}
626
627#[derive(Clone, JSTraceable, MallocSizeOf)]
629pub(crate) struct CustomElementDefinition {
630 #[no_trace]
632 pub(crate) name: LocalName,
633
634 #[no_trace]
636 pub(crate) local_name: LocalName,
637
638 #[conditional_malloc_size_of]
640 pub(crate) constructor: Rc<CustomElementConstructor>,
641
642 pub(crate) observed_attributes: Vec<DOMString>,
644
645 pub(crate) callbacks: LifecycleCallbacks,
647
648 pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
650
651 pub(crate) form_associated: bool,
653
654 pub(crate) disable_internals: bool,
656
657 pub(crate) disable_shadow: bool,
659}
660
661impl CustomElementDefinition {
662 #[expect(clippy::too_many_arguments)]
663 fn new(
664 name: LocalName,
665 local_name: LocalName,
666 constructor: Rc<CustomElementConstructor>,
667 observed_attributes: Vec<DOMString>,
668 callbacks: LifecycleCallbacks,
669 form_associated: bool,
670 disable_internals: bool,
671 disable_shadow: bool,
672 ) -> CustomElementDefinition {
673 CustomElementDefinition {
674 name,
675 local_name,
676 constructor,
677 observed_attributes,
678 callbacks,
679 construction_stack: Default::default(),
680 form_associated,
681 disable_internals,
682 disable_shadow,
683 }
684 }
685
686 pub(crate) fn is_autonomous(&self) -> bool {
688 self.name == self.local_name
689 }
690
691 #[expect(unsafe_code)]
693 pub(crate) fn create_element(
694 &self,
695 document: &Document,
696 prefix: Option<Prefix>,
697 registry: Option<DomRoot<CustomElementRegistry>>,
698 can_gc: CanGc,
700 ) -> Fallible<DomRoot<Element>> {
701 let window = document.window();
702 let cx = GlobalScope::get_cx();
703 rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback()));
705 rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>());
706 {
707 let _ac = JSAutoRealm::new(*cx, self.constructor.callback());
709 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
712 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
713 let args = HandleValueArray::empty();
714 if unsafe {
715 !Construct1(*cx, constructor.handle(), &args, element.handle_mut())
716 } {
717 Err(Error::JSFailed)
718 } else {
719 Ok(())
720 }
721 })
722 })?;
723 }
724
725 rooted!(in(*cx) let element_val = ObjectValue(element.get()));
726 let element: DomRoot<Element> =
727 match SafeFromJSValConvertible::safe_from_jsval(cx, element_val.handle(), (), can_gc) {
728 Ok(ConversionResult::Success(element)) => element,
729 Ok(ConversionResult::Failure(..)) => {
730 return Err(Error::Type(
731 c"Constructor did not return a DOM node".to_owned(),
732 ));
733 },
734 _ => return Err(Error::JSFailed),
735 };
736
737 assert!(element.is::<HTMLElement>());
743
744 if element.HasAttributes() ||
750 element.upcast::<Node>().children_count() > 0 ||
751 element.upcast::<Node>().has_parent() ||
752 &*element.upcast::<Node>().owner_doc() != document ||
753 *element.namespace() != ns!(html) ||
754 *element.local_name() != self.local_name
755 {
756 return Err(Error::NotSupported(None));
757 }
758
759 element.set_prefix(prefix);
761
762 element.set_custom_element_registry(registry);
767
768 Ok(element)
769 }
770
771 pub(crate) fn has_attribute_changed_callback(&self) -> bool {
772 self.callbacks.attribute_changed_callback.is_some()
773 }
774}
775
776pub(crate) fn upgrade_element(
778 cx: &mut JSContext,
779 definition: Rc<CustomElementDefinition>,
780 element: &Element,
781) {
782 let state = element.get_custom_element_state();
784 if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
785 return;
786 }
787
788 element.set_custom_element_definition(Rc::clone(&definition));
790
791 element.set_custom_element_state(CustomElementState::Failed);
793
794 let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
798 for attr in element.attrs().borrow().iter() {
799 let local_name = attr.local_name().clone();
800 let namespace = attr.namespace().clone();
801 custom_element_reaction_stack.enqueue_callback_reaction(
802 element,
803 CallbackReaction::AttributeChanged(local_name, None, Some(&*attr.value()), namespace),
804 Some(definition.clone()),
805 );
806 }
807
808 if element.is_connected() {
811 ScriptThread::enqueue_callback_reaction(
812 element,
813 CallbackReaction::Connected,
814 Some(definition.clone()),
815 );
816 }
817
818 definition
820 .construction_stack
821 .borrow_mut()
822 .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
823
824 let result = run_upgrade_constructor(cx, &definition, element);
826
827 definition.construction_stack.borrow_mut().pop();
829
830 if let Err(error) = result {
832 element.clear_custom_element_definition();
834
835 element.clear_reaction_queue();
837
838 let global = GlobalScope::current().expect("No current global");
840
841 let mut realm = enter_auto_realm(cx, &*global);
842 let cx = &mut realm.current_realm();
843
844 throw_dom_exception(cx.into(), &global, error, CanGc::from_cx(cx));
845 report_pending_exception(cx);
846
847 return;
848 }
849
850 if let Some(html_element) = element.downcast::<HTMLElement>() &&
852 html_element.is_form_associated_custom_element()
853 {
854 html_element.reset_form_owner(cx);
858 if let Some(form) = html_element.form_owner() {
859 form.upcast::<Node>().rev_version();
862 }
866
867 element.check_disabled_attribute();
874 element.check_ancestors_disabled_state_for_form_control();
875 element.update_read_write_state_from_readonly_attribute();
876
877 if element.disabled_state() {
880 ScriptThread::enqueue_callback_reaction(
881 element,
882 CallbackReaction::FormDisabled(true),
883 Some(definition),
884 )
885 }
886 }
887
888 element.set_custom_element_state(CustomElementState::Custom);
890}
891
892#[expect(unsafe_code)]
895fn run_upgrade_constructor(
896 cx: &mut JSContext,
897 definition: &CustomElementDefinition,
898 element: &Element,
899) -> ErrorResult {
900 let constructor = &definition.constructor;
901 let window = element.owner_window();
902 rooted!(&in(cx) let constructor_val = ObjectValue(constructor.callback()));
903 rooted!(&in(cx) let mut element_val = UndefinedValue());
904 element.safe_to_jsval(cx.into(), element_val.handle_mut(), CanGc::from_cx(cx));
905 rooted!(&in(cx) let mut construct_result = ptr::null_mut::<JSObject>());
906 {
907 if definition.disable_shadow && element.is_shadow_host() {
910 return Err(Error::NotSupported(None));
911 }
912
913 let mut realm = AutoRealm::new(cx, std::ptr::NonNull::new(constructor.callback()).unwrap());
915 let cx = &mut *realm;
916
917 let args = HandleValueArray::empty();
918 element.set_custom_element_state(CustomElementState::Precustomized);
920
921 run_a_script::<DomTypeHolder, _>(window.upcast(), || {
924 run_a_callback::<DomTypeHolder, _>(window.upcast(), || {
925 if unsafe {
926 !Construct1(
927 cx.raw_cx(),
928 constructor_val.handle(),
929 &args,
930 construct_result.handle_mut(),
931 )
932 } {
933 Err(Error::JSFailed)
934 } else {
935 Ok(())
936 }
937 })
938 })?;
939
940 let mut same = false;
941 rooted!(&in(cx) let construct_result_val = ObjectValue(construct_result.get()));
942
943 if unsafe {
945 !SameValue(
946 cx,
947 construct_result_val.handle(),
948 element_val.handle(),
949 &mut same,
950 )
951 } {
952 return Err(Error::JSFailed);
953 }
954 if !same {
955 return Err(Error::Type(
956 c"Returned element is not SameValue as the upgraded element".to_owned(),
957 ));
958 }
959 }
960 Ok(())
961}
962
963pub(crate) fn try_upgrade_element(element: &Element) {
965 let document = element.owner_document();
968 let namespace = element.namespace();
969 let local_name = element.local_name();
970 let is = element.get_is();
971 if let Some(definition) =
972 document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
973 {
974 ScriptThread::enqueue_upgrade_reaction(element, definition);
977 }
978}
979
980#[derive(JSTraceable, MallocSizeOf)]
981#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
982pub(crate) enum CustomElementReaction {
983 Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
984 Callback(
985 #[conditional_malloc_size_of] Rc<Function>,
986 #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
987 ),
988}
989
990impl CustomElementReaction {
991 pub(crate) fn invoke(&self, cx: &mut JSContext, element: &Element) {
993 match *self {
995 CustomElementReaction::Upgrade(ref definition) => {
996 upgrade_element(cx, definition.clone(), element)
997 },
998 CustomElementReaction::Callback(ref callback, ref arguments) => {
999 let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1001 rooted!(&in(cx) let mut value: JSVal);
1002 let _ = callback.Call_(
1003 cx,
1004 element,
1005 arguments,
1006 value.handle_mut(),
1007 ExceptionHandling::Report,
1008 );
1009 },
1010 }
1011 }
1012}
1013
1014pub(crate) enum CallbackReaction<'a> {
1015 Connected,
1016 Disconnected,
1017 Adopted(DomRoot<Document>, DomRoot<Document>),
1018 AttributeChanged(
1019 LocalName,
1020 Option<&'a AttrValue>,
1021 Option<&'a AttrValue>,
1022 Namespace,
1023 ),
1024 FormAssociated(Option<DomRoot<HTMLFormElement>>),
1025 FormDisabled(bool),
1026 FormReset,
1027 ConnectedMove,
1028}
1029
1030#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1032enum BackupElementQueueFlag {
1033 Processing,
1034 NotProcessing,
1035}
1036
1037#[derive(JSTraceable, MallocSizeOf)]
1042#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1043#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1044pub(crate) struct CustomElementReactionStack {
1045 stack: DomRefCell<Vec<ElementQueue>>,
1046 backup_queue: ElementQueue,
1047 processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1048}
1049
1050impl CustomElementReactionStack {
1051 pub(crate) fn new() -> CustomElementReactionStack {
1052 CustomElementReactionStack {
1053 stack: DomRefCell::new(Vec::new()),
1054 backup_queue: ElementQueue::new(),
1055 processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1056 }
1057 }
1058
1059 pub(crate) fn push_new_element_queue(&self) {
1060 self.stack.borrow_mut().push(ElementQueue::new());
1061 }
1062
1063 pub(crate) fn pop_current_element_queue(&self, cx: &mut JSContext) {
1064 rooted_vec!(let mut stack);
1065 mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1066
1067 if let Some(current_queue) = stack.last() {
1068 current_queue.invoke_reactions(cx);
1069 }
1070 stack.pop();
1071
1072 mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1073 self.stack.borrow_mut().append(&mut *stack);
1074 }
1075
1076 pub(crate) fn invoke_backup_element_queue(&self, cx: &mut JSContext) {
1079 self.backup_queue.invoke_reactions(cx);
1081
1082 self.processing_backup_element_queue
1084 .set(BackupElementQueueFlag::NotProcessing);
1085 }
1086
1087 pub(crate) fn enqueue_element(&self, element: &Element) {
1089 if let Some(current_queue) = self.stack.borrow().last() {
1090 current_queue.append_element(element);
1092 } else {
1093 self.backup_queue.append_element(element);
1095
1096 if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1098 return;
1099 }
1100
1101 self.processing_backup_element_queue
1103 .set(BackupElementQueueFlag::Processing);
1104
1105 ScriptThread::enqueue_microtask(Microtask::CustomElementReaction);
1107 }
1108 }
1109
1110 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1112 pub(crate) fn enqueue_callback_reaction(
1113 &self,
1114 element: &Element,
1115 reaction: CallbackReaction,
1116 definition: Option<Rc<CustomElementDefinition>>,
1117 ) {
1118 let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1120 Some(definition) => definition,
1121 None => return,
1122 };
1123
1124 let (callback, args) = match reaction {
1127 CallbackReaction::Connected => {
1128 (definition.callbacks.connected_callback.clone(), Vec::new())
1129 },
1130 CallbackReaction::Disconnected => (
1131 definition.callbacks.disconnected_callback.clone(),
1132 Vec::new(),
1133 ),
1134 CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1135 let args = vec![Heap::default(), Heap::default()];
1136 args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1137 args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1138 (definition.callbacks.adopted_callback.clone(), args)
1139 },
1140 CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1141 if !definition
1143 .observed_attributes
1144 .iter()
1145 .any(|attr| *attr == *local_name)
1146 {
1147 return;
1148 }
1149
1150 let cx = GlobalScope::get_cx();
1151 let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get());
1155
1156 let local_name = DOMString::from(&*local_name);
1157 rooted!(in(*cx) let mut name_value = UndefinedValue());
1158 local_name.safe_to_jsval(cx, name_value.handle_mut(), CanGc::deprecated_note());
1159
1160 rooted!(in(*cx) let mut old_value = NullValue());
1161 if let Some(old_val) = old_val {
1162 old_val.safe_to_jsval(cx, old_value.handle_mut(), CanGc::deprecated_note());
1163 }
1164
1165 rooted!(in(*cx) let mut value = NullValue());
1166 if let Some(val) = val {
1167 val.safe_to_jsval(cx, value.handle_mut(), CanGc::deprecated_note());
1168 }
1169
1170 rooted!(in(*cx) let mut namespace_value = NullValue());
1171 if namespace != ns!() {
1172 let namespace = DOMString::from(&*namespace);
1173 namespace.safe_to_jsval(
1174 cx,
1175 namespace_value.handle_mut(),
1176 CanGc::deprecated_note(),
1177 );
1178 }
1179
1180 let args = vec![
1181 Heap::default(),
1182 Heap::default(),
1183 Heap::default(),
1184 Heap::default(),
1185 ];
1186 args[0].set(name_value.get());
1187 args[1].set(old_value.get());
1188 args[2].set(value.get());
1189 args[3].set(namespace_value.get());
1190
1191 (
1192 definition.callbacks.attribute_changed_callback.clone(),
1193 args,
1194 )
1195 },
1196 CallbackReaction::FormAssociated(form) => {
1197 let args = vec![Heap::default()];
1198 if let Some(form) = form {
1199 args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1200 } else {
1201 args[0].set(NullValue());
1202 }
1203 (definition.callbacks.form_associated_callback.clone(), args)
1204 },
1205 CallbackReaction::FormDisabled(disabled) => {
1206 let cx = GlobalScope::get_cx();
1207 rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
1208 let args = vec![Heap::default()];
1209 args[0].set(disabled_value.get());
1210 (definition.callbacks.form_disabled_callback.clone(), args)
1211 },
1212 CallbackReaction::FormReset => {
1213 (definition.callbacks.form_reset_callback.clone(), Vec::new())
1214 },
1215 CallbackReaction::ConnectedMove => {
1216 let callback = definition.callbacks.connected_move_callback.clone();
1217 if callback.is_none() {
1219 let disconnected_callback = definition.callbacks.disconnected_callback.clone();
1222
1223 let connected_callback = definition.callbacks.connected_callback.clone();
1226
1227 if disconnected_callback.is_none() && connected_callback.is_none() {
1230 return;
1231 }
1232
1233 if let Some(disconnected_callback) = disconnected_callback {
1237 element.push_callback_reaction(disconnected_callback, Box::new([]));
1238 }
1239 if let Some(connected_callback) = connected_callback {
1242 element.push_callback_reaction(connected_callback, Box::new([]));
1243 }
1244
1245 self.enqueue_element(element);
1246 return;
1247 }
1248
1249 (callback, Vec::new())
1250 },
1251 };
1252
1253 let callback = match callback {
1255 Some(callback) => callback,
1256 None => return,
1257 };
1258
1259 element.push_callback_reaction(callback, args.into_boxed_slice());
1262
1263 self.enqueue_element(element);
1265 }
1266
1267 pub(crate) fn enqueue_upgrade_reaction(
1269 &self,
1270 element: &Element,
1271 definition: Rc<CustomElementDefinition>,
1272 ) {
1273 element.push_upgrade_reaction(definition);
1276
1277 self.enqueue_element(element);
1279 }
1280}
1281
1282#[derive(JSTraceable, MallocSizeOf)]
1284#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1285struct ElementQueue {
1286 queue: DomRefCell<VecDeque<Dom<Element>>>,
1287}
1288
1289impl ElementQueue {
1290 fn new() -> ElementQueue {
1291 ElementQueue {
1292 queue: Default::default(),
1293 }
1294 }
1295
1296 fn invoke_reactions(&self, cx: &mut JSContext) {
1298 while let Some(element) = self.next_element() {
1300 element.invoke_reactions(cx)
1301 }
1302 self.queue.borrow_mut().clear();
1303 }
1304
1305 fn next_element(&self) -> Option<DomRoot<Element>> {
1306 self.queue
1307 .borrow_mut()
1308 .pop_front()
1309 .as_deref()
1310 .map(DomRoot::from_ref)
1311 }
1312
1313 fn append_element(&self, element: &Element) {
1314 self.queue.borrow_mut().push_back(Dom::from_ref(element));
1315 }
1316}
1317
1318pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1320 let mut chars = name.chars();
1323 if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1324 return false;
1325 }
1326
1327 let mut has_dash = false;
1328
1329 for c in chars {
1330 if c == '-' {
1331 has_dash = true;
1332 continue;
1333 }
1334
1335 if !is_potential_custom_element_char(c) {
1336 return false;
1337 }
1338 }
1339
1340 if !has_dash {
1341 return false;
1342 }
1343
1344 if name == "annotation-xml" ||
1345 name == "color-profile" ||
1346 name == "font-face" ||
1347 name == "font-face-src" ||
1348 name == "font-face-uri" ||
1349 name == "font-face-format" ||
1350 name == "font-face-name" ||
1351 name == "missing-glyph"
1352 {
1353 return false;
1354 }
1355
1356 true
1357}
1358
1359fn is_potential_custom_element_char(c: char) -> bool {
1362 c == '-' ||
1363 c == '.' ||
1364 c == '_' ||
1365 c == '\u{B7}' ||
1366 c.is_ascii_digit() ||
1367 c.is_ascii_lowercase() ||
1368 ('\u{C0}'..='\u{D6}').contains(&c) ||
1369 ('\u{D8}'..='\u{F6}').contains(&c) ||
1370 ('\u{F8}'..='\u{37D}').contains(&c) ||
1371 ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1372 ('\u{200C}'..='\u{200D}').contains(&c) ||
1373 ('\u{203F}'..='\u{2040}').contains(&c) ||
1374 ('\u{2070}'..='\u{218F}').contains(&c) ||
1375 ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1376 ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1377 ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1378 ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1379 ('\u{10000}'..='\u{EFFFF}').contains(&c)
1380}
1381
1382fn is_extendable_element_interface(element: &str) -> bool {
1383 element == "a" ||
1384 element == "abbr" ||
1385 element == "acronym" ||
1386 element == "address" ||
1387 element == "area" ||
1388 element == "article" ||
1389 element == "aside" ||
1390 element == "audio" ||
1391 element == "b" ||
1392 element == "base" ||
1393 element == "bdi" ||
1394 element == "bdo" ||
1395 element == "big" ||
1396 element == "blockquote" ||
1397 element == "body" ||
1398 element == "br" ||
1399 element == "button" ||
1400 element == "canvas" ||
1401 element == "caption" ||
1402 element == "center" ||
1403 element == "cite" ||
1404 element == "code" ||
1405 element == "col" ||
1406 element == "colgroup" ||
1407 element == "data" ||
1408 element == "datalist" ||
1409 element == "dd" ||
1410 element == "del" ||
1411 element == "details" ||
1412 element == "dfn" ||
1413 element == "dialog" ||
1414 element == "dir" ||
1415 element == "div" ||
1416 element == "dl" ||
1417 element == "dt" ||
1418 element == "em" ||
1419 element == "embed" ||
1420 element == "fieldset" ||
1421 element == "figcaption" ||
1422 element == "figure" ||
1423 element == "font" ||
1424 element == "footer" ||
1425 element == "form" ||
1426 element == "frame" ||
1427 element == "frameset" ||
1428 element == "h1" ||
1429 element == "h2" ||
1430 element == "h3" ||
1431 element == "h4" ||
1432 element == "h5" ||
1433 element == "h6" ||
1434 element == "head" ||
1435 element == "header" ||
1436 element == "hgroup" ||
1437 element == "hr" ||
1438 element == "html" ||
1439 element == "i" ||
1440 element == "iframe" ||
1441 element == "img" ||
1442 element == "input" ||
1443 element == "ins" ||
1444 element == "kbd" ||
1445 element == "label" ||
1446 element == "legend" ||
1447 element == "li" ||
1448 element == "link" ||
1449 element == "listing" ||
1450 element == "main" ||
1451 element == "map" ||
1452 element == "mark" ||
1453 element == "marquee" ||
1454 element == "menu" ||
1455 element == "meta" ||
1456 element == "meter" ||
1457 element == "nav" ||
1458 element == "nobr" ||
1459 element == "noframes" ||
1460 element == "noscript" ||
1461 element == "object" ||
1462 element == "ol" ||
1463 element == "optgroup" ||
1464 element == "option" ||
1465 element == "output" ||
1466 element == "p" ||
1467 element == "param" ||
1468 element == "picture" ||
1469 element == "plaintext" ||
1470 element == "pre" ||
1471 element == "progress" ||
1472 element == "q" ||
1473 element == "rp" ||
1474 element == "rt" ||
1475 element == "ruby" ||
1476 element == "s" ||
1477 element == "samp" ||
1478 element == "script" ||
1479 element == "section" ||
1480 element == "select" ||
1481 element == "slot" ||
1482 element == "small" ||
1483 element == "source" ||
1484 element == "span" ||
1485 element == "strike" ||
1486 element == "strong" ||
1487 element == "style" ||
1488 element == "sub" ||
1489 element == "summary" ||
1490 element == "sup" ||
1491 element == "table" ||
1492 element == "tbody" ||
1493 element == "td" ||
1494 element == "template" ||
1495 element == "textarea" ||
1496 element == "tfoot" ||
1497 element == "th" ||
1498 element == "thead" ||
1499 element == "time" ||
1500 element == "title" ||
1501 element == "tr" ||
1502 element == "tt" ||
1503 element == "track" ||
1504 element == "u" ||
1505 element == "ul" ||
1506 element == "var" ||
1507 element == "video" ||
1508 element == "wbr" ||
1509 element == "xmp"
1510}