1use std::default::Default;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, QualName, local_name, ns};
10use js::context::{JSContext, NoGC};
11use js::rust::HandleObject;
12use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
13use rustc_hash::FxHashSet;
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<Rc<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(&self, cx: &mut JSContext, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
278 if self.is_body_or_frameset() {
279 let document = self.owner_document();
280 if document.has_browsing_context() {
281 document.window().SetOnerror(cx, listener)
282 }
283 } else {
284 self.upcast::<EventTarget>()
286 .set_error_event_handler(cx, "error", listener)
287 }
288 }
289
290 fn GetOnload(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
292 if self.is_body_or_frameset() {
293 let document = self.owner_document();
294 if document.has_browsing_context() {
295 document.window().GetOnload(cx)
296 } else {
297 None
298 }
299 } else {
300 self.upcast::<EventTarget>()
301 .get_event_handler_common(cx, "load")
302 }
303 }
304
305 fn SetOnload(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
307 if self.is_body_or_frameset() {
308 let document = self.owner_document();
309 if document.has_browsing_context() {
310 document.window().SetOnload(cx, listener)
311 }
312 } else {
313 self.upcast::<EventTarget>()
314 .set_event_handler_common(cx, "load", listener)
315 }
316 }
317
318 fn GetOnblur(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
320 if self.is_body_or_frameset() {
321 let document = self.owner_document();
322 if document.has_browsing_context() {
323 document.window().GetOnblur(cx)
324 } else {
325 None
326 }
327 } else {
328 self.upcast::<EventTarget>()
329 .get_event_handler_common(cx, "blur")
330 }
331 }
332
333 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
335 if self.is_body_or_frameset() {
336 let document = self.owner_document();
337 if document.has_browsing_context() {
338 document.window().SetOnblur(cx, listener)
339 }
340 } else {
341 self.upcast::<EventTarget>()
342 .set_event_handler_common(cx, "blur", listener)
343 }
344 }
345
346 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
348 if self.is_body_or_frameset() {
349 let document = self.owner_document();
350 if document.has_browsing_context() {
351 document.window().GetOnfocus(cx)
352 } else {
353 None
354 }
355 } else {
356 self.upcast::<EventTarget>()
357 .get_event_handler_common(cx, "focus")
358 }
359 }
360
361 fn SetOnfocus(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
363 if self.is_body_or_frameset() {
364 let document = self.owner_document();
365 if document.has_browsing_context() {
366 document.window().SetOnfocus(cx, listener)
367 }
368 } else {
369 self.upcast::<EventTarget>()
370 .set_event_handler_common(cx, "focus", listener)
371 }
372 }
373
374 fn GetOnresize(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
376 if self.is_body_or_frameset() {
377 let document = self.owner_document();
378 if document.has_browsing_context() {
379 document.window().GetOnresize(cx)
380 } else {
381 None
382 }
383 } else {
384 self.upcast::<EventTarget>()
385 .get_event_handler_common(cx, "resize")
386 }
387 }
388
389 fn SetOnresize(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
391 if self.is_body_or_frameset() {
392 let document = self.owner_document();
393 if document.has_browsing_context() {
394 document.window().SetOnresize(cx, listener)
395 }
396 } else {
397 self.upcast::<EventTarget>()
398 .set_event_handler_common(cx, "resize", listener)
399 }
400 }
401
402 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
404 if self.is_body_or_frameset() {
405 let document = self.owner_document();
406 if document.has_browsing_context() {
407 document.window().GetOnscroll(cx)
408 } else {
409 None
410 }
411 } else {
412 self.upcast::<EventTarget>()
413 .get_event_handler_common(cx, "scroll")
414 }
415 }
416
417 fn SetOnscroll(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
419 if self.is_body_or_frameset() {
420 let document = self.owner_document();
421 if document.has_browsing_context() {
422 document.window().SetOnscroll(cx, listener)
423 }
424 } else {
425 self.upcast::<EventTarget>()
426 .set_event_handler_common(cx, "scroll", listener)
427 }
428 }
429
430 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
432 let atoms = self
433 .element
434 .get_tokenlist_attribute(&local_name!("itemtype"));
435
436 if atoms.is_empty() {
437 return None;
438 }
439
440 Some(
441 FxHashSet::from_iter(
442 atoms
443 .iter()
444 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
445 )
446 .into_iter()
447 .collect(),
448 )
449 }
450
451 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
453 let atoms = self
454 .element
455 .get_tokenlist_attribute(&local_name!("itemprop"));
456
457 if atoms.is_empty() {
458 return None;
459 }
460
461 Some(
462 FxHashSet::from_iter(
463 atoms
464 .iter()
465 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
466 )
467 .into_iter()
468 .collect(),
469 )
470 }
471
472 fn Click(&self, cx: &mut JSContext) {
474 let element = self.as_element();
475 if element.disabled_state() {
476 return;
477 }
478 if element.click_in_progress() {
479 return;
480 }
481 element.set_click_in_progress(true);
482
483 self.upcast::<Node>()
484 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
485 element.set_click_in_progress(false);
486 }
487
488 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
490 if !self
495 .upcast::<Node>()
496 .run_the_focusing_steps(cx, None, FocusTrigger::Other)
497 {
498 return;
502 }
503
504 if !options.preventScroll {
511 let scroll_axis = ScrollAxisState {
512 position: ScrollLogicalPosition::Center,
513 requirement: ScrollRequirement::IfNotVisible,
514 };
515 self.upcast::<Element>().scroll_into_view_with_options(
516 cx,
517 ScrollBehavior::Smooth,
518 scroll_axis,
519 scroll_axis,
520 None,
521 None,
522 );
523 }
524 }
525
526 fn Blur(&self, cx: &mut JSContext) {
528 if !self.as_element().focus_state() {
531 return;
532 }
533 self.owner_document()
535 .focus_handler()
536 .focus(cx, &FocusableArea::Viewport);
537 }
538
539 #[expect(unsafe_code)]
541 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
542 self.owner_window()
543 .scroll_container_query(
544 Some(self.upcast()),
545 ScrollContainerQueryFlags::ForScrollParent,
546 )
547 .and_then(|response| match response {
548 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
549 ScrollContainerResponse::Element(parent_node_address, _) => {
550 let node = unsafe { from_untrusted_node_address(parent_node_address) };
551 DomRoot::downcast(node)
552 },
553 })
554 }
555
556 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
558 if self.is::<HTMLBodyElement>() || self.element.is_root() {
559 return None;
560 }
561
562 let node = self.upcast::<Node>();
563 let window = self.owner_window();
564 let (element, _) = window.offset_parent_query(node);
565
566 element
567 }
568
569 fn OffsetTop(&self) -> i32 {
571 if self.is_body_element() {
572 return 0;
573 }
574
575 let node = self.upcast::<Node>();
576 let window = self.owner_window();
577 let (_, rect) = window.offset_parent_query(node);
578
579 rect.origin.y.to_nearest_px()
580 }
581
582 fn OffsetLeft(&self) -> i32 {
584 if self.is_body_element() {
585 return 0;
586 }
587
588 let node = self.upcast::<Node>();
589 let window = self.owner_window();
590 let (_, rect) = window.offset_parent_query(node);
591
592 rect.origin.x.to_nearest_px()
593 }
594
595 fn OffsetWidth(&self) -> i32 {
597 let node = self.upcast::<Node>();
598 let window = self.owner_window();
599 let (_, rect) = window.offset_parent_query(node);
600
601 rect.size.width.to_nearest_px()
602 }
603
604 fn OffsetHeight(&self) -> i32 {
606 let node = self.upcast::<Node>();
607 let window = self.owner_window();
608 let (_, rect) = window.offset_parent_query(node);
609
610 rect.size.height.to_nearest_px()
611 }
612
613 fn InnerText(&self) -> DOMString {
615 self.get_inner_outer_text()
616 }
617
618 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
620 self.set_inner_text(cx, input)
621 }
622
623 fn GetOuterText(&self) -> Fallible<DOMString> {
625 Ok(self.get_inner_outer_text())
626 }
627
628 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
630 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
632 return Err(Error::NoModificationAllowed(Some(
633 "Cannot modify HTML element as its parent element is null".into(),
634 )));
635 };
636
637 let node = self.upcast::<Node>();
638 let document = self.owner_document();
639
640 let next = node.GetNextSibling();
642
643 let previous = node.GetPreviousSibling();
645
646 let fragment = self.rendered_text_fragment(cx, input);
649
650 if fragment.upcast::<Node>().children_count() == 0 {
653 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
654
655 fragment
656 .upcast::<Node>()
657 .AppendChild(cx, text_node.upcast())?;
658 }
659
660 parent.ReplaceChild(cx, fragment.upcast(), node)?;
662
663 if let Some(next_sibling) = next &&
666 let Some(node) = next_sibling.GetPreviousSibling()
667 {
668 Self::merge_with_the_next_text_node(cx, &node);
669 }
670
671 if let Some(previous) = previous {
673 Self::merge_with_the_next_text_node(cx, &previous)
674 }
675
676 Ok(())
677 }
678
679 fn Translate(&self) -> bool {
681 self.as_element().is_translate_enabled()
682 }
683
684 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
686 self.as_element().set_string_attribute(
687 cx,
688 &html5ever::local_name!("translate"),
689 match yesno {
690 true => DOMString::from_static("yes"),
691 false => DOMString::from_static("no"),
692 },
693 );
694 }
695
696 make_enumerated_getter!(
698 ContentEditable,
699 "contenteditable",
700 "true" | "false" | "plaintext-only",
701 missing => "inherit",
702 invalid => "inherit",
703 empty => "true"
704 );
705
706 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
708 let attr_name = &local_name!("contenteditable");
709 if value.eq_ignore_ascii_case("inherit") {
710 self.element.remove_attribute_by_name(cx, attr_name);
712 } else if value.eq_ignore_ascii_case("true") ||
713 value.eq_ignore_ascii_case("false") ||
714 value.eq_ignore_ascii_case("plaintext-only")
715 {
716 let lower_value = value.to_ascii_lowercase();
720 self.element
721 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
722 } else {
723 return Err(Error::Syntax(Some(
725 "Invalid attribute for HTML element".into(),
726 )));
727 };
728 Ok(())
729 }
730
731 fn IsContentEditable(&self) -> bool {
733 self.upcast::<Node>().is_editable_or_editing_host()
735 }
736
737 fn AttachInternals(&self, cx: &mut JSContext) -> Fallible<DomRoot<ElementInternals>> {
739 if self.element.get_is().is_some() {
741 return Err(Error::NotSupported(Some(
742 "Local name of HTML element must not be set".into(),
743 )));
744 }
745
746 let lookup_registry = {
748 let registry = self.as_element().custom_element_registry();
751 if registry
752 .as_ref()
753 .is_some_and(|registry| registry.is_scoped())
754 {
755 registry
756 } else {
757 self.upcast::<Node>().owner_doc().custom_element_registry()
758 }
759 };
760 let definition = CustomElementRegistry::lookup_custom_element_definition(
761 lookup_registry.as_deref(),
762 self.upcast::<Element>().namespace(),
763 self.as_element().local_name(),
764 None,
765 );
766
767 let definition = match definition {
769 Some(definition) => definition,
770 None => {
771 return Err(Error::NotSupported(Some(
772 "Custom element definition is missing".into(),
773 )));
774 },
775 };
776
777 if definition.disable_internals {
779 return Err(Error::NotSupported(Some(
780 "Custom element definition's `disabledFeatures` must not include \"internals\""
781 .into(),
782 )));
783 }
784
785 let internals = self.ensure_element_internals(cx);
787 if internals.attached() {
788 return Err(Error::NotSupported(Some(
789 "HTML element's internals are already attached".into(),
790 )));
791 }
792
793 if !matches!(
796 self.element.get_custom_element_state(),
797 CustomElementState::Precustomized | CustomElementState::Custom
798 ) {
799 return Err(Error::NotSupported(Some(
800 "HTML element is not yet upgraded".into(),
801 )));
802 }
803
804 if self.is_form_associated_custom_element() {
805 self.element.init_state_for_internals();
806 }
807
808 internals.set_attached();
810 Ok(internals)
811 }
812
813 fn Nonce(&self) -> DOMString {
815 self.as_element().nonce_value().into()
816 }
817
818 fn SetNonce(&self, cx: &mut JSContext, value: DOMString) {
820 self.as_element()
821 .update_nonce_internal_slot(String::from(value), cx.no_gc())
822 }
823
824 fn Autofocus(&self) -> bool {
826 self.element.has_attribute(&local_name!("autofocus"))
827 }
828
829 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
831 self.element
832 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
833 }
834
835 fn TabIndex(&self) -> i32 {
837 self.element.tab_index()
838 }
839
840 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
842 self.element
843 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
844 }
845
846 make_getter!(AccessKey, "accesskey");
848
849 make_setter!(SetAccessKey, "accesskey");
851
852 fn AccessKeyLabel(&self) -> DOMString {
854 if !self.element.has_attribute(&local_name!("accesskey")) {
858 return Default::default();
859 }
860
861 let access_key_string =
862 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
863
864 #[cfg(target_os = "macos")]
865 let access_key_label = format!("⌃⌥{access_key_string}");
866 #[cfg(not(target_os = "macos"))]
867 let access_key_label = format!("Alt+Shift+{access_key_string}");
868
869 access_key_label.into()
870 }
871}
872
873fn append_text_node_to_fragment(
874 cx: &mut JSContext,
875 document: &Document,
876 fragment: &DocumentFragment,
877 text: String,
878) {
879 let text = Text::new(cx, DOMString::from(text), document);
880 fragment
881 .upcast::<Node>()
882 .AppendChild(cx, text.upcast())
883 .unwrap();
884}
885
886impl HTMLElement {
887 pub(crate) fn is_labelable_element(&self) -> bool {
889 match self.upcast::<Node>().type_id() {
890 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
891 HTMLElementTypeId::HTMLInputElement => !matches!(
892 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
893 InputType::Hidden(_)
894 ),
895 HTMLElementTypeId::HTMLButtonElement |
896 HTMLElementTypeId::HTMLMeterElement |
897 HTMLElementTypeId::HTMLOutputElement |
898 HTMLElementTypeId::HTMLProgressElement |
899 HTMLElementTypeId::HTMLSelectElement |
900 HTMLElementTypeId::HTMLTextAreaElement => true,
901 _ => self.is_form_associated_custom_element(),
902 },
903 _ => false,
904 }
905 }
906
907 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
909 if let Some(definition) = self.as_element().get_custom_element_definition() {
910 definition.is_autonomous() && definition.form_associated
911 } else {
912 false
913 }
914 }
915
916 pub(crate) fn is_listed_element(&self) -> bool {
918 match self.upcast::<Node>().type_id() {
919 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
920 HTMLElementTypeId::HTMLButtonElement |
921 HTMLElementTypeId::HTMLFieldSetElement |
922 HTMLElementTypeId::HTMLInputElement |
923 HTMLElementTypeId::HTMLObjectElement |
924 HTMLElementTypeId::HTMLOutputElement |
925 HTMLElementTypeId::HTMLSelectElement |
926 HTMLElementTypeId::HTMLTextAreaElement => true,
927 _ => self.is_form_associated_custom_element(),
928 },
929 _ => false,
930 }
931 }
932
933 pub(crate) fn is_body_element(&self) -> bool {
935 let self_node = self.upcast::<Node>();
936 self_node.GetParentNode().is_some_and(|parent| {
937 let parent_node = parent.upcast::<Node>();
938 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
939 parent_node.is::<HTMLHtmlElement>() &&
940 self_node
941 .preceding_siblings()
942 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
943 })
944 }
945
946 pub(crate) fn is_submittable_element(&self) -> bool {
948 match self.upcast::<Node>().type_id() {
949 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
950 HTMLElementTypeId::HTMLButtonElement |
951 HTMLElementTypeId::HTMLInputElement |
952 HTMLElementTypeId::HTMLSelectElement |
953 HTMLElementTypeId::HTMLTextAreaElement => true,
954 _ => self.is_form_associated_custom_element(),
955 },
956 _ => false,
957 }
958 }
959
960 pub(crate) fn label_at<'a>(
963 &self,
964 no_gc: &'a NoGC,
965 index: u32,
966 ) -> Option<UnrootedDom<'a, Node>> {
967 let element = self.as_element();
968
969 let root_element = element.root_element();
980 let root_node = root_element.upcast::<Node>();
981 root_node
982 .traverse_preorder_non_rooting(no_gc, ShadowIncluding::No)
983 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
984 .filter(|elem| match elem.GetControl() {
985 Some(control) => &*control == self,
986 _ => false,
987 })
988 .nth(index as usize)
989 .map(UnrootedDom::upcast)
990 }
991
992 pub(crate) fn labels_count(&self) -> u32 {
995 let element = self.as_element();
997 let root_element = element.root_element();
998 let root_node = root_element.upcast::<Node>();
999 root_node
1000 .traverse_preorder(ShadowIncluding::No)
1001 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
1002 .filter(|elem| match elem.GetControl() {
1003 Some(control) => &*control == self,
1004 _ => false,
1005 })
1006 .count() as u32
1007 }
1008
1009 pub(crate) fn directionality(&self) -> Option<String> {
1013 let element_direction = &self.Dir();
1014
1015 if element_direction == "ltr" {
1016 return Some("ltr".to_owned());
1017 }
1018
1019 if element_direction == "rtl" {
1020 return Some("rtl".to_owned());
1021 }
1022
1023 if let Some(input) = self.downcast::<HTMLInputElement>() &&
1024 matches!(*input.input_type(), InputType::Tel(_))
1025 {
1026 return Some("ltr".to_owned());
1027 }
1028
1029 if element_direction == "auto" {
1030 if let Some(directionality) = self
1031 .downcast::<HTMLInputElement>()
1032 .and_then(|input| input.auto_directionality())
1033 {
1034 return Some(directionality);
1035 }
1036
1037 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
1038 return Some(area.auto_directionality());
1039 }
1040 }
1041
1042 None
1049 }
1050
1051 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
1053 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
1054
1055 let is_implicit_summary_element = self.is_implicit_summary_element();
1057 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
1058 return;
1059 }
1060
1061 let parent = if is_implicit_summary_element {
1063 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1064 .unwrap()
1065 } else {
1066 self.upcast::<Node>()
1067 .GetParentNode()
1068 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1069 .unwrap()
1070 };
1071
1072 parent.toggle(cx);
1075 }
1076
1077 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1079 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1082 return false;
1083 };
1084
1085 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1087 return false;
1088 };
1089
1090 details
1094 .find_corresponding_summary_element()
1095 .is_some_and(|summary| &*summary == self.upcast())
1096 }
1097
1098 fn is_implicit_summary_element(&self) -> bool {
1101 self.containing_shadow_root()
1105 .as_deref()
1106 .map(ShadowRoot::Host)
1107 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1108 }
1109
1110 fn rendered_text_fragment(
1112 &self,
1113 cx: &mut JSContext,
1114 input: DOMString,
1115 ) -> DomRoot<DocumentFragment> {
1116 let document = self.owner_document();
1118 let fragment = DocumentFragment::new(cx, &document);
1119
1120 let input = input.str();
1123 let mut position = input.chars().peekable();
1124
1125 let mut text = String::new();
1127
1128 while let Some(ch) = position.next() {
1130 match ch {
1131 '\u{000A}' | '\u{000D}' => {
1134 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1135 position.next();
1138 }
1139
1140 if !text.is_empty() {
1141 append_text_node_to_fragment(cx, &document, &fragment, text);
1142 text = String::new();
1143 }
1144
1145 let br = Element::create(
1146 cx,
1147 QualName::new(None, ns!(html), local_name!("br")),
1148 None,
1149 &document,
1150 ElementCreator::ScriptCreated,
1151 CustomElementCreationMode::Asynchronous,
1152 None,
1153 );
1154 fragment
1155 .upcast::<Node>()
1156 .AppendChild(cx, br.upcast())
1157 .unwrap();
1158 },
1159 _ => {
1160 text.push(ch);
1163 },
1164 }
1165 }
1166
1167 if !text.is_empty() {
1170 append_text_node_to_fragment(cx, &document, &fragment, text);
1171 }
1172
1173 fragment
1174 }
1175
1176 fn merge_with_the_next_text_node(cx: &mut JSContext, node: &Node) {
1182 if !node.is::<Text>() {
1184 return;
1185 }
1186
1187 let next = match node.GetNextSibling() {
1189 Some(next) => next,
1190 None => return,
1191 };
1192
1193 if !next.is::<Text>() {
1195 return;
1196 }
1197 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1199 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1200 node_chars
1201 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1202 .expect("Got chars from Text");
1203
1204 next.remove_self(cx);
1206 }
1207
1208 fn update_assigned_access_key(&self) {
1212 if !self.element.has_attribute(&local_name!("accesskey")) {
1214 self.owner_document()
1216 .event_handler()
1217 .unassign_access_key(self);
1218 }
1219
1220 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1222 let string_view = attribute_value.str();
1223 let values = string_view.split_html_space_characters();
1224
1225 for value in values {
1228 let mut characters = value.chars();
1231 let Some(character) = characters.next() else {
1232 continue;
1233 };
1234 if characters.count() > 0 {
1235 continue;
1236 }
1237
1238 let Some(code) = character_to_code(character) else {
1241 continue;
1242 };
1243
1244 self.owner_document()
1249 .event_handler()
1250 .assign_access_key(self, code);
1251 return;
1252 }
1253
1254 self.owner_document()
1260 .event_handler()
1261 .unassign_access_key(self);
1262 }
1263}
1264
1265impl VirtualMethods for HTMLElement {
1266 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1267 Some(self.as_element() as &dyn VirtualMethods)
1268 }
1269
1270 fn attribute_mutated(
1271 &self,
1272 cx: &mut JSContext,
1273 attr: AttrRef<'_>,
1274 mutation: AttributeMutation,
1275 ) {
1276 self.super_type()
1277 .unwrap()
1278 .attribute_mutated(cx, attr, mutation);
1279 let element = self.as_element();
1280 match (attr.local_name(), mutation) {
1281 (&local_name!("accesskey"), ..) => {
1282 self.update_assigned_access_key();
1283 },
1284 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1285 self.form_attribute_mutated(cx, mutation);
1286 },
1287 (&local_name!("disabled"), AttributeMutation::Set(..))
1289 if self.is_form_associated_custom_element() && element.enabled_state() =>
1290 {
1291 element.set_disabled_state(true);
1292 element.set_enabled_state(false);
1293 ScriptThread::enqueue_callback_reaction(
1294 cx,
1295 element,
1296 CallbackReaction::FormDisabled(true),
1297 None,
1298 );
1299 },
1300 (&local_name!("disabled"), AttributeMutation::Removed)
1303 if self.is_form_associated_custom_element() && element.disabled_state() =>
1304 {
1305 element.set_disabled_state(false);
1306 element.set_enabled_state(true);
1307 element.check_ancestors_disabled_state_for_form_control();
1308 if element.enabled_state() {
1309 ScriptThread::enqueue_callback_reaction(
1310 cx,
1311 element,
1312 CallbackReaction::FormDisabled(false),
1313 None,
1314 );
1315 }
1316 },
1317 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1318 match mutation {
1319 AttributeMutation::Set(..) => {
1320 element.set_read_write_state(true);
1321 },
1322 AttributeMutation::Removed => {
1323 element.set_read_write_state(false);
1324 },
1325 }
1326 },
1327 (&local_name!("nonce"), mutation) => match mutation {
1328 AttributeMutation::Set(..) => {
1329 let nonce = &**attr.value();
1330 element.update_nonce_internal_slot(nonce.to_owned(), cx.no_gc());
1331 },
1332 AttributeMutation::Removed => {
1333 element.update_nonce_internal_slot("".to_owned(), cx.no_gc());
1334 },
1335 },
1336 _ => {},
1337 }
1338 }
1339
1340 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1341 if let Some(super_type) = self.super_type() {
1342 super_type.bind_to_tree(cx, context);
1343 }
1344
1345 let element = self.as_element();
1348 if self.is_form_associated_custom_element() && element.enabled_state() {
1349 element.check_ancestors_disabled_state_for_form_control();
1350 if element.disabled_state() {
1351 ScriptThread::enqueue_callback_reaction(
1352 cx,
1353 element,
1354 CallbackReaction::FormDisabled(true),
1355 None,
1356 );
1357 }
1358 }
1359
1360 if element.has_attribute(&local_name!("accesskey")) {
1361 self.update_assigned_access_key();
1362 }
1363 }
1364
1365 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1369 let document = self.owner_document();
1371
1372 let element = self.as_element();
1382 if document
1383 .focus_handler()
1384 .focused_area()
1385 .element()
1386 .is_some_and(|focused_element| focused_element == element)
1387 {
1388 document
1389 .focus_handler()
1390 .set_focused_area(FocusableArea::Viewport);
1391 }
1392
1393 if let Some(super_type) = self.super_type() {
1398 super_type.unbind_from_tree(cx, context);
1399 }
1400
1401 if self.is_form_associated_custom_element() && element.disabled_state() {
1412 element.check_disabled_attribute();
1413 element.check_ancestors_disabled_state_for_form_control();
1414 if element.enabled_state() {
1415 ScriptThread::enqueue_callback_reaction(
1416 cx,
1417 element,
1418 CallbackReaction::FormDisabled(false),
1419 None,
1420 );
1421 }
1422 }
1423
1424 if element.has_attribute(&local_name!("accesskey")) {
1425 self.owner_document()
1426 .event_handler()
1427 .unassign_access_key(self);
1428 }
1429 }
1430
1431 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1432 if is_element_affected_by_legacy_background_presentational_hint(
1433 self.element.namespace(),
1434 self.element.local_name(),
1435 ) && attr.local_name() == &local_name!("background")
1436 {
1437 return true;
1438 }
1439
1440 self.super_type()
1441 .unwrap()
1442 .attribute_affects_presentational_hints(attr)
1443 }
1444
1445 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1446 match *name {
1447 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1448 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1449 local_name!("background")
1450 if is_element_affected_by_legacy_background_presentational_hint(
1451 self.element.namespace(),
1452 self.element.local_name(),
1453 ) =>
1454 {
1455 AttrValue::from_resolved_url(
1456 &self.owner_document().base_url().get_arc(),
1457 value.into(),
1458 )
1459 },
1460 _ => self
1461 .super_type()
1462 .unwrap()
1463 .parse_plain_attribute(name, value),
1464 }
1465 }
1466
1467 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1469 if let Some(super_type) = self.super_type() {
1473 super_type.moving_steps(cx, context);
1474 }
1475
1476 if let Some(form_control) = self.element.as_maybe_form_control() {
1480 form_control.moving_steps(cx)
1481 }
1482 }
1483}
1484
1485impl Activatable for HTMLElement {
1486 fn as_element(&self) -> &Element {
1487 &self.element
1488 }
1489
1490 fn is_instance_activatable(&self) -> bool {
1491 self.element.local_name() == &local_name!("summary")
1492 }
1493
1494 fn activation_behavior(
1496 &self,
1497 cx: &mut js::context::JSContext,
1498 _event: &Event,
1499 _target: &EventTarget,
1500 ) {
1501 self.summary_activation_behavior(cx);
1502 }
1503}
1504
1505impl FormControl for HTMLElement {
1511 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1512 debug_assert!(self.is_form_associated_custom_element());
1513 self.element
1514 .get_element_internals()
1515 .and_then(|e| e.form_owner())
1516 }
1517
1518 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>) {
1519 debug_assert!(self.is_form_associated_custom_element());
1520 self.ensure_element_internals(cx).set_form_owner(form);
1521 }
1522
1523 fn to_html_element(&self) -> &HTMLElement {
1524 self
1525 }
1526}