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