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
168impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
169 fn Style(&self, can_gc: CanGc) -> DomRoot<CSSStyleDeclaration> {
171 self.style_decl.or_init(|| {
172 let global = self.owner_window();
173 CSSStyleDeclaration::new(
174 &global,
175 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
176 None,
177 CSSModificationAccess::ReadWrite,
178 can_gc,
179 )
180 })
181 }
182
183 make_getter!(Title, "title");
185 make_setter!(SetTitle, "title");
187
188 make_getter!(Lang, "lang");
190 make_setter!(SetLang, "lang");
192
193 make_enumerated_getter!(
195 Dir,
196 "dir",
197 "ltr" | "rtl" | "auto",
198 missing => "",
199 invalid => ""
200 );
201
202 make_setter!(SetDir, "dir");
204
205 make_bool_getter!(Hidden, "hidden");
207 make_bool_setter!(SetHidden, "hidden");
209
210 global_event_handlers!(NoOnload);
212
213 fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
215 self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
216 }
217
218 fn GetOnerror(&self, can_gc: CanGc) -> 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().GetOnerror()
224 } else {
225 None
226 }
227 } else {
228 self.upcast::<EventTarget>()
229 .get_event_handler_common("error", can_gc)
230 }
231 }
232
233 fn SetOnerror(&self, listener: 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().SetOnerror(listener)
239 }
240 } else {
241 self.upcast::<EventTarget>()
243 .set_error_event_handler("error", listener)
244 }
245 }
246
247 fn GetOnload(&self, can_gc: CanGc) -> 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().GetOnload()
253 } else {
254 None
255 }
256 } else {
257 self.upcast::<EventTarget>()
258 .get_event_handler_common("load", can_gc)
259 }
260 }
261
262 fn SetOnload(&self, listener: 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().SetOnload(listener)
268 }
269 } else {
270 self.upcast::<EventTarget>()
271 .set_event_handler_common("load", listener)
272 }
273 }
274
275 fn GetOnblur(&self, can_gc: CanGc) -> 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().GetOnblur()
281 } else {
282 None
283 }
284 } else {
285 self.upcast::<EventTarget>()
286 .get_event_handler_common("blur", can_gc)
287 }
288 }
289
290 fn SetOnblur(&self, listener: 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().SetOnblur(listener)
296 }
297 } else {
298 self.upcast::<EventTarget>()
299 .set_event_handler_common("blur", listener)
300 }
301 }
302
303 fn GetOnfocus(&self, can_gc: CanGc) -> 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().GetOnfocus()
309 } else {
310 None
311 }
312 } else {
313 self.upcast::<EventTarget>()
314 .get_event_handler_common("focus", can_gc)
315 }
316 }
317
318 fn SetOnfocus(&self, listener: 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().SetOnfocus(listener)
324 }
325 } else {
326 self.upcast::<EventTarget>()
327 .set_event_handler_common("focus", listener)
328 }
329 }
330
331 fn GetOnresize(&self, can_gc: CanGc) -> 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().GetOnresize()
337 } else {
338 None
339 }
340 } else {
341 self.upcast::<EventTarget>()
342 .get_event_handler_common("resize", can_gc)
343 }
344 }
345
346 fn SetOnresize(&self, listener: 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().SetOnresize(listener)
352 }
353 } else {
354 self.upcast::<EventTarget>()
355 .set_event_handler_common("resize", listener)
356 }
357 }
358
359 fn GetOnscroll(&self, can_gc: CanGc) -> 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().GetOnscroll()
365 } else {
366 None
367 }
368 } else {
369 self.upcast::<EventTarget>()
370 .get_event_handler_common("scroll", can_gc)
371 }
372 }
373
374 fn SetOnscroll(&self, listener: 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().SetOnscroll(listener)
380 }
381 } else {
382 self.upcast::<EventTarget>()
383 .set_event_handler_common("scroll", listener)
384 }
385 }
386
387 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
389 let atoms = self
390 .element
391 .get_tokenlist_attribute(&local_name!("itemtype"));
392
393 if atoms.is_empty() {
394 return None;
395 }
396
397 #[expect(clippy::mutable_key_type)]
398 let mut item_attr_values = HashSet::new();
400 for attr_value in &atoms {
401 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
402 }
403
404 Some(item_attr_values.into_iter().collect())
405 }
406
407 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
409 let atoms = self
410 .element
411 .get_tokenlist_attribute(&local_name!("itemprop"));
412
413 if atoms.is_empty() {
414 return None;
415 }
416
417 #[expect(clippy::mutable_key_type)]
418 let mut item_attr_values = HashSet::new();
420 for attr_value in &atoms {
421 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
422 }
423
424 Some(item_attr_values.into_iter().collect())
425 }
426
427 fn Click(&self, can_gc: CanGc) {
429 let element = self.as_element();
430 if element.disabled_state() {
431 return;
432 }
433 if element.click_in_progress() {
434 return;
435 }
436 element.set_click_in_progress(true);
437
438 self.upcast::<Node>()
439 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
440 element.set_click_in_progress(false);
441 }
442
443 fn Focus(&self, options: &FocusOptions, can_gc: CanGc) {
445 let document = self.owner_document();
448 document.request_focus_with_options(
449 Some(self.upcast()),
450 FocusInitiator::Local,
451 FocusOptions {
452 preventScroll: options.preventScroll,
453 },
454 can_gc,
455 );
456 }
457
458 fn Blur(&self, can_gc: CanGc) {
460 if !self.as_element().focus_state() {
463 return;
464 }
465 let document = self.owner_document();
467 document.request_focus(None, FocusInitiator::Local, can_gc);
468 }
469
470 #[expect(unsafe_code)]
472 fn GetScrollParent(&self) -> Option<DomRoot<Element>> {
473 self.owner_window()
474 .scroll_container_query(
475 Some(self.upcast()),
476 ScrollContainerQueryFlags::ForScrollParent,
477 )
478 .and_then(|response| match response {
479 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
480 ScrollContainerResponse::Element(parent_node_address, _) => {
481 let node = unsafe { from_untrusted_node_address(parent_node_address) };
482 DomRoot::downcast(node)
483 },
484 })
485 }
486
487 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
489 if self.is::<HTMLBodyElement>() || self.upcast::<Element>().is_root() {
490 return None;
491 }
492
493 let node = self.upcast::<Node>();
494 let window = self.owner_window();
495 let (element, _) = window.offset_parent_query(node);
496
497 element
498 }
499
500 fn OffsetTop(&self) -> i32 {
502 if self.is_body_element() {
503 return 0;
504 }
505
506 let node = self.upcast::<Node>();
507 let window = self.owner_window();
508 let (_, rect) = window.offset_parent_query(node);
509
510 rect.origin.y.to_nearest_px()
511 }
512
513 fn OffsetLeft(&self) -> i32 {
515 if self.is_body_element() {
516 return 0;
517 }
518
519 let node = self.upcast::<Node>();
520 let window = self.owner_window();
521 let (_, rect) = window.offset_parent_query(node);
522
523 rect.origin.x.to_nearest_px()
524 }
525
526 fn OffsetWidth(&self) -> i32 {
528 let node = self.upcast::<Node>();
529 let window = self.owner_window();
530 let (_, rect) = window.offset_parent_query(node);
531
532 rect.size.width.to_nearest_px()
533 }
534
535 fn OffsetHeight(&self) -> i32 {
537 let node = self.upcast::<Node>();
538 let window = self.owner_window();
539 let (_, rect) = window.offset_parent_query(node);
540
541 rect.size.height.to_nearest_px()
542 }
543
544 fn InnerText(&self) -> DOMString {
546 self.get_inner_outer_text()
547 }
548
549 fn SetInnerText(&self, input: DOMString, can_gc: CanGc) {
551 self.set_inner_text(input, can_gc)
552 }
553
554 fn GetOuterText(&self) -> Fallible<DOMString> {
556 Ok(self.get_inner_outer_text())
557 }
558
559 fn SetOuterText(&self, input: DOMString, can_gc: CanGc) -> Fallible<()> {
561 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
563 return Err(Error::NoModificationAllowed(None));
564 };
565
566 let node = self.upcast::<Node>();
567 let document = self.owner_document();
568
569 let next = node.GetNextSibling();
571
572 let previous = node.GetPreviousSibling();
574
575 let fragment = self.rendered_text_fragment(input, can_gc);
578
579 if fragment.upcast::<Node>().children_count() == 0 {
582 let text_node = Text::new(DOMString::from("".to_owned()), &document, can_gc);
583
584 fragment
585 .upcast::<Node>()
586 .AppendChild(text_node.upcast(), can_gc)?;
587 }
588
589 parent.ReplaceChild(fragment.upcast(), node, can_gc)?;
591
592 if let Some(next_sibling) = next {
595 if let Some(node) = next_sibling.GetPreviousSibling() {
596 Self::merge_with_the_next_text_node(node, can_gc);
597 }
598 }
599
600 if let Some(previous) = previous {
602 Self::merge_with_the_next_text_node(previous, can_gc)
603 }
604
605 Ok(())
606 }
607
608 fn Translate(&self) -> bool {
610 self.as_element().is_translate_enabled()
611 }
612
613 fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
615 self.as_element().set_string_attribute(
616 &html5ever::local_name!("translate"),
617 match yesno {
618 true => DOMString::from("yes"),
619 false => DOMString::from("no"),
620 },
621 can_gc,
622 );
623 }
624
625 fn ContentEditable(&self) -> DOMString {
627 self.as_element()
629 .get_attribute(&ns!(), &local_name!("contenteditable"))
630 .map(|attr| DOMString::from(&**attr.value()))
631 .unwrap_or_else(|| DOMString::from("inherit"))
632 }
633
634 fn SetContentEditable(&self, _: DOMString) {
636 warn!("The contentEditable attribute is not implemented yet");
638 }
639
640 fn IsContentEditable(&self) -> bool {
642 false
644 }
645
646 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
648 let element = self.as_element();
649 if element.get_is().is_some() {
651 return Err(Error::NotSupported(None));
652 }
653
654 let registry = self.owner_window().CustomElements();
659 let definition = registry.lookup_definition(self.as_element().local_name(), None);
660
661 let definition = match definition {
663 Some(definition) => definition,
664 None => return Err(Error::NotSupported(None)),
665 };
666
667 if definition.disable_internals {
669 return Err(Error::NotSupported(None));
670 }
671
672 let internals = element.ensure_element_internals(can_gc);
674 if internals.attached() {
675 return Err(Error::NotSupported(None));
676 }
677
678 if !matches!(
681 element.get_custom_element_state(),
682 CustomElementState::Precustomized | CustomElementState::Custom
683 ) {
684 return Err(Error::NotSupported(None));
685 }
686
687 if self.is_form_associated_custom_element() {
688 element.init_state_for_internals();
689 }
690
691 internals.set_attached();
693 Ok(internals)
694 }
695
696 fn Nonce(&self) -> DOMString {
698 self.as_element().nonce_value().into()
699 }
700
701 fn SetNonce(&self, value: DOMString) {
703 self.as_element()
704 .update_nonce_internal_slot(value.to_string())
705 }
706
707 fn Autofocus(&self) -> bool {
709 self.element.has_attribute(&local_name!("autofocus"))
710 }
711
712 fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
714 self.element
715 .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
716 }
717}
718
719fn append_text_node_to_fragment(
720 document: &Document,
721 fragment: &DocumentFragment,
722 text: String,
723 can_gc: CanGc,
724) {
725 let text = Text::new(DOMString::from(text), document, can_gc);
726 fragment
727 .upcast::<Node>()
728 .AppendChild(text.upcast(), can_gc)
729 .unwrap();
730}
731
732static DATA_PREFIX: &str = "data-";
735static DATA_HYPHEN_SEPARATOR: char = '\x2d';
736
737fn to_snake_case(name: DOMString) -> DOMString {
738 let mut attr_name = String::with_capacity(name.len() + DATA_PREFIX.len());
739 attr_name.push_str(DATA_PREFIX);
740 for ch in name.str().chars() {
741 if ch.is_ascii_uppercase() {
742 attr_name.push(DATA_HYPHEN_SEPARATOR);
743 attr_name.push(ch.to_ascii_lowercase());
744 } else {
745 attr_name.push(ch);
746 }
747 }
748 DOMString::from(attr_name)
749}
750
751fn to_camel_case(name: &str) -> Option<DOMString> {
757 if !name.starts_with(DATA_PREFIX) {
758 return None;
759 }
760 let name = &name[5..];
761 let has_uppercase = name.chars().any(|curr_char| curr_char.is_ascii_uppercase());
762 if has_uppercase {
763 return None;
764 }
765 let mut result = String::with_capacity(name.len().saturating_sub(DATA_PREFIX.len()));
766 let mut name_chars = name.chars();
767 while let Some(curr_char) = name_chars.next() {
768 if curr_char == DATA_HYPHEN_SEPARATOR {
770 if let Some(next_char) = name_chars.next() {
771 if next_char.is_ascii_lowercase() {
772 result.push(next_char.to_ascii_uppercase());
773 } else {
774 result.push(curr_char);
775 result.push(next_char);
776 }
777 } else {
778 result.push(curr_char);
779 }
780 } else {
781 result.push(curr_char);
782 }
783 }
784 Some(DOMString::from(result))
785}
786
787impl HTMLElement {
788 pub(crate) fn set_custom_attr(
789 &self,
790 name: DOMString,
791 value: DOMString,
792 can_gc: CanGc,
793 ) -> ErrorResult {
794 if name
795 .str()
796 .chars()
797 .skip_while(|&ch| ch != '\u{2d}')
798 .nth(1)
799 .is_some_and(|ch| ch.is_ascii_lowercase())
800 {
801 return Err(Error::Syntax(None));
802 }
803 self.as_element()
804 .set_custom_attribute(to_snake_case(name), value, can_gc)
805 }
806
807 pub(crate) fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> {
808 let local_name = LocalName::from(to_snake_case(local_name));
810 self.as_element()
811 .get_attribute(&ns!(), &local_name)
812 .map(|attr| {
813 DOMString::from(&**attr.value()) })
815 }
816
817 pub(crate) fn delete_custom_attr(&self, local_name: DOMString, can_gc: CanGc) {
818 let local_name = LocalName::from(to_snake_case(local_name));
820 self.as_element()
821 .remove_attribute(&ns!(), &local_name, can_gc);
822 }
823
824 pub(crate) fn is_labelable_element(&self) -> bool {
826 match self.upcast::<Node>().type_id() {
827 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
828 HTMLElementTypeId::HTMLInputElement => {
829 self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
830 },
831 HTMLElementTypeId::HTMLButtonElement |
832 HTMLElementTypeId::HTMLMeterElement |
833 HTMLElementTypeId::HTMLOutputElement |
834 HTMLElementTypeId::HTMLProgressElement |
835 HTMLElementTypeId::HTMLSelectElement |
836 HTMLElementTypeId::HTMLTextAreaElement => true,
837 _ => self.is_form_associated_custom_element(),
838 },
839 _ => false,
840 }
841 }
842
843 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
845 if let Some(definition) = self.as_element().get_custom_element_definition() {
846 definition.is_autonomous() && definition.form_associated
847 } else {
848 false
849 }
850 }
851
852 pub(crate) fn is_listed_element(&self) -> bool {
854 match self.upcast::<Node>().type_id() {
855 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
856 HTMLElementTypeId::HTMLButtonElement |
857 HTMLElementTypeId::HTMLFieldSetElement |
858 HTMLElementTypeId::HTMLInputElement |
859 HTMLElementTypeId::HTMLObjectElement |
860 HTMLElementTypeId::HTMLOutputElement |
861 HTMLElementTypeId::HTMLSelectElement |
862 HTMLElementTypeId::HTMLTextAreaElement => true,
863 _ => self.is_form_associated_custom_element(),
864 },
865 _ => false,
866 }
867 }
868
869 pub(crate) fn is_body_element(&self) -> bool {
871 let self_node = self.upcast::<Node>();
872 self_node.GetParentNode().is_some_and(|parent| {
873 let parent_node = parent.upcast::<Node>();
874 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
875 parent_node.is::<HTMLHtmlElement>() &&
876 self_node
877 .preceding_siblings()
878 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
879 })
880 }
881
882 pub(crate) fn is_submittable_element(&self) -> bool {
884 match self.upcast::<Node>().type_id() {
885 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
886 HTMLElementTypeId::HTMLButtonElement |
887 HTMLElementTypeId::HTMLInputElement |
888 HTMLElementTypeId::HTMLSelectElement |
889 HTMLElementTypeId::HTMLTextAreaElement => true,
890 _ => self.is_form_associated_custom_element(),
891 },
892 _ => false,
893 }
894 }
895
896 pub(crate) fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> {
897 let element = self.as_element();
898 element
899 .attrs()
900 .iter()
901 .filter_map(|attr| {
902 let raw_name = attr.local_name();
903 to_camel_case(raw_name)
904 })
905 .collect()
906 }
907
908 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
911 let element = self.as_element();
912
913 let root_element = element.root_element();
924 let root_node = root_element.upcast::<Node>();
925 root_node
926 .traverse_preorder(ShadowIncluding::No)
927 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
928 .filter(|elem| match elem.GetControl() {
929 Some(control) => &*control == self,
930 _ => false,
931 })
932 .nth(index as usize)
933 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
934 }
935
936 pub(crate) fn labels_count(&self) -> u32 {
939 let element = self.as_element();
941 let root_element = element.root_element();
942 let root_node = root_element.upcast::<Node>();
943 root_node
944 .traverse_preorder(ShadowIncluding::No)
945 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
946 .filter(|elem| match elem.GetControl() {
947 Some(control) => &*control == self,
948 _ => false,
949 })
950 .count() as u32
951 }
952
953 pub(crate) fn directionality(&self) -> Option<String> {
957 let element_direction = &self.Dir();
958
959 if element_direction == "ltr" {
960 return Some("ltr".to_owned());
961 }
962
963 if element_direction == "rtl" {
964 return Some("rtl".to_owned());
965 }
966
967 if let Some(input) = self.downcast::<HTMLInputElement>() {
968 if input.input_type() == InputType::Tel {
969 return Some("ltr".to_owned());
970 }
971 }
972
973 if element_direction == "auto" {
974 if let Some(directionality) = self
975 .downcast::<HTMLInputElement>()
976 .and_then(|input| input.auto_directionality())
977 {
978 return Some(directionality);
979 }
980
981 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
982 return Some(area.auto_directionality());
983 }
984 }
985
986 None
993 }
994
995 pub(crate) fn summary_activation_behavior(&self) {
997 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
998
999 if !self.is_a_summary_for_its_parent_details() {
1001 return;
1002 }
1003
1004 let parent = if self.is_implicit_summary_element() {
1006 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1007 .unwrap()
1008 } else {
1009 self.upcast::<Node>()
1010 .GetParentNode()
1011 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1012 .unwrap()
1013 };
1014
1015 parent.toggle();
1018 }
1019
1020 fn is_a_summary_for_its_parent_details(&self) -> bool {
1022 if self.is_implicit_summary_element() {
1023 return true;
1024 }
1025
1026 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1029 return false;
1030 };
1031
1032 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1034 return false;
1035 };
1036
1037 details
1041 .find_corresponding_summary_element()
1042 .is_some_and(|summary| &*summary == self.upcast())
1043 }
1044
1045 fn is_implicit_summary_element(&self) -> bool {
1048 self.containing_shadow_root()
1052 .as_deref()
1053 .map(ShadowRoot::Host)
1054 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1055 }
1056
1057 fn rendered_text_fragment(&self, input: DOMString, can_gc: CanGc) -> DomRoot<DocumentFragment> {
1059 let document = self.owner_document();
1061 let fragment = DocumentFragment::new(&document, can_gc);
1062
1063 let input = input.str();
1066 let mut position = input.chars().peekable();
1067
1068 let mut text = String::new();
1070
1071 while let Some(ch) = position.next() {
1073 match ch {
1074 '\u{000A}' | '\u{000D}' => {
1077 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1078 position.next();
1081 }
1082
1083 if !text.is_empty() {
1084 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1085 text = String::new();
1086 }
1087
1088 let br = Element::create(
1089 QualName::new(None, ns!(html), local_name!("br")),
1090 None,
1091 &document,
1092 ElementCreator::ScriptCreated,
1093 CustomElementCreationMode::Asynchronous,
1094 None,
1095 can_gc,
1096 );
1097 fragment
1098 .upcast::<Node>()
1099 .AppendChild(br.upcast(), can_gc)
1100 .unwrap();
1101 },
1102 _ => {
1103 text.push(ch);
1106 },
1107 }
1108 }
1109
1110 if !text.is_empty() {
1113 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1114 }
1115
1116 fragment
1117 }
1118
1119 fn merge_with_the_next_text_node(node: DomRoot<Node>, can_gc: CanGc) {
1125 if !node.is::<Text>() {
1127 return;
1128 }
1129
1130 let next = match node.GetNextSibling() {
1132 Some(next) => next,
1133 None => return,
1134 };
1135
1136 if !next.is::<Text>() {
1138 return;
1139 }
1140 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1142 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1143 node_chars
1144 .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1145 .expect("Got chars from Text");
1146
1147 next.remove_self(can_gc);
1149 }
1150}
1151
1152impl VirtualMethods for HTMLElement {
1153 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1154 Some(self.as_element() as &dyn VirtualMethods)
1155 }
1156
1157 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1158 self.super_type()
1159 .unwrap()
1160 .attribute_mutated(attr, mutation, can_gc);
1161 let element = self.as_element();
1162 match (attr.local_name(), mutation) {
1163 (name, mutation)
1165 if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1166 {
1167 let evtarget = self.upcast::<EventTarget>();
1168 let event_name = &name[2..];
1169 match mutation {
1170 AttributeMutation::Set(..) => {
1172 let source = &**attr.value();
1173 let source_line = 1; evtarget.set_event_handler_uncompiled(
1175 self.owner_window().get_url(),
1176 source_line,
1177 event_name,
1178 source,
1179 );
1180 },
1181 AttributeMutation::Removed => {
1183 evtarget.set_event_handler_common::<EventHandlerNonNull>(event_name, None);
1184 },
1185 }
1186 },
1187
1188 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1189 self.form_attribute_mutated(mutation, can_gc);
1190 },
1191 (&local_name!("disabled"), AttributeMutation::Set(..))
1193 if self.is_form_associated_custom_element() && element.enabled_state() =>
1194 {
1195 element.set_disabled_state(true);
1196 element.set_enabled_state(false);
1197 ScriptThread::enqueue_callback_reaction(
1198 element,
1199 CallbackReaction::FormDisabled(true),
1200 None,
1201 );
1202 },
1203 (&local_name!("disabled"), AttributeMutation::Removed)
1206 if self.is_form_associated_custom_element() && element.disabled_state() =>
1207 {
1208 element.set_disabled_state(false);
1209 element.set_enabled_state(true);
1210 element.check_ancestors_disabled_state_for_form_control();
1211 if element.enabled_state() {
1212 ScriptThread::enqueue_callback_reaction(
1213 element,
1214 CallbackReaction::FormDisabled(false),
1215 None,
1216 );
1217 }
1218 },
1219 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1220 match mutation {
1221 AttributeMutation::Set(..) => {
1222 element.set_read_write_state(true);
1223 },
1224 AttributeMutation::Removed => {
1225 element.set_read_write_state(false);
1226 },
1227 }
1228 },
1229 (&local_name!("nonce"), mutation) => match mutation {
1230 AttributeMutation::Set(..) => {
1231 let nonce = &**attr.value();
1232 element.update_nonce_internal_slot(nonce.to_owned());
1233 },
1234 AttributeMutation::Removed => {
1235 element.update_nonce_internal_slot("".to_owned());
1236 },
1237 },
1238 _ => {},
1239 }
1240 }
1241
1242 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1243 if let Some(super_type) = self.super_type() {
1244 super_type.bind_to_tree(context, can_gc);
1245 }
1246 let element = self.as_element();
1247 element.update_sequentially_focusable_status(can_gc);
1248
1249 if self.is_form_associated_custom_element() && element.enabled_state() {
1252 element.check_ancestors_disabled_state_for_form_control();
1253 if element.disabled_state() {
1254 ScriptThread::enqueue_callback_reaction(
1255 element,
1256 CallbackReaction::FormDisabled(true),
1257 None,
1258 );
1259 }
1260 }
1261 }
1262
1263 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1264 if let Some(super_type) = self.super_type() {
1265 super_type.unbind_from_tree(context, can_gc);
1266 }
1267
1268 let element = self.as_element();
1273 if self.is_form_associated_custom_element() && element.disabled_state() {
1274 element.check_disabled_attribute();
1275 element.check_ancestors_disabled_state_for_form_control();
1276 if element.enabled_state() {
1277 ScriptThread::enqueue_callback_reaction(
1278 element,
1279 CallbackReaction::FormDisabled(false),
1280 None,
1281 );
1282 }
1283 }
1284 }
1285
1286 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1287 let element = self.upcast::<Element>();
1288 if is_element_affected_by_legacy_background_presentational_hint(
1289 element.namespace(),
1290 element.local_name(),
1291 ) && attr.local_name() == &local_name!("background")
1292 {
1293 return true;
1294 }
1295
1296 self.super_type()
1297 .unwrap()
1298 .attribute_affects_presentational_hints(attr)
1299 }
1300
1301 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1302 let element = self.upcast::<Element>();
1303 match *name {
1304 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1305 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1306 local_name!("background")
1307 if is_element_affected_by_legacy_background_presentational_hint(
1308 element.namespace(),
1309 element.local_name(),
1310 ) =>
1311 {
1312 AttrValue::from_resolved_url(
1313 &self.owner_document().base_url().get_arc(),
1314 value.into(),
1315 )
1316 },
1317 _ => self
1318 .super_type()
1319 .unwrap()
1320 .parse_plain_attribute(name, value),
1321 }
1322 }
1323}
1324
1325impl Activatable for HTMLElement {
1326 fn as_element(&self) -> &Element {
1327 self.upcast::<Element>()
1328 }
1329
1330 fn is_instance_activatable(&self) -> bool {
1331 self.as_element().local_name() == &local_name!("summary")
1332 }
1333
1334 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
1336 self.summary_activation_behavior();
1337 }
1338}
1339
1340impl FormControl for HTMLElement {
1346 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1347 debug_assert!(self.is_form_associated_custom_element());
1348 self.as_element()
1349 .get_element_internals()
1350 .and_then(|e| e.form_owner())
1351 }
1352
1353 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1354 debug_assert!(self.is_form_associated_custom_element());
1355 self.as_element()
1356 .ensure_element_internals(CanGc::note())
1357 .set_form_owner(form);
1358 }
1359
1360 fn to_element(&self) -> &Element {
1361 self.as_element()
1362 }
1363
1364 fn is_listed(&self) -> bool {
1365 debug_assert!(self.is_form_associated_custom_element());
1366 true
1367 }
1368
1369 }