1use std::collections::HashSet;
6use std::default::Default;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use html5ever::{LocalName, Prefix, QualName, local_name, ns};
11use js::context::JSContext;
12use js::rust::HandleObject;
13use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
14use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
15use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
16use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
17use style::attr::AttrValue;
18use stylo_dom::ElementState;
19
20use crate::dom::activation::Activatable;
21use crate::dom::attr::Attr;
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::HTMLOrSVGElementBinding::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::{CallbackReaction, CustomElementState};
41use crate::dom::document::Document;
42use crate::dom::document::focus::FocusableArea;
43use crate::dom::document_event_handler::character_to_code;
44use crate::dom::documentfragment::DocumentFragment;
45use crate::dom::domstringmap::DOMStringMap;
46use crate::dom::element::{
47 AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
48 is_element_affected_by_legacy_background_presentational_hint,
49};
50use crate::dom::elementinternals::ElementInternals;
51use crate::dom::event::Event;
52use crate::dom::eventtarget::EventTarget;
53use crate::dom::html::htmlbodyelement::HTMLBodyElement;
54use crate::dom::html::htmldetailselement::HTMLDetailsElement;
55use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
56use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
57use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
58use crate::dom::html::htmllabelelement::HTMLLabelElement;
59use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
60use crate::dom::html::input_element::HTMLInputElement;
61use crate::dom::htmlformelement::FormControlElementHelpers;
62use crate::dom::input_element::input_type::InputType;
63use crate::dom::medialist::MediaList;
64use crate::dom::node::{
65 BindContext, MoveContext, Node, NodeTraits, ShadowIncluding, UnbindContext,
66 from_untrusted_node_address,
67};
68use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement};
69use crate::dom::shadowroot::ShadowRoot;
70use crate::dom::text::Text;
71use crate::dom::virtualmethods::VirtualMethods;
72use crate::script_runtime::CanGc;
73use crate::script_thread::ScriptThread;
74
75#[dom_struct]
76pub(crate) struct HTMLElement {
77 element: Element,
78 style_decl: MutNullableDom<CSSStyleDeclaration>,
79 dataset: MutNullableDom<DOMStringMap>,
80}
81
82impl HTMLElement {
83 pub(crate) fn new_inherited(
84 tag_name: LocalName,
85 prefix: Option<Prefix>,
86 document: &Document,
87 ) -> HTMLElement {
88 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
89 }
90
91 pub(crate) fn new_inherited_with_state(
92 state: ElementState,
93 tag_name: LocalName,
94 prefix: Option<Prefix>,
95 document: &Document,
96 ) -> HTMLElement {
97 HTMLElement {
98 element: Element::new_inherited_with_state(
99 state,
100 tag_name,
101 ns!(html),
102 prefix,
103 document,
104 ),
105 style_decl: Default::default(),
106 dataset: Default::default(),
107 }
108 }
109
110 pub(crate) fn new(
111 cx: &mut js::context::JSContext,
112 local_name: LocalName,
113 prefix: Option<Prefix>,
114 document: &Document,
115 proto: Option<HandleObject>,
116 ) -> DomRoot<HTMLElement> {
117 Node::reflect_node_with_proto(
118 cx,
119 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
120 document,
121 proto,
122 )
123 }
124
125 fn is_body_or_frameset(&self) -> bool {
126 let eventtarget = self.upcast::<EventTarget>();
127 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
128 }
129
130 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
136 let node = self.upcast::<Node>();
137 let window = node.owner_window();
138 let element = self.as_element();
139
140 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
142 if element_not_rendered {
143 return node.GetTextContent().unwrap();
144 }
145
146 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
147 let text = window
148 .layout()
149 .query_element_inner_outer_text(node.to_trusted_node_address());
150
151 DOMString::from(text)
152 }
153
154 pub(crate) fn set_inner_text(&self, cx: &mut JSContext, input: DOMString) {
156 let fragment = self.rendered_text_fragment(cx, input);
159
160 Node::replace_all(cx, Some(fragment.upcast()), self.upcast::<Node>());
162 }
163
164 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
166 self.element
170 .get_attribute(&local_name!("media"))
171 .is_none_or(|media| {
172 MediaList::matches_environment(&self.owner_document(), &media.value())
173 })
174 }
175
176 pub(crate) fn is_editing_host(&self) -> bool {
178 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
180 }
183}
184
185impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
186 fn Style(&self, can_gc: CanGc) -> DomRoot<CSSStyleDeclaration> {
188 self.style_decl.or_init(|| {
189 let global = self.owner_window();
190 CSSStyleDeclaration::new(
191 &global,
192 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
193 None,
194 CSSModificationAccess::ReadWrite,
195 can_gc,
196 )
197 })
198 }
199
200 make_getter!(Title, "title");
202 make_setter!(cx, SetTitle, "title");
204
205 make_getter!(Lang, "lang");
207 make_setter!(cx, SetLang, "lang");
209
210 make_enumerated_getter!(
212 Dir,
213 "dir",
214 "ltr" | "rtl" | "auto",
215 missing => "",
216 invalid => ""
217 );
218
219 make_setter!(cx, SetDir, "dir");
221
222 make_bool_getter!(Hidden, "hidden");
224 make_bool_setter!(cx, SetHidden, "hidden");
226
227 global_event_handlers!(NoOnload);
229
230 fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
232 self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
233 }
234
235 fn GetOnerror(&self, cx: &mut JSContext) -> Option<Rc<OnErrorEventHandlerNonNull>> {
237 if self.is_body_or_frameset() {
238 let document = self.owner_document();
239 if document.has_browsing_context() {
240 document.window().GetOnerror(cx)
241 } else {
242 None
243 }
244 } else {
245 self.upcast::<EventTarget>()
246 .get_event_handler_common(cx, "error")
247 }
248 }
249
250 fn SetOnerror(&self, cx: &mut JSContext, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
252 if self.is_body_or_frameset() {
253 let document = self.owner_document();
254 if document.has_browsing_context() {
255 document.window().SetOnerror(cx, listener)
256 }
257 } else {
258 self.upcast::<EventTarget>()
260 .set_error_event_handler(cx, "error", listener)
261 }
262 }
263
264 fn GetOnload(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
266 if self.is_body_or_frameset() {
267 let document = self.owner_document();
268 if document.has_browsing_context() {
269 document.window().GetOnload(cx)
270 } else {
271 None
272 }
273 } else {
274 self.upcast::<EventTarget>()
275 .get_event_handler_common(cx, "load")
276 }
277 }
278
279 fn SetOnload(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
281 if self.is_body_or_frameset() {
282 let document = self.owner_document();
283 if document.has_browsing_context() {
284 document.window().SetOnload(cx, listener)
285 }
286 } else {
287 self.upcast::<EventTarget>()
288 .set_event_handler_common(cx, "load", listener)
289 }
290 }
291
292 fn GetOnblur(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
294 if self.is_body_or_frameset() {
295 let document = self.owner_document();
296 if document.has_browsing_context() {
297 document.window().GetOnblur(cx)
298 } else {
299 None
300 }
301 } else {
302 self.upcast::<EventTarget>()
303 .get_event_handler_common(cx, "blur")
304 }
305 }
306
307 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
309 if self.is_body_or_frameset() {
310 let document = self.owner_document();
311 if document.has_browsing_context() {
312 document.window().SetOnblur(cx, listener)
313 }
314 } else {
315 self.upcast::<EventTarget>()
316 .set_event_handler_common(cx, "blur", listener)
317 }
318 }
319
320 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
322 if self.is_body_or_frameset() {
323 let document = self.owner_document();
324 if document.has_browsing_context() {
325 document.window().GetOnfocus(cx)
326 } else {
327 None
328 }
329 } else {
330 self.upcast::<EventTarget>()
331 .get_event_handler_common(cx, "focus")
332 }
333 }
334
335 fn SetOnfocus(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
337 if self.is_body_or_frameset() {
338 let document = self.owner_document();
339 if document.has_browsing_context() {
340 document.window().SetOnfocus(cx, listener)
341 }
342 } else {
343 self.upcast::<EventTarget>()
344 .set_event_handler_common(cx, "focus", listener)
345 }
346 }
347
348 fn GetOnresize(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
350 if self.is_body_or_frameset() {
351 let document = self.owner_document();
352 if document.has_browsing_context() {
353 document.window().GetOnresize(cx)
354 } else {
355 None
356 }
357 } else {
358 self.upcast::<EventTarget>()
359 .get_event_handler_common(cx, "resize")
360 }
361 }
362
363 fn SetOnresize(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
365 if self.is_body_or_frameset() {
366 let document = self.owner_document();
367 if document.has_browsing_context() {
368 document.window().SetOnresize(cx, listener)
369 }
370 } else {
371 self.upcast::<EventTarget>()
372 .set_event_handler_common(cx, "resize", listener)
373 }
374 }
375
376 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
378 if self.is_body_or_frameset() {
379 let document = self.owner_document();
380 if document.has_browsing_context() {
381 document.window().GetOnscroll(cx)
382 } else {
383 None
384 }
385 } else {
386 self.upcast::<EventTarget>()
387 .get_event_handler_common(cx, "scroll")
388 }
389 }
390
391 fn SetOnscroll(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
393 if self.is_body_or_frameset() {
394 let document = self.owner_document();
395 if document.has_browsing_context() {
396 document.window().SetOnscroll(cx, listener)
397 }
398 } else {
399 self.upcast::<EventTarget>()
400 .set_event_handler_common(cx, "scroll", listener)
401 }
402 }
403
404 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
406 let atoms = self
407 .element
408 .get_tokenlist_attribute(&local_name!("itemtype"));
409
410 if atoms.is_empty() {
411 return None;
412 }
413
414 #[expect(clippy::mutable_key_type)]
415 let mut item_attr_values = HashSet::new();
417 for attr_value in &atoms {
418 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
419 }
420
421 Some(item_attr_values.into_iter().collect())
422 }
423
424 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
426 let atoms = self
427 .element
428 .get_tokenlist_attribute(&local_name!("itemprop"));
429
430 if atoms.is_empty() {
431 return None;
432 }
433
434 #[expect(clippy::mutable_key_type)]
435 let mut item_attr_values = HashSet::new();
437 for attr_value in &atoms {
438 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
439 }
440
441 Some(item_attr_values.into_iter().collect())
442 }
443
444 fn Click(&self, can_gc: CanGc) {
446 let element = self.as_element();
447 if element.disabled_state() {
448 return;
449 }
450 if element.click_in_progress() {
451 return;
452 }
453 element.set_click_in_progress(true);
454
455 self.upcast::<Node>()
456 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
457 element.set_click_in_progress(false);
458 }
459
460 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
462 if !self.upcast::<Node>().run_the_focusing_steps(cx, None) {
467 return;
471 }
472
473 if !options.preventScroll {
480 let scroll_axis = ScrollAxisState {
481 position: ScrollLogicalPosition::Center,
482 requirement: ScrollRequirement::IfNotVisible,
483 };
484 self.upcast::<Element>().scroll_into_view_with_options(
485 ScrollBehavior::Smooth,
486 scroll_axis,
487 scroll_axis,
488 None,
489 None,
490 );
491 }
492 }
493
494 fn Blur(&self, cx: &mut JSContext) {
496 if !self.as_element().focus_state() {
499 return;
500 }
501 self.owner_document()
503 .focus_handler()
504 .focus(cx, FocusableArea::Viewport);
505 }
506
507 #[expect(unsafe_code)]
509 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
510 self.owner_window()
511 .scroll_container_query(
512 Some(self.upcast()),
513 ScrollContainerQueryFlags::ForScrollParent,
514 )
515 .and_then(|response| match response {
516 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
517 ScrollContainerResponse::Element(parent_node_address, _) => {
518 let node = unsafe { from_untrusted_node_address(parent_node_address) };
519 DomRoot::downcast(node)
520 },
521 })
522 }
523
524 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
526 if self.is::<HTMLBodyElement>() || self.element.is_root() {
527 return None;
528 }
529
530 let node = self.upcast::<Node>();
531 let window = self.owner_window();
532 let (element, _) = window.offset_parent_query(node);
533
534 element
535 }
536
537 fn OffsetTop(&self) -> i32 {
539 if self.is_body_element() {
540 return 0;
541 }
542
543 let node = self.upcast::<Node>();
544 let window = self.owner_window();
545 let (_, rect) = window.offset_parent_query(node);
546
547 rect.origin.y.to_nearest_px()
548 }
549
550 fn OffsetLeft(&self) -> i32 {
552 if self.is_body_element() {
553 return 0;
554 }
555
556 let node = self.upcast::<Node>();
557 let window = self.owner_window();
558 let (_, rect) = window.offset_parent_query(node);
559
560 rect.origin.x.to_nearest_px()
561 }
562
563 fn OffsetWidth(&self) -> i32 {
565 let node = self.upcast::<Node>();
566 let window = self.owner_window();
567 let (_, rect) = window.offset_parent_query(node);
568
569 rect.size.width.to_nearest_px()
570 }
571
572 fn OffsetHeight(&self) -> i32 {
574 let node = self.upcast::<Node>();
575 let window = self.owner_window();
576 let (_, rect) = window.offset_parent_query(node);
577
578 rect.size.height.to_nearest_px()
579 }
580
581 fn InnerText(&self) -> DOMString {
583 self.get_inner_outer_text()
584 }
585
586 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
588 self.set_inner_text(cx, input)
589 }
590
591 fn GetOuterText(&self) -> Fallible<DOMString> {
593 Ok(self.get_inner_outer_text())
594 }
595
596 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
598 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
600 return Err(Error::NoModificationAllowed(None));
601 };
602
603 let node = self.upcast::<Node>();
604 let document = self.owner_document();
605
606 let next = node.GetNextSibling();
608
609 let previous = node.GetPreviousSibling();
611
612 let fragment = self.rendered_text_fragment(cx, input);
615
616 if fragment.upcast::<Node>().children_count() == 0 {
619 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
620
621 fragment
622 .upcast::<Node>()
623 .AppendChild(cx, text_node.upcast())?;
624 }
625
626 parent.ReplaceChild(cx, fragment.upcast(), node)?;
628
629 if let Some(next_sibling) = next {
632 if let Some(node) = next_sibling.GetPreviousSibling() {
633 Self::merge_with_the_next_text_node(cx, node);
634 }
635 }
636
637 if let Some(previous) = previous {
639 Self::merge_with_the_next_text_node(cx, previous)
640 }
641
642 Ok(())
643 }
644
645 fn Translate(&self) -> bool {
647 self.as_element().is_translate_enabled()
648 }
649
650 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
652 self.as_element().set_string_attribute(
653 &html5ever::local_name!("translate"),
654 match yesno {
655 true => DOMString::from("yes"),
656 false => DOMString::from("no"),
657 },
658 CanGc::from_cx(cx),
659 );
660 }
661
662 make_enumerated_getter!(
664 ContentEditable,
665 "contenteditable",
666 "true" | "false" | "plaintext-only",
667 missing => "inherit",
668 invalid => "inherit",
669 empty => "true"
670 );
671
672 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
674 let lower_value = value.to_ascii_lowercase();
675 let attr_name = &local_name!("contenteditable");
676 match lower_value.as_ref() {
677 "inherit" => {
679 self.element
680 .remove_attribute_by_name(attr_name, CanGc::from_cx(cx));
681 },
682 "true" | "false" | "plaintext-only" => {
686 self.element.set_attribute(
687 attr_name,
688 AttrValue::String(lower_value),
689 CanGc::from_cx(cx),
690 );
691 },
692 _ => return Err(Error::Syntax(None)),
694 };
695 Ok(())
696 }
697
698 fn IsContentEditable(&self) -> bool {
700 self.upcast::<Node>().is_editable_or_editing_host()
702 }
703
704 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
706 if self.element.get_is().is_some() {
708 return Err(Error::NotSupported(None));
709 }
710
711 let registry = self.owner_window().CustomElements();
716 let definition = registry.lookup_definition(self.as_element().local_name(), None);
717
718 let definition = match definition {
720 Some(definition) => definition,
721 None => return Err(Error::NotSupported(None)),
722 };
723
724 if definition.disable_internals {
726 return Err(Error::NotSupported(None));
727 }
728
729 let internals = self.element.ensure_element_internals(can_gc);
731 if internals.attached() {
732 return Err(Error::NotSupported(None));
733 }
734
735 if !matches!(
738 self.element.get_custom_element_state(),
739 CustomElementState::Precustomized | CustomElementState::Custom
740 ) {
741 return Err(Error::NotSupported(None));
742 }
743
744 if self.is_form_associated_custom_element() {
745 self.element.init_state_for_internals();
746 }
747
748 internals.set_attached();
750 Ok(internals)
751 }
752
753 fn Nonce(&self) -> DOMString {
755 self.as_element().nonce_value().into()
756 }
757
758 fn SetNonce(&self, _cx: &mut JSContext, value: DOMString) {
760 self.as_element()
761 .update_nonce_internal_slot(value.to_string())
762 }
763
764 fn Autofocus(&self) -> bool {
766 self.element.has_attribute(&local_name!("autofocus"))
767 }
768
769 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
771 self.element
772 .set_bool_attribute(&local_name!("autofocus"), autofocus, CanGc::from_cx(cx));
773 }
774
775 fn TabIndex(&self) -> i32 {
777 self.element.tab_index()
778 }
779
780 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
782 self.element
783 .set_int_attribute(&local_name!("tabindex"), tab_index, CanGc::from_cx(cx));
784 }
785
786 make_getter!(AccessKey, "accesskey");
788
789 make_setter!(cx, SetAccessKey, "accesskey");
791
792 fn AccessKeyLabel(&self) -> DOMString {
794 if !self.element.has_attribute(&local_name!("accesskey")) {
798 return Default::default();
799 }
800
801 let access_key_string = self
802 .element
803 .get_string_attribute(&local_name!("accesskey"))
804 .to_string();
805
806 #[cfg(target_os = "macos")]
807 let access_key_label = format!("⌃⌥{access_key_string}");
808 #[cfg(not(target_os = "macos"))]
809 let access_key_label = format!("Alt+Shift+{access_key_string}");
810
811 access_key_label.into()
812 }
813}
814
815fn append_text_node_to_fragment(
816 cx: &mut JSContext,
817 document: &Document,
818 fragment: &DocumentFragment,
819 text: String,
820) {
821 let text = Text::new(cx, DOMString::from(text), document);
822 fragment
823 .upcast::<Node>()
824 .AppendChild(cx, text.upcast())
825 .unwrap();
826}
827
828impl HTMLElement {
829 pub(crate) fn is_labelable_element(&self) -> bool {
831 match self.upcast::<Node>().type_id() {
832 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
833 HTMLElementTypeId::HTMLInputElement => !matches!(
834 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
835 InputType::Hidden(_)
836 ),
837 HTMLElementTypeId::HTMLButtonElement |
838 HTMLElementTypeId::HTMLMeterElement |
839 HTMLElementTypeId::HTMLOutputElement |
840 HTMLElementTypeId::HTMLProgressElement |
841 HTMLElementTypeId::HTMLSelectElement |
842 HTMLElementTypeId::HTMLTextAreaElement => true,
843 _ => self.is_form_associated_custom_element(),
844 },
845 _ => false,
846 }
847 }
848
849 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
851 if let Some(definition) = self.as_element().get_custom_element_definition() {
852 definition.is_autonomous() && definition.form_associated
853 } else {
854 false
855 }
856 }
857
858 pub(crate) fn is_listed_element(&self) -> bool {
860 match self.upcast::<Node>().type_id() {
861 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
862 HTMLElementTypeId::HTMLButtonElement |
863 HTMLElementTypeId::HTMLFieldSetElement |
864 HTMLElementTypeId::HTMLInputElement |
865 HTMLElementTypeId::HTMLObjectElement |
866 HTMLElementTypeId::HTMLOutputElement |
867 HTMLElementTypeId::HTMLSelectElement |
868 HTMLElementTypeId::HTMLTextAreaElement => true,
869 _ => self.is_form_associated_custom_element(),
870 },
871 _ => false,
872 }
873 }
874
875 pub(crate) fn is_body_element(&self) -> bool {
877 let self_node = self.upcast::<Node>();
878 self_node.GetParentNode().is_some_and(|parent| {
879 let parent_node = parent.upcast::<Node>();
880 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
881 parent_node.is::<HTMLHtmlElement>() &&
882 self_node
883 .preceding_siblings()
884 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
885 })
886 }
887
888 pub(crate) fn is_submittable_element(&self) -> bool {
890 match self.upcast::<Node>().type_id() {
891 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
892 HTMLElementTypeId::HTMLButtonElement |
893 HTMLElementTypeId::HTMLInputElement |
894 HTMLElementTypeId::HTMLSelectElement |
895 HTMLElementTypeId::HTMLTextAreaElement => true,
896 _ => self.is_form_associated_custom_element(),
897 },
898 _ => false,
899 }
900 }
901
902 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
905 let element = self.as_element();
906
907 let root_element = element.root_element();
918 let root_node = root_element.upcast::<Node>();
919 root_node
920 .traverse_preorder(ShadowIncluding::No)
921 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
922 .filter(|elem| match elem.GetControl() {
923 Some(control) => &*control == self,
924 _ => false,
925 })
926 .nth(index as usize)
927 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
928 }
929
930 pub(crate) fn labels_count(&self) -> u32 {
933 let element = self.as_element();
935 let root_element = element.root_element();
936 let root_node = root_element.upcast::<Node>();
937 root_node
938 .traverse_preorder(ShadowIncluding::No)
939 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
940 .filter(|elem| match elem.GetControl() {
941 Some(control) => &*control == self,
942 _ => false,
943 })
944 .count() as u32
945 }
946
947 pub(crate) fn directionality(&self) -> Option<String> {
951 let element_direction = &self.Dir();
952
953 if element_direction == "ltr" {
954 return Some("ltr".to_owned());
955 }
956
957 if element_direction == "rtl" {
958 return Some("rtl".to_owned());
959 }
960
961 if let Some(input) = self.downcast::<HTMLInputElement>() {
962 if matches!(*input.input_type(), InputType::Tel(_)) {
963 return Some("ltr".to_owned());
964 }
965 }
966
967 if element_direction == "auto" {
968 if let Some(directionality) = self
969 .downcast::<HTMLInputElement>()
970 .and_then(|input| input.auto_directionality())
971 {
972 return Some(directionality);
973 }
974
975 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
976 return Some(area.auto_directionality());
977 }
978 }
979
980 None
987 }
988
989 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
991 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
992
993 if !self.is_a_summary_for_its_parent_details() {
995 return;
996 }
997
998 let parent = if self.is_implicit_summary_element() {
1000 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1001 .unwrap()
1002 } else {
1003 self.upcast::<Node>()
1004 .GetParentNode()
1005 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1006 .unwrap()
1007 };
1008
1009 parent.toggle(cx);
1012 }
1013
1014 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1016 if self.is_implicit_summary_element() {
1017 return true;
1018 }
1019
1020 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1023 return false;
1024 };
1025
1026 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1028 return false;
1029 };
1030
1031 details
1035 .find_corresponding_summary_element()
1036 .is_some_and(|summary| &*summary == self.upcast())
1037 }
1038
1039 fn is_implicit_summary_element(&self) -> bool {
1042 self.containing_shadow_root()
1046 .as_deref()
1047 .map(ShadowRoot::Host)
1048 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1049 }
1050
1051 fn rendered_text_fragment(
1053 &self,
1054 cx: &mut JSContext,
1055 input: DOMString,
1056 ) -> DomRoot<DocumentFragment> {
1057 let document = self.owner_document();
1059 let fragment = DocumentFragment::new(cx, &document);
1060
1061 let input = input.str();
1064 let mut position = input.chars().peekable();
1065
1066 let mut text = String::new();
1068
1069 while let Some(ch) = position.next() {
1071 match ch {
1072 '\u{000A}' | '\u{000D}' => {
1075 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1076 position.next();
1079 }
1080
1081 if !text.is_empty() {
1082 append_text_node_to_fragment(cx, &document, &fragment, text);
1083 text = String::new();
1084 }
1085
1086 let br = Element::create(
1087 cx,
1088 QualName::new(None, ns!(html), local_name!("br")),
1089 None,
1090 &document,
1091 ElementCreator::ScriptCreated,
1092 CustomElementCreationMode::Asynchronous,
1093 None,
1094 );
1095 fragment
1096 .upcast::<Node>()
1097 .AppendChild(cx, br.upcast())
1098 .unwrap();
1099 },
1100 _ => {
1101 text.push(ch);
1104 },
1105 }
1106 }
1107
1108 if !text.is_empty() {
1111 append_text_node_to_fragment(cx, &document, &fragment, text);
1112 }
1113
1114 fragment
1115 }
1116
1117 fn merge_with_the_next_text_node(cx: &mut JSContext, node: DomRoot<Node>) {
1123 if !node.is::<Text>() {
1125 return;
1126 }
1127
1128 let next = match node.GetNextSibling() {
1130 Some(next) => next,
1131 None => return,
1132 };
1133
1134 if !next.is::<Text>() {
1136 return;
1137 }
1138 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1140 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1141 node_chars
1142 .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1143 .expect("Got chars from Text");
1144
1145 next.remove_self(cx);
1147 }
1148
1149 fn update_assigned_access_key(&self) {
1153 if !self.element.has_attribute(&local_name!("accesskey")) {
1155 self.owner_document()
1157 .event_handler()
1158 .unassign_access_key(self);
1159 }
1160
1161 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1163 let string_view = attribute_value.str();
1164 let values = string_view.split_html_space_characters();
1165
1166 for value in values {
1169 let mut characters = value.chars();
1172 let Some(character) = characters.next() else {
1173 continue;
1174 };
1175 if characters.count() > 0 {
1176 continue;
1177 }
1178
1179 let Some(code) = character_to_code(character) else {
1182 continue;
1183 };
1184
1185 self.owner_document()
1190 .event_handler()
1191 .assign_access_key(self, code);
1192 return;
1193 }
1194
1195 self.owner_document()
1201 .event_handler()
1202 .unassign_access_key(self);
1203 }
1204}
1205
1206impl VirtualMethods for HTMLElement {
1207 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1208 Some(self.as_element() as &dyn VirtualMethods)
1209 }
1210
1211 fn attribute_mutated(&self, cx: &mut JSContext, attr: &Attr, mutation: AttributeMutation) {
1212 self.super_type()
1213 .unwrap()
1214 .attribute_mutated(cx, attr, mutation);
1215 let element = self.as_element();
1216 match (attr.local_name(), mutation) {
1217 (name, mutation)
1219 if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1220 {
1221 let evtarget = self.upcast::<EventTarget>();
1222 let event_name = &name[2..];
1223 match mutation {
1224 AttributeMutation::Set(..) => {
1226 let source = &**attr.value();
1227 let source_line = 1; evtarget.set_event_handler_uncompiled(
1229 self.owner_window().get_url(),
1230 source_line,
1231 event_name,
1232 source,
1233 );
1234 },
1235 AttributeMutation::Removed => {
1237 evtarget
1238 .set_event_handler_common::<EventHandlerNonNull>(cx, event_name, None);
1239 },
1240 }
1241 },
1242
1243 (&local_name!("accesskey"), ..) => {
1244 self.update_assigned_access_key();
1245 },
1246 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1247 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
1248 },
1249 (&local_name!("disabled"), AttributeMutation::Set(..))
1251 if self.is_form_associated_custom_element() && element.enabled_state() =>
1252 {
1253 element.set_disabled_state(true);
1254 element.set_enabled_state(false);
1255 ScriptThread::enqueue_callback_reaction(
1256 element,
1257 CallbackReaction::FormDisabled(true),
1258 None,
1259 );
1260 },
1261 (&local_name!("disabled"), AttributeMutation::Removed)
1264 if self.is_form_associated_custom_element() && element.disabled_state() =>
1265 {
1266 element.set_disabled_state(false);
1267 element.set_enabled_state(true);
1268 element.check_ancestors_disabled_state_for_form_control();
1269 if element.enabled_state() {
1270 ScriptThread::enqueue_callback_reaction(
1271 element,
1272 CallbackReaction::FormDisabled(false),
1273 None,
1274 );
1275 }
1276 },
1277 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1278 match mutation {
1279 AttributeMutation::Set(..) => {
1280 element.set_read_write_state(true);
1281 },
1282 AttributeMutation::Removed => {
1283 element.set_read_write_state(false);
1284 },
1285 }
1286 },
1287 (&local_name!("nonce"), mutation) => match mutation {
1288 AttributeMutation::Set(..) => {
1289 let nonce = &**attr.value();
1290 element.update_nonce_internal_slot(nonce.to_owned());
1291 },
1292 AttributeMutation::Removed => {
1293 element.update_nonce_internal_slot("".to_owned());
1294 },
1295 },
1296 _ => {},
1297 }
1298 }
1299
1300 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1301 if let Some(super_type) = self.super_type() {
1302 super_type.bind_to_tree(cx, context);
1303 }
1304
1305 let element = self.as_element();
1308 if self.is_form_associated_custom_element() && element.enabled_state() {
1309 element.check_ancestors_disabled_state_for_form_control();
1310 if element.disabled_state() {
1311 ScriptThread::enqueue_callback_reaction(
1312 element,
1313 CallbackReaction::FormDisabled(true),
1314 None,
1315 );
1316 }
1317 }
1318
1319 if element.has_attribute(&local_name!("accesskey")) {
1320 self.update_assigned_access_key();
1321 }
1322 }
1323
1324 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1328 let document = self.owner_document();
1330
1331 let element = self.as_element();
1341 if document
1342 .focus_handler()
1343 .focused_area()
1344 .element()
1345 .is_some_and(|focused_element| focused_element == element)
1346 {
1347 document
1348 .focus_handler()
1349 .set_focused_area(FocusableArea::Viewport);
1350 }
1351
1352 if let Some(super_type) = self.super_type() {
1357 super_type.unbind_from_tree(cx, context);
1358 }
1359
1360 if self.is_form_associated_custom_element() && element.disabled_state() {
1371 element.check_disabled_attribute();
1372 element.check_ancestors_disabled_state_for_form_control();
1373 if element.enabled_state() {
1374 ScriptThread::enqueue_callback_reaction(
1375 element,
1376 CallbackReaction::FormDisabled(false),
1377 None,
1378 );
1379 }
1380 }
1381
1382 if element.has_attribute(&local_name!("accesskey")) {
1383 self.owner_document()
1384 .event_handler()
1385 .unassign_access_key(self);
1386 }
1387 }
1388
1389 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1390 if is_element_affected_by_legacy_background_presentational_hint(
1391 self.element.namespace(),
1392 self.element.local_name(),
1393 ) && attr.local_name() == &local_name!("background")
1394 {
1395 return true;
1396 }
1397
1398 self.super_type()
1399 .unwrap()
1400 .attribute_affects_presentational_hints(attr)
1401 }
1402
1403 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1404 match *name {
1405 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1406 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1407 local_name!("background")
1408 if is_element_affected_by_legacy_background_presentational_hint(
1409 self.element.namespace(),
1410 self.element.local_name(),
1411 ) =>
1412 {
1413 AttrValue::from_resolved_url(
1414 &self.owner_document().base_url().get_arc(),
1415 value.into(),
1416 )
1417 },
1418 _ => self
1419 .super_type()
1420 .unwrap()
1421 .parse_plain_attribute(name, value),
1422 }
1423 }
1424
1425 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1427 if let Some(super_type) = self.super_type() {
1431 super_type.moving_steps(cx, context);
1432 }
1433
1434 if let Some(form_control) = self.element.as_maybe_form_control() {
1438 form_control.moving_steps(cx)
1439 }
1440 }
1441}
1442
1443impl Activatable for HTMLElement {
1444 fn as_element(&self) -> &Element {
1445 &self.element
1446 }
1447
1448 fn is_instance_activatable(&self) -> bool {
1449 self.element.local_name() == &local_name!("summary")
1450 }
1451
1452 fn activation_behavior(
1454 &self,
1455 cx: &mut js::context::JSContext,
1456 _event: &Event,
1457 _target: &EventTarget,
1458 ) {
1459 self.summary_activation_behavior(cx);
1460 }
1461}
1462
1463impl FormControl for HTMLElement {
1469 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1470 debug_assert!(self.is_form_associated_custom_element());
1471 self.element
1472 .get_element_internals()
1473 .and_then(|e| e.form_owner())
1474 }
1475
1476 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1477 debug_assert!(self.is_form_associated_custom_element());
1478 self.element
1479 .ensure_element_internals(CanGc::deprecated_note())
1480 .set_form_owner(form);
1481 }
1482
1483 fn to_element(&self) -> &Element {
1484 &self.element
1485 }
1486
1487 fn is_listed(&self) -> bool {
1488 debug_assert!(self.is_form_associated_custom_element());
1489 true
1490 }
1491
1492 }