1use std::default::Default;
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, QualName, local_name, ns};
9use js::context::{JSContext, NoGC};
10use js::rust::HandleObject;
11use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
12use rustc_hash::FxHashSet;
13use script_bindings::callback::RootedCallback;
14use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
15use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
16use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
17use script_bindings::dom::UnrootedDom;
18use style::attr::AttrValue;
19use stylo_dom::ElementState;
20
21use crate::dom::activation::Activatable;
22use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
23use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
24 EventHandlerNonNull, OnErrorEventHandlerNonNull,
25};
26use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
27use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
28use crate::dom::bindings::codegen::Bindings::HTMLOrSVGOrMathMLElementBinding::FocusOptions;
29use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
30use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
31use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
32use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
33use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
34use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
35use crate::dom::bindings::str::DOMString;
36use crate::dom::characterdata::CharacterData;
37use crate::dom::css::cssstyledeclaration::{
38 CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
39};
40use crate::dom::customelementregistry::{
41 CallbackReaction, CustomElementRegistry, CustomElementState,
42};
43use crate::dom::document::Document;
44use crate::dom::document::focus::FocusableArea;
45use crate::dom::document_event_handler::character_to_code;
46use crate::dom::documentfragment::DocumentFragment;
47use crate::dom::domstringmap::DOMStringMap;
48use crate::dom::element::attributes::storage::AttrRef;
49use crate::dom::element::{
50 AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
51 is_element_affected_by_legacy_background_presentational_hint,
52};
53use crate::dom::event::Event;
54use crate::dom::eventtarget::EventTarget;
55use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
56use crate::dom::html::form_controls::input_type::InputType;
57use crate::dom::html::htmlbodyelement::HTMLBodyElement;
58use crate::dom::html::htmldetailselement::HTMLDetailsElement;
59use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
60use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
61use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
62use crate::dom::html::htmllabelelement::HTMLLabelElement;
63use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
64use crate::dom::html::internals::elementinternals::ElementInternals;
65use crate::dom::htmlformelement::FormControlElementHelpers;
66use crate::dom::iterators::ShadowIncluding;
67use crate::dom::medialist::MediaList;
68use crate::dom::node::focus::FocusTrigger;
69use crate::dom::node::virtualmethods::VirtualMethods;
70use crate::dom::node::{
71 BindContext, MoveContext, Node, NodeDamage, NodeTraits, UnbindContext,
72 from_untrusted_node_address,
73};
74use crate::dom::shadowroot::shadowroot::ShadowRoot;
75use crate::dom::text::Text;
76use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement};
77use crate::event_loop::script_thread::ScriptThread;
78
79#[dom_struct]
80pub(crate) struct HTMLElement {
81 element: Element,
82 style_decl: MutNullableDom<CSSStyleDeclaration>,
83 dataset: MutNullableDom<DOMStringMap>,
84}
85
86impl HTMLElement {
87 pub(crate) fn new_inherited(
88 tag_name: LocalName,
89 prefix: Option<Prefix>,
90 document: &Document,
91 ) -> HTMLElement {
92 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
93 }
94
95 pub(crate) fn new_inherited_with_state(
96 state: ElementState,
97 tag_name: LocalName,
98 prefix: Option<Prefix>,
99 document: &Document,
100 ) -> HTMLElement {
101 HTMLElement {
102 element: Element::new_inherited_with_state(
103 state,
104 tag_name,
105 ns!(html),
106 prefix,
107 document,
108 ),
109 style_decl: Default::default(),
110 dataset: Default::default(),
111 }
112 }
113
114 pub(crate) fn new(
115 cx: &mut js::context::JSContext,
116 local_name: LocalName,
117 prefix: Option<Prefix>,
118 document: &Document,
119 proto: Option<HandleObject>,
120 ) -> DomRoot<HTMLElement> {
121 Node::reflect_node_with_proto(
122 cx,
123 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
124 document,
125 proto,
126 )
127 }
128
129 fn is_body_or_frameset(&self) -> bool {
130 let eventtarget = self.upcast::<EventTarget>();
131 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
132 }
133
134 pub(crate) fn get_inner_outer_text(&self, cx: &mut JSContext) -> DOMString {
140 let node = self.upcast::<Node>();
141 let window = node.owner_window();
142 let element = self.as_element();
143
144 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
146 if element_not_rendered {
147 return node.GetTextContent().unwrap();
148 }
149
150 window.layout_reflow(cx, QueryMsg::ElementInnerOuterTextQuery);
151 let text = window
152 .layout()
153 .query_element_inner_outer_text(node.to_trusted_node_address());
154
155 DOMString::from(text)
156 }
157
158 pub(crate) fn set_inner_text(&self, cx: &mut JSContext, input: DOMString) {
160 let fragment = self.rendered_text_fragment(cx, input);
163
164 Node::replace_all(cx, Some(fragment.upcast()), self.upcast::<Node>());
166 }
167
168 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
170 self.element
174 .get_attribute_string_ref(&local_name!("media"))
175 .is_none_or(|media| {
176 let media = media.as_attr_ref();
177 MediaList::matches_environment(&self.owner_document(), &media.value())
178 })
179 }
180
181 pub(crate) fn is_editing_host(&self) -> bool {
183 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
185 }
188
189 pub(crate) fn previously_focused_element(&self, no_gc: &NoGC) -> Option<DomRoot<Element>> {
190 self.upcast::<Element>()
191 .ensure_rare_data(no_gc)
192 .previously_focused_element
193 .get()
194 }
195
196 pub(crate) fn set_previously_focused_element(&self, element: Option<&Element>, no_gc: &NoGC) {
197 self.upcast::<Element>()
198 .ensure_rare_data(no_gc)
199 .previously_focused_element
200 .set(element);
201 }
202
203 pub(crate) fn ensure_element_internals(&self, cx: &mut JSContext) -> DomRoot<ElementInternals> {
204 let element = self.upcast::<Element>();
205 let Some(element_internals) = element.get_element_internals() else {
206 let internals = ElementInternals::new(cx, self);
207 element.ensure_rare_data(cx.no_gc()).element_internals =
208 Some(Dom::from_ref(&*internals));
209 return internals;
210 };
211 element_internals
212 }
213
214 pub(crate) fn is_disableable_form_control(&self) -> bool {
215 match self.upcast::<Node>().type_id() {
216 NodeTypeId::Element(ElementTypeId::HTMLElement(
217 HTMLElementTypeId::HTMLButtonElement |
218 HTMLElementTypeId::HTMLInputElement |
219 HTMLElementTypeId::HTMLSelectElement |
220 HTMLElementTypeId::HTMLTextAreaElement,
221 )) => true,
222 NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => {
223 self.is_form_associated_custom_element()
224 },
225 _ => false,
226 }
227 }
228
229 pub(crate) fn set_disabled_state_and_notify(&self, cx: &mut JSContext, disabled: bool) {
232 debug_assert!(self.is_disableable_form_control());
233 let element = self.as_element();
234 if disabled == element.disabled_state() {
235 return;
236 }
237
238 element.set_disabled_state(cx.no_gc(), disabled);
239 element.set_enabled_state(cx.no_gc(), !disabled);
240
241 if self.is_form_associated_custom_element() {
242 ScriptThread::enqueue_callback_reaction(
243 cx,
244 element,
245 CallbackReaction::FormDisabled(disabled),
246 None,
247 );
248 }
249 }
250
251 pub(crate) fn maybe_update_form_control_disabled(&self, cx: &mut JSContext) {
254 if !self.is_disableable_form_control() {
255 return;
256 }
257 let element = self.as_element();
258 let disabled = element.has_attribute(&local_name!("disabled")) ||
259 element.is_disabled_by_ancestor_fieldset(cx.no_gc());
260 self.set_disabled_state_and_notify(cx, disabled);
261 }
262}
263
264impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
265 fn Style(&self, cx: &mut JSContext) -> DomRoot<CSSStyleDeclaration> {
267 self.style_decl.or_init(|| {
268 let global = self.owner_window();
269 CSSStyleDeclaration::new(
270 cx,
271 &global,
272 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
273 None,
274 CSSModificationAccess::ReadWrite,
275 )
276 })
277 }
278
279 make_getter!(Title, "title");
281 make_setter!(SetTitle, "title");
283
284 make_getter!(Lang, "lang");
286 make_setter!(SetLang, "lang");
288
289 make_enumerated_getter!(
291 Dir,
292 "dir",
293 "ltr" | "rtl" | "auto",
294 missing => "",
295 invalid => ""
296 );
297
298 make_setter!(SetDir, "dir");
300
301 make_bool_getter!(Hidden, "hidden");
303 make_bool_setter!(SetHidden, "hidden");
305
306 make_bool_getter!(Inert, "inert");
308 make_bool_setter!(SetInert, "inert");
309
310 global_event_handlers!(NoOnload);
312
313 fn Dataset(&self, cx: &mut JSContext) -> DomRoot<DOMStringMap> {
315 self.dataset.or_init(|| DOMStringMap::new(cx, self))
316 }
317
318 fn GetOnerror(&self, cx: &mut JSContext) -> Option<RootedCallback<OnErrorEventHandlerNonNull>> {
320 if self.is_body_or_frameset() {
321 let document = self.owner_document();
322 if document.has_browsing_context() {
323 document.window().GetOnerror(cx)
324 } else {
325 None
326 }
327 } else {
328 self.upcast::<EventTarget>()
329 .get_event_handler_common(cx, "error")
330 }
331 }
332
333 fn SetOnerror(
335 &self,
336 cx: &mut JSContext,
337 listener: Option<RootedCallback<OnErrorEventHandlerNonNull>>,
338 ) {
339 if self.is_body_or_frameset() {
340 let document = self.owner_document();
341 if document.has_browsing_context() {
342 document.window().SetOnerror(cx, listener)
343 }
344 } else {
345 self.upcast::<EventTarget>()
347 .set_error_event_handler(cx, "error", listener)
348 }
349 }
350
351 fn GetOnload(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
353 if self.is_body_or_frameset() {
354 let document = self.owner_document();
355 if document.has_browsing_context() {
356 document.window().GetOnload(cx)
357 } else {
358 None
359 }
360 } else {
361 self.upcast::<EventTarget>()
362 .get_event_handler_common(cx, "load")
363 }
364 }
365
366 fn SetOnload(&self, cx: &mut JSContext, listener: Option<RootedCallback<EventHandlerNonNull>>) {
368 if self.is_body_or_frameset() {
369 let document = self.owner_document();
370 if document.has_browsing_context() {
371 document.window().SetOnload(cx, listener)
372 }
373 } else {
374 self.upcast::<EventTarget>()
375 .set_event_handler_common(cx, "load", listener)
376 }
377 }
378
379 fn GetOnblur(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
381 if self.is_body_or_frameset() {
382 let document = self.owner_document();
383 if document.has_browsing_context() {
384 document.window().GetOnblur(cx)
385 } else {
386 None
387 }
388 } else {
389 self.upcast::<EventTarget>()
390 .get_event_handler_common(cx, "blur")
391 }
392 }
393
394 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<RootedCallback<EventHandlerNonNull>>) {
396 if self.is_body_or_frameset() {
397 let document = self.owner_document();
398 if document.has_browsing_context() {
399 document.window().SetOnblur(cx, listener)
400 }
401 } else {
402 self.upcast::<EventTarget>()
403 .set_event_handler_common(cx, "blur", listener)
404 }
405 }
406
407 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
409 if self.is_body_or_frameset() {
410 let document = self.owner_document();
411 if document.has_browsing_context() {
412 document.window().GetOnfocus(cx)
413 } else {
414 None
415 }
416 } else {
417 self.upcast::<EventTarget>()
418 .get_event_handler_common(cx, "focus")
419 }
420 }
421
422 fn SetOnfocus(
424 &self,
425 cx: &mut JSContext,
426 listener: Option<RootedCallback<EventHandlerNonNull>>,
427 ) {
428 if self.is_body_or_frameset() {
429 let document = self.owner_document();
430 if document.has_browsing_context() {
431 document.window().SetOnfocus(cx, listener)
432 }
433 } else {
434 self.upcast::<EventTarget>()
435 .set_event_handler_common(cx, "focus", listener)
436 }
437 }
438
439 fn GetOnresize(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
441 if self.is_body_or_frameset() {
442 let document = self.owner_document();
443 if document.has_browsing_context() {
444 document.window().GetOnresize(cx)
445 } else {
446 None
447 }
448 } else {
449 self.upcast::<EventTarget>()
450 .get_event_handler_common(cx, "resize")
451 }
452 }
453
454 fn SetOnresize(
456 &self,
457 cx: &mut JSContext,
458 listener: Option<RootedCallback<EventHandlerNonNull>>,
459 ) {
460 if self.is_body_or_frameset() {
461 let document = self.owner_document();
462 if document.has_browsing_context() {
463 document.window().SetOnresize(cx, listener)
464 }
465 } else {
466 self.upcast::<EventTarget>()
467 .set_event_handler_common(cx, "resize", listener)
468 }
469 }
470
471 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
473 if self.is_body_or_frameset() {
474 let document = self.owner_document();
475 if document.has_browsing_context() {
476 document.window().GetOnscroll(cx)
477 } else {
478 None
479 }
480 } else {
481 self.upcast::<EventTarget>()
482 .get_event_handler_common(cx, "scroll")
483 }
484 }
485
486 fn SetOnscroll(
488 &self,
489 cx: &mut JSContext,
490 listener: Option<RootedCallback<EventHandlerNonNull>>,
491 ) {
492 if self.is_body_or_frameset() {
493 let document = self.owner_document();
494 if document.has_browsing_context() {
495 document.window().SetOnscroll(cx, listener)
496 }
497 } else {
498 self.upcast::<EventTarget>()
499 .set_event_handler_common(cx, "scroll", listener)
500 }
501 }
502
503 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
505 let atoms = self
506 .element
507 .get_tokenlist_attribute(&local_name!("itemtype"));
508
509 if atoms.is_empty() {
510 return None;
511 }
512
513 Some(
514 FxHashSet::from_iter(
515 atoms
516 .iter()
517 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
518 )
519 .into_iter()
520 .collect(),
521 )
522 }
523
524 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
526 let atoms = self
527 .element
528 .get_tokenlist_attribute(&local_name!("itemprop"));
529
530 if atoms.is_empty() {
531 return None;
532 }
533
534 Some(
535 FxHashSet::from_iter(
536 atoms
537 .iter()
538 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
539 )
540 .into_iter()
541 .collect(),
542 )
543 }
544
545 fn Click(&self, cx: &mut JSContext) {
547 let element = self.as_element();
548 if element.disabled_state() {
549 return;
550 }
551 if element.click_in_progress() {
552 return;
553 }
554 element.set_click_in_progress(true);
555
556 self.upcast::<Node>()
557 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
558 element.set_click_in_progress(false);
559 }
560
561 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
563 if !self
568 .upcast::<Node>()
569 .run_the_focusing_steps(cx, None, FocusTrigger::Script)
570 {
571 return;
575 }
576
577 if !options.preventScroll {
584 let scroll_axis = ScrollAxisState {
585 position: ScrollLogicalPosition::Center,
586 requirement: ScrollRequirement::IfNotVisible,
587 };
588 self.upcast::<Element>().scroll_into_view_with_options(
589 cx,
590 ScrollBehavior::Smooth,
591 scroll_axis,
592 scroll_axis,
593 None,
594 None,
595 );
596 }
597 }
598
599 fn Blur(&self, cx: &mut JSContext) {
601 if !self.as_element().focus_state() {
604 return;
605 }
606 self.owner_document().focus_handler().focus(
608 cx,
609 &FocusableArea::Viewport,
610 FocusTrigger::Script,
611 );
612 }
613
614 #[expect(unsafe_code)]
616 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
617 self.owner_window()
618 .scroll_container_query(
619 Some(self.upcast()),
620 ScrollContainerQueryFlags::ForScrollParent,
621 )
622 .and_then(|response| match response {
623 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
624 ScrollContainerResponse::Element(parent_node_address, _) => {
625 let node = unsafe { from_untrusted_node_address(parent_node_address) };
626 DomRoot::downcast(node)
627 },
628 })
629 }
630
631 fn GetOffsetParent(&self, cx: &mut JSContext) -> Option<DomRoot<Element>> {
633 if self.is::<HTMLBodyElement>() || self.element.is_root() {
634 return None;
635 }
636
637 let node = self.upcast::<Node>();
638 let window = self.owner_window();
639 let (element, _) = window.offset_parent_query(cx, node);
640
641 element
642 }
643
644 fn OffsetTop(&self, cx: &mut JSContext) -> i32 {
646 if self.is_body_element() {
647 return 0;
648 }
649
650 let node = self.upcast::<Node>();
651 let window = self.owner_window();
652 let (_, rect) = window.offset_parent_query(cx, node);
653
654 rect.origin.y.to_nearest_px()
655 }
656
657 fn OffsetLeft(&self, cx: &mut JSContext) -> i32 {
659 if self.is_body_element() {
660 return 0;
661 }
662
663 let node = self.upcast::<Node>();
664 let window = self.owner_window();
665 let (_, rect) = window.offset_parent_query(cx, node);
666
667 rect.origin.x.to_nearest_px()
668 }
669
670 fn OffsetWidth(&self, cx: &mut JSContext) -> i32 {
672 let node = self.upcast::<Node>();
673 let window = self.owner_window();
674 let (_, rect) = window.offset_parent_query(cx, node);
675
676 rect.size.width.to_nearest_px()
677 }
678
679 fn OffsetHeight(&self, cx: &mut JSContext) -> i32 {
681 let node = self.upcast::<Node>();
682 let window = self.owner_window();
683 let (_, rect) = window.offset_parent_query(cx, node);
684
685 rect.size.height.to_nearest_px()
686 }
687
688 fn InnerText(&self, cx: &mut JSContext) -> DOMString {
690 self.get_inner_outer_text(cx)
691 }
692
693 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
695 self.set_inner_text(cx, input)
696 }
697
698 fn GetOuterText(&self, cx: &mut JSContext) -> Fallible<DOMString> {
700 Ok(self.get_inner_outer_text(cx))
701 }
702
703 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
705 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
707 return Err(Error::NoModificationAllowed(Some(
708 "Cannot modify HTML element as its parent element is null".into(),
709 )));
710 };
711
712 let node = self.upcast::<Node>();
713 let document = self.owner_document();
714
715 let next = node.GetNextSibling();
717
718 let previous = node.GetPreviousSibling();
720
721 let fragment = self.rendered_text_fragment(cx, input);
724
725 if fragment.upcast::<Node>().children_count() == 0 {
728 let text_node = Text::new(cx, DOMString::new(), &document);
729
730 fragment
731 .upcast::<Node>()
732 .AppendChild(cx, text_node.upcast())?;
733 }
734
735 parent.ReplaceChild(cx, fragment.upcast(), node)?;
737
738 if let Some(next_sibling) = next &&
741 let Some(node) = next_sibling.GetPreviousSibling()
742 {
743 Self::merge_with_the_next_text_node(cx, &node);
744 }
745
746 if let Some(previous) = previous {
748 Self::merge_with_the_next_text_node(cx, &previous)
749 }
750
751 Ok(())
752 }
753
754 fn Translate(&self) -> bool {
756 self.as_element().is_translate_enabled()
757 }
758
759 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
761 self.as_element().set_string_attribute(
762 cx,
763 &html5ever::local_name!("translate"),
764 match yesno {
765 true => DOMString::from_static("yes"),
766 false => DOMString::from_static("no"),
767 },
768 );
769 }
770
771 make_enumerated_getter!(
773 ContentEditable,
774 "contenteditable",
775 "true" | "false" | "plaintext-only",
776 missing => "inherit",
777 invalid => "inherit",
778 empty => "true"
779 );
780
781 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
783 let attr_name = &local_name!("contenteditable");
784 if value.eq_ignore_ascii_case("inherit") {
785 self.element.remove_attribute_by_name(cx, attr_name);
787 } else if value.eq_ignore_ascii_case("true") ||
788 value.eq_ignore_ascii_case("false") ||
789 value.eq_ignore_ascii_case("plaintext-only")
790 {
791 let lower_value = value.to_ascii_lowercase();
795 self.element
796 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
797 } else {
798 return Err(Error::Syntax(Some(
800 "Invalid attribute for HTML element".into(),
801 )));
802 };
803 Ok(())
804 }
805
806 fn IsContentEditable(&self) -> bool {
808 self.upcast::<Node>().is_editable_or_editing_host()
810 }
811
812 fn AttachInternals(&self, cx: &mut JSContext) -> Fallible<DomRoot<ElementInternals>> {
814 if self.element.get_is().is_some() {
816 return Err(Error::NotSupported(Some(
817 "Local name of HTML element must not be set".into(),
818 )));
819 }
820
821 let lookup_registry = {
823 let registry = self.as_element().custom_element_registry();
826 if registry
827 .as_ref()
828 .is_some_and(|registry| registry.is_scoped())
829 {
830 registry
831 } else {
832 self.upcast::<Node>().owner_doc().custom_element_registry()
833 }
834 };
835 let definition = CustomElementRegistry::lookup_custom_element_definition(
836 lookup_registry.as_deref(),
837 self.upcast::<Element>().namespace(),
838 self.as_element().local_name(),
839 None,
840 );
841
842 let definition = match definition {
844 Some(definition) => definition,
845 None => {
846 return Err(Error::NotSupported(Some(
847 "Custom element definition is missing".into(),
848 )));
849 },
850 };
851
852 if definition.disable_internals {
854 return Err(Error::NotSupported(Some(
855 "Custom element definition's `disabledFeatures` must not include \"internals\""
856 .into(),
857 )));
858 }
859
860 let internals = self.ensure_element_internals(cx);
862 if internals.attached() {
863 return Err(Error::NotSupported(Some(
864 "HTML element's internals are already attached".into(),
865 )));
866 }
867
868 if !matches!(
871 self.element.get_custom_element_state(),
872 CustomElementState::Precustomized | CustomElementState::Custom
873 ) {
874 return Err(Error::NotSupported(Some(
875 "HTML element is not yet upgraded".into(),
876 )));
877 }
878
879 if self.is_form_associated_custom_element() {
880 self.element.init_state_for_internals(cx.no_gc());
881 }
882
883 internals.set_attached();
885 Ok(internals)
886 }
887
888 fn Nonce(&self) -> DOMString {
890 self.as_element().nonce_value().into()
891 }
892
893 fn SetNonce(&self, cx: &mut JSContext, value: DOMString) {
895 self.as_element()
896 .update_nonce_internal_slot(String::from(value), cx.no_gc())
897 }
898
899 fn Autofocus(&self) -> bool {
901 self.element.has_attribute(&local_name!("autofocus"))
902 }
903
904 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
906 self.element
907 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
908 }
909
910 fn TabIndex(&self) -> i32 {
912 self.element.tab_index()
913 }
914
915 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
917 self.element
918 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
919 }
920
921 make_getter!(AccessKey, "accesskey");
923
924 make_setter!(SetAccessKey, "accesskey");
926
927 fn AccessKeyLabel(&self) -> DOMString {
929 if !self.element.has_attribute(&local_name!("accesskey")) {
933 return Default::default();
934 }
935
936 let access_key_string =
937 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
938
939 #[cfg(target_os = "macos")]
940 let access_key_label = format!("⌃⌥{access_key_string}");
941 #[cfg(not(target_os = "macos"))]
942 let access_key_label = format!("Alt+Shift+{access_key_string}");
943
944 access_key_label.into()
945 }
946}
947
948fn append_text_node_to_fragment(
949 cx: &mut JSContext,
950 document: &Document,
951 fragment: &DocumentFragment,
952 text: String,
953) {
954 let text = Text::new(cx, DOMString::from(text), document);
955 fragment
956 .upcast::<Node>()
957 .AppendChild(cx, text.upcast())
958 .unwrap();
959}
960
961impl HTMLElement {
962 pub(crate) fn is_labelable_element(&self) -> bool {
964 match self.upcast::<Node>().type_id() {
965 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
966 HTMLElementTypeId::HTMLInputElement => !matches!(
967 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
968 InputType::Hidden(_)
969 ),
970 HTMLElementTypeId::HTMLButtonElement |
971 HTMLElementTypeId::HTMLMeterElement |
972 HTMLElementTypeId::HTMLOutputElement |
973 HTMLElementTypeId::HTMLProgressElement |
974 HTMLElementTypeId::HTMLSelectElement |
975 HTMLElementTypeId::HTMLTextAreaElement => true,
976 _ => self.is_form_associated_custom_element(),
977 },
978 _ => false,
979 }
980 }
981
982 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
984 if let Some(definition) = self.as_element().get_custom_element_definition() {
985 definition.is_autonomous() && definition.form_associated
986 } else {
987 false
988 }
989 }
990
991 pub(crate) fn is_listed_element(&self) -> bool {
993 match self.upcast::<Node>().type_id() {
994 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
995 HTMLElementTypeId::HTMLButtonElement |
996 HTMLElementTypeId::HTMLFieldSetElement |
997 HTMLElementTypeId::HTMLInputElement |
998 HTMLElementTypeId::HTMLObjectElement |
999 HTMLElementTypeId::HTMLOutputElement |
1000 HTMLElementTypeId::HTMLSelectElement |
1001 HTMLElementTypeId::HTMLTextAreaElement => true,
1002 _ => self.is_form_associated_custom_element(),
1003 },
1004 _ => false,
1005 }
1006 }
1007
1008 pub(crate) fn is_body_element(&self) -> bool {
1010 let self_node = self.upcast::<Node>();
1011 self_node.GetParentNode().is_some_and(|parent| {
1012 let parent_node = parent.upcast::<Node>();
1013 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
1014 parent_node.is::<HTMLHtmlElement>() &&
1015 self_node
1016 .preceding_siblings()
1017 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
1018 })
1019 }
1020
1021 pub(crate) fn is_submittable_element(&self) -> bool {
1023 match self.upcast::<Node>().type_id() {
1024 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
1025 HTMLElementTypeId::HTMLButtonElement |
1026 HTMLElementTypeId::HTMLInputElement |
1027 HTMLElementTypeId::HTMLSelectElement |
1028 HTMLElementTypeId::HTMLTextAreaElement => true,
1029 _ => self.is_form_associated_custom_element(),
1030 },
1031 _ => false,
1032 }
1033 }
1034
1035 pub(crate) fn label_at<'a>(
1038 &self,
1039 no_gc: &'a NoGC,
1040 index: u32,
1041 ) -> Option<UnrootedDom<'a, Node>> {
1042 let element = self.as_element();
1043
1044 let root_element = element.root_element();
1055 let root_node = root_element.upcast::<Node>();
1056 root_node
1057 .traverse_preorder_unrooted(no_gc, ShadowIncluding::No)
1058 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
1059 .filter(|elem| match elem.GetControl(no_gc) {
1060 Some(control) => &*control == self,
1061 _ => false,
1062 })
1063 .nth(index as usize)
1064 .map(UnrootedDom::upcast)
1065 }
1066
1067 pub(crate) fn labels_count(&self, no_gc: &NoGC) -> u32 {
1070 let element = self.as_element();
1072 let root_element = element.root_element();
1073 let root_node = root_element.upcast::<Node>();
1074 root_node
1075 .traverse_preorder_unrooted(no_gc, ShadowIncluding::No)
1076 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
1077 .filter(|elem| match elem.GetControl(no_gc) {
1078 Some(control) => &*control == self,
1079 _ => false,
1080 })
1081 .count() as u32
1082 }
1083
1084 pub(crate) fn directionality(&self) -> Option<String> {
1088 let element_direction = &self.Dir();
1089
1090 if element_direction == "ltr" {
1091 return Some("ltr".to_owned());
1092 }
1093
1094 if element_direction == "rtl" {
1095 return Some("rtl".to_owned());
1096 }
1097
1098 if let Some(input) = self.downcast::<HTMLInputElement>() &&
1099 matches!(*input.input_type(), InputType::Tel(_))
1100 {
1101 return Some("ltr".to_owned());
1102 }
1103
1104 if element_direction == "auto" {
1105 if let Some(directionality) = self
1106 .downcast::<HTMLInputElement>()
1107 .and_then(|input| input.auto_directionality())
1108 {
1109 return Some(directionality);
1110 }
1111
1112 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
1113 return Some(area.auto_directionality());
1114 }
1115 }
1116
1117 None
1124 }
1125
1126 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
1128 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
1129
1130 let is_implicit_summary_element = self.is_implicit_summary_element();
1132 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
1133 return;
1134 }
1135
1136 let parent = if is_implicit_summary_element {
1138 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1139 .unwrap()
1140 } else {
1141 self.upcast::<Node>()
1142 .GetParentNode()
1143 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1144 .unwrap()
1145 };
1146
1147 parent.toggle(cx);
1150 }
1151
1152 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1154 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1157 return false;
1158 };
1159
1160 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1162 return false;
1163 };
1164
1165 details
1169 .find_corresponding_summary_element()
1170 .is_some_and(|summary| &*summary == self.upcast())
1171 }
1172
1173 fn is_implicit_summary_element(&self) -> bool {
1176 self.containing_shadow_root()
1180 .as_deref()
1181 .map(ShadowRoot::Host)
1182 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1183 }
1184
1185 fn rendered_text_fragment(
1187 &self,
1188 cx: &mut JSContext,
1189 input: DOMString,
1190 ) -> DomRoot<DocumentFragment> {
1191 let document = self.owner_document();
1193 let fragment = DocumentFragment::new(cx, &document);
1194
1195 let input = input.str();
1198 let mut position = input.chars().peekable();
1199
1200 let mut text = String::new();
1202
1203 while let Some(ch) = position.next() {
1205 match ch {
1206 '\u{000A}' | '\u{000D}' => {
1209 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1210 position.next();
1213 }
1214
1215 if !text.is_empty() {
1216 append_text_node_to_fragment(cx, &document, &fragment, text);
1217 text = String::new();
1218 }
1219
1220 let br = Element::create(
1221 cx,
1222 QualName::new(None, ns!(html), local_name!("br")),
1223 None,
1224 &document,
1225 ElementCreator::ScriptCreated,
1226 CustomElementCreationMode::Asynchronous,
1227 None,
1228 );
1229 fragment
1230 .upcast::<Node>()
1231 .AppendChild(cx, br.upcast())
1232 .unwrap();
1233 },
1234 _ => {
1235 text.push(ch);
1238 },
1239 }
1240 }
1241
1242 if !text.is_empty() {
1245 append_text_node_to_fragment(cx, &document, &fragment, text);
1246 }
1247
1248 fragment
1249 }
1250
1251 fn merge_with_the_next_text_node(cx: &mut JSContext, node: &Node) {
1257 if !node.is::<Text>() {
1259 return;
1260 }
1261
1262 let next = match node.GetNextSibling() {
1264 Some(next) => next,
1265 None => return,
1266 };
1267
1268 if !next.is::<Text>() {
1270 return;
1271 }
1272 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1274 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1275 node_chars
1276 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1277 .expect("Got chars from Text");
1278
1279 next.remove_self(cx);
1281 }
1282
1283 fn update_assigned_access_key(&self) {
1287 if !self.element.has_attribute(&local_name!("accesskey")) {
1289 self.owner_document()
1291 .event_handler()
1292 .unassign_access_key(self);
1293 }
1294
1295 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1297 let string_view = attribute_value.str();
1298 let values = string_view.split_html_space_characters();
1299
1300 for value in values {
1303 let mut characters = value.chars();
1306 let Some(character) = characters.next() else {
1307 continue;
1308 };
1309 if characters.count() > 0 {
1310 continue;
1311 }
1312
1313 let Some(code) = character_to_code(character) else {
1316 continue;
1317 };
1318
1319 self.owner_document()
1324 .event_handler()
1325 .assign_access_key(self, code);
1326 return;
1327 }
1328
1329 self.owner_document()
1335 .event_handler()
1336 .unassign_access_key(self);
1337 }
1338}
1339
1340impl VirtualMethods for HTMLElement {
1341 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1342 Some(self.as_element() as &dyn VirtualMethods)
1343 }
1344
1345 fn attribute_mutated(
1346 &self,
1347 cx: &mut JSContext,
1348 attr: AttrRef<'_>,
1349 mutation: AttributeMutation,
1350 ) {
1351 self.super_type()
1352 .unwrap()
1353 .attribute_mutated(cx, attr, mutation);
1354 let element = self.as_element();
1355 match (attr.local_name(), mutation) {
1356 (&local_name!("accesskey"), ..) => {
1357 self.update_assigned_access_key();
1358 },
1359 (&local_name!("inert"), mutation) => {
1361 let is_blocked_document = element.is_document_element() &&
1365 element.owner_document().is_blocked_by_a_modal_dialog();
1366
1367 element.set_state(
1368 cx.no_gc(),
1369 ElementState::INERT,
1370 is_blocked_document || !matches!(mutation, AttributeMutation::Removed),
1371 );
1372 },
1373 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1374 self.form_attribute_mutated(cx, mutation);
1375 },
1376 (&local_name!("disabled"), _) => {
1379 self.maybe_update_form_control_disabled(cx);
1380 },
1381 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1382 match mutation {
1383 AttributeMutation::Set(_) => {
1384 element.set_read_write_state(cx.no_gc(), true);
1385 },
1386 AttributeMutation::Removed => {
1387 element.set_read_write_state(cx.no_gc(), false);
1388 },
1389 }
1390 },
1391 (&local_name!("nonce"), mutation) => match mutation {
1392 AttributeMutation::Set(_) => {
1393 let nonce = &**attr.value();
1394 element.update_nonce_internal_slot(nonce.to_owned(), cx.no_gc());
1395 },
1396 AttributeMutation::Removed => {
1397 element.update_nonce_internal_slot(String::new(), cx.no_gc());
1398 },
1399 },
1400 (name, _) if &**name == "containertiming" || &**name == "containertimingignore" => {
1404 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
1405 },
1406 _ => {},
1407 }
1408 }
1409
1410 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1411 if let Some(super_type) = self.super_type() {
1412 super_type.bind_to_tree(cx, context);
1413 }
1414 self.maybe_update_form_control_disabled(cx);
1415 if self.element.has_attribute(&local_name!("accesskey")) {
1416 self.update_assigned_access_key();
1417 }
1418 }
1419
1420 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1424 let document = self.owner_document_unrooted(cx.no_gc());
1426
1427 let element = self.as_element();
1437 if document
1438 .focus_handler()
1439 .focused_area()
1440 .element()
1441 .is_some_and(|focused_element| focused_element == element)
1442 {
1443 document
1444 .focus_handler()
1445 .set_focused_area(cx.no_gc(), FocusableArea::Viewport);
1446 }
1447
1448 if let Some(super_type) = self.super_type() {
1453 super_type.unbind_from_tree(cx, context);
1454 }
1455
1456 self.maybe_update_form_control_disabled(cx);
1467
1468 if element.has_attribute(&local_name!("accesskey")) {
1469 self.owner_document()
1470 .event_handler()
1471 .unassign_access_key(self);
1472 }
1473 }
1474
1475 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1476 if is_element_affected_by_legacy_background_presentational_hint(
1477 self.element.namespace(),
1478 self.element.local_name(),
1479 ) && attr.local_name() == &local_name!("background")
1480 {
1481 return true;
1482 }
1483
1484 self.super_type()
1485 .unwrap()
1486 .attribute_affects_presentational_hints(attr)
1487 }
1488
1489 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1490 match *name {
1491 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1492 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1493 local_name!("background")
1494 if is_element_affected_by_legacy_background_presentational_hint(
1495 self.element.namespace(),
1496 self.element.local_name(),
1497 ) =>
1498 {
1499 AttrValue::from_resolved_url(
1500 &self.owner_document().base_url().get_arc(),
1501 value.into(),
1502 )
1503 },
1504 _ => self
1505 .super_type()
1506 .unwrap()
1507 .parse_plain_attribute(name, value),
1508 }
1509 }
1510
1511 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1513 if let Some(super_type) = self.super_type() {
1517 super_type.moving_steps(cx, context);
1518 }
1519
1520 if let Some(form_control) = self.element.as_maybe_form_control() {
1524 form_control.moving_steps(cx);
1525
1526 self.maybe_update_form_control_disabled(cx);
1529 }
1530 }
1531}
1532
1533impl Activatable for HTMLElement {
1534 fn as_element(&self) -> &Element {
1535 &self.element
1536 }
1537
1538 fn is_instance_activatable(&self) -> bool {
1539 self.element.local_name() == &local_name!("summary")
1540 }
1541
1542 fn activation_behavior(
1544 &self,
1545 cx: &mut js::context::JSContext,
1546 _event: &Event,
1547 _target: &EventTarget,
1548 ) {
1549 self.summary_activation_behavior(cx);
1550 }
1551}
1552
1553impl FormControl for HTMLElement {
1559 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1560 debug_assert!(self.is_form_associated_custom_element());
1561 self.element
1562 .get_element_internals()
1563 .and_then(|e| e.form_owner())
1564 }
1565
1566 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>) {
1567 debug_assert!(self.is_form_associated_custom_element());
1568 self.ensure_element_internals(cx).set_form_owner(form);
1569 }
1570
1571 fn to_html_element(&self) -> &HTMLElement {
1572 self
1573 }
1574}