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