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, NodeTraits, UnbindContext, from_untrusted_node_address,
72};
73use crate::dom::shadowroot::ShadowRoot;
74use crate::dom::text::Text;
75use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement};
76use crate::event_loop::script_thread::ScriptThread;
77
78#[dom_struct]
79pub(crate) struct HTMLElement {
80 element: Element,
81 style_decl: MutNullableDom<CSSStyleDeclaration>,
82 dataset: MutNullableDom<DOMStringMap>,
83}
84
85impl HTMLElement {
86 pub(crate) fn new_inherited(
87 tag_name: LocalName,
88 prefix: Option<Prefix>,
89 document: &Document,
90 ) -> HTMLElement {
91 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
92 }
93
94 pub(crate) fn new_inherited_with_state(
95 state: ElementState,
96 tag_name: LocalName,
97 prefix: Option<Prefix>,
98 document: &Document,
99 ) -> HTMLElement {
100 HTMLElement {
101 element: Element::new_inherited_with_state(
102 state,
103 tag_name,
104 ns!(html),
105 prefix,
106 document,
107 ),
108 style_decl: Default::default(),
109 dataset: Default::default(),
110 }
111 }
112
113 pub(crate) fn new(
114 cx: &mut js::context::JSContext,
115 local_name: LocalName,
116 prefix: Option<Prefix>,
117 document: &Document,
118 proto: Option<HandleObject>,
119 ) -> DomRoot<HTMLElement> {
120 Node::reflect_node_with_proto(
121 cx,
122 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
123 document,
124 proto,
125 )
126 }
127
128 fn is_body_or_frameset(&self) -> bool {
129 let eventtarget = self.upcast::<EventTarget>();
130 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
131 }
132
133 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
139 let node = self.upcast::<Node>();
140 let window = node.owner_window();
141 let element = self.as_element();
142
143 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
145 if element_not_rendered {
146 return node.GetTextContent().unwrap();
147 }
148
149 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
150 let text = window
151 .layout()
152 .query_element_inner_outer_text(node.to_trusted_node_address());
153
154 DOMString::from(text)
155 }
156
157 pub(crate) fn set_inner_text(&self, cx: &mut JSContext, input: DOMString) {
159 let fragment = self.rendered_text_fragment(cx, input);
162
163 Node::replace_all(cx, Some(fragment.upcast()), self.upcast::<Node>());
165 }
166
167 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
169 self.element
173 .get_attribute_string_value(&local_name!("media"))
174 .is_none_or(|media| MediaList::matches_environment(&self.owner_document(), &media))
175 }
176
177 pub(crate) fn is_editing_host(&self) -> bool {
179 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
181 }
184
185 pub(crate) fn previously_focused_element(&self, no_gc: &NoGC) -> Option<DomRoot<Element>> {
186 self.upcast::<Element>()
187 .ensure_rare_data(no_gc)
188 .previously_focused_element
189 .get()
190 }
191
192 pub(crate) fn set_previously_focused_element(&self, element: Option<&Element>, no_gc: &NoGC) {
193 self.upcast::<Element>()
194 .ensure_rare_data(no_gc)
195 .previously_focused_element
196 .set(element);
197 }
198
199 pub(crate) fn ensure_element_internals(&self, cx: &mut JSContext) -> DomRoot<ElementInternals> {
200 let element = self.upcast::<Element>();
201 let Some(element_internals) = element.get_element_internals() else {
202 let internals = ElementInternals::new(cx, self);
203 element.ensure_rare_data(cx.no_gc()).element_internals =
204 Some(Dom::from_ref(&*internals));
205 return internals;
206 };
207 element_internals
208 }
209}
210
211impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
212 fn Style(&self, cx: &mut JSContext) -> DomRoot<CSSStyleDeclaration> {
214 self.style_decl.or_init(|| {
215 let global = self.owner_window();
216 CSSStyleDeclaration::new(
217 cx,
218 &global,
219 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
220 None,
221 CSSModificationAccess::ReadWrite,
222 )
223 })
224 }
225
226 make_getter!(Title, "title");
228 make_setter!(SetTitle, "title");
230
231 make_getter!(Lang, "lang");
233 make_setter!(SetLang, "lang");
235
236 make_enumerated_getter!(
238 Dir,
239 "dir",
240 "ltr" | "rtl" | "auto",
241 missing => "",
242 invalid => ""
243 );
244
245 make_setter!(SetDir, "dir");
247
248 make_bool_getter!(Hidden, "hidden");
250 make_bool_setter!(SetHidden, "hidden");
252
253 global_event_handlers!(NoOnload);
255
256 fn Dataset(&self, cx: &mut JSContext) -> DomRoot<DOMStringMap> {
258 self.dataset.or_init(|| DOMStringMap::new(cx, self))
259 }
260
261 fn GetOnerror(&self, cx: &mut JSContext) -> Option<RootedCallback<OnErrorEventHandlerNonNull>> {
263 if self.is_body_or_frameset() {
264 let document = self.owner_document();
265 if document.has_browsing_context() {
266 document.window().GetOnerror(cx)
267 } else {
268 None
269 }
270 } else {
271 self.upcast::<EventTarget>()
272 .get_event_handler_common(cx, "error")
273 }
274 }
275
276 fn SetOnerror(
278 &self,
279 cx: &mut JSContext,
280 listener: Option<RootedCallback<OnErrorEventHandlerNonNull>>,
281 ) {
282 if self.is_body_or_frameset() {
283 let document = self.owner_document();
284 if document.has_browsing_context() {
285 document.window().SetOnerror(cx, listener)
286 }
287 } else {
288 self.upcast::<EventTarget>()
290 .set_error_event_handler(cx, "error", listener)
291 }
292 }
293
294 fn GetOnload(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
296 if self.is_body_or_frameset() {
297 let document = self.owner_document();
298 if document.has_browsing_context() {
299 document.window().GetOnload(cx)
300 } else {
301 None
302 }
303 } else {
304 self.upcast::<EventTarget>()
305 .get_event_handler_common(cx, "load")
306 }
307 }
308
309 fn SetOnload(&self, cx: &mut JSContext, listener: Option<RootedCallback<EventHandlerNonNull>>) {
311 if self.is_body_or_frameset() {
312 let document = self.owner_document();
313 if document.has_browsing_context() {
314 document.window().SetOnload(cx, listener)
315 }
316 } else {
317 self.upcast::<EventTarget>()
318 .set_event_handler_common(cx, "load", listener)
319 }
320 }
321
322 fn GetOnblur(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
324 if self.is_body_or_frameset() {
325 let document = self.owner_document();
326 if document.has_browsing_context() {
327 document.window().GetOnblur(cx)
328 } else {
329 None
330 }
331 } else {
332 self.upcast::<EventTarget>()
333 .get_event_handler_common(cx, "blur")
334 }
335 }
336
337 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<RootedCallback<EventHandlerNonNull>>) {
339 if self.is_body_or_frameset() {
340 let document = self.owner_document();
341 if document.has_browsing_context() {
342 document.window().SetOnblur(cx, listener)
343 }
344 } else {
345 self.upcast::<EventTarget>()
346 .set_event_handler_common(cx, "blur", listener)
347 }
348 }
349
350 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
352 if self.is_body_or_frameset() {
353 let document = self.owner_document();
354 if document.has_browsing_context() {
355 document.window().GetOnfocus(cx)
356 } else {
357 None
358 }
359 } else {
360 self.upcast::<EventTarget>()
361 .get_event_handler_common(cx, "focus")
362 }
363 }
364
365 fn SetOnfocus(
367 &self,
368 cx: &mut JSContext,
369 listener: Option<RootedCallback<EventHandlerNonNull>>,
370 ) {
371 if self.is_body_or_frameset() {
372 let document = self.owner_document();
373 if document.has_browsing_context() {
374 document.window().SetOnfocus(cx, listener)
375 }
376 } else {
377 self.upcast::<EventTarget>()
378 .set_event_handler_common(cx, "focus", listener)
379 }
380 }
381
382 fn GetOnresize(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
384 if self.is_body_or_frameset() {
385 let document = self.owner_document();
386 if document.has_browsing_context() {
387 document.window().GetOnresize(cx)
388 } else {
389 None
390 }
391 } else {
392 self.upcast::<EventTarget>()
393 .get_event_handler_common(cx, "resize")
394 }
395 }
396
397 fn SetOnresize(
399 &self,
400 cx: &mut JSContext,
401 listener: Option<RootedCallback<EventHandlerNonNull>>,
402 ) {
403 if self.is_body_or_frameset() {
404 let document = self.owner_document();
405 if document.has_browsing_context() {
406 document.window().SetOnresize(cx, listener)
407 }
408 } else {
409 self.upcast::<EventTarget>()
410 .set_event_handler_common(cx, "resize", listener)
411 }
412 }
413
414 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<RootedCallback<EventHandlerNonNull>> {
416 if self.is_body_or_frameset() {
417 let document = self.owner_document();
418 if document.has_browsing_context() {
419 document.window().GetOnscroll(cx)
420 } else {
421 None
422 }
423 } else {
424 self.upcast::<EventTarget>()
425 .get_event_handler_common(cx, "scroll")
426 }
427 }
428
429 fn SetOnscroll(
431 &self,
432 cx: &mut JSContext,
433 listener: Option<RootedCallback<EventHandlerNonNull>>,
434 ) {
435 if self.is_body_or_frameset() {
436 let document = self.owner_document();
437 if document.has_browsing_context() {
438 document.window().SetOnscroll(cx, listener)
439 }
440 } else {
441 self.upcast::<EventTarget>()
442 .set_event_handler_common(cx, "scroll", listener)
443 }
444 }
445
446 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
448 let atoms = self
449 .element
450 .get_tokenlist_attribute(&local_name!("itemtype"));
451
452 if atoms.is_empty() {
453 return None;
454 }
455
456 Some(
457 FxHashSet::from_iter(
458 atoms
459 .iter()
460 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
461 )
462 .into_iter()
463 .collect(),
464 )
465 }
466
467 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
469 let atoms = self
470 .element
471 .get_tokenlist_attribute(&local_name!("itemprop"));
472
473 if atoms.is_empty() {
474 return None;
475 }
476
477 Some(
478 FxHashSet::from_iter(
479 atoms
480 .iter()
481 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
482 )
483 .into_iter()
484 .collect(),
485 )
486 }
487
488 fn Click(&self, cx: &mut JSContext) {
490 let element = self.as_element();
491 if element.disabled_state() {
492 return;
493 }
494 if element.click_in_progress() {
495 return;
496 }
497 element.set_click_in_progress(true);
498
499 self.upcast::<Node>()
500 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
501 element.set_click_in_progress(false);
502 }
503
504 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
506 if !self
511 .upcast::<Node>()
512 .run_the_focusing_steps(cx, None, FocusTrigger::Other)
513 {
514 return;
518 }
519
520 if !options.preventScroll {
527 let scroll_axis = ScrollAxisState {
528 position: ScrollLogicalPosition::Center,
529 requirement: ScrollRequirement::IfNotVisible,
530 };
531 self.upcast::<Element>().scroll_into_view_with_options(
532 cx,
533 ScrollBehavior::Smooth,
534 scroll_axis,
535 scroll_axis,
536 None,
537 None,
538 );
539 }
540 }
541
542 fn Blur(&self, cx: &mut JSContext) {
544 if !self.as_element().focus_state() {
547 return;
548 }
549 self.owner_document()
551 .focus_handler()
552 .focus(cx, &FocusableArea::Viewport);
553 }
554
555 #[expect(unsafe_code)]
557 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
558 self.owner_window()
559 .scroll_container_query(
560 Some(self.upcast()),
561 ScrollContainerQueryFlags::ForScrollParent,
562 )
563 .and_then(|response| match response {
564 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
565 ScrollContainerResponse::Element(parent_node_address, _) => {
566 let node = unsafe { from_untrusted_node_address(parent_node_address) };
567 DomRoot::downcast(node)
568 },
569 })
570 }
571
572 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
574 if self.is::<HTMLBodyElement>() || self.element.is_root() {
575 return None;
576 }
577
578 let node = self.upcast::<Node>();
579 let window = self.owner_window();
580 let (element, _) = window.offset_parent_query(node);
581
582 element
583 }
584
585 fn OffsetTop(&self) -> i32 {
587 if self.is_body_element() {
588 return 0;
589 }
590
591 let node = self.upcast::<Node>();
592 let window = self.owner_window();
593 let (_, rect) = window.offset_parent_query(node);
594
595 rect.origin.y.to_nearest_px()
596 }
597
598 fn OffsetLeft(&self) -> i32 {
600 if self.is_body_element() {
601 return 0;
602 }
603
604 let node = self.upcast::<Node>();
605 let window = self.owner_window();
606 let (_, rect) = window.offset_parent_query(node);
607
608 rect.origin.x.to_nearest_px()
609 }
610
611 fn OffsetWidth(&self) -> i32 {
613 let node = self.upcast::<Node>();
614 let window = self.owner_window();
615 let (_, rect) = window.offset_parent_query(node);
616
617 rect.size.width.to_nearest_px()
618 }
619
620 fn OffsetHeight(&self) -> i32 {
622 let node = self.upcast::<Node>();
623 let window = self.owner_window();
624 let (_, rect) = window.offset_parent_query(node);
625
626 rect.size.height.to_nearest_px()
627 }
628
629 fn InnerText(&self) -> DOMString {
631 self.get_inner_outer_text()
632 }
633
634 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
636 self.set_inner_text(cx, input)
637 }
638
639 fn GetOuterText(&self) -> Fallible<DOMString> {
641 Ok(self.get_inner_outer_text())
642 }
643
644 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
646 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
648 return Err(Error::NoModificationAllowed(Some(
649 "Cannot modify HTML element as its parent element is null".into(),
650 )));
651 };
652
653 let node = self.upcast::<Node>();
654 let document = self.owner_document();
655
656 let next = node.GetNextSibling();
658
659 let previous = node.GetPreviousSibling();
661
662 let fragment = self.rendered_text_fragment(cx, input);
665
666 if fragment.upcast::<Node>().children_count() == 0 {
669 let text_node = Text::new(cx, DOMString::new(), &document);
670
671 fragment
672 .upcast::<Node>()
673 .AppendChild(cx, text_node.upcast())?;
674 }
675
676 parent.ReplaceChild(cx, fragment.upcast(), node)?;
678
679 if let Some(next_sibling) = next &&
682 let Some(node) = next_sibling.GetPreviousSibling()
683 {
684 Self::merge_with_the_next_text_node(cx, &node);
685 }
686
687 if let Some(previous) = previous {
689 Self::merge_with_the_next_text_node(cx, &previous)
690 }
691
692 Ok(())
693 }
694
695 fn Translate(&self) -> bool {
697 self.as_element().is_translate_enabled()
698 }
699
700 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
702 self.as_element().set_string_attribute(
703 cx,
704 &html5ever::local_name!("translate"),
705 match yesno {
706 true => DOMString::from_static("yes"),
707 false => DOMString::from_static("no"),
708 },
709 );
710 }
711
712 make_enumerated_getter!(
714 ContentEditable,
715 "contenteditable",
716 "true" | "false" | "plaintext-only",
717 missing => "inherit",
718 invalid => "inherit",
719 empty => "true"
720 );
721
722 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
724 let attr_name = &local_name!("contenteditable");
725 if value.eq_ignore_ascii_case("inherit") {
726 self.element.remove_attribute_by_name(cx, attr_name);
728 } else if value.eq_ignore_ascii_case("true") ||
729 value.eq_ignore_ascii_case("false") ||
730 value.eq_ignore_ascii_case("plaintext-only")
731 {
732 let lower_value = value.to_ascii_lowercase();
736 self.element
737 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
738 } else {
739 return Err(Error::Syntax(Some(
741 "Invalid attribute for HTML element".into(),
742 )));
743 };
744 Ok(())
745 }
746
747 fn IsContentEditable(&self) -> bool {
749 self.upcast::<Node>().is_editable_or_editing_host()
751 }
752
753 fn AttachInternals(&self, cx: &mut JSContext) -> Fallible<DomRoot<ElementInternals>> {
755 if self.element.get_is().is_some() {
757 return Err(Error::NotSupported(Some(
758 "Local name of HTML element must not be set".into(),
759 )));
760 }
761
762 let lookup_registry = {
764 let registry = self.as_element().custom_element_registry();
767 if registry
768 .as_ref()
769 .is_some_and(|registry| registry.is_scoped())
770 {
771 registry
772 } else {
773 self.upcast::<Node>().owner_doc().custom_element_registry()
774 }
775 };
776 let definition = CustomElementRegistry::lookup_custom_element_definition(
777 lookup_registry.as_deref(),
778 self.upcast::<Element>().namespace(),
779 self.as_element().local_name(),
780 None,
781 );
782
783 let definition = match definition {
785 Some(definition) => definition,
786 None => {
787 return Err(Error::NotSupported(Some(
788 "Custom element definition is missing".into(),
789 )));
790 },
791 };
792
793 if definition.disable_internals {
795 return Err(Error::NotSupported(Some(
796 "Custom element definition's `disabledFeatures` must not include \"internals\""
797 .into(),
798 )));
799 }
800
801 let internals = self.ensure_element_internals(cx);
803 if internals.attached() {
804 return Err(Error::NotSupported(Some(
805 "HTML element's internals are already attached".into(),
806 )));
807 }
808
809 if !matches!(
812 self.element.get_custom_element_state(),
813 CustomElementState::Precustomized | CustomElementState::Custom
814 ) {
815 return Err(Error::NotSupported(Some(
816 "HTML element is not yet upgraded".into(),
817 )));
818 }
819
820 if self.is_form_associated_custom_element() {
821 self.element.init_state_for_internals();
822 }
823
824 internals.set_attached();
826 Ok(internals)
827 }
828
829 fn Nonce(&self) -> DOMString {
831 self.as_element().nonce_value().into()
832 }
833
834 fn SetNonce(&self, cx: &mut JSContext, value: DOMString) {
836 self.as_element()
837 .update_nonce_internal_slot(String::from(value), cx.no_gc())
838 }
839
840 fn Autofocus(&self) -> bool {
842 self.element.has_attribute(&local_name!("autofocus"))
843 }
844
845 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
847 self.element
848 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
849 }
850
851 fn TabIndex(&self) -> i32 {
853 self.element.tab_index()
854 }
855
856 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
858 self.element
859 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
860 }
861
862 make_getter!(AccessKey, "accesskey");
864
865 make_setter!(SetAccessKey, "accesskey");
867
868 fn AccessKeyLabel(&self) -> DOMString {
870 if !self.element.has_attribute(&local_name!("accesskey")) {
874 return Default::default();
875 }
876
877 let access_key_string =
878 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
879
880 #[cfg(target_os = "macos")]
881 let access_key_label = format!("⌃⌥{access_key_string}");
882 #[cfg(not(target_os = "macos"))]
883 let access_key_label = format!("Alt+Shift+{access_key_string}");
884
885 access_key_label.into()
886 }
887}
888
889fn append_text_node_to_fragment(
890 cx: &mut JSContext,
891 document: &Document,
892 fragment: &DocumentFragment,
893 text: String,
894) {
895 let text = Text::new(cx, DOMString::from(text), document);
896 fragment
897 .upcast::<Node>()
898 .AppendChild(cx, text.upcast())
899 .unwrap();
900}
901
902impl HTMLElement {
903 pub(crate) fn is_labelable_element(&self) -> bool {
905 match self.upcast::<Node>().type_id() {
906 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
907 HTMLElementTypeId::HTMLInputElement => !matches!(
908 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
909 InputType::Hidden(_)
910 ),
911 HTMLElementTypeId::HTMLButtonElement |
912 HTMLElementTypeId::HTMLMeterElement |
913 HTMLElementTypeId::HTMLOutputElement |
914 HTMLElementTypeId::HTMLProgressElement |
915 HTMLElementTypeId::HTMLSelectElement |
916 HTMLElementTypeId::HTMLTextAreaElement => true,
917 _ => self.is_form_associated_custom_element(),
918 },
919 _ => false,
920 }
921 }
922
923 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
925 if let Some(definition) = self.as_element().get_custom_element_definition() {
926 definition.is_autonomous() && definition.form_associated
927 } else {
928 false
929 }
930 }
931
932 pub(crate) fn is_listed_element(&self) -> bool {
934 match self.upcast::<Node>().type_id() {
935 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
936 HTMLElementTypeId::HTMLButtonElement |
937 HTMLElementTypeId::HTMLFieldSetElement |
938 HTMLElementTypeId::HTMLInputElement |
939 HTMLElementTypeId::HTMLObjectElement |
940 HTMLElementTypeId::HTMLOutputElement |
941 HTMLElementTypeId::HTMLSelectElement |
942 HTMLElementTypeId::HTMLTextAreaElement => true,
943 _ => self.is_form_associated_custom_element(),
944 },
945 _ => false,
946 }
947 }
948
949 pub(crate) fn is_body_element(&self) -> bool {
951 let self_node = self.upcast::<Node>();
952 self_node.GetParentNode().is_some_and(|parent| {
953 let parent_node = parent.upcast::<Node>();
954 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
955 parent_node.is::<HTMLHtmlElement>() &&
956 self_node
957 .preceding_siblings()
958 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
959 })
960 }
961
962 pub(crate) fn is_submittable_element(&self) -> bool {
964 match self.upcast::<Node>().type_id() {
965 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
966 HTMLElementTypeId::HTMLButtonElement |
967 HTMLElementTypeId::HTMLInputElement |
968 HTMLElementTypeId::HTMLSelectElement |
969 HTMLElementTypeId::HTMLTextAreaElement => true,
970 _ => self.is_form_associated_custom_element(),
971 },
972 _ => false,
973 }
974 }
975
976 pub(crate) fn label_at<'a>(
979 &self,
980 no_gc: &'a NoGC,
981 index: u32,
982 ) -> Option<UnrootedDom<'a, Node>> {
983 let element = self.as_element();
984
985 let root_element = element.root_element();
996 let root_node = root_element.upcast::<Node>();
997 root_node
998 .traverse_preorder_non_rooting(no_gc, ShadowIncluding::No)
999 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
1000 .filter(|elem| match elem.GetControl(no_gc) {
1001 Some(control) => &*control == self,
1002 _ => false,
1003 })
1004 .nth(index as usize)
1005 .map(UnrootedDom::upcast)
1006 }
1007
1008 pub(crate) fn labels_count(&self, no_gc: &NoGC) -> u32 {
1011 let element = self.as_element();
1013 let root_element = element.root_element();
1014 let root_node = root_element.upcast::<Node>();
1015 root_node
1016 .traverse_preorder_non_rooting(no_gc, ShadowIncluding::No)
1017 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
1018 .filter(|elem| match elem.GetControl(no_gc) {
1019 Some(control) => &*control == self,
1020 _ => false,
1021 })
1022 .count() as u32
1023 }
1024
1025 pub(crate) fn directionality(&self) -> Option<String> {
1029 let element_direction = &self.Dir();
1030
1031 if element_direction == "ltr" {
1032 return Some("ltr".to_owned());
1033 }
1034
1035 if element_direction == "rtl" {
1036 return Some("rtl".to_owned());
1037 }
1038
1039 if let Some(input) = self.downcast::<HTMLInputElement>() &&
1040 matches!(*input.input_type(), InputType::Tel(_))
1041 {
1042 return Some("ltr".to_owned());
1043 }
1044
1045 if element_direction == "auto" {
1046 if let Some(directionality) = self
1047 .downcast::<HTMLInputElement>()
1048 .and_then(|input| input.auto_directionality())
1049 {
1050 return Some(directionality);
1051 }
1052
1053 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
1054 return Some(area.auto_directionality());
1055 }
1056 }
1057
1058 None
1065 }
1066
1067 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
1069 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
1070
1071 let is_implicit_summary_element = self.is_implicit_summary_element();
1073 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
1074 return;
1075 }
1076
1077 let parent = if is_implicit_summary_element {
1079 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1080 .unwrap()
1081 } else {
1082 self.upcast::<Node>()
1083 .GetParentNode()
1084 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1085 .unwrap()
1086 };
1087
1088 parent.toggle(cx);
1091 }
1092
1093 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1095 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1098 return false;
1099 };
1100
1101 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1103 return false;
1104 };
1105
1106 details
1110 .find_corresponding_summary_element()
1111 .is_some_and(|summary| &*summary == self.upcast())
1112 }
1113
1114 fn is_implicit_summary_element(&self) -> bool {
1117 self.containing_shadow_root()
1121 .as_deref()
1122 .map(ShadowRoot::Host)
1123 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1124 }
1125
1126 fn rendered_text_fragment(
1128 &self,
1129 cx: &mut JSContext,
1130 input: DOMString,
1131 ) -> DomRoot<DocumentFragment> {
1132 let document = self.owner_document();
1134 let fragment = DocumentFragment::new(cx, &document);
1135
1136 let input = input.str();
1139 let mut position = input.chars().peekable();
1140
1141 let mut text = String::new();
1143
1144 while let Some(ch) = position.next() {
1146 match ch {
1147 '\u{000A}' | '\u{000D}' => {
1150 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1151 position.next();
1154 }
1155
1156 if !text.is_empty() {
1157 append_text_node_to_fragment(cx, &document, &fragment, text);
1158 text = String::new();
1159 }
1160
1161 let br = Element::create(
1162 cx,
1163 QualName::new(None, ns!(html), local_name!("br")),
1164 None,
1165 &document,
1166 ElementCreator::ScriptCreated,
1167 CustomElementCreationMode::Asynchronous,
1168 None,
1169 );
1170 fragment
1171 .upcast::<Node>()
1172 .AppendChild(cx, br.upcast())
1173 .unwrap();
1174 },
1175 _ => {
1176 text.push(ch);
1179 },
1180 }
1181 }
1182
1183 if !text.is_empty() {
1186 append_text_node_to_fragment(cx, &document, &fragment, text);
1187 }
1188
1189 fragment
1190 }
1191
1192 fn merge_with_the_next_text_node(cx: &mut JSContext, node: &Node) {
1198 if !node.is::<Text>() {
1200 return;
1201 }
1202
1203 let next = match node.GetNextSibling() {
1205 Some(next) => next,
1206 None => return,
1207 };
1208
1209 if !next.is::<Text>() {
1211 return;
1212 }
1213 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1215 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1216 node_chars
1217 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1218 .expect("Got chars from Text");
1219
1220 next.remove_self(cx);
1222 }
1223
1224 fn update_assigned_access_key(&self) {
1228 if !self.element.has_attribute(&local_name!("accesskey")) {
1230 self.owner_document()
1232 .event_handler()
1233 .unassign_access_key(self);
1234 }
1235
1236 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1238 let string_view = attribute_value.str();
1239 let values = string_view.split_html_space_characters();
1240
1241 for value in values {
1244 let mut characters = value.chars();
1247 let Some(character) = characters.next() else {
1248 continue;
1249 };
1250 if characters.count() > 0 {
1251 continue;
1252 }
1253
1254 let Some(code) = character_to_code(character) else {
1257 continue;
1258 };
1259
1260 self.owner_document()
1265 .event_handler()
1266 .assign_access_key(self, code);
1267 return;
1268 }
1269
1270 self.owner_document()
1276 .event_handler()
1277 .unassign_access_key(self);
1278 }
1279}
1280
1281impl VirtualMethods for HTMLElement {
1282 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1283 Some(self.as_element() as &dyn VirtualMethods)
1284 }
1285
1286 fn attribute_mutated(
1287 &self,
1288 cx: &mut JSContext,
1289 attr: AttrRef<'_>,
1290 mutation: AttributeMutation,
1291 ) {
1292 self.super_type()
1293 .unwrap()
1294 .attribute_mutated(cx, attr, mutation);
1295 let element = self.as_element();
1296 match (attr.local_name(), mutation) {
1297 (&local_name!("accesskey"), ..) => {
1298 self.update_assigned_access_key();
1299 },
1300 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1301 self.form_attribute_mutated(cx, mutation);
1302 },
1303 (&local_name!("disabled"), AttributeMutation::Set(..))
1305 if self.is_form_associated_custom_element() && element.enabled_state() =>
1306 {
1307 element.set_disabled_state(true);
1308 element.set_enabled_state(false);
1309 ScriptThread::enqueue_callback_reaction(
1310 cx,
1311 element,
1312 CallbackReaction::FormDisabled(true),
1313 None,
1314 );
1315 },
1316 (&local_name!("disabled"), AttributeMutation::Removed)
1319 if self.is_form_associated_custom_element() && element.disabled_state() =>
1320 {
1321 element.set_disabled_state(false);
1322 element.set_enabled_state(true);
1323 element.check_ancestors_disabled_state_for_form_control();
1324 if element.enabled_state() {
1325 ScriptThread::enqueue_callback_reaction(
1326 cx,
1327 element,
1328 CallbackReaction::FormDisabled(false),
1329 None,
1330 );
1331 }
1332 },
1333 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1334 match mutation {
1335 AttributeMutation::Set(..) => {
1336 element.set_read_write_state(true);
1337 },
1338 AttributeMutation::Removed => {
1339 element.set_read_write_state(false);
1340 },
1341 }
1342 },
1343 (&local_name!("nonce"), mutation) => match mutation {
1344 AttributeMutation::Set(..) => {
1345 let nonce = &**attr.value();
1346 element.update_nonce_internal_slot(nonce.to_owned(), cx.no_gc());
1347 },
1348 AttributeMutation::Removed => {
1349 element.update_nonce_internal_slot(String::new(), cx.no_gc());
1350 },
1351 },
1352 _ => {},
1353 }
1354 }
1355
1356 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1357 if let Some(super_type) = self.super_type() {
1358 super_type.bind_to_tree(cx, context);
1359 }
1360
1361 let element = self.as_element();
1364 if self.is_form_associated_custom_element() && element.enabled_state() {
1365 element.check_ancestors_disabled_state_for_form_control();
1366 if element.disabled_state() {
1367 ScriptThread::enqueue_callback_reaction(
1368 cx,
1369 element,
1370 CallbackReaction::FormDisabled(true),
1371 None,
1372 );
1373 }
1374 }
1375
1376 if element.has_attribute(&local_name!("accesskey")) {
1377 self.update_assigned_access_key();
1378 }
1379 }
1380
1381 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1385 let document = self.owner_document();
1387
1388 let element = self.as_element();
1398 if document
1399 .focus_handler()
1400 .focused_area()
1401 .element()
1402 .is_some_and(|focused_element| focused_element == element)
1403 {
1404 document
1405 .focus_handler()
1406 .set_focused_area(FocusableArea::Viewport);
1407 }
1408
1409 if let Some(super_type) = self.super_type() {
1414 super_type.unbind_from_tree(cx, context);
1415 }
1416
1417 if self.is_form_associated_custom_element() && element.disabled_state() {
1428 element.check_disabled_attribute();
1429 element.check_ancestors_disabled_state_for_form_control();
1430 if element.enabled_state() {
1431 ScriptThread::enqueue_callback_reaction(
1432 cx,
1433 element,
1434 CallbackReaction::FormDisabled(false),
1435 None,
1436 );
1437 }
1438 }
1439
1440 if element.has_attribute(&local_name!("accesskey")) {
1441 self.owner_document()
1442 .event_handler()
1443 .unassign_access_key(self);
1444 }
1445 }
1446
1447 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1448 if is_element_affected_by_legacy_background_presentational_hint(
1449 self.element.namespace(),
1450 self.element.local_name(),
1451 ) && attr.local_name() == &local_name!("background")
1452 {
1453 return true;
1454 }
1455
1456 self.super_type()
1457 .unwrap()
1458 .attribute_affects_presentational_hints(attr)
1459 }
1460
1461 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1462 match *name {
1463 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1464 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1465 local_name!("background")
1466 if is_element_affected_by_legacy_background_presentational_hint(
1467 self.element.namespace(),
1468 self.element.local_name(),
1469 ) =>
1470 {
1471 AttrValue::from_resolved_url(
1472 &self.owner_document().base_url().get_arc(),
1473 value.into(),
1474 )
1475 },
1476 _ => self
1477 .super_type()
1478 .unwrap()
1479 .parse_plain_attribute(name, value),
1480 }
1481 }
1482
1483 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1485 if let Some(super_type) = self.super_type() {
1489 super_type.moving_steps(cx, context);
1490 }
1491
1492 if let Some(form_control) = self.element.as_maybe_form_control() {
1496 form_control.moving_steps(cx)
1497 }
1498 }
1499}
1500
1501impl Activatable for HTMLElement {
1502 fn as_element(&self) -> &Element {
1503 &self.element
1504 }
1505
1506 fn is_instance_activatable(&self) -> bool {
1507 self.element.local_name() == &local_name!("summary")
1508 }
1509
1510 fn activation_behavior(
1512 &self,
1513 cx: &mut js::context::JSContext,
1514 _event: &Event,
1515 _target: &EventTarget,
1516 ) {
1517 self.summary_activation_behavior(cx);
1518 }
1519}
1520
1521impl FormControl for HTMLElement {
1527 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1528 debug_assert!(self.is_form_associated_custom_element());
1529 self.element
1530 .get_element_internals()
1531 .and_then(|e| e.form_owner())
1532 }
1533
1534 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>) {
1535 debug_assert!(self.is_form_associated_custom_element());
1536 self.ensure_element_internals(cx).set_form_owner(form);
1537 }
1538
1539 fn to_html_element(&self) -> &HTMLElement {
1540 self
1541 }
1542}