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::bindings::codegen::Bindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
22use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
23 EventHandlerNonNull, OnErrorEventHandlerNonNull,
24};
25use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
26use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
27use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
28use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
29use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
30use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
31use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
32use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
33use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
34use crate::dom::bindings::str::DOMString;
35use crate::dom::characterdata::CharacterData;
36use crate::dom::css::cssstyledeclaration::{
37 CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
38};
39use crate::dom::customelementregistry::{CallbackReaction, CustomElementState};
40use crate::dom::document::Document;
41use crate::dom::document::focus::FocusableArea;
42use crate::dom::document_event_handler::character_to_code;
43use crate::dom::documentfragment::DocumentFragment;
44use crate::dom::domstringmap::DOMStringMap;
45use crate::dom::element::attributes::storage::AttrRef;
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::iterators::ShadowIncluding;
64use crate::dom::medialist::MediaList;
65use crate::dom::node::{
66 BindContext, MoveContext, Node, NodeTraits, UnbindContext, 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_string_value(&local_name!("media"))
171 .is_none_or(|media| MediaList::matches_environment(&self.owner_document(), &media))
172 }
173
174 pub(crate) fn is_editing_host(&self) -> bool {
176 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
178 }
181}
182
183impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
184 fn Style(&self, cx: &mut JSContext) -> DomRoot<CSSStyleDeclaration> {
186 self.style_decl.or_init(|| {
187 let global = self.owner_window();
188 CSSStyleDeclaration::new(
189 cx,
190 &global,
191 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
192 None,
193 CSSModificationAccess::ReadWrite,
194 )
195 })
196 }
197
198 make_getter!(Title, "title");
200 make_setter!(cx, SetTitle, "title");
202
203 make_getter!(Lang, "lang");
205 make_setter!(cx, SetLang, "lang");
207
208 make_enumerated_getter!(
210 Dir,
211 "dir",
212 "ltr" | "rtl" | "auto",
213 missing => "",
214 invalid => ""
215 );
216
217 make_setter!(cx, SetDir, "dir");
219
220 make_bool_getter!(Hidden, "hidden");
222 make_bool_setter!(cx, SetHidden, "hidden");
224
225 global_event_handlers!(NoOnload);
227
228 fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
230 self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
231 }
232
233 fn GetOnerror(&self, cx: &mut JSContext) -> Option<Rc<OnErrorEventHandlerNonNull>> {
235 if self.is_body_or_frameset() {
236 let document = self.owner_document();
237 if document.has_browsing_context() {
238 document.window().GetOnerror(cx)
239 } else {
240 None
241 }
242 } else {
243 self.upcast::<EventTarget>()
244 .get_event_handler_common(cx, "error")
245 }
246 }
247
248 fn SetOnerror(&self, cx: &mut JSContext, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
250 if self.is_body_or_frameset() {
251 let document = self.owner_document();
252 if document.has_browsing_context() {
253 document.window().SetOnerror(cx, listener)
254 }
255 } else {
256 self.upcast::<EventTarget>()
258 .set_error_event_handler(cx, "error", listener)
259 }
260 }
261
262 fn GetOnload(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
264 if self.is_body_or_frameset() {
265 let document = self.owner_document();
266 if document.has_browsing_context() {
267 document.window().GetOnload(cx)
268 } else {
269 None
270 }
271 } else {
272 self.upcast::<EventTarget>()
273 .get_event_handler_common(cx, "load")
274 }
275 }
276
277 fn SetOnload(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
279 if self.is_body_or_frameset() {
280 let document = self.owner_document();
281 if document.has_browsing_context() {
282 document.window().SetOnload(cx, listener)
283 }
284 } else {
285 self.upcast::<EventTarget>()
286 .set_event_handler_common(cx, "load", listener)
287 }
288 }
289
290 fn GetOnblur(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
292 if self.is_body_or_frameset() {
293 let document = self.owner_document();
294 if document.has_browsing_context() {
295 document.window().GetOnblur(cx)
296 } else {
297 None
298 }
299 } else {
300 self.upcast::<EventTarget>()
301 .get_event_handler_common(cx, "blur")
302 }
303 }
304
305 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
307 if self.is_body_or_frameset() {
308 let document = self.owner_document();
309 if document.has_browsing_context() {
310 document.window().SetOnblur(cx, listener)
311 }
312 } else {
313 self.upcast::<EventTarget>()
314 .set_event_handler_common(cx, "blur", listener)
315 }
316 }
317
318 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
320 if self.is_body_or_frameset() {
321 let document = self.owner_document();
322 if document.has_browsing_context() {
323 document.window().GetOnfocus(cx)
324 } else {
325 None
326 }
327 } else {
328 self.upcast::<EventTarget>()
329 .get_event_handler_common(cx, "focus")
330 }
331 }
332
333 fn SetOnfocus(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
335 if self.is_body_or_frameset() {
336 let document = self.owner_document();
337 if document.has_browsing_context() {
338 document.window().SetOnfocus(cx, listener)
339 }
340 } else {
341 self.upcast::<EventTarget>()
342 .set_event_handler_common(cx, "focus", listener)
343 }
344 }
345
346 fn GetOnresize(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
348 if self.is_body_or_frameset() {
349 let document = self.owner_document();
350 if document.has_browsing_context() {
351 document.window().GetOnresize(cx)
352 } else {
353 None
354 }
355 } else {
356 self.upcast::<EventTarget>()
357 .get_event_handler_common(cx, "resize")
358 }
359 }
360
361 fn SetOnresize(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
363 if self.is_body_or_frameset() {
364 let document = self.owner_document();
365 if document.has_browsing_context() {
366 document.window().SetOnresize(cx, listener)
367 }
368 } else {
369 self.upcast::<EventTarget>()
370 .set_event_handler_common(cx, "resize", listener)
371 }
372 }
373
374 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
376 if self.is_body_or_frameset() {
377 let document = self.owner_document();
378 if document.has_browsing_context() {
379 document.window().GetOnscroll(cx)
380 } else {
381 None
382 }
383 } else {
384 self.upcast::<EventTarget>()
385 .get_event_handler_common(cx, "scroll")
386 }
387 }
388
389 fn SetOnscroll(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
391 if self.is_body_or_frameset() {
392 let document = self.owner_document();
393 if document.has_browsing_context() {
394 document.window().SetOnscroll(cx, listener)
395 }
396 } else {
397 self.upcast::<EventTarget>()
398 .set_event_handler_common(cx, "scroll", listener)
399 }
400 }
401
402 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
404 let atoms = self
405 .element
406 .get_tokenlist_attribute(&local_name!("itemtype"));
407
408 if atoms.is_empty() {
409 return None;
410 }
411
412 #[expect(clippy::mutable_key_type)]
413 let mut item_attr_values = HashSet::new();
415 for attr_value in &atoms {
416 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
417 }
418
419 Some(item_attr_values.into_iter().collect())
420 }
421
422 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
424 let atoms = self
425 .element
426 .get_tokenlist_attribute(&local_name!("itemprop"));
427
428 if atoms.is_empty() {
429 return None;
430 }
431
432 #[expect(clippy::mutable_key_type)]
433 let mut item_attr_values = HashSet::new();
435 for attr_value in &atoms {
436 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
437 }
438
439 Some(item_attr_values.into_iter().collect())
440 }
441
442 fn Click(&self, cx: &mut JSContext) {
444 let element = self.as_element();
445 if element.disabled_state() {
446 return;
447 }
448 if element.click_in_progress() {
449 return;
450 }
451 element.set_click_in_progress(true);
452
453 self.upcast::<Node>()
454 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
455 element.set_click_in_progress(false);
456 }
457
458 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
460 if !self.upcast::<Node>().run_the_focusing_steps(cx, None) {
465 return;
469 }
470
471 if !options.preventScroll {
478 let scroll_axis = ScrollAxisState {
479 position: ScrollLogicalPosition::Center,
480 requirement: ScrollRequirement::IfNotVisible,
481 };
482 self.upcast::<Element>().scroll_into_view_with_options(
483 cx,
484 ScrollBehavior::Smooth,
485 scroll_axis,
486 scroll_axis,
487 None,
488 None,
489 );
490 }
491 }
492
493 fn Blur(&self, cx: &mut JSContext) {
495 if !self.as_element().focus_state() {
498 return;
499 }
500 self.owner_document()
502 .focus_handler()
503 .focus(cx, FocusableArea::Viewport);
504 }
505
506 #[expect(unsafe_code)]
508 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
509 self.owner_window()
510 .scroll_container_query(
511 Some(self.upcast()),
512 ScrollContainerQueryFlags::ForScrollParent,
513 )
514 .and_then(|response| match response {
515 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
516 ScrollContainerResponse::Element(parent_node_address, _) => {
517 let node = unsafe { from_untrusted_node_address(parent_node_address) };
518 DomRoot::downcast(node)
519 },
520 })
521 }
522
523 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
525 if self.is::<HTMLBodyElement>() || self.element.is_root() {
526 return None;
527 }
528
529 let node = self.upcast::<Node>();
530 let window = self.owner_window();
531 let (element, _) = window.offset_parent_query(node);
532
533 element
534 }
535
536 fn OffsetTop(&self) -> i32 {
538 if self.is_body_element() {
539 return 0;
540 }
541
542 let node = self.upcast::<Node>();
543 let window = self.owner_window();
544 let (_, rect) = window.offset_parent_query(node);
545
546 rect.origin.y.to_nearest_px()
547 }
548
549 fn OffsetLeft(&self) -> i32 {
551 if self.is_body_element() {
552 return 0;
553 }
554
555 let node = self.upcast::<Node>();
556 let window = self.owner_window();
557 let (_, rect) = window.offset_parent_query(node);
558
559 rect.origin.x.to_nearest_px()
560 }
561
562 fn OffsetWidth(&self) -> i32 {
564 let node = self.upcast::<Node>();
565 let window = self.owner_window();
566 let (_, rect) = window.offset_parent_query(node);
567
568 rect.size.width.to_nearest_px()
569 }
570
571 fn OffsetHeight(&self) -> i32 {
573 let node = self.upcast::<Node>();
574 let window = self.owner_window();
575 let (_, rect) = window.offset_parent_query(node);
576
577 rect.size.height.to_nearest_px()
578 }
579
580 fn InnerText(&self) -> DOMString {
582 self.get_inner_outer_text()
583 }
584
585 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
587 self.set_inner_text(cx, input)
588 }
589
590 fn GetOuterText(&self) -> Fallible<DOMString> {
592 Ok(self.get_inner_outer_text())
593 }
594
595 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
597 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
599 return Err(Error::NoModificationAllowed(None));
600 };
601
602 let node = self.upcast::<Node>();
603 let document = self.owner_document();
604
605 let next = node.GetNextSibling();
607
608 let previous = node.GetPreviousSibling();
610
611 let fragment = self.rendered_text_fragment(cx, input);
614
615 if fragment.upcast::<Node>().children_count() == 0 {
618 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
619
620 fragment
621 .upcast::<Node>()
622 .AppendChild(cx, text_node.upcast())?;
623 }
624
625 parent.ReplaceChild(cx, fragment.upcast(), node)?;
627
628 if let Some(next_sibling) = next &&
631 let Some(node) = next_sibling.GetPreviousSibling()
632 {
633 Self::merge_with_the_next_text_node(cx, node);
634 }
635
636 if let Some(previous) = previous {
638 Self::merge_with_the_next_text_node(cx, previous)
639 }
640
641 Ok(())
642 }
643
644 fn Translate(&self) -> bool {
646 self.as_element().is_translate_enabled()
647 }
648
649 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
651 self.as_element().set_string_attribute(
652 cx,
653 &html5ever::local_name!("translate"),
654 match yesno {
655 true => DOMString::from("yes"),
656 false => DOMString::from("no"),
657 },
658 );
659 }
660
661 make_enumerated_getter!(
663 ContentEditable,
664 "contenteditable",
665 "true" | "false" | "plaintext-only",
666 missing => "inherit",
667 invalid => "inherit",
668 empty => "true"
669 );
670
671 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
673 let lower_value = value.to_ascii_lowercase();
674 let attr_name = &local_name!("contenteditable");
675 match lower_value.as_ref() {
676 "inherit" => {
678 self.element.remove_attribute_by_name(cx, attr_name);
679 },
680 "true" | "false" | "plaintext-only" => {
684 self.element
685 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
686 },
687 _ => return Err(Error::Syntax(None)),
689 };
690 Ok(())
691 }
692
693 fn IsContentEditable(&self) -> bool {
695 self.upcast::<Node>().is_editable_or_editing_host()
697 }
698
699 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
701 if self.element.get_is().is_some() {
703 return Err(Error::NotSupported(None));
704 }
705
706 let registry = self.owner_window().CustomElements();
711 let definition = registry.lookup_definition(self.as_element().local_name(), None);
712
713 let definition = match definition {
715 Some(definition) => definition,
716 None => return Err(Error::NotSupported(None)),
717 };
718
719 if definition.disable_internals {
721 return Err(Error::NotSupported(None));
722 }
723
724 let internals = self.element.ensure_element_internals(can_gc);
726 if internals.attached() {
727 return Err(Error::NotSupported(None));
728 }
729
730 if !matches!(
733 self.element.get_custom_element_state(),
734 CustomElementState::Precustomized | CustomElementState::Custom
735 ) {
736 return Err(Error::NotSupported(None));
737 }
738
739 if self.is_form_associated_custom_element() {
740 self.element.init_state_for_internals();
741 }
742
743 internals.set_attached();
745 Ok(internals)
746 }
747
748 fn Nonce(&self) -> DOMString {
750 self.as_element().nonce_value().into()
751 }
752
753 fn SetNonce(&self, _cx: &mut JSContext, value: DOMString) {
755 self.as_element()
756 .update_nonce_internal_slot(String::from(value))
757 }
758
759 fn Autofocus(&self) -> bool {
761 self.element.has_attribute(&local_name!("autofocus"))
762 }
763
764 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
766 self.element
767 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
768 }
769
770 fn TabIndex(&self) -> i32 {
772 self.element.tab_index()
773 }
774
775 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
777 self.element
778 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
779 }
780
781 make_getter!(AccessKey, "accesskey");
783
784 make_setter!(cx, SetAccessKey, "accesskey");
786
787 fn AccessKeyLabel(&self) -> DOMString {
789 if !self.element.has_attribute(&local_name!("accesskey")) {
793 return Default::default();
794 }
795
796 let access_key_string =
797 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
798
799 #[cfg(target_os = "macos")]
800 let access_key_label = format!("⌃⌥{access_key_string}");
801 #[cfg(not(target_os = "macos"))]
802 let access_key_label = format!("Alt+Shift+{access_key_string}");
803
804 access_key_label.into()
805 }
806}
807
808fn append_text_node_to_fragment(
809 cx: &mut JSContext,
810 document: &Document,
811 fragment: &DocumentFragment,
812 text: String,
813) {
814 let text = Text::new(cx, DOMString::from(text), document);
815 fragment
816 .upcast::<Node>()
817 .AppendChild(cx, text.upcast())
818 .unwrap();
819}
820
821impl HTMLElement {
822 pub(crate) fn is_labelable_element(&self) -> bool {
824 match self.upcast::<Node>().type_id() {
825 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
826 HTMLElementTypeId::HTMLInputElement => !matches!(
827 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
828 InputType::Hidden(_)
829 ),
830 HTMLElementTypeId::HTMLButtonElement |
831 HTMLElementTypeId::HTMLMeterElement |
832 HTMLElementTypeId::HTMLOutputElement |
833 HTMLElementTypeId::HTMLProgressElement |
834 HTMLElementTypeId::HTMLSelectElement |
835 HTMLElementTypeId::HTMLTextAreaElement => true,
836 _ => self.is_form_associated_custom_element(),
837 },
838 _ => false,
839 }
840 }
841
842 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
844 if let Some(definition) = self.as_element().get_custom_element_definition() {
845 definition.is_autonomous() && definition.form_associated
846 } else {
847 false
848 }
849 }
850
851 pub(crate) fn is_listed_element(&self) -> bool {
853 match self.upcast::<Node>().type_id() {
854 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
855 HTMLElementTypeId::HTMLButtonElement |
856 HTMLElementTypeId::HTMLFieldSetElement |
857 HTMLElementTypeId::HTMLInputElement |
858 HTMLElementTypeId::HTMLObjectElement |
859 HTMLElementTypeId::HTMLOutputElement |
860 HTMLElementTypeId::HTMLSelectElement |
861 HTMLElementTypeId::HTMLTextAreaElement => true,
862 _ => self.is_form_associated_custom_element(),
863 },
864 _ => false,
865 }
866 }
867
868 pub(crate) fn is_body_element(&self) -> bool {
870 let self_node = self.upcast::<Node>();
871 self_node.GetParentNode().is_some_and(|parent| {
872 let parent_node = parent.upcast::<Node>();
873 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
874 parent_node.is::<HTMLHtmlElement>() &&
875 self_node
876 .preceding_siblings()
877 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
878 })
879 }
880
881 pub(crate) fn is_submittable_element(&self) -> bool {
883 match self.upcast::<Node>().type_id() {
884 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
885 HTMLElementTypeId::HTMLButtonElement |
886 HTMLElementTypeId::HTMLInputElement |
887 HTMLElementTypeId::HTMLSelectElement |
888 HTMLElementTypeId::HTMLTextAreaElement => true,
889 _ => self.is_form_associated_custom_element(),
890 },
891 _ => false,
892 }
893 }
894
895 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
898 let element = self.as_element();
899
900 let root_element = element.root_element();
911 let root_node = root_element.upcast::<Node>();
912 root_node
913 .traverse_preorder(ShadowIncluding::No)
914 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
915 .filter(|elem| match elem.GetControl() {
916 Some(control) => &*control == self,
917 _ => false,
918 })
919 .nth(index as usize)
920 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
921 }
922
923 pub(crate) fn labels_count(&self) -> u32 {
926 let element = self.as_element();
928 let root_element = element.root_element();
929 let root_node = root_element.upcast::<Node>();
930 root_node
931 .traverse_preorder(ShadowIncluding::No)
932 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
933 .filter(|elem| match elem.GetControl() {
934 Some(control) => &*control == self,
935 _ => false,
936 })
937 .count() as u32
938 }
939
940 pub(crate) fn directionality(&self) -> Option<String> {
944 let element_direction = &self.Dir();
945
946 if element_direction == "ltr" {
947 return Some("ltr".to_owned());
948 }
949
950 if element_direction == "rtl" {
951 return Some("rtl".to_owned());
952 }
953
954 if let Some(input) = self.downcast::<HTMLInputElement>() &&
955 matches!(*input.input_type(), InputType::Tel(_))
956 {
957 return Some("ltr".to_owned());
958 }
959
960 if element_direction == "auto" {
961 if let Some(directionality) = self
962 .downcast::<HTMLInputElement>()
963 .and_then(|input| input.auto_directionality())
964 {
965 return Some(directionality);
966 }
967
968 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
969 return Some(area.auto_directionality());
970 }
971 }
972
973 None
980 }
981
982 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
984 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
985
986 let is_implicit_summary_element = self.is_implicit_summary_element();
988 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
989 return;
990 }
991
992 let parent = if is_implicit_summary_element {
994 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
995 .unwrap()
996 } else {
997 self.upcast::<Node>()
998 .GetParentNode()
999 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1000 .unwrap()
1001 };
1002
1003 parent.toggle(cx);
1006 }
1007
1008 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1010 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1013 return false;
1014 };
1015
1016 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1018 return false;
1019 };
1020
1021 details
1025 .find_corresponding_summary_element()
1026 .is_some_and(|summary| &*summary == self.upcast())
1027 }
1028
1029 fn is_implicit_summary_element(&self) -> bool {
1032 self.containing_shadow_root()
1036 .as_deref()
1037 .map(ShadowRoot::Host)
1038 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1039 }
1040
1041 fn rendered_text_fragment(
1043 &self,
1044 cx: &mut JSContext,
1045 input: DOMString,
1046 ) -> DomRoot<DocumentFragment> {
1047 let document = self.owner_document();
1049 let fragment = DocumentFragment::new(cx, &document);
1050
1051 let input = input.str();
1054 let mut position = input.chars().peekable();
1055
1056 let mut text = String::new();
1058
1059 while let Some(ch) = position.next() {
1061 match ch {
1062 '\u{000A}' | '\u{000D}' => {
1065 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1066 position.next();
1069 }
1070
1071 if !text.is_empty() {
1072 append_text_node_to_fragment(cx, &document, &fragment, text);
1073 text = String::new();
1074 }
1075
1076 let br = Element::create(
1077 cx,
1078 QualName::new(None, ns!(html), local_name!("br")),
1079 None,
1080 &document,
1081 ElementCreator::ScriptCreated,
1082 CustomElementCreationMode::Asynchronous,
1083 None,
1084 );
1085 fragment
1086 .upcast::<Node>()
1087 .AppendChild(cx, br.upcast())
1088 .unwrap();
1089 },
1090 _ => {
1091 text.push(ch);
1094 },
1095 }
1096 }
1097
1098 if !text.is_empty() {
1101 append_text_node_to_fragment(cx, &document, &fragment, text);
1102 }
1103
1104 fragment
1105 }
1106
1107 fn merge_with_the_next_text_node(cx: &mut JSContext, node: DomRoot<Node>) {
1113 if !node.is::<Text>() {
1115 return;
1116 }
1117
1118 let next = match node.GetNextSibling() {
1120 Some(next) => next,
1121 None => return,
1122 };
1123
1124 if !next.is::<Text>() {
1126 return;
1127 }
1128 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1130 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1131 node_chars
1132 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1133 .expect("Got chars from Text");
1134
1135 next.remove_self(cx);
1137 }
1138
1139 fn update_assigned_access_key(&self) {
1143 if !self.element.has_attribute(&local_name!("accesskey")) {
1145 self.owner_document()
1147 .event_handler()
1148 .unassign_access_key(self);
1149 }
1150
1151 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1153 let string_view = attribute_value.str();
1154 let values = string_view.split_html_space_characters();
1155
1156 for value in values {
1159 let mut characters = value.chars();
1162 let Some(character) = characters.next() else {
1163 continue;
1164 };
1165 if characters.count() > 0 {
1166 continue;
1167 }
1168
1169 let Some(code) = character_to_code(character) else {
1172 continue;
1173 };
1174
1175 self.owner_document()
1180 .event_handler()
1181 .assign_access_key(self, code);
1182 return;
1183 }
1184
1185 self.owner_document()
1191 .event_handler()
1192 .unassign_access_key(self);
1193 }
1194}
1195
1196impl VirtualMethods for HTMLElement {
1197 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1198 Some(self.as_element() as &dyn VirtualMethods)
1199 }
1200
1201 fn attribute_mutated(
1202 &self,
1203 cx: &mut JSContext,
1204 attr: AttrRef<'_>,
1205 mutation: AttributeMutation,
1206 ) {
1207 self.super_type()
1208 .unwrap()
1209 .attribute_mutated(cx, attr, mutation);
1210 let element = self.as_element();
1211 match (attr.local_name(), mutation) {
1212 (&local_name!("accesskey"), ..) => {
1213 self.update_assigned_access_key();
1214 },
1215 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1216 self.form_attribute_mutated(cx, mutation);
1217 },
1218 (&local_name!("disabled"), AttributeMutation::Set(..))
1220 if self.is_form_associated_custom_element() && element.enabled_state() =>
1221 {
1222 element.set_disabled_state(true);
1223 element.set_enabled_state(false);
1224 ScriptThread::enqueue_callback_reaction(
1225 element,
1226 CallbackReaction::FormDisabled(true),
1227 None,
1228 );
1229 },
1230 (&local_name!("disabled"), AttributeMutation::Removed)
1233 if self.is_form_associated_custom_element() && element.disabled_state() =>
1234 {
1235 element.set_disabled_state(false);
1236 element.set_enabled_state(true);
1237 element.check_ancestors_disabled_state_for_form_control();
1238 if element.enabled_state() {
1239 ScriptThread::enqueue_callback_reaction(
1240 element,
1241 CallbackReaction::FormDisabled(false),
1242 None,
1243 );
1244 }
1245 },
1246 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1247 match mutation {
1248 AttributeMutation::Set(..) => {
1249 element.set_read_write_state(true);
1250 },
1251 AttributeMutation::Removed => {
1252 element.set_read_write_state(false);
1253 },
1254 }
1255 },
1256 (&local_name!("nonce"), mutation) => match mutation {
1257 AttributeMutation::Set(..) => {
1258 let nonce = &**attr.value();
1259 element.update_nonce_internal_slot(nonce.to_owned());
1260 },
1261 AttributeMutation::Removed => {
1262 element.update_nonce_internal_slot("".to_owned());
1263 },
1264 },
1265 _ => {},
1266 }
1267 }
1268
1269 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1270 if let Some(super_type) = self.super_type() {
1271 super_type.bind_to_tree(cx, context);
1272 }
1273
1274 let element = self.as_element();
1277 if self.is_form_associated_custom_element() && element.enabled_state() {
1278 element.check_ancestors_disabled_state_for_form_control();
1279 if element.disabled_state() {
1280 ScriptThread::enqueue_callback_reaction(
1281 element,
1282 CallbackReaction::FormDisabled(true),
1283 None,
1284 );
1285 }
1286 }
1287
1288 if element.has_attribute(&local_name!("accesskey")) {
1289 self.update_assigned_access_key();
1290 }
1291 }
1292
1293 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1297 let document = self.owner_document();
1299
1300 let element = self.as_element();
1310 if document
1311 .focus_handler()
1312 .focused_area()
1313 .element()
1314 .is_some_and(|focused_element| focused_element == element)
1315 {
1316 document
1317 .focus_handler()
1318 .set_focused_area(FocusableArea::Viewport);
1319 }
1320
1321 if let Some(super_type) = self.super_type() {
1326 super_type.unbind_from_tree(cx, context);
1327 }
1328
1329 if self.is_form_associated_custom_element() && element.disabled_state() {
1340 element.check_disabled_attribute();
1341 element.check_ancestors_disabled_state_for_form_control();
1342 if element.enabled_state() {
1343 ScriptThread::enqueue_callback_reaction(
1344 element,
1345 CallbackReaction::FormDisabled(false),
1346 None,
1347 );
1348 }
1349 }
1350
1351 if element.has_attribute(&local_name!("accesskey")) {
1352 self.owner_document()
1353 .event_handler()
1354 .unassign_access_key(self);
1355 }
1356 }
1357
1358 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1359 if is_element_affected_by_legacy_background_presentational_hint(
1360 self.element.namespace(),
1361 self.element.local_name(),
1362 ) && attr.local_name() == &local_name!("background")
1363 {
1364 return true;
1365 }
1366
1367 self.super_type()
1368 .unwrap()
1369 .attribute_affects_presentational_hints(attr)
1370 }
1371
1372 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1373 match *name {
1374 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1375 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1376 local_name!("background")
1377 if is_element_affected_by_legacy_background_presentational_hint(
1378 self.element.namespace(),
1379 self.element.local_name(),
1380 ) =>
1381 {
1382 AttrValue::from_resolved_url(
1383 &self.owner_document().base_url().get_arc(),
1384 value.into(),
1385 )
1386 },
1387 _ => self
1388 .super_type()
1389 .unwrap()
1390 .parse_plain_attribute(name, value),
1391 }
1392 }
1393
1394 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1396 if let Some(super_type) = self.super_type() {
1400 super_type.moving_steps(cx, context);
1401 }
1402
1403 if let Some(form_control) = self.element.as_maybe_form_control() {
1407 form_control.moving_steps(cx)
1408 }
1409 }
1410}
1411
1412impl Activatable for HTMLElement {
1413 fn as_element(&self) -> &Element {
1414 &self.element
1415 }
1416
1417 fn is_instance_activatable(&self) -> bool {
1418 self.element.local_name() == &local_name!("summary")
1419 }
1420
1421 fn activation_behavior(
1423 &self,
1424 cx: &mut js::context::JSContext,
1425 _event: &Event,
1426 _target: &EventTarget,
1427 ) {
1428 self.summary_activation_behavior(cx);
1429 }
1430}
1431
1432impl FormControl for HTMLElement {
1438 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1439 debug_assert!(self.is_form_associated_custom_element());
1440 self.element
1441 .get_element_internals()
1442 .and_then(|e| e.form_owner())
1443 }
1444
1445 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1446 debug_assert!(self.is_form_associated_custom_element());
1447 self.element
1448 .ensure_element_internals(CanGc::deprecated_note())
1449 .set_form_owner(form);
1450 }
1451
1452 fn to_element(&self) -> &Element {
1453 &self.element
1454 }
1455
1456 fn is_listed(&self) -> bool {
1457 debug_assert!(self.is_form_associated_custom_element());
1458 true
1459 }
1460
1461 }