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::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
42use crate::dom::elementinternals::ElementInternals;
43use crate::dom::event::Event;
44use crate::dom::eventtarget::EventTarget;
45use crate::dom::html::htmlbodyelement::HTMLBodyElement;
46use crate::dom::html::htmldetailselement::HTMLDetailsElement;
47use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
48use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
49use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
50use crate::dom::html::htmlinputelement::{HTMLInputElement, InputType};
51use crate::dom::html::htmllabelelement::HTMLLabelElement;
52use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
53use crate::dom::node::{
54 BindContext, Node, NodeTraits, ShadowIncluding, UnbindContext, from_untrusted_node_address,
55};
56use crate::dom::shadowroot::ShadowRoot;
57use crate::dom::text::Text;
58use crate::dom::virtualmethods::VirtualMethods;
59use crate::script_runtime::CanGc;
60use crate::script_thread::ScriptThread;
61
62#[dom_struct]
63pub(crate) struct HTMLElement {
64 element: Element,
65 style_decl: MutNullableDom<CSSStyleDeclaration>,
66 dataset: MutNullableDom<DOMStringMap>,
67}
68
69impl HTMLElement {
70 pub(crate) fn new_inherited(
71 tag_name: LocalName,
72 prefix: Option<Prefix>,
73 document: &Document,
74 ) -> HTMLElement {
75 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
76 }
77
78 pub(crate) fn new_inherited_with_state(
79 state: ElementState,
80 tag_name: LocalName,
81 prefix: Option<Prefix>,
82 document: &Document,
83 ) -> HTMLElement {
84 HTMLElement {
85 element: Element::new_inherited_with_state(
86 state,
87 tag_name,
88 ns!(html),
89 prefix,
90 document,
91 ),
92 style_decl: Default::default(),
93 dataset: Default::default(),
94 }
95 }
96
97 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
98 pub(crate) fn new(
99 local_name: LocalName,
100 prefix: Option<Prefix>,
101 document: &Document,
102 proto: Option<HandleObject>,
103 can_gc: CanGc,
104 ) -> DomRoot<HTMLElement> {
105 Node::reflect_node_with_proto(
106 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
107 document,
108 proto,
109 can_gc,
110 )
111 }
112
113 fn is_body_or_frameset(&self) -> bool {
114 let eventtarget = self.upcast::<EventTarget>();
115 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
116 }
117
118 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
124 let node = self.upcast::<Node>();
125 let window = node.owner_window();
126 let element = self.as_element();
127
128 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
130 if element_not_rendered {
131 return node.GetTextContent().unwrap();
132 }
133
134 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
135 let text = window
136 .layout()
137 .query_element_inner_outer_text(node.to_trusted_node_address());
138
139 DOMString::from(text)
140 }
141
142 pub(crate) fn set_inner_text(&self, input: DOMString, can_gc: CanGc) {
144 let fragment = self.rendered_text_fragment(input, can_gc);
147
148 Node::replace_all(Some(fragment.upcast()), self.upcast::<Node>(), can_gc);
150 }
151}
152
153impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
154 fn Style(&self, can_gc: CanGc) -> DomRoot<CSSStyleDeclaration> {
156 self.style_decl.or_init(|| {
157 let global = self.owner_window();
158 CSSStyleDeclaration::new(
159 &global,
160 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
161 None,
162 CSSModificationAccess::ReadWrite,
163 can_gc,
164 )
165 })
166 }
167
168 make_getter!(Title, "title");
170 make_setter!(SetTitle, "title");
172
173 make_getter!(Lang, "lang");
175 make_setter!(SetLang, "lang");
177
178 make_enumerated_getter!(
180 Dir,
181 "dir",
182 "ltr" | "rtl" | "auto",
183 missing => "",
184 invalid => ""
185 );
186
187 make_setter!(SetDir, "dir");
189
190 make_bool_getter!(Hidden, "hidden");
192 make_bool_setter!(SetHidden, "hidden");
194
195 global_event_handlers!(NoOnload);
197
198 fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
200 self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
201 }
202
203 fn GetOnerror(&self, can_gc: CanGc) -> Option<Rc<OnErrorEventHandlerNonNull>> {
205 if self.is_body_or_frameset() {
206 let document = self.owner_document();
207 if document.has_browsing_context() {
208 document.window().GetOnerror()
209 } else {
210 None
211 }
212 } else {
213 self.upcast::<EventTarget>()
214 .get_event_handler_common("error", can_gc)
215 }
216 }
217
218 fn SetOnerror(&self, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
220 if self.is_body_or_frameset() {
221 let document = self.owner_document();
222 if document.has_browsing_context() {
223 document.window().SetOnerror(listener)
224 }
225 } else {
226 self.upcast::<EventTarget>()
228 .set_error_event_handler("error", listener)
229 }
230 }
231
232 fn GetOnload(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
234 if self.is_body_or_frameset() {
235 let document = self.owner_document();
236 if document.has_browsing_context() {
237 document.window().GetOnload()
238 } else {
239 None
240 }
241 } else {
242 self.upcast::<EventTarget>()
243 .get_event_handler_common("load", can_gc)
244 }
245 }
246
247 fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) {
249 if self.is_body_or_frameset() {
250 let document = self.owner_document();
251 if document.has_browsing_context() {
252 document.window().SetOnload(listener)
253 }
254 } else {
255 self.upcast::<EventTarget>()
256 .set_event_handler_common("load", listener)
257 }
258 }
259
260 fn GetOnblur(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
262 if self.is_body_or_frameset() {
263 let document = self.owner_document();
264 if document.has_browsing_context() {
265 document.window().GetOnblur()
266 } else {
267 None
268 }
269 } else {
270 self.upcast::<EventTarget>()
271 .get_event_handler_common("blur", can_gc)
272 }
273 }
274
275 fn SetOnblur(&self, listener: Option<Rc<EventHandlerNonNull>>) {
277 if self.is_body_or_frameset() {
278 let document = self.owner_document();
279 if document.has_browsing_context() {
280 document.window().SetOnblur(listener)
281 }
282 } else {
283 self.upcast::<EventTarget>()
284 .set_event_handler_common("blur", listener)
285 }
286 }
287
288 fn GetOnfocus(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
290 if self.is_body_or_frameset() {
291 let document = self.owner_document();
292 if document.has_browsing_context() {
293 document.window().GetOnfocus()
294 } else {
295 None
296 }
297 } else {
298 self.upcast::<EventTarget>()
299 .get_event_handler_common("focus", can_gc)
300 }
301 }
302
303 fn SetOnfocus(&self, listener: Option<Rc<EventHandlerNonNull>>) {
305 if self.is_body_or_frameset() {
306 let document = self.owner_document();
307 if document.has_browsing_context() {
308 document.window().SetOnfocus(listener)
309 }
310 } else {
311 self.upcast::<EventTarget>()
312 .set_event_handler_common("focus", listener)
313 }
314 }
315
316 fn GetOnresize(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
318 if self.is_body_or_frameset() {
319 let document = self.owner_document();
320 if document.has_browsing_context() {
321 document.window().GetOnresize()
322 } else {
323 None
324 }
325 } else {
326 self.upcast::<EventTarget>()
327 .get_event_handler_common("resize", can_gc)
328 }
329 }
330
331 fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) {
333 if self.is_body_or_frameset() {
334 let document = self.owner_document();
335 if document.has_browsing_context() {
336 document.window().SetOnresize(listener)
337 }
338 } else {
339 self.upcast::<EventTarget>()
340 .set_event_handler_common("resize", listener)
341 }
342 }
343
344 fn GetOnscroll(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
346 if self.is_body_or_frameset() {
347 let document = self.owner_document();
348 if document.has_browsing_context() {
349 document.window().GetOnscroll()
350 } else {
351 None
352 }
353 } else {
354 self.upcast::<EventTarget>()
355 .get_event_handler_common("scroll", can_gc)
356 }
357 }
358
359 fn SetOnscroll(&self, listener: Option<Rc<EventHandlerNonNull>>) {
361 if self.is_body_or_frameset() {
362 let document = self.owner_document();
363 if document.has_browsing_context() {
364 document.window().SetOnscroll(listener)
365 }
366 } else {
367 self.upcast::<EventTarget>()
368 .set_event_handler_common("scroll", listener)
369 }
370 }
371
372 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
374 let atoms = self
375 .element
376 .get_tokenlist_attribute(&local_name!("itemtype"));
377
378 if atoms.is_empty() {
379 return None;
380 }
381
382 #[allow(clippy::mutable_key_type)]
383 let mut item_attr_values = HashSet::new();
385 for attr_value in &atoms {
386 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
387 }
388
389 Some(item_attr_values.into_iter().collect())
390 }
391
392 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
394 let atoms = self
395 .element
396 .get_tokenlist_attribute(&local_name!("itemprop"));
397
398 if atoms.is_empty() {
399 return None;
400 }
401
402 #[allow(clippy::mutable_key_type)]
403 let mut item_attr_values = HashSet::new();
405 for attr_value in &atoms {
406 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
407 }
408
409 Some(item_attr_values.into_iter().collect())
410 }
411
412 fn Click(&self, can_gc: CanGc) {
414 let element = self.as_element();
415 if element.disabled_state() {
416 return;
417 }
418 if element.click_in_progress() {
419 return;
420 }
421 element.set_click_in_progress(true);
422
423 self.upcast::<Node>()
424 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
425 element.set_click_in_progress(false);
426 }
427
428 fn Focus(&self, options: &FocusOptions, can_gc: CanGc) {
430 let document = self.owner_document();
433 document.request_focus_with_options(
434 Some(self.upcast()),
435 FocusInitiator::Local,
436 FocusOptions {
437 preventScroll: options.preventScroll,
438 },
439 can_gc,
440 );
441 }
442
443 fn Blur(&self, can_gc: CanGc) {
445 if !self.as_element().focus_state() {
448 return;
449 }
450 let document = self.owner_document();
452 document.request_focus(None, FocusInitiator::Local, can_gc);
453 }
454
455 #[expect(unsafe_code)]
457 fn GetScrollParent(&self) -> Option<DomRoot<Element>> {
458 self.owner_window()
459 .scroll_container_query(
460 Some(self.upcast()),
461 ScrollContainerQueryFlags::ForScrollParent,
462 )
463 .and_then(|response| match response {
464 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
465 ScrollContainerResponse::Element(parent_node_address, _) => {
466 let node = unsafe { from_untrusted_node_address(parent_node_address) };
467 DomRoot::downcast(node)
468 },
469 })
470 }
471
472 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
474 if self.is::<HTMLBodyElement>() || self.upcast::<Element>().is_root() {
475 return None;
476 }
477
478 let node = self.upcast::<Node>();
479 let window = self.owner_window();
480 let (element, _) = window.offset_parent_query(node);
481
482 element
483 }
484
485 fn OffsetTop(&self) -> i32 {
487 if self.is_body_element() {
488 return 0;
489 }
490
491 let node = self.upcast::<Node>();
492 let window = self.owner_window();
493 let (_, rect) = window.offset_parent_query(node);
494
495 rect.origin.y.to_nearest_px()
496 }
497
498 fn OffsetLeft(&self) -> i32 {
500 if self.is_body_element() {
501 return 0;
502 }
503
504 let node = self.upcast::<Node>();
505 let window = self.owner_window();
506 let (_, rect) = window.offset_parent_query(node);
507
508 rect.origin.x.to_nearest_px()
509 }
510
511 fn OffsetWidth(&self) -> i32 {
513 let node = self.upcast::<Node>();
514 let window = self.owner_window();
515 let (_, rect) = window.offset_parent_query(node);
516
517 rect.size.width.to_nearest_px()
518 }
519
520 fn OffsetHeight(&self) -> i32 {
522 let node = self.upcast::<Node>();
523 let window = self.owner_window();
524 let (_, rect) = window.offset_parent_query(node);
525
526 rect.size.height.to_nearest_px()
527 }
528
529 fn InnerText(&self) -> DOMString {
531 self.get_inner_outer_text()
532 }
533
534 fn SetInnerText(&self, input: DOMString, can_gc: CanGc) {
536 self.set_inner_text(input, can_gc)
537 }
538
539 fn GetOuterText(&self) -> Fallible<DOMString> {
541 Ok(self.get_inner_outer_text())
542 }
543
544 fn SetOuterText(&self, input: DOMString, can_gc: CanGc) -> Fallible<()> {
546 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
548 return Err(Error::NoModificationAllowed);
549 };
550
551 let node = self.upcast::<Node>();
552 let document = self.owner_document();
553
554 let next = node.GetNextSibling();
556
557 let previous = node.GetPreviousSibling();
559
560 let fragment = self.rendered_text_fragment(input, can_gc);
563
564 if fragment.upcast::<Node>().children_count() == 0 {
567 let text_node = Text::new(DOMString::from("".to_owned()), &document, can_gc);
568
569 fragment
570 .upcast::<Node>()
571 .AppendChild(text_node.upcast(), can_gc)?;
572 }
573
574 parent.ReplaceChild(fragment.upcast(), node, can_gc)?;
576
577 if let Some(next_sibling) = next {
580 if let Some(node) = next_sibling.GetPreviousSibling() {
581 Self::merge_with_the_next_text_node(node, can_gc);
582 }
583 }
584
585 if let Some(previous) = previous {
587 Self::merge_with_the_next_text_node(previous, can_gc)
588 }
589
590 Ok(())
591 }
592
593 fn Translate(&self) -> bool {
595 self.as_element().is_translate_enabled()
596 }
597
598 fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
600 self.as_element().set_string_attribute(
601 &html5ever::local_name!("translate"),
602 match yesno {
603 true => DOMString::from("yes"),
604 false => DOMString::from("no"),
605 },
606 can_gc,
607 );
608 }
609
610 fn ContentEditable(&self) -> DOMString {
612 self.as_element()
614 .get_attribute(&ns!(), &local_name!("contenteditable"))
615 .map(|attr| DOMString::from(&**attr.value()))
616 .unwrap_or_else(|| DOMString::from("inherit"))
617 }
618
619 fn SetContentEditable(&self, _: DOMString) {
621 warn!("The contentEditable attribute is not implemented yet");
623 }
624
625 fn IsContentEditable(&self) -> bool {
627 false
629 }
630
631 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
633 let element = self.as_element();
634 if element.get_is().is_some() {
636 return Err(Error::NotSupported);
637 }
638
639 let registry = self.owner_window().CustomElements();
644 let definition = registry.lookup_definition(self.as_element().local_name(), None);
645
646 let definition = match definition {
648 Some(definition) => definition,
649 None => return Err(Error::NotSupported),
650 };
651
652 if definition.disable_internals {
654 return Err(Error::NotSupported);
655 }
656
657 let internals = element.ensure_element_internals(can_gc);
659 if internals.attached() {
660 return Err(Error::NotSupported);
661 }
662
663 if !matches!(
666 element.get_custom_element_state(),
667 CustomElementState::Precustomized | CustomElementState::Custom
668 ) {
669 return Err(Error::NotSupported);
670 }
671
672 if self.is_form_associated_custom_element() {
673 element.init_state_for_internals();
674 }
675
676 internals.set_attached();
678 Ok(internals)
679 }
680
681 fn Nonce(&self) -> DOMString {
683 self.as_element().nonce_value().into()
684 }
685
686 fn SetNonce(&self, value: DOMString) {
688 self.as_element()
689 .update_nonce_internal_slot(value.to_string())
690 }
691
692 fn Autofocus(&self) -> bool {
694 self.element.has_attribute(&local_name!("autofocus"))
695 }
696
697 fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
699 self.element
700 .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
701 }
702}
703
704fn append_text_node_to_fragment(
705 document: &Document,
706 fragment: &DocumentFragment,
707 text: String,
708 can_gc: CanGc,
709) {
710 let text = Text::new(DOMString::from(text), document, can_gc);
711 fragment
712 .upcast::<Node>()
713 .AppendChild(text.upcast(), can_gc)
714 .unwrap();
715}
716
717static DATA_PREFIX: &str = "data-";
720static DATA_HYPHEN_SEPARATOR: char = '\x2d';
721
722fn to_snake_case(name: DOMString) -> DOMString {
723 let mut attr_name = String::with_capacity(name.len() + DATA_PREFIX.len());
724 attr_name.push_str(DATA_PREFIX);
725 for ch in name.str().chars() {
726 if ch.is_ascii_uppercase() {
727 attr_name.push(DATA_HYPHEN_SEPARATOR);
728 attr_name.push(ch.to_ascii_lowercase());
729 } else {
730 attr_name.push(ch);
731 }
732 }
733 DOMString::from(attr_name)
734}
735
736fn to_camel_case(name: &str) -> Option<DOMString> {
742 if !name.starts_with(DATA_PREFIX) {
743 return None;
744 }
745 let name = &name[5..];
746 let has_uppercase = name.chars().any(|curr_char| curr_char.is_ascii_uppercase());
747 if has_uppercase {
748 return None;
749 }
750 let mut result = String::with_capacity(name.len().saturating_sub(DATA_PREFIX.len()));
751 let mut name_chars = name.chars();
752 while let Some(curr_char) = name_chars.next() {
753 if curr_char == DATA_HYPHEN_SEPARATOR {
755 if let Some(next_char) = name_chars.next() {
756 if next_char.is_ascii_lowercase() {
757 result.push(next_char.to_ascii_uppercase());
758 } else {
759 result.push(curr_char);
760 result.push(next_char);
761 }
762 } else {
763 result.push(curr_char);
764 }
765 } else {
766 result.push(curr_char);
767 }
768 }
769 Some(DOMString::from(result))
770}
771
772impl HTMLElement {
773 pub(crate) fn set_custom_attr(
774 &self,
775 name: DOMString,
776 value: DOMString,
777 can_gc: CanGc,
778 ) -> ErrorResult {
779 if name
780 .str()
781 .chars()
782 .skip_while(|&ch| ch != '\u{2d}')
783 .nth(1)
784 .is_some_and(|ch| ch.is_ascii_lowercase())
785 {
786 return Err(Error::Syntax(None));
787 }
788 self.as_element()
789 .set_custom_attribute(to_snake_case(name), value, can_gc)
790 }
791
792 pub(crate) fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> {
793 let local_name = LocalName::from(to_snake_case(local_name));
795 self.as_element()
796 .get_attribute(&ns!(), &local_name)
797 .map(|attr| {
798 DOMString::from(&**attr.value()) })
800 }
801
802 pub(crate) fn delete_custom_attr(&self, local_name: DOMString, can_gc: CanGc) {
803 let local_name = LocalName::from(to_snake_case(local_name));
805 self.as_element()
806 .remove_attribute(&ns!(), &local_name, can_gc);
807 }
808
809 pub(crate) fn is_labelable_element(&self) -> bool {
811 match self.upcast::<Node>().type_id() {
812 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
813 HTMLElementTypeId::HTMLInputElement => {
814 self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
815 },
816 HTMLElementTypeId::HTMLButtonElement |
817 HTMLElementTypeId::HTMLMeterElement |
818 HTMLElementTypeId::HTMLOutputElement |
819 HTMLElementTypeId::HTMLProgressElement |
820 HTMLElementTypeId::HTMLSelectElement |
821 HTMLElementTypeId::HTMLTextAreaElement => true,
822 _ => self.is_form_associated_custom_element(),
823 },
824 _ => false,
825 }
826 }
827
828 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
830 if let Some(definition) = self.as_element().get_custom_element_definition() {
831 definition.is_autonomous() && definition.form_associated
832 } else {
833 false
834 }
835 }
836
837 pub(crate) fn is_listed_element(&self) -> bool {
839 match self.upcast::<Node>().type_id() {
840 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
841 HTMLElementTypeId::HTMLButtonElement |
842 HTMLElementTypeId::HTMLFieldSetElement |
843 HTMLElementTypeId::HTMLInputElement |
844 HTMLElementTypeId::HTMLObjectElement |
845 HTMLElementTypeId::HTMLOutputElement |
846 HTMLElementTypeId::HTMLSelectElement |
847 HTMLElementTypeId::HTMLTextAreaElement => true,
848 _ => self.is_form_associated_custom_element(),
849 },
850 _ => false,
851 }
852 }
853
854 pub(crate) fn is_body_element(&self) -> bool {
856 let self_node = self.upcast::<Node>();
857 self_node.GetParentNode().is_some_and(|parent| {
858 let parent_node = parent.upcast::<Node>();
859 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
860 parent_node.is::<HTMLHtmlElement>() &&
861 self_node
862 .preceding_siblings()
863 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
864 })
865 }
866
867 pub(crate) fn is_submittable_element(&self) -> bool {
869 match self.upcast::<Node>().type_id() {
870 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
871 HTMLElementTypeId::HTMLButtonElement |
872 HTMLElementTypeId::HTMLInputElement |
873 HTMLElementTypeId::HTMLSelectElement |
874 HTMLElementTypeId::HTMLTextAreaElement => true,
875 _ => self.is_form_associated_custom_element(),
876 },
877 _ => false,
878 }
879 }
880
881 pub(crate) fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> {
882 let element = self.as_element();
883 element
884 .attrs()
885 .iter()
886 .filter_map(|attr| {
887 let raw_name = attr.local_name();
888 to_camel_case(raw_name)
889 })
890 .collect()
891 }
892
893 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
896 let element = self.as_element();
897
898 let root_element = element.root_element();
909 let root_node = root_element.upcast::<Node>();
910 root_node
911 .traverse_preorder(ShadowIncluding::No)
912 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
913 .filter(|elem| match elem.GetControl() {
914 Some(control) => &*control == self,
915 _ => false,
916 })
917 .nth(index as usize)
918 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
919 }
920
921 pub(crate) fn labels_count(&self) -> u32 {
924 let element = self.as_element();
926 let root_element = element.root_element();
927 let root_node = root_element.upcast::<Node>();
928 root_node
929 .traverse_preorder(ShadowIncluding::No)
930 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
931 .filter(|elem| match elem.GetControl() {
932 Some(control) => &*control == self,
933 _ => false,
934 })
935 .count() as u32
936 }
937
938 pub(crate) fn directionality(&self) -> Option<String> {
942 let element_direction = &self.Dir();
943
944 if element_direction == "ltr" {
945 return Some("ltr".to_owned());
946 }
947
948 if element_direction == "rtl" {
949 return Some("rtl".to_owned());
950 }
951
952 if let Some(input) = self.downcast::<HTMLInputElement>() {
953 if input.input_type() == InputType::Tel {
954 return Some("ltr".to_owned());
955 }
956 }
957
958 if element_direction == "auto" {
959 if let Some(directionality) = self
960 .downcast::<HTMLInputElement>()
961 .and_then(|input| input.auto_directionality())
962 {
963 return Some(directionality);
964 }
965
966 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
967 return Some(area.auto_directionality());
968 }
969 }
970
971 None
978 }
979
980 pub(crate) fn summary_activation_behavior(&self) {
982 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
983
984 if !self.is_a_summary_for_its_parent_details() {
986 return;
987 }
988
989 let parent = if self.is_implicit_summary_element() {
991 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
992 .unwrap()
993 } else {
994 self.upcast::<Node>()
995 .GetParentNode()
996 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
997 .unwrap()
998 };
999
1000 parent.toggle();
1003 }
1004
1005 fn is_a_summary_for_its_parent_details(&self) -> bool {
1007 if self.is_implicit_summary_element() {
1008 return true;
1009 }
1010
1011 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1014 return false;
1015 };
1016
1017 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1019 return false;
1020 };
1021
1022 details
1026 .find_corresponding_summary_element()
1027 .is_some_and(|summary| &*summary == self.upcast())
1028 }
1029
1030 fn is_implicit_summary_element(&self) -> bool {
1033 self.containing_shadow_root()
1037 .as_deref()
1038 .map(ShadowRoot::Host)
1039 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1040 }
1041
1042 fn rendered_text_fragment(&self, input: DOMString, can_gc: CanGc) -> DomRoot<DocumentFragment> {
1044 let document = self.owner_document();
1046 let fragment = DocumentFragment::new(&document, can_gc);
1047
1048 let input = input.str();
1051 let mut position = input.chars().peekable();
1052
1053 let mut text = String::new();
1055
1056 while let Some(ch) = position.next() {
1058 match ch {
1059 '\u{000A}' | '\u{000D}' => {
1062 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1063 position.next();
1066 }
1067
1068 if !text.is_empty() {
1069 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1070 text = String::new();
1071 }
1072
1073 let br = Element::create(
1074 QualName::new(None, ns!(html), local_name!("br")),
1075 None,
1076 &document,
1077 ElementCreator::ScriptCreated,
1078 CustomElementCreationMode::Asynchronous,
1079 None,
1080 can_gc,
1081 );
1082 fragment
1083 .upcast::<Node>()
1084 .AppendChild(br.upcast(), can_gc)
1085 .unwrap();
1086 },
1087 _ => {
1088 text.push(ch);
1091 },
1092 }
1093 }
1094
1095 if !text.is_empty() {
1098 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1099 }
1100
1101 fragment
1102 }
1103
1104 fn merge_with_the_next_text_node(node: DomRoot<Node>, can_gc: CanGc) {
1110 if !node.is::<Text>() {
1112 return;
1113 }
1114
1115 let next = match node.GetNextSibling() {
1117 Some(next) => next,
1118 None => return,
1119 };
1120
1121 if !next.is::<Text>() {
1123 return;
1124 }
1125 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1127 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1128 node_chars
1129 .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1130 .expect("Got chars from Text");
1131
1132 next.remove_self(can_gc);
1134 }
1135}
1136
1137impl VirtualMethods for HTMLElement {
1138 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1139 Some(self.as_element() as &dyn VirtualMethods)
1140 }
1141
1142 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1143 self.super_type()
1144 .unwrap()
1145 .attribute_mutated(attr, mutation, can_gc);
1146 let element = self.as_element();
1147 match (attr.local_name(), mutation) {
1148 (name, mutation)
1150 if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1151 {
1152 let evtarget = self.upcast::<EventTarget>();
1153 let event_name = &name[2..];
1154 match mutation {
1155 AttributeMutation::Set(..) => {
1157 let source = &**attr.value();
1158 let source_line = 1; evtarget.set_event_handler_uncompiled(
1160 self.owner_window().get_url(),
1161 source_line,
1162 event_name,
1163 source,
1164 );
1165 },
1166 AttributeMutation::Removed => {
1168 evtarget.set_event_handler_common::<EventHandlerNonNull>(event_name, None);
1169 },
1170 }
1171 },
1172
1173 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1174 self.form_attribute_mutated(mutation, can_gc);
1175 },
1176 (&local_name!("disabled"), AttributeMutation::Set(..))
1178 if self.is_form_associated_custom_element() && element.enabled_state() =>
1179 {
1180 element.set_disabled_state(true);
1181 element.set_enabled_state(false);
1182 ScriptThread::enqueue_callback_reaction(
1183 element,
1184 CallbackReaction::FormDisabled(true),
1185 None,
1186 );
1187 },
1188 (&local_name!("disabled"), AttributeMutation::Removed)
1191 if self.is_form_associated_custom_element() && element.disabled_state() =>
1192 {
1193 element.set_disabled_state(false);
1194 element.set_enabled_state(true);
1195 element.check_ancestors_disabled_state_for_form_control();
1196 if element.enabled_state() {
1197 ScriptThread::enqueue_callback_reaction(
1198 element,
1199 CallbackReaction::FormDisabled(false),
1200 None,
1201 );
1202 }
1203 },
1204 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1205 match mutation {
1206 AttributeMutation::Set(..) => {
1207 element.set_read_write_state(true);
1208 },
1209 AttributeMutation::Removed => {
1210 element.set_read_write_state(false);
1211 },
1212 }
1213 },
1214 (&local_name!("nonce"), mutation) => match mutation {
1215 AttributeMutation::Set(..) => {
1216 let nonce = &**attr.value();
1217 element.update_nonce_internal_slot(nonce.to_owned());
1218 },
1219 AttributeMutation::Removed => {
1220 element.update_nonce_internal_slot("".to_owned());
1221 },
1222 },
1223 _ => {},
1224 }
1225 }
1226
1227 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1228 if let Some(super_type) = self.super_type() {
1229 super_type.bind_to_tree(context, can_gc);
1230 }
1231 let element = self.as_element();
1232 element.update_sequentially_focusable_status(can_gc);
1233
1234 if self.is_form_associated_custom_element() && element.enabled_state() {
1237 element.check_ancestors_disabled_state_for_form_control();
1238 if element.disabled_state() {
1239 ScriptThread::enqueue_callback_reaction(
1240 element,
1241 CallbackReaction::FormDisabled(true),
1242 None,
1243 );
1244 }
1245 }
1246 }
1247
1248 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1249 if let Some(super_type) = self.super_type() {
1250 super_type.unbind_from_tree(context, can_gc);
1251 }
1252
1253 let element = self.as_element();
1258 if self.is_form_associated_custom_element() && element.disabled_state() {
1259 element.check_disabled_attribute();
1260 element.check_ancestors_disabled_state_for_form_control();
1261 if element.enabled_state() {
1262 ScriptThread::enqueue_callback_reaction(
1263 element,
1264 CallbackReaction::FormDisabled(false),
1265 None,
1266 );
1267 }
1268 }
1269 }
1270
1271 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1272 match *name {
1273 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1274 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1275 _ => self
1276 .super_type()
1277 .unwrap()
1278 .parse_plain_attribute(name, value),
1279 }
1280 }
1281}
1282
1283impl Activatable for HTMLElement {
1284 fn as_element(&self) -> &Element {
1285 self.upcast::<Element>()
1286 }
1287
1288 fn is_instance_activatable(&self) -> bool {
1289 self.as_element().local_name() == &local_name!("summary")
1290 }
1291
1292 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
1294 self.summary_activation_behavior();
1295 }
1296}
1297
1298impl FormControl for HTMLElement {
1304 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1305 debug_assert!(self.is_form_associated_custom_element());
1306 self.as_element()
1307 .get_element_internals()
1308 .and_then(|e| e.form_owner())
1309 }
1310
1311 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1312 debug_assert!(self.is_form_associated_custom_element());
1313 self.as_element()
1314 .ensure_element_internals(CanGc::note())
1315 .set_form_owner(form);
1316 }
1317
1318 fn to_element(&self) -> &Element {
1319 self.as_element()
1320 }
1321
1322 fn is_listed(&self) -> bool {
1323 debug_assert!(self.is_form_associated_custom_element());
1324 true
1325 }
1326
1327 }