1use std::collections::HashSet;
6use std::default::Default;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use html5ever::{LocalName, Prefix, QualName, local_name, ns};
11use js::context::JSContext;
12use js::rust::HandleObject;
13use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
14use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
15use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
16use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
17use style::attr::AttrValue;
18use stylo_dom::ElementState;
19
20use crate::dom::activation::Activatable;
21use crate::dom::attr::Attr;
22use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
23use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
24 EventHandlerNonNull, OnErrorEventHandlerNonNull,
25};
26use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
27use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
28use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
29use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
30use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
31use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
32use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
33use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
34use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
35use crate::dom::bindings::str::DOMString;
36use crate::dom::characterdata::CharacterData;
37use crate::dom::css::cssstyledeclaration::{
38 CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
39};
40use crate::dom::customelementregistry::{CallbackReaction, CustomElementState};
41use crate::dom::document::Document;
42use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
43use crate::dom::document_event_handler::character_to_code;
44use crate::dom::documentfragment::DocumentFragment;
45use crate::dom::domstringmap::DOMStringMap;
46use crate::dom::element::{
47 AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
48 is_element_affected_by_legacy_background_presentational_hint,
49};
50use crate::dom::elementinternals::ElementInternals;
51use crate::dom::event::Event;
52use crate::dom::eventtarget::EventTarget;
53use crate::dom::html::htmlbodyelement::HTMLBodyElement;
54use crate::dom::html::htmldetailselement::HTMLDetailsElement;
55use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
56use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
57use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
58use crate::dom::html::htmllabelelement::HTMLLabelElement;
59use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
60use crate::dom::html::input_element::HTMLInputElement;
61use crate::dom::htmlformelement::FormControlElementHelpers;
62use crate::dom::input_element::input_type::InputType;
63use crate::dom::medialist::MediaList;
64use crate::dom::node::{
65 BindContext, MoveContext, Node, NodeTraits, ShadowIncluding, UnbindContext,
66 from_untrusted_node_address,
67};
68use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement};
69use crate::dom::shadowroot::ShadowRoot;
70use crate::dom::text::Text;
71use crate::dom::virtualmethods::VirtualMethods;
72use crate::script_runtime::CanGc;
73use crate::script_thread::ScriptThread;
74
75#[dom_struct]
76pub(crate) struct HTMLElement {
77 element: Element,
78 style_decl: MutNullableDom<CSSStyleDeclaration>,
79 dataset: MutNullableDom<DOMStringMap>,
80}
81
82impl HTMLElement {
83 pub(crate) fn new_inherited(
84 tag_name: LocalName,
85 prefix: Option<Prefix>,
86 document: &Document,
87 ) -> HTMLElement {
88 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
89 }
90
91 pub(crate) fn new_inherited_with_state(
92 state: ElementState,
93 tag_name: LocalName,
94 prefix: Option<Prefix>,
95 document: &Document,
96 ) -> HTMLElement {
97 HTMLElement {
98 element: Element::new_inherited_with_state(
99 state,
100 tag_name,
101 ns!(html),
102 prefix,
103 document,
104 ),
105 style_decl: Default::default(),
106 dataset: Default::default(),
107 }
108 }
109
110 pub(crate) fn new(
111 cx: &mut js::context::JSContext,
112 local_name: LocalName,
113 prefix: Option<Prefix>,
114 document: &Document,
115 proto: Option<HandleObject>,
116 ) -> DomRoot<HTMLElement> {
117 Node::reflect_node_with_proto(
118 cx,
119 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
120 document,
121 proto,
122 )
123 }
124
125 fn is_body_or_frameset(&self) -> bool {
126 let eventtarget = self.upcast::<EventTarget>();
127 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
128 }
129
130 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
136 let node = self.upcast::<Node>();
137 let window = node.owner_window();
138 let element = self.as_element();
139
140 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
142 if element_not_rendered {
143 return node.GetTextContent().unwrap();
144 }
145
146 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
147 let text = window
148 .layout()
149 .query_element_inner_outer_text(node.to_trusted_node_address());
150
151 DOMString::from(text)
152 }
153
154 pub(crate) fn set_inner_text(&self, cx: &mut JSContext, input: DOMString) {
156 let fragment = self.rendered_text_fragment(cx, input);
159
160 Node::replace_all(cx, Some(fragment.upcast()), self.upcast::<Node>());
162 }
163
164 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
166 self.element
170 .get_attribute(&local_name!("media"))
171 .is_none_or(|media| {
172 MediaList::matches_environment(&self.owner_document(), &media.value())
173 })
174 }
175
176 pub(crate) fn is_editing_host(&self) -> bool {
178 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
180 }
183}
184
185impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
186 fn Style(&self, can_gc: CanGc) -> DomRoot<CSSStyleDeclaration> {
188 self.style_decl.or_init(|| {
189 let global = self.owner_window();
190 CSSStyleDeclaration::new(
191 &global,
192 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
193 None,
194 CSSModificationAccess::ReadWrite,
195 can_gc,
196 )
197 })
198 }
199
200 make_getter!(Title, "title");
202 make_setter!(SetTitle, "title");
204
205 make_getter!(Lang, "lang");
207 make_setter!(SetLang, "lang");
209
210 make_enumerated_getter!(
212 Dir,
213 "dir",
214 "ltr" | "rtl" | "auto",
215 missing => "",
216 invalid => ""
217 );
218
219 make_setter!(SetDir, "dir");
221
222 make_bool_getter!(Hidden, "hidden");
224 make_bool_setter!(SetHidden, "hidden");
226
227 global_event_handlers!(NoOnload);
229
230 fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
232 self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
233 }
234
235 fn GetOnerror(&self, can_gc: CanGc) -> Option<Rc<OnErrorEventHandlerNonNull>> {
237 if self.is_body_or_frameset() {
238 let document = self.owner_document();
239 if document.has_browsing_context() {
240 document.window().GetOnerror()
241 } else {
242 None
243 }
244 } else {
245 self.upcast::<EventTarget>()
246 .get_event_handler_common("error", can_gc)
247 }
248 }
249
250 fn SetOnerror(&self, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
252 if self.is_body_or_frameset() {
253 let document = self.owner_document();
254 if document.has_browsing_context() {
255 document.window().SetOnerror(listener)
256 }
257 } else {
258 self.upcast::<EventTarget>()
260 .set_error_event_handler("error", listener)
261 }
262 }
263
264 fn GetOnload(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
266 if self.is_body_or_frameset() {
267 let document = self.owner_document();
268 if document.has_browsing_context() {
269 document.window().GetOnload()
270 } else {
271 None
272 }
273 } else {
274 self.upcast::<EventTarget>()
275 .get_event_handler_common("load", can_gc)
276 }
277 }
278
279 fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) {
281 if self.is_body_or_frameset() {
282 let document = self.owner_document();
283 if document.has_browsing_context() {
284 document.window().SetOnload(listener)
285 }
286 } else {
287 self.upcast::<EventTarget>()
288 .set_event_handler_common("load", listener)
289 }
290 }
291
292 fn GetOnblur(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
294 if self.is_body_or_frameset() {
295 let document = self.owner_document();
296 if document.has_browsing_context() {
297 document.window().GetOnblur()
298 } else {
299 None
300 }
301 } else {
302 self.upcast::<EventTarget>()
303 .get_event_handler_common("blur", can_gc)
304 }
305 }
306
307 fn SetOnblur(&self, listener: Option<Rc<EventHandlerNonNull>>) {
309 if self.is_body_or_frameset() {
310 let document = self.owner_document();
311 if document.has_browsing_context() {
312 document.window().SetOnblur(listener)
313 }
314 } else {
315 self.upcast::<EventTarget>()
316 .set_event_handler_common("blur", listener)
317 }
318 }
319
320 fn GetOnfocus(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
322 if self.is_body_or_frameset() {
323 let document = self.owner_document();
324 if document.has_browsing_context() {
325 document.window().GetOnfocus()
326 } else {
327 None
328 }
329 } else {
330 self.upcast::<EventTarget>()
331 .get_event_handler_common("focus", can_gc)
332 }
333 }
334
335 fn SetOnfocus(&self, listener: Option<Rc<EventHandlerNonNull>>) {
337 if self.is_body_or_frameset() {
338 let document = self.owner_document();
339 if document.has_browsing_context() {
340 document.window().SetOnfocus(listener)
341 }
342 } else {
343 self.upcast::<EventTarget>()
344 .set_event_handler_common("focus", listener)
345 }
346 }
347
348 fn GetOnresize(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
350 if self.is_body_or_frameset() {
351 let document = self.owner_document();
352 if document.has_browsing_context() {
353 document.window().GetOnresize()
354 } else {
355 None
356 }
357 } else {
358 self.upcast::<EventTarget>()
359 .get_event_handler_common("resize", can_gc)
360 }
361 }
362
363 fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) {
365 if self.is_body_or_frameset() {
366 let document = self.owner_document();
367 if document.has_browsing_context() {
368 document.window().SetOnresize(listener)
369 }
370 } else {
371 self.upcast::<EventTarget>()
372 .set_event_handler_common("resize", listener)
373 }
374 }
375
376 fn GetOnscroll(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
378 if self.is_body_or_frameset() {
379 let document = self.owner_document();
380 if document.has_browsing_context() {
381 document.window().GetOnscroll()
382 } else {
383 None
384 }
385 } else {
386 self.upcast::<EventTarget>()
387 .get_event_handler_common("scroll", can_gc)
388 }
389 }
390
391 fn SetOnscroll(&self, listener: Option<Rc<EventHandlerNonNull>>) {
393 if self.is_body_or_frameset() {
394 let document = self.owner_document();
395 if document.has_browsing_context() {
396 document.window().SetOnscroll(listener)
397 }
398 } else {
399 self.upcast::<EventTarget>()
400 .set_event_handler_common("scroll", listener)
401 }
402 }
403
404 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
406 let atoms = self
407 .element
408 .get_tokenlist_attribute(&local_name!("itemtype"));
409
410 if atoms.is_empty() {
411 return None;
412 }
413
414 #[expect(clippy::mutable_key_type)]
415 let mut item_attr_values = HashSet::new();
417 for attr_value in &atoms {
418 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
419 }
420
421 Some(item_attr_values.into_iter().collect())
422 }
423
424 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
426 let atoms = self
427 .element
428 .get_tokenlist_attribute(&local_name!("itemprop"));
429
430 if atoms.is_empty() {
431 return None;
432 }
433
434 #[expect(clippy::mutable_key_type)]
435 let mut item_attr_values = HashSet::new();
437 for attr_value in &atoms {
438 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
439 }
440
441 Some(item_attr_values.into_iter().collect())
442 }
443
444 fn Click(&self, can_gc: CanGc) {
446 let element = self.as_element();
447 if element.disabled_state() {
448 return;
449 }
450 if element.click_in_progress() {
451 return;
452 }
453 element.set_click_in_progress(true);
454
455 self.upcast::<Node>()
456 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
457 element.set_click_in_progress(false);
458 }
459
460 fn Focus(&self, options: &FocusOptions, can_gc: CanGc) {
462 if !self.upcast::<Node>().run_the_focusing_steps(None, can_gc) {
467 return;
471 }
472
473 if !options.preventScroll {
480 let scroll_axis = ScrollAxisState {
481 position: ScrollLogicalPosition::Center,
482 requirement: ScrollRequirement::IfNotVisible,
483 };
484 self.upcast::<Element>().scroll_into_view_with_options(
485 ScrollBehavior::Smooth,
486 scroll_axis,
487 scroll_axis,
488 None,
489 None,
490 );
491 }
492 }
493
494 fn Blur(&self, can_gc: CanGc) {
496 if !self.as_element().focus_state() {
499 return;
500 }
501 self.owner_document().focus_handler().focus(
503 FocusOperation::Focus(FocusableArea::Viewport),
504 FocusInitiator::Local,
505 can_gc,
506 );
507 }
508
509 #[expect(unsafe_code)]
511 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
512 self.owner_window()
513 .scroll_container_query(
514 Some(self.upcast()),
515 ScrollContainerQueryFlags::ForScrollParent,
516 )
517 .and_then(|response| match response {
518 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
519 ScrollContainerResponse::Element(parent_node_address, _) => {
520 let node = unsafe { from_untrusted_node_address(parent_node_address) };
521 DomRoot::downcast(node)
522 },
523 })
524 }
525
526 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
528 if self.is::<HTMLBodyElement>() || self.element.is_root() {
529 return None;
530 }
531
532 let node = self.upcast::<Node>();
533 let window = self.owner_window();
534 let (element, _) = window.offset_parent_query(node);
535
536 element
537 }
538
539 fn OffsetTop(&self) -> i32 {
541 if self.is_body_element() {
542 return 0;
543 }
544
545 let node = self.upcast::<Node>();
546 let window = self.owner_window();
547 let (_, rect) = window.offset_parent_query(node);
548
549 rect.origin.y.to_nearest_px()
550 }
551
552 fn OffsetLeft(&self) -> i32 {
554 if self.is_body_element() {
555 return 0;
556 }
557
558 let node = self.upcast::<Node>();
559 let window = self.owner_window();
560 let (_, rect) = window.offset_parent_query(node);
561
562 rect.origin.x.to_nearest_px()
563 }
564
565 fn OffsetWidth(&self) -> i32 {
567 let node = self.upcast::<Node>();
568 let window = self.owner_window();
569 let (_, rect) = window.offset_parent_query(node);
570
571 rect.size.width.to_nearest_px()
572 }
573
574 fn OffsetHeight(&self) -> i32 {
576 let node = self.upcast::<Node>();
577 let window = self.owner_window();
578 let (_, rect) = window.offset_parent_query(node);
579
580 rect.size.height.to_nearest_px()
581 }
582
583 fn InnerText(&self) -> DOMString {
585 self.get_inner_outer_text()
586 }
587
588 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
590 self.set_inner_text(cx, input)
591 }
592
593 fn GetOuterText(&self) -> Fallible<DOMString> {
595 Ok(self.get_inner_outer_text())
596 }
597
598 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
600 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
602 return Err(Error::NoModificationAllowed(None));
603 };
604
605 let node = self.upcast::<Node>();
606 let document = self.owner_document();
607
608 let next = node.GetNextSibling();
610
611 let previous = node.GetPreviousSibling();
613
614 let fragment = self.rendered_text_fragment(cx, input);
617
618 if fragment.upcast::<Node>().children_count() == 0 {
621 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
622
623 fragment
624 .upcast::<Node>()
625 .AppendChild(cx, text_node.upcast())?;
626 }
627
628 parent.ReplaceChild(cx, fragment.upcast(), node)?;
630
631 if let Some(next_sibling) = next {
634 if let Some(node) = next_sibling.GetPreviousSibling() {
635 Self::merge_with_the_next_text_node(cx, node);
636 }
637 }
638
639 if let Some(previous) = previous {
641 Self::merge_with_the_next_text_node(cx, previous)
642 }
643
644 Ok(())
645 }
646
647 fn Translate(&self) -> bool {
649 self.as_element().is_translate_enabled()
650 }
651
652 fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
654 self.as_element().set_string_attribute(
655 &html5ever::local_name!("translate"),
656 match yesno {
657 true => DOMString::from("yes"),
658 false => DOMString::from("no"),
659 },
660 can_gc,
661 );
662 }
663
664 make_enumerated_getter!(
666 ContentEditable,
667 "contenteditable",
668 "true" | "false" | "plaintext-only",
669 missing => "inherit",
670 invalid => "inherit",
671 empty => "true"
672 );
673
674 fn SetContentEditable(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
676 let lower_value = value.to_ascii_lowercase();
677 let attr_name = &local_name!("contenteditable");
678 match lower_value.as_ref() {
679 "inherit" => {
681 self.element.remove_attribute_by_name(attr_name, can_gc);
682 },
683 "true" | "false" | "plaintext-only" => {
687 self.element
688 .set_attribute(attr_name, AttrValue::String(lower_value), can_gc);
689 },
690 _ => return Err(Error::Syntax(None)),
692 };
693 Ok(())
694 }
695
696 fn IsContentEditable(&self) -> bool {
698 self.upcast::<Node>().is_editable_or_editing_host()
700 }
701
702 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
704 if self.element.get_is().is_some() {
706 return Err(Error::NotSupported(None));
707 }
708
709 let registry = self.owner_window().CustomElements();
714 let definition = registry.lookup_definition(self.as_element().local_name(), None);
715
716 let definition = match definition {
718 Some(definition) => definition,
719 None => return Err(Error::NotSupported(None)),
720 };
721
722 if definition.disable_internals {
724 return Err(Error::NotSupported(None));
725 }
726
727 let internals = self.element.ensure_element_internals(can_gc);
729 if internals.attached() {
730 return Err(Error::NotSupported(None));
731 }
732
733 if !matches!(
736 self.element.get_custom_element_state(),
737 CustomElementState::Precustomized | CustomElementState::Custom
738 ) {
739 return Err(Error::NotSupported(None));
740 }
741
742 if self.is_form_associated_custom_element() {
743 self.element.init_state_for_internals();
744 }
745
746 internals.set_attached();
748 Ok(internals)
749 }
750
751 fn Nonce(&self) -> DOMString {
753 self.as_element().nonce_value().into()
754 }
755
756 fn SetNonce(&self, value: DOMString) {
758 self.as_element()
759 .update_nonce_internal_slot(value.to_string())
760 }
761
762 fn Autofocus(&self) -> bool {
764 self.element.has_attribute(&local_name!("autofocus"))
765 }
766
767 fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
769 self.element
770 .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
771 }
772
773 fn TabIndex(&self) -> i32 {
775 self.element.tab_index()
776 }
777
778 fn SetTabIndex(&self, tab_index: i32, can_gc: CanGc) {
780 self.element
781 .set_int_attribute(&local_name!("tabindex"), tab_index, can_gc);
782 }
783
784 make_getter!(AccessKey, "accesskey");
786
787 make_setter!(SetAccessKey, "accesskey");
789
790 fn AccessKeyLabel(&self) -> DOMString {
792 if !self.element.has_attribute(&local_name!("accesskey")) {
796 return Default::default();
797 }
798
799 let access_key_string = self
800 .element
801 .get_string_attribute(&local_name!("accesskey"))
802 .to_string();
803
804 #[cfg(target_os = "macos")]
805 let access_key_label = format!("⌃⌥{access_key_string}");
806 #[cfg(not(target_os = "macos"))]
807 let access_key_label = format!("Alt+Shift+{access_key_string}");
808
809 access_key_label.into()
810 }
811}
812
813fn append_text_node_to_fragment(
814 cx: &mut JSContext,
815 document: &Document,
816 fragment: &DocumentFragment,
817 text: String,
818) {
819 let text = Text::new(cx, DOMString::from(text), document);
820 fragment
821 .upcast::<Node>()
822 .AppendChild(cx, text.upcast())
823 .unwrap();
824}
825
826impl HTMLElement {
827 pub(crate) fn is_labelable_element(&self) -> bool {
829 match self.upcast::<Node>().type_id() {
830 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
831 HTMLElementTypeId::HTMLInputElement => !matches!(
832 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
833 InputType::Hidden(_)
834 ),
835 HTMLElementTypeId::HTMLButtonElement |
836 HTMLElementTypeId::HTMLMeterElement |
837 HTMLElementTypeId::HTMLOutputElement |
838 HTMLElementTypeId::HTMLProgressElement |
839 HTMLElementTypeId::HTMLSelectElement |
840 HTMLElementTypeId::HTMLTextAreaElement => true,
841 _ => self.is_form_associated_custom_element(),
842 },
843 _ => false,
844 }
845 }
846
847 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
849 if let Some(definition) = self.as_element().get_custom_element_definition() {
850 definition.is_autonomous() && definition.form_associated
851 } else {
852 false
853 }
854 }
855
856 pub(crate) fn is_listed_element(&self) -> bool {
858 match self.upcast::<Node>().type_id() {
859 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
860 HTMLElementTypeId::HTMLButtonElement |
861 HTMLElementTypeId::HTMLFieldSetElement |
862 HTMLElementTypeId::HTMLInputElement |
863 HTMLElementTypeId::HTMLObjectElement |
864 HTMLElementTypeId::HTMLOutputElement |
865 HTMLElementTypeId::HTMLSelectElement |
866 HTMLElementTypeId::HTMLTextAreaElement => true,
867 _ => self.is_form_associated_custom_element(),
868 },
869 _ => false,
870 }
871 }
872
873 pub(crate) fn is_body_element(&self) -> bool {
875 let self_node = self.upcast::<Node>();
876 self_node.GetParentNode().is_some_and(|parent| {
877 let parent_node = parent.upcast::<Node>();
878 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
879 parent_node.is::<HTMLHtmlElement>() &&
880 self_node
881 .preceding_siblings()
882 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
883 })
884 }
885
886 pub(crate) fn is_submittable_element(&self) -> bool {
888 match self.upcast::<Node>().type_id() {
889 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
890 HTMLElementTypeId::HTMLButtonElement |
891 HTMLElementTypeId::HTMLInputElement |
892 HTMLElementTypeId::HTMLSelectElement |
893 HTMLElementTypeId::HTMLTextAreaElement => true,
894 _ => self.is_form_associated_custom_element(),
895 },
896 _ => false,
897 }
898 }
899
900 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
903 let element = self.as_element();
904
905 let root_element = element.root_element();
916 let root_node = root_element.upcast::<Node>();
917 root_node
918 .traverse_preorder(ShadowIncluding::No)
919 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
920 .filter(|elem| match elem.GetControl() {
921 Some(control) => &*control == self,
922 _ => false,
923 })
924 .nth(index as usize)
925 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
926 }
927
928 pub(crate) fn labels_count(&self) -> u32 {
931 let element = self.as_element();
933 let root_element = element.root_element();
934 let root_node = root_element.upcast::<Node>();
935 root_node
936 .traverse_preorder(ShadowIncluding::No)
937 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
938 .filter(|elem| match elem.GetControl() {
939 Some(control) => &*control == self,
940 _ => false,
941 })
942 .count() as u32
943 }
944
945 pub(crate) fn directionality(&self) -> Option<String> {
949 let element_direction = &self.Dir();
950
951 if element_direction == "ltr" {
952 return Some("ltr".to_owned());
953 }
954
955 if element_direction == "rtl" {
956 return Some("rtl".to_owned());
957 }
958
959 if let Some(input) = self.downcast::<HTMLInputElement>() {
960 if matches!(*input.input_type(), InputType::Tel(_)) {
961 return Some("ltr".to_owned());
962 }
963 }
964
965 if element_direction == "auto" {
966 if let Some(directionality) = self
967 .downcast::<HTMLInputElement>()
968 .and_then(|input| input.auto_directionality())
969 {
970 return Some(directionality);
971 }
972
973 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
974 return Some(area.auto_directionality());
975 }
976 }
977
978 None
985 }
986
987 pub(crate) fn summary_activation_behavior(&self) {
989 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
990
991 if !self.is_a_summary_for_its_parent_details() {
993 return;
994 }
995
996 let parent = if self.is_implicit_summary_element() {
998 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
999 .unwrap()
1000 } else {
1001 self.upcast::<Node>()
1002 .GetParentNode()
1003 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1004 .unwrap()
1005 };
1006
1007 parent.toggle();
1010 }
1011
1012 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1014 if self.is_implicit_summary_element() {
1015 return true;
1016 }
1017
1018 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1021 return false;
1022 };
1023
1024 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1026 return false;
1027 };
1028
1029 details
1033 .find_corresponding_summary_element()
1034 .is_some_and(|summary| &*summary == self.upcast())
1035 }
1036
1037 fn is_implicit_summary_element(&self) -> bool {
1040 self.containing_shadow_root()
1044 .as_deref()
1045 .map(ShadowRoot::Host)
1046 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1047 }
1048
1049 fn rendered_text_fragment(
1051 &self,
1052 cx: &mut JSContext,
1053 input: DOMString,
1054 ) -> DomRoot<DocumentFragment> {
1055 let document = self.owner_document();
1057 let fragment = DocumentFragment::new(cx, &document);
1058
1059 let input = input.str();
1062 let mut position = input.chars().peekable();
1063
1064 let mut text = String::new();
1066
1067 while let Some(ch) = position.next() {
1069 match ch {
1070 '\u{000A}' | '\u{000D}' => {
1073 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1074 position.next();
1077 }
1078
1079 if !text.is_empty() {
1080 append_text_node_to_fragment(cx, &document, &fragment, text);
1081 text = String::new();
1082 }
1083
1084 let br = Element::create(
1085 cx,
1086 QualName::new(None, ns!(html), local_name!("br")),
1087 None,
1088 &document,
1089 ElementCreator::ScriptCreated,
1090 CustomElementCreationMode::Asynchronous,
1091 None,
1092 );
1093 fragment
1094 .upcast::<Node>()
1095 .AppendChild(cx, br.upcast())
1096 .unwrap();
1097 },
1098 _ => {
1099 text.push(ch);
1102 },
1103 }
1104 }
1105
1106 if !text.is_empty() {
1109 append_text_node_to_fragment(cx, &document, &fragment, text);
1110 }
1111
1112 fragment
1113 }
1114
1115 fn merge_with_the_next_text_node(cx: &mut JSContext, node: DomRoot<Node>) {
1121 if !node.is::<Text>() {
1123 return;
1124 }
1125
1126 let next = match node.GetNextSibling() {
1128 Some(next) => next,
1129 None => return,
1130 };
1131
1132 if !next.is::<Text>() {
1134 return;
1135 }
1136 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1138 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1139 node_chars
1140 .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1141 .expect("Got chars from Text");
1142
1143 next.remove_self(cx);
1145 }
1146
1147 fn update_assigned_access_key(&self) {
1151 if !self.element.has_attribute(&local_name!("accesskey")) {
1153 self.owner_document()
1155 .event_handler()
1156 .unassign_access_key(self);
1157 }
1158
1159 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1161 let string_view = attribute_value.str();
1162 let values = string_view.split_html_space_characters();
1163
1164 for value in values {
1167 let mut characters = value.chars();
1170 let Some(character) = characters.next() else {
1171 continue;
1172 };
1173 if characters.count() > 0 {
1174 continue;
1175 }
1176
1177 let Some(code) = character_to_code(character) else {
1180 continue;
1181 };
1182
1183 self.owner_document()
1188 .event_handler()
1189 .assign_access_key(self, code);
1190 return;
1191 }
1192
1193 self.owner_document()
1199 .event_handler()
1200 .unassign_access_key(self);
1201 }
1202}
1203
1204impl VirtualMethods for HTMLElement {
1205 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1206 Some(self.as_element() as &dyn VirtualMethods)
1207 }
1208
1209 fn attribute_mutated(
1210 &self,
1211 cx: &mut js::context::JSContext,
1212 attr: &Attr,
1213 mutation: AttributeMutation,
1214 ) {
1215 self.super_type()
1216 .unwrap()
1217 .attribute_mutated(cx, attr, mutation);
1218 let element = self.as_element();
1219 match (attr.local_name(), mutation) {
1220 (name, mutation)
1222 if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1223 {
1224 let evtarget = self.upcast::<EventTarget>();
1225 let event_name = &name[2..];
1226 match mutation {
1227 AttributeMutation::Set(..) => {
1229 let source = &**attr.value();
1230 let source_line = 1; evtarget.set_event_handler_uncompiled(
1232 self.owner_window().get_url(),
1233 source_line,
1234 event_name,
1235 source,
1236 );
1237 },
1238 AttributeMutation::Removed => {
1240 evtarget.set_event_handler_common::<EventHandlerNonNull>(event_name, None);
1241 },
1242 }
1243 },
1244
1245 (&local_name!("accesskey"), ..) => {
1246 self.update_assigned_access_key();
1247 },
1248 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1249 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
1250 },
1251 (&local_name!("disabled"), AttributeMutation::Set(..))
1253 if self.is_form_associated_custom_element() && element.enabled_state() =>
1254 {
1255 element.set_disabled_state(true);
1256 element.set_enabled_state(false);
1257 ScriptThread::enqueue_callback_reaction(
1258 element,
1259 CallbackReaction::FormDisabled(true),
1260 None,
1261 );
1262 },
1263 (&local_name!("disabled"), AttributeMutation::Removed)
1266 if self.is_form_associated_custom_element() && element.disabled_state() =>
1267 {
1268 element.set_disabled_state(false);
1269 element.set_enabled_state(true);
1270 element.check_ancestors_disabled_state_for_form_control();
1271 if element.enabled_state() {
1272 ScriptThread::enqueue_callback_reaction(
1273 element,
1274 CallbackReaction::FormDisabled(false),
1275 None,
1276 );
1277 }
1278 },
1279 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1280 match mutation {
1281 AttributeMutation::Set(..) => {
1282 element.set_read_write_state(true);
1283 },
1284 AttributeMutation::Removed => {
1285 element.set_read_write_state(false);
1286 },
1287 }
1288 },
1289 (&local_name!("nonce"), mutation) => match mutation {
1290 AttributeMutation::Set(..) => {
1291 let nonce = &**attr.value();
1292 element.update_nonce_internal_slot(nonce.to_owned());
1293 },
1294 AttributeMutation::Removed => {
1295 element.update_nonce_internal_slot("".to_owned());
1296 },
1297 },
1298 _ => {},
1299 }
1300 }
1301
1302 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1303 if let Some(super_type) = self.super_type() {
1304 super_type.bind_to_tree(cx, context);
1305 }
1306
1307 let element = self.as_element();
1310 if self.is_form_associated_custom_element() && element.enabled_state() {
1311 element.check_ancestors_disabled_state_for_form_control();
1312 if element.disabled_state() {
1313 ScriptThread::enqueue_callback_reaction(
1314 element,
1315 CallbackReaction::FormDisabled(true),
1316 None,
1317 );
1318 }
1319 }
1320
1321 if element.has_attribute(&local_name!("accesskey")) {
1322 self.update_assigned_access_key();
1323 }
1324 }
1325
1326 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1330 let document = self.owner_document();
1332
1333 let element = self.as_element();
1343 if document
1344 .focus_handler()
1345 .focused_element()
1346 .is_some_and(|focused_element| &*focused_element == element)
1347 {
1348 document.focus_handler().set_focused_element(None);
1349 }
1350
1351 if let Some(super_type) = self.super_type() {
1356 super_type.unbind_from_tree(context, can_gc);
1357 }
1358
1359 if self.is_form_associated_custom_element() && element.disabled_state() {
1370 element.check_disabled_attribute();
1371 element.check_ancestors_disabled_state_for_form_control();
1372 if element.enabled_state() {
1373 ScriptThread::enqueue_callback_reaction(
1374 element,
1375 CallbackReaction::FormDisabled(false),
1376 None,
1377 );
1378 }
1379 }
1380
1381 if element.has_attribute(&local_name!("accesskey")) {
1382 self.owner_document()
1383 .event_handler()
1384 .unassign_access_key(self);
1385 }
1386 }
1387
1388 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1389 if is_element_affected_by_legacy_background_presentational_hint(
1390 self.element.namespace(),
1391 self.element.local_name(),
1392 ) && attr.local_name() == &local_name!("background")
1393 {
1394 return true;
1395 }
1396
1397 self.super_type()
1398 .unwrap()
1399 .attribute_affects_presentational_hints(attr)
1400 }
1401
1402 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1403 match *name {
1404 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1405 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1406 local_name!("background")
1407 if is_element_affected_by_legacy_background_presentational_hint(
1408 self.element.namespace(),
1409 self.element.local_name(),
1410 ) =>
1411 {
1412 AttrValue::from_resolved_url(
1413 &self.owner_document().base_url().get_arc(),
1414 value.into(),
1415 )
1416 },
1417 _ => self
1418 .super_type()
1419 .unwrap()
1420 .parse_plain_attribute(name, value),
1421 }
1422 }
1423
1424 fn moving_steps(&self, context: &MoveContext, can_gc: CanGc) {
1426 if let Some(super_type) = self.super_type() {
1430 super_type.moving_steps(context, can_gc);
1431 }
1432
1433 if let Some(form_control) = self.element.as_maybe_form_control() {
1437 form_control.moving_steps(can_gc)
1438 }
1439 }
1440}
1441
1442impl Activatable for HTMLElement {
1443 fn as_element(&self) -> &Element {
1444 &self.element
1445 }
1446
1447 fn is_instance_activatable(&self) -> bool {
1448 self.element.local_name() == &local_name!("summary")
1449 }
1450
1451 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
1453 self.summary_activation_behavior();
1454 }
1455}
1456
1457impl FormControl for HTMLElement {
1463 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1464 debug_assert!(self.is_form_associated_custom_element());
1465 self.element
1466 .get_element_internals()
1467 .and_then(|e| e.form_owner())
1468 }
1469
1470 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1471 debug_assert!(self.is_form_associated_custom_element());
1472 self.element
1473 .ensure_element_internals(CanGc::note())
1474 .set_form_owner(form);
1475 }
1476
1477 fn to_element(&self) -> &Element {
1478 &self.element
1479 }
1480
1481 fn is_listed(&self) -> bool {
1482 debug_assert!(self.is_form_associated_custom_element());
1483 true
1484 }
1485
1486 }