1use std::default::Default;
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, QualName, local_name, ns};
9use js::context::{JSContext, NoGC};
10use js::rust::HandleObject;
11use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
12use rustc_hash::FxHashSet;
13use script_bindings::callback::RootedCallback;
14use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
15use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
16use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
17use script_bindings::dom::UnrootedDom;
18use style::attr::AttrValue;
19use stylo_dom::ElementState;
20
21use crate::dom::activation::Activatable;
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::{
41 CallbackReaction, CustomElementRegistry, CustomElementState,
42};
43use crate::dom::document::Document;
44use crate::dom::document::focus::FocusableArea;
45use crate::dom::document_event_handler::character_to_code;
46use crate::dom::documentfragment::DocumentFragment;
47use crate::dom::domstringmap::DOMStringMap;
48use crate::dom::element::attributes::storage::AttrRef;
49use crate::dom::element::{
50 AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
51 is_element_affected_by_legacy_background_presentational_hint,
52};
53use crate::dom::event::Event;
54use crate::dom::eventtarget::EventTarget;
55use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
56use crate::dom::html::form_controls::input_type::InputType;
57use crate::dom::html::htmlbodyelement::HTMLBodyElement;
58use crate::dom::html::htmldetailselement::HTMLDetailsElement;
59use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
60use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
61use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
62use crate::dom::html::htmllabelelement::HTMLLabelElement;
63use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
64use crate::dom::html::internals::elementinternals::ElementInternals;
65use crate::dom::htmlformelement::FormControlElementHelpers;
66use crate::dom::iterators::ShadowIncluding;
67use crate::dom::medialist::MediaList;
68use crate::dom::node::focus::FocusTrigger;
69use crate::dom::node::virtualmethods::VirtualMethods;
70use crate::dom::node::{
71 BindContext, MoveContext, Node, NodeDamage, NodeTraits, UnbindContext,
72 from_untrusted_node_address,
73};
74use crate::dom::shadowroot::shadowroot::ShadowRoot;
75use crate::dom::text::Text;
76use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement};
77use crate::event_loop::script_thread::ScriptThread;
78
79#[dom_struct]
80pub(crate) struct HTMLElement {
81 element: Element,
82 style_decl: MutNullableDom<CSSStyleDeclaration>,
83 dataset: MutNullableDom<DOMStringMap>,
84}
85
86impl HTMLElement {
87 pub(crate) fn new_inherited(
88 tag_name: LocalName,
89 prefix: Option<Prefix>,
90 document: &Document,
91 ) -> HTMLElement {
92 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
93 }
94
95 pub(crate) fn new_inherited_with_state(
96 state: ElementState,
97 tag_name: LocalName,
98 prefix: Option<Prefix>,
99 document: &Document,
100 ) -> HTMLElement {
101 HTMLElement {
102 element: Element::new_inherited_with_state(
103 state,
104 tag_name,
105 ns!(html),
106 prefix,
107 document,
108 ),
109 style_decl: Default::default(),
110 dataset: Default::default(),
111 }
112 }
113
114 pub(crate) fn new(
115 cx: &mut js::context::JSContext,
116 local_name: LocalName,
117 prefix: Option<Prefix>,
118 document: &Document,
119 proto: Option<HandleObject>,
120 ) -> DomRoot<HTMLElement> {
121 Node::reflect_node_with_proto(
122 cx,
123 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
124 document,
125 proto,
126 )
127 }
128
129 fn is_body_or_frameset(&self) -> bool {
130 let eventtarget = self.upcast::<EventTarget>();
131 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
132 }
133
134 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
140 let node = self.upcast::<Node>();
141 let window = node.owner_window();
142 let element = self.as_element();
143
144 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
146 if element_not_rendered {
147 return node.GetTextContent().unwrap();
148 }
149
150 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
151 let text = window
152 .layout()
153 .query_element_inner_outer_text(node.to_trusted_node_address());
154
155 DOMString::from(text)
156 }
157
158 pub(crate) fn set_inner_text(&self, cx: &mut JSContext, input: DOMString) {
160 let fragment = self.rendered_text_fragment(cx, input);
163
164 Node::replace_all(cx, Some(fragment.upcast()), self.upcast::<Node>());
166 }
167
168 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
170 self.element
174 .get_attribute_string_ref(&local_name!("media"))
175 .is_none_or(|media| {
176 let media = media.as_attr_ref();
177 MediaList::matches_environment(&self.owner_document(), &media.value())
178 })
179 }
180
181 pub(crate) fn is_editing_host(&self) -> bool {
183 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
185 }
188
189 pub(crate) fn previously_focused_element(&self, no_gc: &NoGC) -> Option<DomRoot<Element>> {
190 self.upcast::<Element>()
191 .ensure_rare_data(no_gc)
192 .previously_focused_element
193 .get()
194 }
195
196 pub(crate) fn set_previously_focused_element(&self, element: Option<&Element>, no_gc: &NoGC) {
197 self.upcast::<Element>()
198 .ensure_rare_data(no_gc)
199 .previously_focused_element
200 .set(element);
201 }
202
203 pub(crate) fn ensure_element_internals(&self, cx: &mut JSContext) -> DomRoot<ElementInternals> {
204 let element = self.upcast::<Element>();
205 let Some(element_internals) = element.get_element_internals() else {
206 let internals = ElementInternals::new(cx, self);
207 element.ensure_rare_data(cx.no_gc()).element_internals =
208 Some(Dom::from_ref(&*internals));
209 return internals;
210 };
211 element_internals
212 }
213}
214
215impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
216 fn Style(&self, cx: &mut JSContext) -> DomRoot<CSSStyleDeclaration> {
218 self.style_decl.or_init(|| {
219 let global = self.owner_window();
220 CSSStyleDeclaration::new(
221 cx,
222 &global,
223 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
224 None,
225 CSSModificationAccess::ReadWrite,
226 )
227 })
228 }
229
230 make_getter!(Title, "title");
232 make_setter!(SetTitle, "title");
234
235 make_getter!(Lang, "lang");
237 make_setter!(SetLang, "lang");
239
240 make_enumerated_getter!(
242 Dir,
243 "dir",
244 "ltr" | "rtl" | "auto",
245 missing => "",
246 invalid => ""
247 );
248
249 make_setter!(SetDir, "dir");
251
252 make_bool_getter!(Hidden, "hidden");
254 make_bool_setter!(SetHidden, "hidden");
256
257 global_event_handlers!(NoOnload);
259
260 fn Dataset(&self, cx: &mut JSContext) -> DomRoot<DOMStringMap> {
262 self.dataset.or_init(|| DOMStringMap::new(cx, self))
263 }
264
265 fn GetOnerror(&self, cx: &mut JSContext) -> Option<RootedCallback<OnErrorEventHandlerNonNull>> {
267 if self.is_body_or_frameset() {
268 let document = self.owner_document();
269 if document.has_browsing_context() {
270 document.window().GetOnerror(cx)
271 } else {
272 None
273 }
274 } else {
275 self.upcast::<EventTarget>()
276 .get_event_handler_common(cx, "error")
277 }
278 }
279
280 fn SetOnerror(
282 &self,
283 cx: &mut JSContext,
284 listener: Option<RootedCallback<OnErrorEventHandlerNonNull>>,
285 ) {
286 if self.is_body_or_frameset() {
287 let document = self.owner_document();
288 if document.has_browsing_context() {
289 document.window().SetOnerror(cx, listener)
290 }
291 } else {
292 self.upcast::<EventTarget>()
294 .set_error_event_handler(cx, "error", listener)
295 }
296 }
297
298 fn GetOnload(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
300 if self.is_body_or_frameset() {
301 let document = self.owner_document();
302 if document.has_browsing_context() {
303 document.window().GetOnload(cx)
304 } else {
305 None
306 }
307 } else {
308 self.upcast::<EventTarget>()
309 .get_event_handler_common(cx, "load")
310 }
311 }
312
313 fn SetOnload(&self, cx: &mut JSContext, listener: Option<RootedCallback<EventHandlerNonNull>>) {
315 if self.is_body_or_frameset() {
316 let document = self.owner_document();
317 if document.has_browsing_context() {
318 document.window().SetOnload(cx, listener)
319 }
320 } else {
321 self.upcast::<EventTarget>()
322 .set_event_handler_common(cx, "load", listener)
323 }
324 }
325
326 fn GetOnblur(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
328 if self.is_body_or_frameset() {
329 let document = self.owner_document();
330 if document.has_browsing_context() {
331 document.window().GetOnblur(cx)
332 } else {
333 None
334 }
335 } else {
336 self.upcast::<EventTarget>()
337 .get_event_handler_common(cx, "blur")
338 }
339 }
340
341 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<RootedCallback<EventHandlerNonNull>>) {
343 if self.is_body_or_frameset() {
344 let document = self.owner_document();
345 if document.has_browsing_context() {
346 document.window().SetOnblur(cx, listener)
347 }
348 } else {
349 self.upcast::<EventTarget>()
350 .set_event_handler_common(cx, "blur", listener)
351 }
352 }
353
354 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
356 if self.is_body_or_frameset() {
357 let document = self.owner_document();
358 if document.has_browsing_context() {
359 document.window().GetOnfocus(cx)
360 } else {
361 None
362 }
363 } else {
364 self.upcast::<EventTarget>()
365 .get_event_handler_common(cx, "focus")
366 }
367 }
368
369 fn SetOnfocus(
371 &self,
372 cx: &mut JSContext,
373 listener: Option<RootedCallback<EventHandlerNonNull>>,
374 ) {
375 if self.is_body_or_frameset() {
376 let document = self.owner_document();
377 if document.has_browsing_context() {
378 document.window().SetOnfocus(cx, listener)
379 }
380 } else {
381 self.upcast::<EventTarget>()
382 .set_event_handler_common(cx, "focus", listener)
383 }
384 }
385
386 fn GetOnresize(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
388 if self.is_body_or_frameset() {
389 let document = self.owner_document();
390 if document.has_browsing_context() {
391 document.window().GetOnresize(cx)
392 } else {
393 None
394 }
395 } else {
396 self.upcast::<EventTarget>()
397 .get_event_handler_common(cx, "resize")
398 }
399 }
400
401 fn SetOnresize(
403 &self,
404 cx: &mut JSContext,
405 listener: Option<RootedCallback<EventHandlerNonNull>>,
406 ) {
407 if self.is_body_or_frameset() {
408 let document = self.owner_document();
409 if document.has_browsing_context() {
410 document.window().SetOnresize(cx, listener)
411 }
412 } else {
413 self.upcast::<EventTarget>()
414 .set_event_handler_common(cx, "resize", listener)
415 }
416 }
417
418 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
420 if self.is_body_or_frameset() {
421 let document = self.owner_document();
422 if document.has_browsing_context() {
423 document.window().GetOnscroll(cx)
424 } else {
425 None
426 }
427 } else {
428 self.upcast::<EventTarget>()
429 .get_event_handler_common(cx, "scroll")
430 }
431 }
432
433 fn SetOnscroll(
435 &self,
436 cx: &mut JSContext,
437 listener: Option<RootedCallback<EventHandlerNonNull>>,
438 ) {
439 if self.is_body_or_frameset() {
440 let document = self.owner_document();
441 if document.has_browsing_context() {
442 document.window().SetOnscroll(cx, listener)
443 }
444 } else {
445 self.upcast::<EventTarget>()
446 .set_event_handler_common(cx, "scroll", listener)
447 }
448 }
449
450 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
452 let atoms = self
453 .element
454 .get_tokenlist_attribute(&local_name!("itemtype"));
455
456 if atoms.is_empty() {
457 return None;
458 }
459
460 Some(
461 FxHashSet::from_iter(
462 atoms
463 .iter()
464 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
465 )
466 .into_iter()
467 .collect(),
468 )
469 }
470
471 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
473 let atoms = self
474 .element
475 .get_tokenlist_attribute(&local_name!("itemprop"));
476
477 if atoms.is_empty() {
478 return None;
479 }
480
481 Some(
482 FxHashSet::from_iter(
483 atoms
484 .iter()
485 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
486 )
487 .into_iter()
488 .collect(),
489 )
490 }
491
492 fn Click(&self, cx: &mut JSContext) {
494 let element = self.as_element();
495 if element.disabled_state() {
496 return;
497 }
498 if element.click_in_progress() {
499 return;
500 }
501 element.set_click_in_progress(true);
502
503 self.upcast::<Node>()
504 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
505 element.set_click_in_progress(false);
506 }
507
508 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
510 if !self
515 .upcast::<Node>()
516 .run_the_focusing_steps(cx, None, FocusTrigger::Other)
517 {
518 return;
522 }
523
524 if !options.preventScroll {
531 let scroll_axis = ScrollAxisState {
532 position: ScrollLogicalPosition::Center,
533 requirement: ScrollRequirement::IfNotVisible,
534 };
535 self.upcast::<Element>().scroll_into_view_with_options(
536 cx,
537 ScrollBehavior::Smooth,
538 scroll_axis,
539 scroll_axis,
540 None,
541 None,
542 );
543 }
544 }
545
546 fn Blur(&self, cx: &mut JSContext) {
548 if !self.as_element().focus_state() {
551 return;
552 }
553 self.owner_document()
555 .focus_handler()
556 .focus(cx, &FocusableArea::Viewport);
557 }
558
559 #[expect(unsafe_code)]
561 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
562 self.owner_window()
563 .scroll_container_query(
564 Some(self.upcast()),
565 ScrollContainerQueryFlags::ForScrollParent,
566 )
567 .and_then(|response| match response {
568 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
569 ScrollContainerResponse::Element(parent_node_address, _) => {
570 let node = unsafe { from_untrusted_node_address(parent_node_address) };
571 DomRoot::downcast(node)
572 },
573 })
574 }
575
576 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
578 if self.is::<HTMLBodyElement>() || self.element.is_root() {
579 return None;
580 }
581
582 let node = self.upcast::<Node>();
583 let window = self.owner_window();
584 let (element, _) = window.offset_parent_query(node);
585
586 element
587 }
588
589 fn OffsetTop(&self) -> i32 {
591 if self.is_body_element() {
592 return 0;
593 }
594
595 let node = self.upcast::<Node>();
596 let window = self.owner_window();
597 let (_, rect) = window.offset_parent_query(node);
598
599 rect.origin.y.to_nearest_px()
600 }
601
602 fn OffsetLeft(&self) -> i32 {
604 if self.is_body_element() {
605 return 0;
606 }
607
608 let node = self.upcast::<Node>();
609 let window = self.owner_window();
610 let (_, rect) = window.offset_parent_query(node);
611
612 rect.origin.x.to_nearest_px()
613 }
614
615 fn OffsetWidth(&self) -> i32 {
617 let node = self.upcast::<Node>();
618 let window = self.owner_window();
619 let (_, rect) = window.offset_parent_query(node);
620
621 rect.size.width.to_nearest_px()
622 }
623
624 fn OffsetHeight(&self) -> i32 {
626 let node = self.upcast::<Node>();
627 let window = self.owner_window();
628 let (_, rect) = window.offset_parent_query(node);
629
630 rect.size.height.to_nearest_px()
631 }
632
633 fn InnerText(&self) -> DOMString {
635 self.get_inner_outer_text()
636 }
637
638 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
640 self.set_inner_text(cx, input)
641 }
642
643 fn GetOuterText(&self) -> Fallible<DOMString> {
645 Ok(self.get_inner_outer_text())
646 }
647
648 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
650 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
652 return Err(Error::NoModificationAllowed(Some(
653 "Cannot modify HTML element as its parent element is null".into(),
654 )));
655 };
656
657 let node = self.upcast::<Node>();
658 let document = self.owner_document();
659
660 let next = node.GetNextSibling();
662
663 let previous = node.GetPreviousSibling();
665
666 let fragment = self.rendered_text_fragment(cx, input);
669
670 if fragment.upcast::<Node>().children_count() == 0 {
673 let text_node = Text::new(cx, DOMString::new(), &document);
674
675 fragment
676 .upcast::<Node>()
677 .AppendChild(cx, text_node.upcast())?;
678 }
679
680 parent.ReplaceChild(cx, fragment.upcast(), node)?;
682
683 if let Some(next_sibling) = next &&
686 let Some(node) = next_sibling.GetPreviousSibling()
687 {
688 Self::merge_with_the_next_text_node(cx, &node);
689 }
690
691 if let Some(previous) = previous {
693 Self::merge_with_the_next_text_node(cx, &previous)
694 }
695
696 Ok(())
697 }
698
699 fn Translate(&self) -> bool {
701 self.as_element().is_translate_enabled()
702 }
703
704 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
706 self.as_element().set_string_attribute(
707 cx,
708 &html5ever::local_name!("translate"),
709 match yesno {
710 true => DOMString::from_static("yes"),
711 false => DOMString::from_static("no"),
712 },
713 );
714 }
715
716 make_enumerated_getter!(
718 ContentEditable,
719 "contenteditable",
720 "true" | "false" | "plaintext-only",
721 missing => "inherit",
722 invalid => "inherit",
723 empty => "true"
724 );
725
726 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
728 let attr_name = &local_name!("contenteditable");
729 if value.eq_ignore_ascii_case("inherit") {
730 self.element.remove_attribute_by_name(cx, attr_name);
732 } else if value.eq_ignore_ascii_case("true") ||
733 value.eq_ignore_ascii_case("false") ||
734 value.eq_ignore_ascii_case("plaintext-only")
735 {
736 let lower_value = value.to_ascii_lowercase();
740 self.element
741 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
742 } else {
743 return Err(Error::Syntax(Some(
745 "Invalid attribute for HTML element".into(),
746 )));
747 };
748 Ok(())
749 }
750
751 fn IsContentEditable(&self) -> bool {
753 self.upcast::<Node>().is_editable_or_editing_host()
755 }
756
757 fn AttachInternals(&self, cx: &mut JSContext) -> Fallible<DomRoot<ElementInternals>> {
759 if self.element.get_is().is_some() {
761 return Err(Error::NotSupported(Some(
762 "Local name of HTML element must not be set".into(),
763 )));
764 }
765
766 let lookup_registry = {
768 let registry = self.as_element().custom_element_registry();
771 if registry
772 .as_ref()
773 .is_some_and(|registry| registry.is_scoped())
774 {
775 registry
776 } else {
777 self.upcast::<Node>().owner_doc().custom_element_registry()
778 }
779 };
780 let definition = CustomElementRegistry::lookup_custom_element_definition(
781 lookup_registry.as_deref(),
782 self.upcast::<Element>().namespace(),
783 self.as_element().local_name(),
784 None,
785 );
786
787 let definition = match definition {
789 Some(definition) => definition,
790 None => {
791 return Err(Error::NotSupported(Some(
792 "Custom element definition is missing".into(),
793 )));
794 },
795 };
796
797 if definition.disable_internals {
799 return Err(Error::NotSupported(Some(
800 "Custom element definition's `disabledFeatures` must not include \"internals\""
801 .into(),
802 )));
803 }
804
805 let internals = self.ensure_element_internals(cx);
807 if internals.attached() {
808 return Err(Error::NotSupported(Some(
809 "HTML element's internals are already attached".into(),
810 )));
811 }
812
813 if !matches!(
816 self.element.get_custom_element_state(),
817 CustomElementState::Precustomized | CustomElementState::Custom
818 ) {
819 return Err(Error::NotSupported(Some(
820 "HTML element is not yet upgraded".into(),
821 )));
822 }
823
824 if self.is_form_associated_custom_element() {
825 self.element.init_state_for_internals();
826 }
827
828 internals.set_attached();
830 Ok(internals)
831 }
832
833 fn Nonce(&self) -> DOMString {
835 self.as_element().nonce_value().into()
836 }
837
838 fn SetNonce(&self, cx: &mut JSContext, value: DOMString) {
840 self.as_element()
841 .update_nonce_internal_slot(String::from(value), cx.no_gc())
842 }
843
844 fn Autofocus(&self) -> bool {
846 self.element.has_attribute(&local_name!("autofocus"))
847 }
848
849 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
851 self.element
852 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
853 }
854
855 fn TabIndex(&self) -> i32 {
857 self.element.tab_index()
858 }
859
860 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
862 self.element
863 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
864 }
865
866 make_getter!(AccessKey, "accesskey");
868
869 make_setter!(SetAccessKey, "accesskey");
871
872 fn AccessKeyLabel(&self) -> DOMString {
874 if !self.element.has_attribute(&local_name!("accesskey")) {
878 return Default::default();
879 }
880
881 let access_key_string =
882 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
883
884 #[cfg(target_os = "macos")]
885 let access_key_label = format!("⌃⌥{access_key_string}");
886 #[cfg(not(target_os = "macos"))]
887 let access_key_label = format!("Alt+Shift+{access_key_string}");
888
889 access_key_label.into()
890 }
891}
892
893fn append_text_node_to_fragment(
894 cx: &mut JSContext,
895 document: &Document,
896 fragment: &DocumentFragment,
897 text: String,
898) {
899 let text = Text::new(cx, DOMString::from(text), document);
900 fragment
901 .upcast::<Node>()
902 .AppendChild(cx, text.upcast())
903 .unwrap();
904}
905
906impl HTMLElement {
907 pub(crate) fn is_labelable_element(&self) -> bool {
909 match self.upcast::<Node>().type_id() {
910 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
911 HTMLElementTypeId::HTMLInputElement => !matches!(
912 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
913 InputType::Hidden(_)
914 ),
915 HTMLElementTypeId::HTMLButtonElement |
916 HTMLElementTypeId::HTMLMeterElement |
917 HTMLElementTypeId::HTMLOutputElement |
918 HTMLElementTypeId::HTMLProgressElement |
919 HTMLElementTypeId::HTMLSelectElement |
920 HTMLElementTypeId::HTMLTextAreaElement => true,
921 _ => self.is_form_associated_custom_element(),
922 },
923 _ => false,
924 }
925 }
926
927 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
929 if let Some(definition) = self.as_element().get_custom_element_definition() {
930 definition.is_autonomous() && definition.form_associated
931 } else {
932 false
933 }
934 }
935
936 pub(crate) fn is_listed_element(&self) -> bool {
938 match self.upcast::<Node>().type_id() {
939 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
940 HTMLElementTypeId::HTMLButtonElement |
941 HTMLElementTypeId::HTMLFieldSetElement |
942 HTMLElementTypeId::HTMLInputElement |
943 HTMLElementTypeId::HTMLObjectElement |
944 HTMLElementTypeId::HTMLOutputElement |
945 HTMLElementTypeId::HTMLSelectElement |
946 HTMLElementTypeId::HTMLTextAreaElement => true,
947 _ => self.is_form_associated_custom_element(),
948 },
949 _ => false,
950 }
951 }
952
953 pub(crate) fn is_body_element(&self) -> bool {
955 let self_node = self.upcast::<Node>();
956 self_node.GetParentNode().is_some_and(|parent| {
957 let parent_node = parent.upcast::<Node>();
958 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
959 parent_node.is::<HTMLHtmlElement>() &&
960 self_node
961 .preceding_siblings()
962 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
963 })
964 }
965
966 pub(crate) fn is_submittable_element(&self) -> bool {
968 match self.upcast::<Node>().type_id() {
969 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
970 HTMLElementTypeId::HTMLButtonElement |
971 HTMLElementTypeId::HTMLInputElement |
972 HTMLElementTypeId::HTMLSelectElement |
973 HTMLElementTypeId::HTMLTextAreaElement => true,
974 _ => self.is_form_associated_custom_element(),
975 },
976 _ => false,
977 }
978 }
979
980 pub(crate) fn label_at<'a>(
983 &self,
984 no_gc: &'a NoGC,
985 index: u32,
986 ) -> Option<UnrootedDom<'a, Node>> {
987 let element = self.as_element();
988
989 let root_element = element.root_element();
1000 let root_node = root_element.upcast::<Node>();
1001 root_node
1002 .traverse_preorder_unrooted(no_gc, ShadowIncluding::No)
1003 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
1004 .filter(|elem| match elem.GetControl(no_gc) {
1005 Some(control) => &*control == self,
1006 _ => false,
1007 })
1008 .nth(index as usize)
1009 .map(UnrootedDom::upcast)
1010 }
1011
1012 pub(crate) fn labels_count(&self, no_gc: &NoGC) -> u32 {
1015 let element = self.as_element();
1017 let root_element = element.root_element();
1018 let root_node = root_element.upcast::<Node>();
1019 root_node
1020 .traverse_preorder_unrooted(no_gc, ShadowIncluding::No)
1021 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
1022 .filter(|elem| match elem.GetControl(no_gc) {
1023 Some(control) => &*control == self,
1024 _ => false,
1025 })
1026 .count() as u32
1027 }
1028
1029 pub(crate) fn directionality(&self) -> Option<String> {
1033 let element_direction = &self.Dir();
1034
1035 if element_direction == "ltr" {
1036 return Some("ltr".to_owned());
1037 }
1038
1039 if element_direction == "rtl" {
1040 return Some("rtl".to_owned());
1041 }
1042
1043 if let Some(input) = self.downcast::<HTMLInputElement>() &&
1044 matches!(*input.input_type(), InputType::Tel(_))
1045 {
1046 return Some("ltr".to_owned());
1047 }
1048
1049 if element_direction == "auto" {
1050 if let Some(directionality) = self
1051 .downcast::<HTMLInputElement>()
1052 .and_then(|input| input.auto_directionality())
1053 {
1054 return Some(directionality);
1055 }
1056
1057 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
1058 return Some(area.auto_directionality());
1059 }
1060 }
1061
1062 None
1069 }
1070
1071 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
1073 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
1074
1075 let is_implicit_summary_element = self.is_implicit_summary_element();
1077 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
1078 return;
1079 }
1080
1081 let parent = if is_implicit_summary_element {
1083 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1084 .unwrap()
1085 } else {
1086 self.upcast::<Node>()
1087 .GetParentNode()
1088 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1089 .unwrap()
1090 };
1091
1092 parent.toggle(cx);
1095 }
1096
1097 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1099 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1102 return false;
1103 };
1104
1105 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1107 return false;
1108 };
1109
1110 details
1114 .find_corresponding_summary_element()
1115 .is_some_and(|summary| &*summary == self.upcast())
1116 }
1117
1118 fn is_implicit_summary_element(&self) -> bool {
1121 self.containing_shadow_root()
1125 .as_deref()
1126 .map(ShadowRoot::Host)
1127 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1128 }
1129
1130 fn rendered_text_fragment(
1132 &self,
1133 cx: &mut JSContext,
1134 input: DOMString,
1135 ) -> DomRoot<DocumentFragment> {
1136 let document = self.owner_document();
1138 let fragment = DocumentFragment::new(cx, &document);
1139
1140 let input = input.str();
1143 let mut position = input.chars().peekable();
1144
1145 let mut text = String::new();
1147
1148 while let Some(ch) = position.next() {
1150 match ch {
1151 '\u{000A}' | '\u{000D}' => {
1154 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1155 position.next();
1158 }
1159
1160 if !text.is_empty() {
1161 append_text_node_to_fragment(cx, &document, &fragment, text);
1162 text = String::new();
1163 }
1164
1165 let br = Element::create(
1166 cx,
1167 QualName::new(None, ns!(html), local_name!("br")),
1168 None,
1169 &document,
1170 ElementCreator::ScriptCreated,
1171 CustomElementCreationMode::Asynchronous,
1172 None,
1173 );
1174 fragment
1175 .upcast::<Node>()
1176 .AppendChild(cx, br.upcast())
1177 .unwrap();
1178 },
1179 _ => {
1180 text.push(ch);
1183 },
1184 }
1185 }
1186
1187 if !text.is_empty() {
1190 append_text_node_to_fragment(cx, &document, &fragment, text);
1191 }
1192
1193 fragment
1194 }
1195
1196 fn merge_with_the_next_text_node(cx: &mut JSContext, node: &Node) {
1202 if !node.is::<Text>() {
1204 return;
1205 }
1206
1207 let next = match node.GetNextSibling() {
1209 Some(next) => next,
1210 None => return,
1211 };
1212
1213 if !next.is::<Text>() {
1215 return;
1216 }
1217 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1219 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1220 node_chars
1221 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1222 .expect("Got chars from Text");
1223
1224 next.remove_self(cx);
1226 }
1227
1228 fn update_assigned_access_key(&self) {
1232 if !self.element.has_attribute(&local_name!("accesskey")) {
1234 self.owner_document()
1236 .event_handler()
1237 .unassign_access_key(self);
1238 }
1239
1240 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1242 let string_view = attribute_value.str();
1243 let values = string_view.split_html_space_characters();
1244
1245 for value in values {
1248 let mut characters = value.chars();
1251 let Some(character) = characters.next() else {
1252 continue;
1253 };
1254 if characters.count() > 0 {
1255 continue;
1256 }
1257
1258 let Some(code) = character_to_code(character) else {
1261 continue;
1262 };
1263
1264 self.owner_document()
1269 .event_handler()
1270 .assign_access_key(self, code);
1271 return;
1272 }
1273
1274 self.owner_document()
1280 .event_handler()
1281 .unassign_access_key(self);
1282 }
1283}
1284
1285impl VirtualMethods for HTMLElement {
1286 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1287 Some(self.as_element() as &dyn VirtualMethods)
1288 }
1289
1290 fn attribute_mutated(
1291 &self,
1292 cx: &mut JSContext,
1293 attr: AttrRef<'_>,
1294 mutation: AttributeMutation,
1295 ) {
1296 self.super_type()
1297 .unwrap()
1298 .attribute_mutated(cx, attr, mutation);
1299 let element = self.as_element();
1300 match (attr.local_name(), mutation) {
1301 (&local_name!("accesskey"), ..) => {
1302 self.update_assigned_access_key();
1303 },
1304 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1305 self.form_attribute_mutated(cx, mutation);
1306 },
1307 (&local_name!("disabled"), AttributeMutation::Set(_))
1309 if self.is_form_associated_custom_element() && element.enabled_state() =>
1310 {
1311 element.set_disabled_state(true);
1312 element.set_enabled_state(false);
1313 ScriptThread::enqueue_callback_reaction(
1314 cx,
1315 element,
1316 CallbackReaction::FormDisabled(true),
1317 None,
1318 );
1319 },
1320 (&local_name!("disabled"), AttributeMutation::Removed)
1323 if self.is_form_associated_custom_element() && element.disabled_state() =>
1324 {
1325 element.set_disabled_state(false);
1326 element.set_enabled_state(true);
1327 element.check_ancestors_disabled_state_for_form_control(cx.no_gc());
1328 if element.enabled_state() {
1329 ScriptThread::enqueue_callback_reaction(
1330 cx,
1331 element,
1332 CallbackReaction::FormDisabled(false),
1333 None,
1334 );
1335 }
1336 },
1337 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1338 match mutation {
1339 AttributeMutation::Set(_) => {
1340 element.set_read_write_state(true);
1341 },
1342 AttributeMutation::Removed => {
1343 element.set_read_write_state(false);
1344 },
1345 }
1346 },
1347 (&local_name!("nonce"), mutation) => match mutation {
1348 AttributeMutation::Set(_) => {
1349 let nonce = &**attr.value();
1350 element.update_nonce_internal_slot(nonce.to_owned(), cx.no_gc());
1351 },
1352 AttributeMutation::Removed => {
1353 element.update_nonce_internal_slot(String::new(), cx.no_gc());
1354 },
1355 },
1356 (name, _) if &**name == "containertiming" || &**name == "containertimingignore" => {
1360 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
1361 },
1362 _ => {},
1363 }
1364 }
1365
1366 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1367 if let Some(super_type) = self.super_type() {
1368 super_type.bind_to_tree(cx, context);
1369 }
1370
1371 let element = self.as_element();
1374 if self.is_form_associated_custom_element() && element.enabled_state() {
1375 element.check_ancestors_disabled_state_for_form_control(cx.no_gc());
1376 if element.disabled_state() {
1377 ScriptThread::enqueue_callback_reaction(
1378 cx,
1379 element,
1380 CallbackReaction::FormDisabled(true),
1381 None,
1382 );
1383 }
1384 }
1385
1386 if element.has_attribute(&local_name!("accesskey")) {
1387 self.update_assigned_access_key();
1388 }
1389 }
1390
1391 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1395 let document = self.owner_document();
1397
1398 let element = self.as_element();
1408 if document
1409 .focus_handler()
1410 .focused_area()
1411 .element()
1412 .is_some_and(|focused_element| focused_element == element)
1413 {
1414 document
1415 .focus_handler()
1416 .set_focused_area(FocusableArea::Viewport);
1417 }
1418
1419 if let Some(super_type) = self.super_type() {
1424 super_type.unbind_from_tree(cx, context);
1425 }
1426
1427 if self.is_form_associated_custom_element() && element.disabled_state() {
1438 element.check_disabled_attribute();
1439 element.check_ancestors_disabled_state_for_form_control(cx.no_gc());
1440 if element.enabled_state() {
1441 ScriptThread::enqueue_callback_reaction(
1442 cx,
1443 element,
1444 CallbackReaction::FormDisabled(false),
1445 None,
1446 );
1447 }
1448 }
1449
1450 if element.has_attribute(&local_name!("accesskey")) {
1451 self.owner_document()
1452 .event_handler()
1453 .unassign_access_key(self);
1454 }
1455 }
1456
1457 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1458 if is_element_affected_by_legacy_background_presentational_hint(
1459 self.element.namespace(),
1460 self.element.local_name(),
1461 ) && attr.local_name() == &local_name!("background")
1462 {
1463 return true;
1464 }
1465
1466 self.super_type()
1467 .unwrap()
1468 .attribute_affects_presentational_hints(attr)
1469 }
1470
1471 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1472 match *name {
1473 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1474 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1475 local_name!("background")
1476 if is_element_affected_by_legacy_background_presentational_hint(
1477 self.element.namespace(),
1478 self.element.local_name(),
1479 ) =>
1480 {
1481 AttrValue::from_resolved_url(
1482 &self.owner_document().base_url().get_arc(),
1483 value.into(),
1484 )
1485 },
1486 _ => self
1487 .super_type()
1488 .unwrap()
1489 .parse_plain_attribute(name, value),
1490 }
1491 }
1492
1493 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1495 if let Some(super_type) = self.super_type() {
1499 super_type.moving_steps(cx, context);
1500 }
1501
1502 if let Some(form_control) = self.element.as_maybe_form_control() {
1506 form_control.moving_steps(cx)
1507 }
1508 }
1509}
1510
1511impl Activatable for HTMLElement {
1512 fn as_element(&self) -> &Element {
1513 &self.element
1514 }
1515
1516 fn is_instance_activatable(&self) -> bool {
1517 self.element.local_name() == &local_name!("summary")
1518 }
1519
1520 fn activation_behavior(
1522 &self,
1523 cx: &mut js::context::JSContext,
1524 _event: &Event,
1525 _target: &EventTarget,
1526 ) {
1527 self.summary_activation_behavior(cx);
1528 }
1529}
1530
1531impl FormControl for HTMLElement {
1537 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1538 debug_assert!(self.is_form_associated_custom_element());
1539 self.element
1540 .get_element_internals()
1541 .and_then(|e| e.form_owner())
1542 }
1543
1544 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>) {
1545 debug_assert!(self.is_form_associated_custom_element());
1546 self.ensure_element_internals(cx).set_form_owner(form);
1547 }
1548
1549 fn to_html_element(&self) -> &HTMLElement {
1550 self
1551 }
1552}