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