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