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::htmlformelement::FormControlElementHelpers;
57use crate::dom::medialist::MediaList;
58use crate::dom::node::{
59 BindContext, MoveContext, Node, NodeTraits, ShadowIncluding, UnbindContext,
60 from_untrusted_node_address,
61};
62use crate::dom::shadowroot::ShadowRoot;
63use crate::dom::text::Text;
64use crate::dom::virtualmethods::VirtualMethods;
65use crate::script_runtime::CanGc;
66use crate::script_thread::ScriptThread;
67
68#[dom_struct]
69pub(crate) struct HTMLElement {
70 element: Element,
71 style_decl: MutNullableDom<CSSStyleDeclaration>,
72 dataset: MutNullableDom<DOMStringMap>,
73}
74
75impl HTMLElement {
76 pub(crate) fn new_inherited(
77 tag_name: LocalName,
78 prefix: Option<Prefix>,
79 document: &Document,
80 ) -> HTMLElement {
81 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
82 }
83
84 pub(crate) fn new_inherited_with_state(
85 state: ElementState,
86 tag_name: LocalName,
87 prefix: Option<Prefix>,
88 document: &Document,
89 ) -> HTMLElement {
90 HTMLElement {
91 element: Element::new_inherited_with_state(
92 state,
93 tag_name,
94 ns!(html),
95 prefix,
96 document,
97 ),
98 style_decl: Default::default(),
99 dataset: Default::default(),
100 }
101 }
102
103 pub(crate) fn new(
104 local_name: LocalName,
105 prefix: Option<Prefix>,
106 document: &Document,
107 proto: Option<HandleObject>,
108 can_gc: CanGc,
109 ) -> DomRoot<HTMLElement> {
110 Node::reflect_node_with_proto(
111 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
112 document,
113 proto,
114 can_gc,
115 )
116 }
117
118 fn is_body_or_frameset(&self) -> bool {
119 let eventtarget = self.upcast::<EventTarget>();
120 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
121 }
122
123 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
129 let node = self.upcast::<Node>();
130 let window = node.owner_window();
131 let element = self.as_element();
132
133 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
135 if element_not_rendered {
136 return node.GetTextContent().unwrap();
137 }
138
139 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
140 let text = window
141 .layout()
142 .query_element_inner_outer_text(node.to_trusted_node_address());
143
144 DOMString::from(text)
145 }
146
147 pub(crate) fn set_inner_text(&self, input: DOMString, can_gc: CanGc) {
149 let fragment = self.rendered_text_fragment(input, can_gc);
152
153 Node::replace_all(Some(fragment.upcast()), self.upcast::<Node>(), can_gc);
155 }
156
157 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
159 self.upcast::<Element>()
163 .get_attribute(&local_name!("media"))
164 .is_none_or(|media| {
165 MediaList::matches_environment(&self.owner_document(), &media.value())
166 })
167 }
168
169 pub(crate) fn is_editing_host(&self) -> bool {
171 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
173 }
176}
177
178impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
179 fn Style(&self, can_gc: CanGc) -> DomRoot<CSSStyleDeclaration> {
181 self.style_decl.or_init(|| {
182 let global = self.owner_window();
183 CSSStyleDeclaration::new(
184 &global,
185 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
186 None,
187 CSSModificationAccess::ReadWrite,
188 can_gc,
189 )
190 })
191 }
192
193 make_getter!(Title, "title");
195 make_setter!(SetTitle, "title");
197
198 make_getter!(Lang, "lang");
200 make_setter!(SetLang, "lang");
202
203 make_enumerated_getter!(
205 Dir,
206 "dir",
207 "ltr" | "rtl" | "auto",
208 missing => "",
209 invalid => ""
210 );
211
212 make_setter!(SetDir, "dir");
214
215 make_bool_getter!(Hidden, "hidden");
217 make_bool_setter!(SetHidden, "hidden");
219
220 global_event_handlers!(NoOnload);
222
223 fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
225 self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
226 }
227
228 fn GetOnerror(&self, can_gc: CanGc) -> Option<Rc<OnErrorEventHandlerNonNull>> {
230 if self.is_body_or_frameset() {
231 let document = self.owner_document();
232 if document.has_browsing_context() {
233 document.window().GetOnerror()
234 } else {
235 None
236 }
237 } else {
238 self.upcast::<EventTarget>()
239 .get_event_handler_common("error", can_gc)
240 }
241 }
242
243 fn SetOnerror(&self, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
245 if self.is_body_or_frameset() {
246 let document = self.owner_document();
247 if document.has_browsing_context() {
248 document.window().SetOnerror(listener)
249 }
250 } else {
251 self.upcast::<EventTarget>()
253 .set_error_event_handler("error", listener)
254 }
255 }
256
257 fn GetOnload(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
259 if self.is_body_or_frameset() {
260 let document = self.owner_document();
261 if document.has_browsing_context() {
262 document.window().GetOnload()
263 } else {
264 None
265 }
266 } else {
267 self.upcast::<EventTarget>()
268 .get_event_handler_common("load", can_gc)
269 }
270 }
271
272 fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) {
274 if self.is_body_or_frameset() {
275 let document = self.owner_document();
276 if document.has_browsing_context() {
277 document.window().SetOnload(listener)
278 }
279 } else {
280 self.upcast::<EventTarget>()
281 .set_event_handler_common("load", listener)
282 }
283 }
284
285 fn GetOnblur(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
287 if self.is_body_or_frameset() {
288 let document = self.owner_document();
289 if document.has_browsing_context() {
290 document.window().GetOnblur()
291 } else {
292 None
293 }
294 } else {
295 self.upcast::<EventTarget>()
296 .get_event_handler_common("blur", can_gc)
297 }
298 }
299
300 fn SetOnblur(&self, listener: Option<Rc<EventHandlerNonNull>>) {
302 if self.is_body_or_frameset() {
303 let document = self.owner_document();
304 if document.has_browsing_context() {
305 document.window().SetOnblur(listener)
306 }
307 } else {
308 self.upcast::<EventTarget>()
309 .set_event_handler_common("blur", listener)
310 }
311 }
312
313 fn GetOnfocus(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
315 if self.is_body_or_frameset() {
316 let document = self.owner_document();
317 if document.has_browsing_context() {
318 document.window().GetOnfocus()
319 } else {
320 None
321 }
322 } else {
323 self.upcast::<EventTarget>()
324 .get_event_handler_common("focus", can_gc)
325 }
326 }
327
328 fn SetOnfocus(&self, listener: Option<Rc<EventHandlerNonNull>>) {
330 if self.is_body_or_frameset() {
331 let document = self.owner_document();
332 if document.has_browsing_context() {
333 document.window().SetOnfocus(listener)
334 }
335 } else {
336 self.upcast::<EventTarget>()
337 .set_event_handler_common("focus", listener)
338 }
339 }
340
341 fn GetOnresize(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
343 if self.is_body_or_frameset() {
344 let document = self.owner_document();
345 if document.has_browsing_context() {
346 document.window().GetOnresize()
347 } else {
348 None
349 }
350 } else {
351 self.upcast::<EventTarget>()
352 .get_event_handler_common("resize", can_gc)
353 }
354 }
355
356 fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) {
358 if self.is_body_or_frameset() {
359 let document = self.owner_document();
360 if document.has_browsing_context() {
361 document.window().SetOnresize(listener)
362 }
363 } else {
364 self.upcast::<EventTarget>()
365 .set_event_handler_common("resize", listener)
366 }
367 }
368
369 fn GetOnscroll(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
371 if self.is_body_or_frameset() {
372 let document = self.owner_document();
373 if document.has_browsing_context() {
374 document.window().GetOnscroll()
375 } else {
376 None
377 }
378 } else {
379 self.upcast::<EventTarget>()
380 .get_event_handler_common("scroll", can_gc)
381 }
382 }
383
384 fn SetOnscroll(&self, listener: Option<Rc<EventHandlerNonNull>>) {
386 if self.is_body_or_frameset() {
387 let document = self.owner_document();
388 if document.has_browsing_context() {
389 document.window().SetOnscroll(listener)
390 }
391 } else {
392 self.upcast::<EventTarget>()
393 .set_event_handler_common("scroll", listener)
394 }
395 }
396
397 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
399 let atoms = self
400 .element
401 .get_tokenlist_attribute(&local_name!("itemtype"));
402
403 if atoms.is_empty() {
404 return None;
405 }
406
407 #[expect(clippy::mutable_key_type)]
408 let mut item_attr_values = HashSet::new();
410 for attr_value in &atoms {
411 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
412 }
413
414 Some(item_attr_values.into_iter().collect())
415 }
416
417 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
419 let atoms = self
420 .element
421 .get_tokenlist_attribute(&local_name!("itemprop"));
422
423 if atoms.is_empty() {
424 return None;
425 }
426
427 #[expect(clippy::mutable_key_type)]
428 let mut item_attr_values = HashSet::new();
430 for attr_value in &atoms {
431 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
432 }
433
434 Some(item_attr_values.into_iter().collect())
435 }
436
437 fn Click(&self, can_gc: CanGc) {
439 let element = self.as_element();
440 if element.disabled_state() {
441 return;
442 }
443 if element.click_in_progress() {
444 return;
445 }
446 element.set_click_in_progress(true);
447
448 self.upcast::<Node>()
449 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
450 element.set_click_in_progress(false);
451 }
452
453 fn Focus(&self, options: &FocusOptions, can_gc: CanGc) {
455 let document = self.owner_document();
458 document.request_focus_with_options(
459 Some(self.upcast()),
460 FocusInitiator::Script,
461 FocusOptions {
462 preventScroll: options.preventScroll,
463 },
464 can_gc,
465 );
466 }
467
468 fn Blur(&self, can_gc: CanGc) {
470 if !self.as_element().focus_state() {
473 return;
474 }
475 let document = self.owner_document();
477 document.request_focus(None, FocusInitiator::Script, can_gc);
478 }
479
480 #[expect(unsafe_code)]
482 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
483 self.owner_window()
484 .scroll_container_query(
485 Some(self.upcast()),
486 ScrollContainerQueryFlags::ForScrollParent,
487 )
488 .and_then(|response| match response {
489 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
490 ScrollContainerResponse::Element(parent_node_address, _) => {
491 let node = unsafe { from_untrusted_node_address(parent_node_address) };
492 DomRoot::downcast(node)
493 },
494 })
495 }
496
497 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
499 if self.is::<HTMLBodyElement>() || self.upcast::<Element>().is_root() {
500 return None;
501 }
502
503 let node = self.upcast::<Node>();
504 let window = self.owner_window();
505 let (element, _) = window.offset_parent_query(node);
506
507 element
508 }
509
510 fn OffsetTop(&self) -> i32 {
512 if self.is_body_element() {
513 return 0;
514 }
515
516 let node = self.upcast::<Node>();
517 let window = self.owner_window();
518 let (_, rect) = window.offset_parent_query(node);
519
520 rect.origin.y.to_nearest_px()
521 }
522
523 fn OffsetLeft(&self) -> i32 {
525 if self.is_body_element() {
526 return 0;
527 }
528
529 let node = self.upcast::<Node>();
530 let window = self.owner_window();
531 let (_, rect) = window.offset_parent_query(node);
532
533 rect.origin.x.to_nearest_px()
534 }
535
536 fn OffsetWidth(&self) -> i32 {
538 let node = self.upcast::<Node>();
539 let window = self.owner_window();
540 let (_, rect) = window.offset_parent_query(node);
541
542 rect.size.width.to_nearest_px()
543 }
544
545 fn OffsetHeight(&self) -> i32 {
547 let node = self.upcast::<Node>();
548 let window = self.owner_window();
549 let (_, rect) = window.offset_parent_query(node);
550
551 rect.size.height.to_nearest_px()
552 }
553
554 fn InnerText(&self) -> DOMString {
556 self.get_inner_outer_text()
557 }
558
559 fn SetInnerText(&self, input: DOMString, can_gc: CanGc) {
561 self.set_inner_text(input, can_gc)
562 }
563
564 fn GetOuterText(&self) -> Fallible<DOMString> {
566 Ok(self.get_inner_outer_text())
567 }
568
569 fn SetOuterText(&self, input: DOMString, can_gc: CanGc) -> Fallible<()> {
571 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
573 return Err(Error::NoModificationAllowed(None));
574 };
575
576 let node = self.upcast::<Node>();
577 let document = self.owner_document();
578
579 let next = node.GetNextSibling();
581
582 let previous = node.GetPreviousSibling();
584
585 let fragment = self.rendered_text_fragment(input, can_gc);
588
589 if fragment.upcast::<Node>().children_count() == 0 {
592 let text_node = Text::new(DOMString::from("".to_owned()), &document, can_gc);
593
594 fragment
595 .upcast::<Node>()
596 .AppendChild(text_node.upcast(), can_gc)?;
597 }
598
599 parent.ReplaceChild(fragment.upcast(), node, can_gc)?;
601
602 if let Some(next_sibling) = next {
605 if let Some(node) = next_sibling.GetPreviousSibling() {
606 Self::merge_with_the_next_text_node(node, can_gc);
607 }
608 }
609
610 if let Some(previous) = previous {
612 Self::merge_with_the_next_text_node(previous, can_gc)
613 }
614
615 Ok(())
616 }
617
618 fn Translate(&self) -> bool {
620 self.as_element().is_translate_enabled()
621 }
622
623 fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
625 self.as_element().set_string_attribute(
626 &html5ever::local_name!("translate"),
627 match yesno {
628 true => DOMString::from("yes"),
629 false => DOMString::from("no"),
630 },
631 can_gc,
632 );
633 }
634
635 make_enumerated_getter!(
637 ContentEditable,
638 "contenteditable",
639 "true" | "false" | "plaintext-only",
640 missing => "inherit",
641 invalid => "inherit",
642 empty => "true"
643 );
644
645 fn SetContentEditable(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
647 let lower_value = value.to_ascii_lowercase();
648 let element = self.upcast::<Element>();
649 let attr_name = &local_name!("contenteditable");
650 match lower_value.as_ref() {
651 "inherit" => {
653 element.remove_attribute_by_name(attr_name, can_gc);
654 },
655 "true" | "false" | "plaintext-only" => {
659 element.set_attribute(attr_name, AttrValue::String(lower_value), can_gc);
660 },
661 _ => return Err(Error::Syntax(None)),
663 };
664 Ok(())
665 }
666
667 fn IsContentEditable(&self) -> bool {
669 self.upcast::<Node>().is_editable_or_editing_host()
671 }
672
673 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
675 let element = self.as_element();
676 if element.get_is().is_some() {
678 return Err(Error::NotSupported(None));
679 }
680
681 let registry = self.owner_window().CustomElements();
686 let definition = registry.lookup_definition(self.as_element().local_name(), None);
687
688 let definition = match definition {
690 Some(definition) => definition,
691 None => return Err(Error::NotSupported(None)),
692 };
693
694 if definition.disable_internals {
696 return Err(Error::NotSupported(None));
697 }
698
699 let internals = element.ensure_element_internals(can_gc);
701 if internals.attached() {
702 return Err(Error::NotSupported(None));
703 }
704
705 if !matches!(
708 element.get_custom_element_state(),
709 CustomElementState::Precustomized | CustomElementState::Custom
710 ) {
711 return Err(Error::NotSupported(None));
712 }
713
714 if self.is_form_associated_custom_element() {
715 element.init_state_for_internals();
716 }
717
718 internals.set_attached();
720 Ok(internals)
721 }
722
723 fn Nonce(&self) -> DOMString {
725 self.as_element().nonce_value().into()
726 }
727
728 fn SetNonce(&self, value: DOMString) {
730 self.as_element()
731 .update_nonce_internal_slot(value.to_string())
732 }
733
734 fn Autofocus(&self) -> bool {
736 self.element.has_attribute(&local_name!("autofocus"))
737 }
738
739 fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
741 self.element
742 .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
743 }
744
745 fn TabIndex(&self) -> i32 {
747 self.element.tab_index()
748 }
749
750 fn SetTabIndex(&self, tab_index: i32, can_gc: CanGc) {
752 self.element
753 .set_int_attribute(&local_name!("tabindex"), tab_index, can_gc);
754 }
755
756 make_getter!(AccessKey, "accesskey");
758
759 make_setter!(SetAccessKey, "accesskey");
761
762 fn AccessKeyLabel(&self) -> DOMString {
764 if !self.element.has_attribute(&local_name!("accesskey")) {
768 return Default::default();
769 }
770
771 let access_key_string = self
772 .element
773 .get_string_attribute(&local_name!("accesskey"))
774 .to_string();
775
776 #[cfg(target_os = "macos")]
777 let access_key_label = format!("⌃⌥{access_key_string}");
778 #[cfg(not(target_os = "macos"))]
779 let access_key_label = format!("Alt+Shift+{access_key_string}");
780
781 access_key_label.into()
782 }
783}
784
785fn append_text_node_to_fragment(
786 document: &Document,
787 fragment: &DocumentFragment,
788 text: String,
789 can_gc: CanGc,
790) {
791 let text = Text::new(DOMString::from(text), document, can_gc);
792 fragment
793 .upcast::<Node>()
794 .AppendChild(text.upcast(), can_gc)
795 .unwrap();
796}
797
798impl HTMLElement {
799 pub(crate) fn is_labelable_element(&self) -> bool {
801 match self.upcast::<Node>().type_id() {
802 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
803 HTMLElementTypeId::HTMLInputElement => {
804 self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
805 },
806 HTMLElementTypeId::HTMLButtonElement |
807 HTMLElementTypeId::HTMLMeterElement |
808 HTMLElementTypeId::HTMLOutputElement |
809 HTMLElementTypeId::HTMLProgressElement |
810 HTMLElementTypeId::HTMLSelectElement |
811 HTMLElementTypeId::HTMLTextAreaElement => true,
812 _ => self.is_form_associated_custom_element(),
813 },
814 _ => false,
815 }
816 }
817
818 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
820 if let Some(definition) = self.as_element().get_custom_element_definition() {
821 definition.is_autonomous() && definition.form_associated
822 } else {
823 false
824 }
825 }
826
827 pub(crate) fn is_listed_element(&self) -> bool {
829 match self.upcast::<Node>().type_id() {
830 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
831 HTMLElementTypeId::HTMLButtonElement |
832 HTMLElementTypeId::HTMLFieldSetElement |
833 HTMLElementTypeId::HTMLInputElement |
834 HTMLElementTypeId::HTMLObjectElement |
835 HTMLElementTypeId::HTMLOutputElement |
836 HTMLElementTypeId::HTMLSelectElement |
837 HTMLElementTypeId::HTMLTextAreaElement => true,
838 _ => self.is_form_associated_custom_element(),
839 },
840 _ => false,
841 }
842 }
843
844 pub(crate) fn is_body_element(&self) -> bool {
846 let self_node = self.upcast::<Node>();
847 self_node.GetParentNode().is_some_and(|parent| {
848 let parent_node = parent.upcast::<Node>();
849 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
850 parent_node.is::<HTMLHtmlElement>() &&
851 self_node
852 .preceding_siblings()
853 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
854 })
855 }
856
857 pub(crate) fn is_submittable_element(&self) -> bool {
859 match self.upcast::<Node>().type_id() {
860 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
861 HTMLElementTypeId::HTMLButtonElement |
862 HTMLElementTypeId::HTMLInputElement |
863 HTMLElementTypeId::HTMLSelectElement |
864 HTMLElementTypeId::HTMLTextAreaElement => true,
865 _ => self.is_form_associated_custom_element(),
866 },
867 _ => false,
868 }
869 }
870
871 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
874 let element = self.as_element();
875
876 let root_element = element.root_element();
887 let root_node = root_element.upcast::<Node>();
888 root_node
889 .traverse_preorder(ShadowIncluding::No)
890 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
891 .filter(|elem| match elem.GetControl() {
892 Some(control) => &*control == self,
893 _ => false,
894 })
895 .nth(index as usize)
896 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
897 }
898
899 pub(crate) fn labels_count(&self) -> u32 {
902 let element = self.as_element();
904 let root_element = element.root_element();
905 let root_node = root_element.upcast::<Node>();
906 root_node
907 .traverse_preorder(ShadowIncluding::No)
908 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
909 .filter(|elem| match elem.GetControl() {
910 Some(control) => &*control == self,
911 _ => false,
912 })
913 .count() as u32
914 }
915
916 pub(crate) fn directionality(&self) -> Option<String> {
920 let element_direction = &self.Dir();
921
922 if element_direction == "ltr" {
923 return Some("ltr".to_owned());
924 }
925
926 if element_direction == "rtl" {
927 return Some("rtl".to_owned());
928 }
929
930 if let Some(input) = self.downcast::<HTMLInputElement>() {
931 if input.input_type() == InputType::Tel {
932 return Some("ltr".to_owned());
933 }
934 }
935
936 if element_direction == "auto" {
937 if let Some(directionality) = self
938 .downcast::<HTMLInputElement>()
939 .and_then(|input| input.auto_directionality())
940 {
941 return Some(directionality);
942 }
943
944 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
945 return Some(area.auto_directionality());
946 }
947 }
948
949 None
956 }
957
958 pub(crate) fn summary_activation_behavior(&self) {
960 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
961
962 if !self.is_a_summary_for_its_parent_details() {
964 return;
965 }
966
967 let parent = if self.is_implicit_summary_element() {
969 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
970 .unwrap()
971 } else {
972 self.upcast::<Node>()
973 .GetParentNode()
974 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
975 .unwrap()
976 };
977
978 parent.toggle();
981 }
982
983 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
985 if self.is_implicit_summary_element() {
986 return true;
987 }
988
989 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
992 return false;
993 };
994
995 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
997 return false;
998 };
999
1000 details
1004 .find_corresponding_summary_element()
1005 .is_some_and(|summary| &*summary == self.upcast())
1006 }
1007
1008 fn is_implicit_summary_element(&self) -> bool {
1011 self.containing_shadow_root()
1015 .as_deref()
1016 .map(ShadowRoot::Host)
1017 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1018 }
1019
1020 fn rendered_text_fragment(&self, input: DOMString, can_gc: CanGc) -> DomRoot<DocumentFragment> {
1022 let document = self.owner_document();
1024 let fragment = DocumentFragment::new(&document, can_gc);
1025
1026 let input = input.str();
1029 let mut position = input.chars().peekable();
1030
1031 let mut text = String::new();
1033
1034 while let Some(ch) = position.next() {
1036 match ch {
1037 '\u{000A}' | '\u{000D}' => {
1040 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1041 position.next();
1044 }
1045
1046 if !text.is_empty() {
1047 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1048 text = String::new();
1049 }
1050
1051 let br = Element::create(
1052 QualName::new(None, ns!(html), local_name!("br")),
1053 None,
1054 &document,
1055 ElementCreator::ScriptCreated,
1056 CustomElementCreationMode::Asynchronous,
1057 None,
1058 can_gc,
1059 );
1060 fragment
1061 .upcast::<Node>()
1062 .AppendChild(br.upcast(), can_gc)
1063 .unwrap();
1064 },
1065 _ => {
1066 text.push(ch);
1069 },
1070 }
1071 }
1072
1073 if !text.is_empty() {
1076 append_text_node_to_fragment(&document, &fragment, text, can_gc);
1077 }
1078
1079 fragment
1080 }
1081
1082 fn merge_with_the_next_text_node(node: DomRoot<Node>, can_gc: CanGc) {
1088 if !node.is::<Text>() {
1090 return;
1091 }
1092
1093 let next = match node.GetNextSibling() {
1095 Some(next) => next,
1096 None => return,
1097 };
1098
1099 if !next.is::<Text>() {
1101 return;
1102 }
1103 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1105 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1106 node_chars
1107 .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1108 .expect("Got chars from Text");
1109
1110 next.remove_self(can_gc);
1112 }
1113
1114 fn update_assigned_access_key(&self) {
1118 if !self.element.has_attribute(&local_name!("accesskey")) {
1120 self.owner_document()
1122 .event_handler()
1123 .unassign_access_key(self);
1124 }
1125
1126 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1128 let string_view = attribute_value.str();
1129 let values = string_view.split_html_space_characters();
1130
1131 for value in values {
1134 let mut characters = value.chars();
1137 let Some(character) = characters.next() else {
1138 continue;
1139 };
1140 if characters.count() > 0 {
1141 continue;
1142 }
1143
1144 if !character.is_ascii_graphic() {
1150 continue;
1151 }
1152
1153 self.owner_document()
1158 .event_handler()
1159 .assign_access_key(self, character);
1160 return;
1161 }
1162
1163 self.owner_document()
1169 .event_handler()
1170 .unassign_access_key(self);
1171 }
1172}
1173
1174impl VirtualMethods for HTMLElement {
1175 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1176 Some(self.as_element() as &dyn VirtualMethods)
1177 }
1178
1179 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1180 self.super_type()
1181 .unwrap()
1182 .attribute_mutated(attr, mutation, can_gc);
1183 let element = self.as_element();
1184 match (attr.local_name(), mutation) {
1185 (name, mutation)
1187 if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1188 {
1189 let evtarget = self.upcast::<EventTarget>();
1190 let event_name = &name[2..];
1191 match mutation {
1192 AttributeMutation::Set(..) => {
1194 let source = &**attr.value();
1195 let source_line = 1; evtarget.set_event_handler_uncompiled(
1197 self.owner_window().get_url(),
1198 source_line,
1199 event_name,
1200 source,
1201 );
1202 },
1203 AttributeMutation::Removed => {
1205 evtarget.set_event_handler_common::<EventHandlerNonNull>(event_name, None);
1206 },
1207 }
1208 },
1209
1210 (&local_name!("accesskey"), ..) => {
1211 self.update_assigned_access_key();
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
1272 let element = self.as_element();
1275 if self.is_form_associated_custom_element() && element.enabled_state() {
1276 element.check_ancestors_disabled_state_for_form_control();
1277 if element.disabled_state() {
1278 ScriptThread::enqueue_callback_reaction(
1279 element,
1280 CallbackReaction::FormDisabled(true),
1281 None,
1282 );
1283 }
1284 }
1285
1286 if element.has_attribute(&local_name!("accesskey")) {
1287 self.update_assigned_access_key();
1288 }
1289 }
1290
1291 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1293 let document = self.owner_document();
1295
1296 let element = self.as_element();
1302 if document
1303 .get_focused_element()
1304 .is_some_and(|focused_element| &*focused_element == element)
1305 {
1306 document.request_focus(None, FocusInitiator::Script, can_gc);
1307 }
1308
1309 if let Some(super_type) = self.super_type() {
1314 super_type.unbind_from_tree(context, can_gc);
1315 }
1316
1317 if self.is_form_associated_custom_element() && element.disabled_state() {
1328 element.check_disabled_attribute();
1329 element.check_ancestors_disabled_state_for_form_control();
1330 if element.enabled_state() {
1331 ScriptThread::enqueue_callback_reaction(
1332 element,
1333 CallbackReaction::FormDisabled(false),
1334 None,
1335 );
1336 }
1337 }
1338
1339 if element.has_attribute(&local_name!("accesskey")) {
1340 self.owner_document()
1341 .event_handler()
1342 .unassign_access_key(self);
1343 }
1344 }
1345
1346 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1347 let element = self.upcast::<Element>();
1348 if is_element_affected_by_legacy_background_presentational_hint(
1349 element.namespace(),
1350 element.local_name(),
1351 ) && attr.local_name() == &local_name!("background")
1352 {
1353 return true;
1354 }
1355
1356 self.super_type()
1357 .unwrap()
1358 .attribute_affects_presentational_hints(attr)
1359 }
1360
1361 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1362 let element = self.upcast::<Element>();
1363 match *name {
1364 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1365 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1366 local_name!("background")
1367 if is_element_affected_by_legacy_background_presentational_hint(
1368 element.namespace(),
1369 element.local_name(),
1370 ) =>
1371 {
1372 AttrValue::from_resolved_url(
1373 &self.owner_document().base_url().get_arc(),
1374 value.into(),
1375 )
1376 },
1377 _ => self
1378 .super_type()
1379 .unwrap()
1380 .parse_plain_attribute(name, value),
1381 }
1382 }
1383
1384 fn moving_steps(&self, context: &MoveContext, can_gc: CanGc) {
1386 if let Some(super_type) = self.super_type() {
1390 super_type.moving_steps(context, can_gc);
1391 }
1392
1393 if let Some(form_control) = self.upcast::<Element>().as_maybe_form_control() {
1397 form_control.moving_steps(can_gc)
1398 }
1399 }
1400}
1401
1402impl Activatable for HTMLElement {
1403 fn as_element(&self) -> &Element {
1404 self.upcast::<Element>()
1405 }
1406
1407 fn is_instance_activatable(&self) -> bool {
1408 self.as_element().local_name() == &local_name!("summary")
1409 }
1410
1411 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
1413 self.summary_activation_behavior();
1414 }
1415}
1416
1417impl FormControl for HTMLElement {
1423 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1424 debug_assert!(self.is_form_associated_custom_element());
1425 self.as_element()
1426 .get_element_internals()
1427 .and_then(|e| e.form_owner())
1428 }
1429
1430 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1431 debug_assert!(self.is_form_associated_custom_element());
1432 self.as_element()
1433 .ensure_element_internals(CanGc::note())
1434 .set_form_owner(form);
1435 }
1436
1437 fn to_element(&self) -> &Element {
1438 self.as_element()
1439 }
1440
1441 fn is_listed(&self) -> bool {
1442 debug_assert!(self.is_form_associated_custom_element());
1443 true
1444 }
1445
1446 }