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::rust::HandleObject;
12use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
13use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
14use style::attr::AttrValue;
15use stylo_dom::ElementState;
16
17use crate::dom::activation::Activatable;
18use crate::dom::attr::Attr;
19use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
20use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
21 EventHandlerNonNull, OnErrorEventHandlerNonNull,
22};
23use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
24use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
25use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
26use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
27use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
28use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
29use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
30use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
31use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
32use crate::dom::bindings::str::DOMString;
33use crate::dom::characterdata::CharacterData;
34use crate::dom::css::cssstyledeclaration::{
35 CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
36};
37use crate::dom::customelementregistry::{CallbackReaction, CustomElementState};
38use crate::dom::document::{Document, FocusInitiator};
39use crate::dom::documentfragment::DocumentFragment;
40use crate::dom::domstringmap::DOMStringMap;
41use crate::dom::element::{
42 AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
43 is_element_affected_by_legacy_background_presentational_hint,
44};
45use crate::dom::elementinternals::ElementInternals;
46use crate::dom::event::Event;
47use crate::dom::eventtarget::EventTarget;
48use crate::dom::html::htmlbodyelement::HTMLBodyElement;
49use crate::dom::html::htmldetailselement::HTMLDetailsElement;
50use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
51use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
52use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
53use crate::dom::html::htmlinputelement::{HTMLInputElement, InputType};
54use crate::dom::html::htmllabelelement::HTMLLabelElement;
55use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
56use crate::dom::medialist::MediaList;
57use crate::dom::node::{
58 BindContext, Node, NodeTraits, ShadowIncluding, UnbindContext, from_untrusted_node_address,
59};
60use crate::dom::shadowroot::ShadowRoot;
61use crate::dom::text::Text;
62use crate::dom::virtualmethods::VirtualMethods;
63use crate::script_runtime::CanGc;
64use crate::script_thread::ScriptThread;
65
66#[dom_struct]
67pub(crate) struct HTMLElement {
68 element: Element,
69 style_decl: MutNullableDom<CSSStyleDeclaration>,
70 dataset: MutNullableDom<DOMStringMap>,
71}
72
73impl HTMLElement {
74 pub(crate) fn new_inherited(
75 tag_name: LocalName,
76 prefix: Option<Prefix>,
77 document: &Document,
78 ) -> HTMLElement {
79 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
80 }
81
82 pub(crate) fn new_inherited_with_state(
83 state: ElementState,
84 tag_name: LocalName,
85 prefix: Option<Prefix>,
86 document: &Document,
87 ) -> HTMLElement {
88 HTMLElement {
89 element: Element::new_inherited_with_state(
90 state,
91 tag_name,
92 ns!(html),
93 prefix,
94 document,
95 ),
96 style_decl: Default::default(),
97 dataset: Default::default(),
98 }
99 }
100
101 pub(crate) fn new(
102 local_name: LocalName,
103 prefix: Option<Prefix>,
104 document: &Document,
105 proto: Option<HandleObject>,
106 can_gc: CanGc,
107 ) -> DomRoot<HTMLElement> {
108 Node::reflect_node_with_proto(
109 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
110 document,
111 proto,
112 can_gc,
113 )
114 }
115
116 fn is_body_or_frameset(&self) -> bool {
117 let eventtarget = self.upcast::<EventTarget>();
118 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
119 }
120
121 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
127 let node = self.upcast::<Node>();
128 let window = node.owner_window();
129 let element = self.as_element();
130
131 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
133 if element_not_rendered {
134 return node.GetTextContent().unwrap();
135 }
136
137 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
138 let text = window
139 .layout()
140 .query_element_inner_outer_text(node.to_trusted_node_address());
141
142 DOMString::from(text)
143 }
144
145 pub(crate) fn set_inner_text(&self, input: DOMString, can_gc: CanGc) {
147 let fragment = self.rendered_text_fragment(input, can_gc);
150
151 Node::replace_all(Some(fragment.upcast()), self.upcast::<Node>(), can_gc);
153 }
154
155 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
157 self.upcast::<Element>()
161 .get_attribute(&ns!(), &local_name!("media"))
162 .is_none_or(|media| {
163 MediaList::matches_environment(&self.owner_document(), &media.value())
164 })
165 }
166
167 pub(crate) fn is_editing_host(&self) -> bool {
169 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
171 }
174}
175
176impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
177 fn Style(&self, can_gc: CanGc) -> DomRoot<CSSStyleDeclaration> {
179 self.style_decl.or_init(|| {
180 let global = self.owner_window();
181 CSSStyleDeclaration::new(
182 &global,
183 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
184 None,
185 CSSModificationAccess::ReadWrite,
186 can_gc,
187 )
188 })
189 }
190
191 make_getter!(Title, "title");
193 make_setter!(SetTitle, "title");
195
196 make_getter!(Lang, "lang");
198 make_setter!(SetLang, "lang");
200
201 make_enumerated_getter!(
203 Dir,
204 "dir",
205 "ltr" | "rtl" | "auto",
206 missing => "",
207 invalid => ""
208 );
209
210 make_setter!(SetDir, "dir");
212
213 make_bool_getter!(Hidden, "hidden");
215 make_bool_setter!(SetHidden, "hidden");
217
218 global_event_handlers!(NoOnload);
220
221 fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
223 self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
224 }
225
226 fn GetOnerror(&self, can_gc: CanGc) -> Option<Rc<OnErrorEventHandlerNonNull>> {
228 if self.is_body_or_frameset() {
229 let document = self.owner_document();
230 if document.has_browsing_context() {
231 document.window().GetOnerror()
232 } else {
233 None
234 }
235 } else {
236 self.upcast::<EventTarget>()
237 .get_event_handler_common("error", can_gc)
238 }
239 }
240
241 fn SetOnerror(&self, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
243 if self.is_body_or_frameset() {
244 let document = self.owner_document();
245 if document.has_browsing_context() {
246 document.window().SetOnerror(listener)
247 }
248 } else {
249 self.upcast::<EventTarget>()
251 .set_error_event_handler("error", listener)
252 }
253 }
254
255 fn GetOnload(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
257 if self.is_body_or_frameset() {
258 let document = self.owner_document();
259 if document.has_browsing_context() {
260 document.window().GetOnload()
261 } else {
262 None
263 }
264 } else {
265 self.upcast::<EventTarget>()
266 .get_event_handler_common("load", can_gc)
267 }
268 }
269
270 fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) {
272 if self.is_body_or_frameset() {
273 let document = self.owner_document();
274 if document.has_browsing_context() {
275 document.window().SetOnload(listener)
276 }
277 } else {
278 self.upcast::<EventTarget>()
279 .set_event_handler_common("load", listener)
280 }
281 }
282
283 fn GetOnblur(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
285 if self.is_body_or_frameset() {
286 let document = self.owner_document();
287 if document.has_browsing_context() {
288 document.window().GetOnblur()
289 } else {
290 None
291 }
292 } else {
293 self.upcast::<EventTarget>()
294 .get_event_handler_common("blur", can_gc)
295 }
296 }
297
298 fn SetOnblur(&self, listener: Option<Rc<EventHandlerNonNull>>) {
300 if self.is_body_or_frameset() {
301 let document = self.owner_document();
302 if document.has_browsing_context() {
303 document.window().SetOnblur(listener)
304 }
305 } else {
306 self.upcast::<EventTarget>()
307 .set_event_handler_common("blur", listener)
308 }
309 }
310
311 fn GetOnfocus(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
313 if self.is_body_or_frameset() {
314 let document = self.owner_document();
315 if document.has_browsing_context() {
316 document.window().GetOnfocus()
317 } else {
318 None
319 }
320 } else {
321 self.upcast::<EventTarget>()
322 .get_event_handler_common("focus", can_gc)
323 }
324 }
325
326 fn SetOnfocus(&self, listener: Option<Rc<EventHandlerNonNull>>) {
328 if self.is_body_or_frameset() {
329 let document = self.owner_document();
330 if document.has_browsing_context() {
331 document.window().SetOnfocus(listener)
332 }
333 } else {
334 self.upcast::<EventTarget>()
335 .set_event_handler_common("focus", listener)
336 }
337 }
338
339 fn GetOnresize(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
341 if self.is_body_or_frameset() {
342 let document = self.owner_document();
343 if document.has_browsing_context() {
344 document.window().GetOnresize()
345 } else {
346 None
347 }
348 } else {
349 self.upcast::<EventTarget>()
350 .get_event_handler_common("resize", can_gc)
351 }
352 }
353
354 fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) {
356 if self.is_body_or_frameset() {
357 let document = self.owner_document();
358 if document.has_browsing_context() {
359 document.window().SetOnresize(listener)
360 }
361 } else {
362 self.upcast::<EventTarget>()
363 .set_event_handler_common("resize", listener)
364 }
365 }
366
367 fn GetOnscroll(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
369 if self.is_body_or_frameset() {
370 let document = self.owner_document();
371 if document.has_browsing_context() {
372 document.window().GetOnscroll()
373 } else {
374 None
375 }
376 } else {
377 self.upcast::<EventTarget>()
378 .get_event_handler_common("scroll", can_gc)
379 }
380 }
381
382 fn SetOnscroll(&self, listener: Option<Rc<EventHandlerNonNull>>) {
384 if self.is_body_or_frameset() {
385 let document = self.owner_document();
386 if document.has_browsing_context() {
387 document.window().SetOnscroll(listener)
388 }
389 } else {
390 self.upcast::<EventTarget>()
391 .set_event_handler_common("scroll", listener)
392 }
393 }
394
395 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
397 let atoms = self
398 .element
399 .get_tokenlist_attribute(&local_name!("itemtype"));
400
401 if atoms.is_empty() {
402 return None;
403 }
404
405 #[expect(clippy::mutable_key_type)]
406 let mut item_attr_values = HashSet::new();
408 for attr_value in &atoms {
409 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
410 }
411
412 Some(item_attr_values.into_iter().collect())
413 }
414
415 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
417 let atoms = self
418 .element
419 .get_tokenlist_attribute(&local_name!("itemprop"));
420
421 if atoms.is_empty() {
422 return None;
423 }
424
425 #[expect(clippy::mutable_key_type)]
426 let mut item_attr_values = HashSet::new();
428 for attr_value in &atoms {
429 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
430 }
431
432 Some(item_attr_values.into_iter().collect())
433 }
434
435 fn Click(&self, can_gc: CanGc) {
437 let element = self.as_element();
438 if element.disabled_state() {
439 return;
440 }
441 if element.click_in_progress() {
442 return;
443 }
444 element.set_click_in_progress(true);
445
446 self.upcast::<Node>()
447 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
448 element.set_click_in_progress(false);
449 }
450
451 fn Focus(&self, options: &FocusOptions, can_gc: CanGc) {
453 let document = self.owner_document();
456 document.request_focus_with_options(
457 Some(self.upcast()),
458 FocusInitiator::Local,
459 FocusOptions {
460 preventScroll: options.preventScroll,
461 },
462 can_gc,
463 );
464 }
465
466 fn Blur(&self, can_gc: CanGc) {
468 if !self.as_element().focus_state() {
471 return;
472 }
473 let document = self.owner_document();
475 document.request_focus(None, FocusInitiator::Local, can_gc);
476 }
477
478 #[expect(unsafe_code)]
480 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
481 self.owner_window()
482 .scroll_container_query(
483 Some(self.upcast()),
484 ScrollContainerQueryFlags::ForScrollParent,
485 )
486 .and_then(|response| match response {
487 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
488 ScrollContainerResponse::Element(parent_node_address, _) => {
489 let node = unsafe { from_untrusted_node_address(parent_node_address) };
490 DomRoot::downcast(node)
491 },
492 })
493 }
494
495 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
497 if self.is::<HTMLBodyElement>() || self.upcast::<Element>().is_root() {
498 return None;
499 }
500
501 let node = self.upcast::<Node>();
502 let window = self.owner_window();
503 let (element, _) = window.offset_parent_query(node);
504
505 element
506 }
507
508 fn OffsetTop(&self) -> i32 {
510 if self.is_body_element() {
511 return 0;
512 }
513
514 let node = self.upcast::<Node>();
515 let window = self.owner_window();
516 let (_, rect) = window.offset_parent_query(node);
517
518 rect.origin.y.to_nearest_px()
519 }
520
521 fn OffsetLeft(&self) -> i32 {
523 if self.is_body_element() {
524 return 0;
525 }
526
527 let node = self.upcast::<Node>();
528 let window = self.owner_window();
529 let (_, rect) = window.offset_parent_query(node);
530
531 rect.origin.x.to_nearest_px()
532 }
533
534 fn OffsetWidth(&self) -> i32 {
536 let node = self.upcast::<Node>();
537 let window = self.owner_window();
538 let (_, rect) = window.offset_parent_query(node);
539
540 rect.size.width.to_nearest_px()
541 }
542
543 fn OffsetHeight(&self) -> i32 {
545 let node = self.upcast::<Node>();
546 let window = self.owner_window();
547 let (_, rect) = window.offset_parent_query(node);
548
549 rect.size.height.to_nearest_px()
550 }
551
552 fn InnerText(&self) -> DOMString {
554 self.get_inner_outer_text()
555 }
556
557 fn SetInnerText(&self, input: DOMString, can_gc: CanGc) {
559 self.set_inner_text(input, can_gc)
560 }
561
562 fn GetOuterText(&self) -> Fallible<DOMString> {
564 Ok(self.get_inner_outer_text())
565 }
566
567 fn SetOuterText(&self, input: DOMString, can_gc: CanGc) -> Fallible<()> {
569 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
571 return Err(Error::NoModificationAllowed(None));
572 };
573
574 let node = self.upcast::<Node>();
575 let document = self.owner_document();
576
577 let next = node.GetNextSibling();
579
580 let previous = node.GetPreviousSibling();
582
583 let fragment = self.rendered_text_fragment(input, can_gc);
586
587 if fragment.upcast::<Node>().children_count() == 0 {
590 let text_node = Text::new(DOMString::from("".to_owned()), &document, can_gc);
591
592 fragment
593 .upcast::<Node>()
594 .AppendChild(text_node.upcast(), can_gc)?;
595 }
596
597 parent.ReplaceChild(fragment.upcast(), node, can_gc)?;
599
600 if let Some(next_sibling) = next {
603 if let Some(node) = next_sibling.GetPreviousSibling() {
604 Self::merge_with_the_next_text_node(node, can_gc);
605 }
606 }
607
608 if let Some(previous) = previous {
610 Self::merge_with_the_next_text_node(previous, can_gc)
611 }
612
613 Ok(())
614 }
615
616 fn Translate(&self) -> bool {
618 self.as_element().is_translate_enabled()
619 }
620
621 fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
623 self.as_element().set_string_attribute(
624 &html5ever::local_name!("translate"),
625 match yesno {
626 true => DOMString::from("yes"),
627 false => DOMString::from("no"),
628 },
629 can_gc,
630 );
631 }
632
633 make_enumerated_getter!(
635 ContentEditable,
636 "contenteditable",
637 "true" | "false" | "plaintext-only",
638 missing => "inherit",
639 invalid => "inherit",
640 empty => "true"
641 );
642
643 fn SetContentEditable(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
645 let lower_value = value.to_ascii_lowercase();
646 let element = self.upcast::<Element>();
647 let attr_name = &local_name!("contenteditable");
648 match lower_value.as_ref() {
649 "inherit" => {
651 element.remove_attribute_by_name(attr_name, can_gc);
652 },
653 "true" | "false" | "plaintext-only" => {
657 element.set_attribute(attr_name, AttrValue::String(lower_value), can_gc);
658 },
659 _ => return Err(Error::Syntax(None)),
661 };
662 Ok(())
663 }
664
665 fn IsContentEditable(&self) -> bool {
667 self.is_editing_host() || self.upcast::<Node>().is_editable()
669 }
670
671 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
673 let element = self.as_element();
674 if element.get_is().is_some() {
676 return Err(Error::NotSupported(None));
677 }
678
679 let registry = self.owner_window().CustomElements();
684 let definition = registry.lookup_definition(self.as_element().local_name(), None);
685
686 let definition = match definition {
688 Some(definition) => definition,
689 None => return Err(Error::NotSupported(None)),
690 };
691
692 if definition.disable_internals {
694 return Err(Error::NotSupported(None));
695 }
696
697 let internals = element.ensure_element_internals(can_gc);
699 if internals.attached() {
700 return Err(Error::NotSupported(None));
701 }
702
703 if !matches!(
706 element.get_custom_element_state(),
707 CustomElementState::Precustomized | CustomElementState::Custom
708 ) {
709 return Err(Error::NotSupported(None));
710 }
711
712 if self.is_form_associated_custom_element() {
713 element.init_state_for_internals();
714 }
715
716 internals.set_attached();
718 Ok(internals)
719 }
720
721 fn Nonce(&self) -> DOMString {
723 self.as_element().nonce_value().into()
724 }
725
726 fn SetNonce(&self, value: DOMString) {
728 self.as_element()
729 .update_nonce_internal_slot(value.to_string())
730 }
731
732 fn Autofocus(&self) -> bool {
734 self.element.has_attribute(&local_name!("autofocus"))
735 }
736
737 fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
739 self.element
740 .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
741 }
742}
743
744fn append_text_node_to_fragment(
745 document: &Document,
746 fragment: &DocumentFragment,
747 text: String,
748 can_gc: CanGc,
749) {
750 let text = Text::new(DOMString::from(text), document, can_gc);
751 fragment
752 .upcast::<Node>()
753 .AppendChild(text.upcast(), can_gc)
754 .unwrap();
755}
756
757static DATA_PREFIX: &str = "data-";
760static DATA_HYPHEN_SEPARATOR: char = '\x2d';
761
762fn to_snake_case(name: DOMString) -> DOMString {
763 let mut attr_name = String::with_capacity(name.len() + DATA_PREFIX.len());
764 attr_name.push_str(DATA_PREFIX);
765 for ch in name.str().chars() {
766 if ch.is_ascii_uppercase() {
767 attr_name.push(DATA_HYPHEN_SEPARATOR);
768 attr_name.push(ch.to_ascii_lowercase());
769 } else {
770 attr_name.push(ch);
771 }
772 }
773 DOMString::from(attr_name)
774}
775
776fn to_camel_case(name: &str) -> Option<DOMString> {
782 if !name.starts_with(DATA_PREFIX) {
783 return None;
784 }
785 let name = &name[5..];
786 let has_uppercase = name.chars().any(|curr_char| curr_char.is_ascii_uppercase());
787 if has_uppercase {
788 return None;
789 }
790 let mut result = String::with_capacity(name.len().saturating_sub(DATA_PREFIX.len()));
791 let mut name_chars = name.chars();
792 while let Some(curr_char) = name_chars.next() {
793 if curr_char == DATA_HYPHEN_SEPARATOR {
795 if let Some(next_char) = name_chars.next() {
796 if next_char.is_ascii_lowercase() {
797 result.push(next_char.to_ascii_uppercase());
798 } else {
799 result.push(curr_char);
800 result.push(next_char);
801 }
802 } else {
803 result.push(curr_char);
804 }
805 } else {
806 result.push(curr_char);
807 }
808 }
809 Some(DOMString::from(result))
810}
811
812impl HTMLElement {
813 pub(crate) fn set_custom_attr(
814 &self,
815 name: DOMString,
816 value: DOMString,
817 can_gc: CanGc,
818 ) -> ErrorResult {
819 if name
820 .str()
821 .chars()
822 .skip_while(|&ch| ch != '\u{2d}')
823 .nth(1)
824 .is_some_and(|ch| ch.is_ascii_lowercase())
825 {
826 return Err(Error::Syntax(None));
827 }
828 self.as_element()
829 .set_custom_attribute(to_snake_case(name), value, can_gc)
830 }
831
832 pub(crate) fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> {
833 let local_name = LocalName::from(to_snake_case(local_name));
835 self.as_element()
836 .get_attribute(&ns!(), &local_name)
837 .map(|attr| {
838 DOMString::from(&**attr.value()) })
840 }
841
842 pub(crate) fn delete_custom_attr(&self, local_name: DOMString, can_gc: CanGc) {
843 let local_name = LocalName::from(to_snake_case(local_name));
845 self.as_element()
846 .remove_attribute(&ns!(), &local_name, can_gc);
847 }
848
849 pub(crate) fn is_labelable_element(&self) -> bool {
851 match self.upcast::<Node>().type_id() {
852 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
853 HTMLElementTypeId::HTMLInputElement => {
854 self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
855 },
856 HTMLElementTypeId::HTMLButtonElement |
857 HTMLElementTypeId::HTMLMeterElement |
858 HTMLElementTypeId::HTMLOutputElement |
859 HTMLElementTypeId::HTMLProgressElement |
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_form_associated_custom_element(&self) -> bool {
870 if let Some(definition) = self.as_element().get_custom_element_definition() {
871 definition.is_autonomous() && definition.form_associated
872 } else {
873 false
874 }
875 }
876
877 pub(crate) fn is_listed_element(&self) -> bool {
879 match self.upcast::<Node>().type_id() {
880 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
881 HTMLElementTypeId::HTMLButtonElement |
882 HTMLElementTypeId::HTMLFieldSetElement |
883 HTMLElementTypeId::HTMLInputElement |
884 HTMLElementTypeId::HTMLObjectElement |
885 HTMLElementTypeId::HTMLOutputElement |
886 HTMLElementTypeId::HTMLSelectElement |
887 HTMLElementTypeId::HTMLTextAreaElement => true,
888 _ => self.is_form_associated_custom_element(),
889 },
890 _ => false,
891 }
892 }
893
894 pub(crate) fn is_body_element(&self) -> bool {
896 let self_node = self.upcast::<Node>();
897 self_node.GetParentNode().is_some_and(|parent| {
898 let parent_node = parent.upcast::<Node>();
899 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
900 parent_node.is::<HTMLHtmlElement>() &&
901 self_node
902 .preceding_siblings()
903 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
904 })
905 }
906
907 pub(crate) fn is_submittable_element(&self) -> bool {
909 match self.upcast::<Node>().type_id() {
910 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
911 HTMLElementTypeId::HTMLButtonElement |
912 HTMLElementTypeId::HTMLInputElement |
913 HTMLElementTypeId::HTMLSelectElement |
914 HTMLElementTypeId::HTMLTextAreaElement => true,
915 _ => self.is_form_associated_custom_element(),
916 },
917 _ => false,
918 }
919 }
920
921 pub(crate) fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> {
922 let element = self.as_element();
923 element
924 .attrs()
925 .iter()
926 .filter_map(|attr| {
927 let raw_name = attr.local_name();
928 to_camel_case(raw_name)
929 })
930 .collect()
931 }
932
933 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
936 let element = self.as_element();
937
938 let root_element = element.root_element();
949 let root_node = root_element.upcast::<Node>();
950 root_node
951 .traverse_preorder(ShadowIncluding::No)
952 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
953 .filter(|elem| match elem.GetControl() {
954 Some(control) => &*control == self,
955 _ => false,
956 })
957 .nth(index as usize)
958 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
959 }
960
961 pub(crate) fn labels_count(&self) -> u32 {
964 let element = self.as_element();
966 let root_element = element.root_element();
967 let root_node = root_element.upcast::<Node>();
968 root_node
969 .traverse_preorder(ShadowIncluding::No)
970 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
971 .filter(|elem| match elem.GetControl() {
972 Some(control) => &*control == self,
973 _ => false,
974 })
975 .count() as u32
976 }
977
978 pub(crate) fn directionality(&self) -> Option<String> {
982 let element_direction = &self.Dir();
983
984 if element_direction == "ltr" {
985 return Some("ltr".to_owned());
986 }
987
988 if element_direction == "rtl" {
989 return Some("rtl".to_owned());
990 }
991
992 if let Some(input) = self.downcast::<HTMLInputElement>() {
993 if input.input_type() == InputType::Tel {
994 return Some("ltr".to_owned());
995 }
996 }
997
998 if element_direction == "auto" {
999 if let Some(directionality) = self
1000 .downcast::<HTMLInputElement>()
1001 .and_then(|input| input.auto_directionality())
1002 {
1003 return Some(directionality);
1004 }
1005
1006 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
1007 return Some(area.auto_directionality());
1008 }
1009 }
1010
1011 None
1018 }
1019
1020 pub(crate) fn summary_activation_behavior(&self) {
1022 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
1023
1024 if !self.is_a_summary_for_its_parent_details() {
1026 return;
1027 }
1028
1029 let parent = if self.is_implicit_summary_element() {
1031 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1032 .unwrap()
1033 } else {
1034 self.upcast::<Node>()
1035 .GetParentNode()
1036 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1037 .unwrap()
1038 };
1039
1040 parent.toggle();
1043 }
1044
1045 fn is_a_summary_for_its_parent_details(&self) -> bool {
1047 if self.is_implicit_summary_element() {
1048 return true;
1049 }
1050
1051 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1054 return false;
1055 };
1056
1057 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1059 return false;
1060 };
1061
1062 details
1066 .find_corresponding_summary_element()
1067 .is_some_and(|summary| &*summary == self.upcast())
1068 }
1069
1070 fn is_implicit_summary_element(&self) -> bool {
1073 self.containing_shadow_root()
1077 .as_deref()
1078 .map(ShadowRoot::Host)
1079 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1080 }
1081
1082 fn rendered_text_fragment(&self, input: DOMString, can_gc: CanGc) -> DomRoot<DocumentFragment> {
1084 let document = self.owner_document();
1086 let fragment = DocumentFragment::new(&document, can_gc);
1087
1088 let input = input.str();
1091 let mut position = input.chars().peekable();
1092
1093 let mut text = String::new();
1095
1096 while let Some(ch) = position.next() {
1098 match ch {
1099 '\u{000A}' | '\u{000D}' => {
1102 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1103 position.next();
1106 }
1107
1108 if !text.is_empty() {
1109 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1110 text = String::new();
1111 }
1112
1113 let br = Element::create(
1114 QualName::new(None, ns!(html), local_name!("br")),
1115 None,
1116 &document,
1117 ElementCreator::ScriptCreated,
1118 CustomElementCreationMode::Asynchronous,
1119 None,
1120 can_gc,
1121 );
1122 fragment
1123 .upcast::<Node>()
1124 .AppendChild(br.upcast(), can_gc)
1125 .unwrap();
1126 },
1127 _ => {
1128 text.push(ch);
1131 },
1132 }
1133 }
1134
1135 if !text.is_empty() {
1138 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1139 }
1140
1141 fragment
1142 }
1143
1144 fn merge_with_the_next_text_node(node: DomRoot<Node>, can_gc: CanGc) {
1150 if !node.is::<Text>() {
1152 return;
1153 }
1154
1155 let next = match node.GetNextSibling() {
1157 Some(next) => next,
1158 None => return,
1159 };
1160
1161 if !next.is::<Text>() {
1163 return;
1164 }
1165 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1167 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1168 node_chars
1169 .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1170 .expect("Got chars from Text");
1171
1172 next.remove_self(can_gc);
1174 }
1175}
1176
1177impl VirtualMethods for HTMLElement {
1178 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1179 Some(self.as_element() as &dyn VirtualMethods)
1180 }
1181
1182 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1183 self.super_type()
1184 .unwrap()
1185 .attribute_mutated(attr, mutation, can_gc);
1186 let element = self.as_element();
1187 match (attr.local_name(), mutation) {
1188 (name, mutation)
1190 if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1191 {
1192 let evtarget = self.upcast::<EventTarget>();
1193 let event_name = &name[2..];
1194 match mutation {
1195 AttributeMutation::Set(..) => {
1197 let source = &**attr.value();
1198 let source_line = 1; evtarget.set_event_handler_uncompiled(
1200 self.owner_window().get_url(),
1201 source_line,
1202 event_name,
1203 source,
1204 );
1205 },
1206 AttributeMutation::Removed => {
1208 evtarget.set_event_handler_common::<EventHandlerNonNull>(event_name, None);
1209 },
1210 }
1211 },
1212
1213 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1214 self.form_attribute_mutated(mutation, can_gc);
1215 },
1216 (&local_name!("disabled"), AttributeMutation::Set(..))
1218 if self.is_form_associated_custom_element() && element.enabled_state() =>
1219 {
1220 element.set_disabled_state(true);
1221 element.set_enabled_state(false);
1222 ScriptThread::enqueue_callback_reaction(
1223 element,
1224 CallbackReaction::FormDisabled(true),
1225 None,
1226 );
1227 },
1228 (&local_name!("disabled"), AttributeMutation::Removed)
1231 if self.is_form_associated_custom_element() && element.disabled_state() =>
1232 {
1233 element.set_disabled_state(false);
1234 element.set_enabled_state(true);
1235 element.check_ancestors_disabled_state_for_form_control();
1236 if element.enabled_state() {
1237 ScriptThread::enqueue_callback_reaction(
1238 element,
1239 CallbackReaction::FormDisabled(false),
1240 None,
1241 );
1242 }
1243 },
1244 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1245 match mutation {
1246 AttributeMutation::Set(..) => {
1247 element.set_read_write_state(true);
1248 },
1249 AttributeMutation::Removed => {
1250 element.set_read_write_state(false);
1251 },
1252 }
1253 },
1254 (&local_name!("nonce"), mutation) => match mutation {
1255 AttributeMutation::Set(..) => {
1256 let nonce = &**attr.value();
1257 element.update_nonce_internal_slot(nonce.to_owned());
1258 },
1259 AttributeMutation::Removed => {
1260 element.update_nonce_internal_slot("".to_owned());
1261 },
1262 },
1263 _ => {},
1264 }
1265 }
1266
1267 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1268 if let Some(super_type) = self.super_type() {
1269 super_type.bind_to_tree(context, can_gc);
1270 }
1271 let element = self.as_element();
1272 element.update_sequentially_focusable_status(can_gc);
1273
1274 if self.is_form_associated_custom_element() && element.enabled_state() {
1277 element.check_ancestors_disabled_state_for_form_control();
1278 if element.disabled_state() {
1279 ScriptThread::enqueue_callback_reaction(
1280 element,
1281 CallbackReaction::FormDisabled(true),
1282 None,
1283 );
1284 }
1285 }
1286 }
1287
1288 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1289 if let Some(super_type) = self.super_type() {
1290 super_type.unbind_from_tree(context, can_gc);
1291 }
1292
1293 let element = self.as_element();
1298 if self.is_form_associated_custom_element() && element.disabled_state() {
1299 element.check_disabled_attribute();
1300 element.check_ancestors_disabled_state_for_form_control();
1301 if element.enabled_state() {
1302 ScriptThread::enqueue_callback_reaction(
1303 element,
1304 CallbackReaction::FormDisabled(false),
1305 None,
1306 );
1307 }
1308 }
1309 }
1310
1311 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1312 let element = self.upcast::<Element>();
1313 if is_element_affected_by_legacy_background_presentational_hint(
1314 element.namespace(),
1315 element.local_name(),
1316 ) && attr.local_name() == &local_name!("background")
1317 {
1318 return true;
1319 }
1320
1321 self.super_type()
1322 .unwrap()
1323 .attribute_affects_presentational_hints(attr)
1324 }
1325
1326 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1327 let element = self.upcast::<Element>();
1328 match *name {
1329 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1330 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1331 local_name!("background")
1332 if is_element_affected_by_legacy_background_presentational_hint(
1333 element.namespace(),
1334 element.local_name(),
1335 ) =>
1336 {
1337 AttrValue::from_resolved_url(
1338 &self.owner_document().base_url().get_arc(),
1339 value.into(),
1340 )
1341 },
1342 _ => self
1343 .super_type()
1344 .unwrap()
1345 .parse_plain_attribute(name, value),
1346 }
1347 }
1348}
1349
1350impl Activatable for HTMLElement {
1351 fn as_element(&self) -> &Element {
1352 self.upcast::<Element>()
1353 }
1354
1355 fn is_instance_activatable(&self) -> bool {
1356 self.as_element().local_name() == &local_name!("summary")
1357 }
1358
1359 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
1361 self.summary_activation_behavior();
1362 }
1363}
1364
1365impl FormControl for HTMLElement {
1371 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1372 debug_assert!(self.is_form_associated_custom_element());
1373 self.as_element()
1374 .get_element_internals()
1375 .and_then(|e| e.form_owner())
1376 }
1377
1378 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1379 debug_assert!(self.is_form_associated_custom_element());
1380 self.as_element()
1381 .ensure_element_internals(CanGc::note())
1382 .set_form_owner(form);
1383 }
1384
1385 fn to_element(&self) -> &Element {
1386 self.as_element()
1387 }
1388
1389 fn is_listed(&self) -> bool {
1390 debug_assert!(self.is_form_associated_custom_element());
1391 true
1392 }
1393
1394 }