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 cx,
486 ScrollBehavior::Smooth,
487 scroll_axis,
488 scroll_axis,
489 None,
490 None,
491 );
492 }
493 }
494
495 fn Blur(&self, cx: &mut JSContext) {
497 if !self.as_element().focus_state() {
500 return;
501 }
502 self.owner_document()
504 .focus_handler()
505 .focus(cx, FocusableArea::Viewport);
506 }
507
508 #[expect(unsafe_code)]
510 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
511 self.owner_window()
512 .scroll_container_query(
513 Some(self.upcast()),
514 ScrollContainerQueryFlags::ForScrollParent,
515 )
516 .and_then(|response| match response {
517 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
518 ScrollContainerResponse::Element(parent_node_address, _) => {
519 let node = unsafe { from_untrusted_node_address(parent_node_address) };
520 DomRoot::downcast(node)
521 },
522 })
523 }
524
525 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
527 if self.is::<HTMLBodyElement>() || self.element.is_root() {
528 return None;
529 }
530
531 let node = self.upcast::<Node>();
532 let window = self.owner_window();
533 let (element, _) = window.offset_parent_query(node);
534
535 element
536 }
537
538 fn OffsetTop(&self) -> i32 {
540 if self.is_body_element() {
541 return 0;
542 }
543
544 let node = self.upcast::<Node>();
545 let window = self.owner_window();
546 let (_, rect) = window.offset_parent_query(node);
547
548 rect.origin.y.to_nearest_px()
549 }
550
551 fn OffsetLeft(&self) -> i32 {
553 if self.is_body_element() {
554 return 0;
555 }
556
557 let node = self.upcast::<Node>();
558 let window = self.owner_window();
559 let (_, rect) = window.offset_parent_query(node);
560
561 rect.origin.x.to_nearest_px()
562 }
563
564 fn OffsetWidth(&self) -> i32 {
566 let node = self.upcast::<Node>();
567 let window = self.owner_window();
568 let (_, rect) = window.offset_parent_query(node);
569
570 rect.size.width.to_nearest_px()
571 }
572
573 fn OffsetHeight(&self) -> i32 {
575 let node = self.upcast::<Node>();
576 let window = self.owner_window();
577 let (_, rect) = window.offset_parent_query(node);
578
579 rect.size.height.to_nearest_px()
580 }
581
582 fn InnerText(&self) -> DOMString {
584 self.get_inner_outer_text()
585 }
586
587 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
589 self.set_inner_text(cx, input)
590 }
591
592 fn GetOuterText(&self) -> Fallible<DOMString> {
594 Ok(self.get_inner_outer_text())
595 }
596
597 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
599 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
601 return Err(Error::NoModificationAllowed(None));
602 };
603
604 let node = self.upcast::<Node>();
605 let document = self.owner_document();
606
607 let next = node.GetNextSibling();
609
610 let previous = node.GetPreviousSibling();
612
613 let fragment = self.rendered_text_fragment(cx, input);
616
617 if fragment.upcast::<Node>().children_count() == 0 {
620 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
621
622 fragment
623 .upcast::<Node>()
624 .AppendChild(cx, text_node.upcast())?;
625 }
626
627 parent.ReplaceChild(cx, fragment.upcast(), node)?;
629
630 if let Some(next_sibling) = next &&
633 let Some(node) = next_sibling.GetPreviousSibling()
634 {
635 Self::merge_with_the_next_text_node(cx, node);
636 }
637
638 if let Some(previous) = previous {
640 Self::merge_with_the_next_text_node(cx, previous)
641 }
642
643 Ok(())
644 }
645
646 fn Translate(&self) -> bool {
648 self.as_element().is_translate_enabled()
649 }
650
651 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
653 self.as_element().set_string_attribute(
654 cx,
655 &html5ever::local_name!("translate"),
656 match yesno {
657 true => DOMString::from("yes"),
658 false => DOMString::from("no"),
659 },
660 );
661 }
662
663 make_enumerated_getter!(
665 ContentEditable,
666 "contenteditable",
667 "true" | "false" | "plaintext-only",
668 missing => "inherit",
669 invalid => "inherit",
670 empty => "true"
671 );
672
673 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
675 let lower_value = value.to_ascii_lowercase();
676 let attr_name = &local_name!("contenteditable");
677 match lower_value.as_ref() {
678 "inherit" => {
680 self.element.remove_attribute_by_name(cx, attr_name);
681 },
682 "true" | "false" | "plaintext-only" => {
686 self.element
687 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
688 },
689 _ => return Err(Error::Syntax(None)),
691 };
692 Ok(())
693 }
694
695 fn IsContentEditable(&self) -> bool {
697 self.upcast::<Node>().is_editable_or_editing_host()
699 }
700
701 fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
703 if self.element.get_is().is_some() {
705 return Err(Error::NotSupported(None));
706 }
707
708 let registry = self.owner_window().CustomElements();
713 let definition = registry.lookup_definition(self.as_element().local_name(), None);
714
715 let definition = match definition {
717 Some(definition) => definition,
718 None => return Err(Error::NotSupported(None)),
719 };
720
721 if definition.disable_internals {
723 return Err(Error::NotSupported(None));
724 }
725
726 let internals = self.element.ensure_element_internals(can_gc);
728 if internals.attached() {
729 return Err(Error::NotSupported(None));
730 }
731
732 if !matches!(
735 self.element.get_custom_element_state(),
736 CustomElementState::Precustomized | CustomElementState::Custom
737 ) {
738 return Err(Error::NotSupported(None));
739 }
740
741 if self.is_form_associated_custom_element() {
742 self.element.init_state_for_internals();
743 }
744
745 internals.set_attached();
747 Ok(internals)
748 }
749
750 fn Nonce(&self) -> DOMString {
752 self.as_element().nonce_value().into()
753 }
754
755 fn SetNonce(&self, _cx: &mut JSContext, value: DOMString) {
757 self.as_element()
758 .update_nonce_internal_slot(value.to_string())
759 }
760
761 fn Autofocus(&self) -> bool {
763 self.element.has_attribute(&local_name!("autofocus"))
764 }
765
766 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
768 self.element
769 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
770 }
771
772 fn TabIndex(&self) -> i32 {
774 self.element.tab_index()
775 }
776
777 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
779 self.element
780 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
781 }
782
783 make_getter!(AccessKey, "accesskey");
785
786 make_setter!(cx, SetAccessKey, "accesskey");
788
789 fn AccessKeyLabel(&self) -> DOMString {
791 if !self.element.has_attribute(&local_name!("accesskey")) {
795 return Default::default();
796 }
797
798 let access_key_string = self
799 .element
800 .get_string_attribute(&local_name!("accesskey"))
801 .to_string();
802
803 #[cfg(target_os = "macos")]
804 let access_key_label = format!("⌃⌥{access_key_string}");
805 #[cfg(not(target_os = "macos"))]
806 let access_key_label = format!("Alt+Shift+{access_key_string}");
807
808 access_key_label.into()
809 }
810}
811
812fn append_text_node_to_fragment(
813 cx: &mut JSContext,
814 document: &Document,
815 fragment: &DocumentFragment,
816 text: String,
817) {
818 let text = Text::new(cx, DOMString::from(text), document);
819 fragment
820 .upcast::<Node>()
821 .AppendChild(cx, text.upcast())
822 .unwrap();
823}
824
825impl HTMLElement {
826 pub(crate) fn is_labelable_element(&self) -> bool {
828 match self.upcast::<Node>().type_id() {
829 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
830 HTMLElementTypeId::HTMLInputElement => !matches!(
831 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
832 InputType::Hidden(_)
833 ),
834 HTMLElementTypeId::HTMLButtonElement |
835 HTMLElementTypeId::HTMLMeterElement |
836 HTMLElementTypeId::HTMLOutputElement |
837 HTMLElementTypeId::HTMLProgressElement |
838 HTMLElementTypeId::HTMLSelectElement |
839 HTMLElementTypeId::HTMLTextAreaElement => true,
840 _ => self.is_form_associated_custom_element(),
841 },
842 _ => false,
843 }
844 }
845
846 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
848 if let Some(definition) = self.as_element().get_custom_element_definition() {
849 definition.is_autonomous() && definition.form_associated
850 } else {
851 false
852 }
853 }
854
855 pub(crate) fn is_listed_element(&self) -> bool {
857 match self.upcast::<Node>().type_id() {
858 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
859 HTMLElementTypeId::HTMLButtonElement |
860 HTMLElementTypeId::HTMLFieldSetElement |
861 HTMLElementTypeId::HTMLInputElement |
862 HTMLElementTypeId::HTMLObjectElement |
863 HTMLElementTypeId::HTMLOutputElement |
864 HTMLElementTypeId::HTMLSelectElement |
865 HTMLElementTypeId::HTMLTextAreaElement => true,
866 _ => self.is_form_associated_custom_element(),
867 },
868 _ => false,
869 }
870 }
871
872 pub(crate) fn is_body_element(&self) -> bool {
874 let self_node = self.upcast::<Node>();
875 self_node.GetParentNode().is_some_and(|parent| {
876 let parent_node = parent.upcast::<Node>();
877 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
878 parent_node.is::<HTMLHtmlElement>() &&
879 self_node
880 .preceding_siblings()
881 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
882 })
883 }
884
885 pub(crate) fn is_submittable_element(&self) -> bool {
887 match self.upcast::<Node>().type_id() {
888 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
889 HTMLElementTypeId::HTMLButtonElement |
890 HTMLElementTypeId::HTMLInputElement |
891 HTMLElementTypeId::HTMLSelectElement |
892 HTMLElementTypeId::HTMLTextAreaElement => true,
893 _ => self.is_form_associated_custom_element(),
894 },
895 _ => false,
896 }
897 }
898
899 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
902 let element = self.as_element();
903
904 let root_element = element.root_element();
915 let root_node = root_element.upcast::<Node>();
916 root_node
917 .traverse_preorder(ShadowIncluding::No)
918 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
919 .filter(|elem| match elem.GetControl() {
920 Some(control) => &*control == self,
921 _ => false,
922 })
923 .nth(index as usize)
924 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
925 }
926
927 pub(crate) fn labels_count(&self) -> u32 {
930 let element = self.as_element();
932 let root_element = element.root_element();
933 let root_node = root_element.upcast::<Node>();
934 root_node
935 .traverse_preorder(ShadowIncluding::No)
936 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
937 .filter(|elem| match elem.GetControl() {
938 Some(control) => &*control == self,
939 _ => false,
940 })
941 .count() as u32
942 }
943
944 pub(crate) fn directionality(&self) -> Option<String> {
948 let element_direction = &self.Dir();
949
950 if element_direction == "ltr" {
951 return Some("ltr".to_owned());
952 }
953
954 if element_direction == "rtl" {
955 return Some("rtl".to_owned());
956 }
957
958 if let Some(input) = self.downcast::<HTMLInputElement>() &&
959 matches!(*input.input_type(), InputType::Tel(_))
960 {
961 return Some("ltr".to_owned());
962 }
963
964 if element_direction == "auto" {
965 if let Some(directionality) = self
966 .downcast::<HTMLInputElement>()
967 .and_then(|input| input.auto_directionality())
968 {
969 return Some(directionality);
970 }
971
972 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
973 return Some(area.auto_directionality());
974 }
975 }
976
977 None
984 }
985
986 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
988 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
989
990 let is_implicit_summary_element = self.is_implicit_summary_element();
992 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
993 return;
994 }
995
996 let parent = if is_implicit_summary_element {
998 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
999 .unwrap()
1000 } else {
1001 self.upcast::<Node>()
1002 .GetParentNode()
1003 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1004 .unwrap()
1005 };
1006
1007 parent.toggle(cx);
1010 }
1011
1012 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1014 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1017 return false;
1018 };
1019
1020 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1022 return false;
1023 };
1024
1025 details
1029 .find_corresponding_summary_element()
1030 .is_some_and(|summary| &*summary == self.upcast())
1031 }
1032
1033 fn is_implicit_summary_element(&self) -> bool {
1036 self.containing_shadow_root()
1040 .as_deref()
1041 .map(ShadowRoot::Host)
1042 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1043 }
1044
1045 fn rendered_text_fragment(
1047 &self,
1048 cx: &mut JSContext,
1049 input: DOMString,
1050 ) -> DomRoot<DocumentFragment> {
1051 let document = self.owner_document();
1053 let fragment = DocumentFragment::new(cx, &document);
1054
1055 let input = input.str();
1058 let mut position = input.chars().peekable();
1059
1060 let mut text = String::new();
1062
1063 while let Some(ch) = position.next() {
1065 match ch {
1066 '\u{000A}' | '\u{000D}' => {
1069 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1070 position.next();
1073 }
1074
1075 if !text.is_empty() {
1076 append_text_node_to_fragment(cx, &document, &fragment, text);
1077 text = String::new();
1078 }
1079
1080 let br = Element::create(
1081 cx,
1082 QualName::new(None, ns!(html), local_name!("br")),
1083 None,
1084 &document,
1085 ElementCreator::ScriptCreated,
1086 CustomElementCreationMode::Asynchronous,
1087 None,
1088 );
1089 fragment
1090 .upcast::<Node>()
1091 .AppendChild(cx, br.upcast())
1092 .unwrap();
1093 },
1094 _ => {
1095 text.push(ch);
1098 },
1099 }
1100 }
1101
1102 if !text.is_empty() {
1105 append_text_node_to_fragment(cx, &document, &fragment, text);
1106 }
1107
1108 fragment
1109 }
1110
1111 fn merge_with_the_next_text_node(cx: &mut JSContext, node: DomRoot<Node>) {
1117 if !node.is::<Text>() {
1119 return;
1120 }
1121
1122 let next = match node.GetNextSibling() {
1124 Some(next) => next,
1125 None => return,
1126 };
1127
1128 if !next.is::<Text>() {
1130 return;
1131 }
1132 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1134 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1135 node_chars
1136 .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1137 .expect("Got chars from Text");
1138
1139 next.remove_self(cx);
1141 }
1142
1143 fn update_assigned_access_key(&self) {
1147 if !self.element.has_attribute(&local_name!("accesskey")) {
1149 self.owner_document()
1151 .event_handler()
1152 .unassign_access_key(self);
1153 }
1154
1155 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1157 let string_view = attribute_value.str();
1158 let values = string_view.split_html_space_characters();
1159
1160 for value in values {
1163 let mut characters = value.chars();
1166 let Some(character) = characters.next() else {
1167 continue;
1168 };
1169 if characters.count() > 0 {
1170 continue;
1171 }
1172
1173 let Some(code) = character_to_code(character) else {
1176 continue;
1177 };
1178
1179 self.owner_document()
1184 .event_handler()
1185 .assign_access_key(self, code);
1186 return;
1187 }
1188
1189 self.owner_document()
1195 .event_handler()
1196 .unassign_access_key(self);
1197 }
1198}
1199
1200impl VirtualMethods for HTMLElement {
1201 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1202 Some(self.as_element() as &dyn VirtualMethods)
1203 }
1204
1205 fn attribute_mutated(
1206 &self,
1207 cx: &mut JSContext,
1208 attr: AttrRef<'_>,
1209 mutation: AttributeMutation,
1210 ) {
1211 self.super_type()
1212 .unwrap()
1213 .attribute_mutated(cx, attr, mutation);
1214 let element = self.as_element();
1215 match (attr.local_name(), mutation) {
1216 (name, mutation)
1218 if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1219 {
1220 let evtarget = self.upcast::<EventTarget>();
1221 let event_name = &name[2..];
1222 match mutation {
1223 AttributeMutation::Set(..) => {
1225 let source = &**attr.value();
1226 let source_line = 1; evtarget.set_event_handler_uncompiled(
1228 self.owner_window().get_url(),
1229 source_line,
1230 event_name,
1231 source,
1232 );
1233 },
1234 AttributeMutation::Removed => {
1236 evtarget
1237 .set_event_handler_common::<EventHandlerNonNull>(cx, event_name, None);
1238 },
1239 }
1240 },
1241
1242 (&local_name!("accesskey"), ..) => {
1243 self.update_assigned_access_key();
1244 },
1245 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1246 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
1247 },
1248 (&local_name!("disabled"), AttributeMutation::Set(..))
1250 if self.is_form_associated_custom_element() && element.enabled_state() =>
1251 {
1252 element.set_disabled_state(true);
1253 element.set_enabled_state(false);
1254 ScriptThread::enqueue_callback_reaction(
1255 element,
1256 CallbackReaction::FormDisabled(true),
1257 None,
1258 );
1259 },
1260 (&local_name!("disabled"), AttributeMutation::Removed)
1263 if self.is_form_associated_custom_element() && element.disabled_state() =>
1264 {
1265 element.set_disabled_state(false);
1266 element.set_enabled_state(true);
1267 element.check_ancestors_disabled_state_for_form_control();
1268 if element.enabled_state() {
1269 ScriptThread::enqueue_callback_reaction(
1270 element,
1271 CallbackReaction::FormDisabled(false),
1272 None,
1273 );
1274 }
1275 },
1276 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1277 match mutation {
1278 AttributeMutation::Set(..) => {
1279 element.set_read_write_state(true);
1280 },
1281 AttributeMutation::Removed => {
1282 element.set_read_write_state(false);
1283 },
1284 }
1285 },
1286 (&local_name!("nonce"), mutation) => match mutation {
1287 AttributeMutation::Set(..) => {
1288 let nonce = &**attr.value();
1289 element.update_nonce_internal_slot(nonce.to_owned());
1290 },
1291 AttributeMutation::Removed => {
1292 element.update_nonce_internal_slot("".to_owned());
1293 },
1294 },
1295 _ => {},
1296 }
1297 }
1298
1299 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1300 if let Some(super_type) = self.super_type() {
1301 super_type.bind_to_tree(cx, context);
1302 }
1303
1304 let element = self.as_element();
1307 if self.is_form_associated_custom_element() && element.enabled_state() {
1308 element.check_ancestors_disabled_state_for_form_control();
1309 if element.disabled_state() {
1310 ScriptThread::enqueue_callback_reaction(
1311 element,
1312 CallbackReaction::FormDisabled(true),
1313 None,
1314 );
1315 }
1316 }
1317
1318 if element.has_attribute(&local_name!("accesskey")) {
1319 self.update_assigned_access_key();
1320 }
1321 }
1322
1323 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1327 let document = self.owner_document();
1329
1330 let element = self.as_element();
1340 if document
1341 .focus_handler()
1342 .focused_area()
1343 .element()
1344 .is_some_and(|focused_element| focused_element == element)
1345 {
1346 document
1347 .focus_handler()
1348 .set_focused_area(FocusableArea::Viewport);
1349 }
1350
1351 if let Some(super_type) = self.super_type() {
1356 super_type.unbind_from_tree(cx, context);
1357 }
1358
1359 if self.is_form_associated_custom_element() && element.disabled_state() {
1370 element.check_disabled_attribute();
1371 element.check_ancestors_disabled_state_for_form_control();
1372 if element.enabled_state() {
1373 ScriptThread::enqueue_callback_reaction(
1374 element,
1375 CallbackReaction::FormDisabled(false),
1376 None,
1377 );
1378 }
1379 }
1380
1381 if element.has_attribute(&local_name!("accesskey")) {
1382 self.owner_document()
1383 .event_handler()
1384 .unassign_access_key(self);
1385 }
1386 }
1387
1388 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1389 if is_element_affected_by_legacy_background_presentational_hint(
1390 self.element.namespace(),
1391 self.element.local_name(),
1392 ) && attr.local_name() == &local_name!("background")
1393 {
1394 return true;
1395 }
1396
1397 self.super_type()
1398 .unwrap()
1399 .attribute_affects_presentational_hints(attr)
1400 }
1401
1402 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1403 match *name {
1404 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1405 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1406 local_name!("background")
1407 if is_element_affected_by_legacy_background_presentational_hint(
1408 self.element.namespace(),
1409 self.element.local_name(),
1410 ) =>
1411 {
1412 AttrValue::from_resolved_url(
1413 &self.owner_document().base_url().get_arc(),
1414 value.into(),
1415 )
1416 },
1417 _ => self
1418 .super_type()
1419 .unwrap()
1420 .parse_plain_attribute(name, value),
1421 }
1422 }
1423
1424 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1426 if let Some(super_type) = self.super_type() {
1430 super_type.moving_steps(cx, context);
1431 }
1432
1433 if let Some(form_control) = self.element.as_maybe_form_control() {
1437 form_control.moving_steps(cx)
1438 }
1439 }
1440}
1441
1442impl Activatable for HTMLElement {
1443 fn as_element(&self) -> &Element {
1444 &self.element
1445 }
1446
1447 fn is_instance_activatable(&self) -> bool {
1448 self.element.local_name() == &local_name!("summary")
1449 }
1450
1451 fn activation_behavior(
1453 &self,
1454 cx: &mut js::context::JSContext,
1455 _event: &Event,
1456 _target: &EventTarget,
1457 ) {
1458 self.summary_activation_behavior(cx);
1459 }
1460}
1461
1462impl FormControl for HTMLElement {
1468 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1469 debug_assert!(self.is_form_associated_custom_element());
1470 self.element
1471 .get_element_internals()
1472 .and_then(|e| e.form_owner())
1473 }
1474
1475 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1476 debug_assert!(self.is_form_associated_custom_element());
1477 self.element
1478 .ensure_element_internals(CanGc::deprecated_note())
1479 .set_form_owner(form);
1480 }
1481
1482 fn to_element(&self) -> &Element {
1483 &self.element
1484 }
1485
1486 fn is_listed(&self) -> bool {
1487 debug_assert!(self.is_form_associated_custom_element());
1488 true
1489 }
1490
1491 }