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::iterators::ShadowIncluding;
64use crate::dom::medialist::MediaList;
65use crate::dom::node::virtualmethods::VirtualMethods;
66use crate::dom::node::{
67 BindContext, MoveContext, Node, NodeTraits, UnbindContext, from_untrusted_node_address,
68};
69use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement};
70use crate::dom::shadowroot::ShadowRoot;
71use crate::dom::text::Text;
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_string_value(&local_name!("media"))
171 .is_none_or(|media| MediaList::matches_environment(&self.owner_document(), &media))
172 }
173
174 pub(crate) fn is_editing_host(&self) -> bool {
176 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
178 }
181
182 pub(crate) fn previously_focused_element(&self) -> Option<DomRoot<Element>> {
183 self.upcast::<Element>()
184 .ensure_rare_data()
185 .previously_focused_element
186 .get()
187 }
188
189 pub(crate) fn set_previously_focused_element(&self, element: Option<&Element>) {
190 self.upcast::<Element>()
191 .ensure_rare_data()
192 .previously_focused_element
193 .set(element);
194 }
195}
196
197impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
198 fn Style(&self, cx: &mut JSContext) -> DomRoot<CSSStyleDeclaration> {
200 self.style_decl.or_init(|| {
201 let global = self.owner_window();
202 CSSStyleDeclaration::new(
203 cx,
204 &global,
205 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
206 None,
207 CSSModificationAccess::ReadWrite,
208 )
209 })
210 }
211
212 make_getter!(Title, "title");
214 make_setter!(cx, SetTitle, "title");
216
217 make_getter!(Lang, "lang");
219 make_setter!(cx, SetLang, "lang");
221
222 make_enumerated_getter!(
224 Dir,
225 "dir",
226 "ltr" | "rtl" | "auto",
227 missing => "",
228 invalid => ""
229 );
230
231 make_setter!(cx, SetDir, "dir");
233
234 make_bool_getter!(Hidden, "hidden");
236 make_bool_setter!(cx, SetHidden, "hidden");
238
239 global_event_handlers!(NoOnload);
241
242 fn Dataset(&self, cx: &mut JSContext) -> DomRoot<DOMStringMap> {
244 self.dataset.or_init(|| DOMStringMap::new(cx, self))
245 }
246
247 fn GetOnerror(&self, cx: &mut JSContext) -> Option<Rc<OnErrorEventHandlerNonNull>> {
249 if self.is_body_or_frameset() {
250 let document = self.owner_document();
251 if document.has_browsing_context() {
252 document.window().GetOnerror(cx)
253 } else {
254 None
255 }
256 } else {
257 self.upcast::<EventTarget>()
258 .get_event_handler_common(cx, "error")
259 }
260 }
261
262 fn SetOnerror(&self, cx: &mut JSContext, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
264 if self.is_body_or_frameset() {
265 let document = self.owner_document();
266 if document.has_browsing_context() {
267 document.window().SetOnerror(cx, listener)
268 }
269 } else {
270 self.upcast::<EventTarget>()
272 .set_error_event_handler(cx, "error", listener)
273 }
274 }
275
276 fn GetOnload(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
278 if self.is_body_or_frameset() {
279 let document = self.owner_document();
280 if document.has_browsing_context() {
281 document.window().GetOnload(cx)
282 } else {
283 None
284 }
285 } else {
286 self.upcast::<EventTarget>()
287 .get_event_handler_common(cx, "load")
288 }
289 }
290
291 fn SetOnload(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
293 if self.is_body_or_frameset() {
294 let document = self.owner_document();
295 if document.has_browsing_context() {
296 document.window().SetOnload(cx, listener)
297 }
298 } else {
299 self.upcast::<EventTarget>()
300 .set_event_handler_common(cx, "load", listener)
301 }
302 }
303
304 fn GetOnblur(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
306 if self.is_body_or_frameset() {
307 let document = self.owner_document();
308 if document.has_browsing_context() {
309 document.window().GetOnblur(cx)
310 } else {
311 None
312 }
313 } else {
314 self.upcast::<EventTarget>()
315 .get_event_handler_common(cx, "blur")
316 }
317 }
318
319 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
321 if self.is_body_or_frameset() {
322 let document = self.owner_document();
323 if document.has_browsing_context() {
324 document.window().SetOnblur(cx, listener)
325 }
326 } else {
327 self.upcast::<EventTarget>()
328 .set_event_handler_common(cx, "blur", listener)
329 }
330 }
331
332 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
334 if self.is_body_or_frameset() {
335 let document = self.owner_document();
336 if document.has_browsing_context() {
337 document.window().GetOnfocus(cx)
338 } else {
339 None
340 }
341 } else {
342 self.upcast::<EventTarget>()
343 .get_event_handler_common(cx, "focus")
344 }
345 }
346
347 fn SetOnfocus(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
349 if self.is_body_or_frameset() {
350 let document = self.owner_document();
351 if document.has_browsing_context() {
352 document.window().SetOnfocus(cx, listener)
353 }
354 } else {
355 self.upcast::<EventTarget>()
356 .set_event_handler_common(cx, "focus", listener)
357 }
358 }
359
360 fn GetOnresize(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
362 if self.is_body_or_frameset() {
363 let document = self.owner_document();
364 if document.has_browsing_context() {
365 document.window().GetOnresize(cx)
366 } else {
367 None
368 }
369 } else {
370 self.upcast::<EventTarget>()
371 .get_event_handler_common(cx, "resize")
372 }
373 }
374
375 fn SetOnresize(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
377 if self.is_body_or_frameset() {
378 let document = self.owner_document();
379 if document.has_browsing_context() {
380 document.window().SetOnresize(cx, listener)
381 }
382 } else {
383 self.upcast::<EventTarget>()
384 .set_event_handler_common(cx, "resize", listener)
385 }
386 }
387
388 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
390 if self.is_body_or_frameset() {
391 let document = self.owner_document();
392 if document.has_browsing_context() {
393 document.window().GetOnscroll(cx)
394 } else {
395 None
396 }
397 } else {
398 self.upcast::<EventTarget>()
399 .get_event_handler_common(cx, "scroll")
400 }
401 }
402
403 fn SetOnscroll(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
405 if self.is_body_or_frameset() {
406 let document = self.owner_document();
407 if document.has_browsing_context() {
408 document.window().SetOnscroll(cx, listener)
409 }
410 } else {
411 self.upcast::<EventTarget>()
412 .set_event_handler_common(cx, "scroll", listener)
413 }
414 }
415
416 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
418 let atoms = self
419 .element
420 .get_tokenlist_attribute(&local_name!("itemtype"));
421
422 if atoms.is_empty() {
423 return None;
424 }
425
426 #[expect(clippy::mutable_key_type)]
427 let mut item_attr_values = HashSet::new();
429 for attr_value in &atoms {
430 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
431 }
432
433 Some(item_attr_values.into_iter().collect())
434 }
435
436 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
438 let atoms = self
439 .element
440 .get_tokenlist_attribute(&local_name!("itemprop"));
441
442 if atoms.is_empty() {
443 return None;
444 }
445
446 #[expect(clippy::mutable_key_type)]
447 let mut item_attr_values = HashSet::new();
449 for attr_value in &atoms {
450 item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
451 }
452
453 Some(item_attr_values.into_iter().collect())
454 }
455
456 fn Click(&self, cx: &mut JSContext) {
458 let element = self.as_element();
459 if element.disabled_state() {
460 return;
461 }
462 if element.click_in_progress() {
463 return;
464 }
465 element.set_click_in_progress(true);
466
467 self.upcast::<Node>()
468 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
469 element.set_click_in_progress(false);
470 }
471
472 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
474 if !self.upcast::<Node>().run_the_focusing_steps(cx, None) {
479 return;
483 }
484
485 if !options.preventScroll {
492 let scroll_axis = ScrollAxisState {
493 position: ScrollLogicalPosition::Center,
494 requirement: ScrollRequirement::IfNotVisible,
495 };
496 self.upcast::<Element>().scroll_into_view_with_options(
497 cx,
498 ScrollBehavior::Smooth,
499 scroll_axis,
500 scroll_axis,
501 None,
502 None,
503 );
504 }
505 }
506
507 fn Blur(&self, cx: &mut JSContext) {
509 if !self.as_element().focus_state() {
512 return;
513 }
514 self.owner_document()
516 .focus_handler()
517 .focus(cx, FocusableArea::Viewport);
518 }
519
520 #[expect(unsafe_code)]
522 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
523 self.owner_window()
524 .scroll_container_query(
525 Some(self.upcast()),
526 ScrollContainerQueryFlags::ForScrollParent,
527 )
528 .and_then(|response| match response {
529 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
530 ScrollContainerResponse::Element(parent_node_address, _) => {
531 let node = unsafe { from_untrusted_node_address(parent_node_address) };
532 DomRoot::downcast(node)
533 },
534 })
535 }
536
537 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
539 if self.is::<HTMLBodyElement>() || self.element.is_root() {
540 return None;
541 }
542
543 let node = self.upcast::<Node>();
544 let window = self.owner_window();
545 let (element, _) = window.offset_parent_query(node);
546
547 element
548 }
549
550 fn OffsetTop(&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.y.to_nearest_px()
561 }
562
563 fn OffsetLeft(&self) -> i32 {
565 if self.is_body_element() {
566 return 0;
567 }
568
569 let node = self.upcast::<Node>();
570 let window = self.owner_window();
571 let (_, rect) = window.offset_parent_query(node);
572
573 rect.origin.x.to_nearest_px()
574 }
575
576 fn OffsetWidth(&self) -> i32 {
578 let node = self.upcast::<Node>();
579 let window = self.owner_window();
580 let (_, rect) = window.offset_parent_query(node);
581
582 rect.size.width.to_nearest_px()
583 }
584
585 fn OffsetHeight(&self) -> i32 {
587 let node = self.upcast::<Node>();
588 let window = self.owner_window();
589 let (_, rect) = window.offset_parent_query(node);
590
591 rect.size.height.to_nearest_px()
592 }
593
594 fn InnerText(&self) -> DOMString {
596 self.get_inner_outer_text()
597 }
598
599 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
601 self.set_inner_text(cx, input)
602 }
603
604 fn GetOuterText(&self) -> Fallible<DOMString> {
606 Ok(self.get_inner_outer_text())
607 }
608
609 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
611 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
613 return Err(Error::NoModificationAllowed(None));
614 };
615
616 let node = self.upcast::<Node>();
617 let document = self.owner_document();
618
619 let next = node.GetNextSibling();
621
622 let previous = node.GetPreviousSibling();
624
625 let fragment = self.rendered_text_fragment(cx, input);
628
629 if fragment.upcast::<Node>().children_count() == 0 {
632 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
633
634 fragment
635 .upcast::<Node>()
636 .AppendChild(cx, text_node.upcast())?;
637 }
638
639 parent.ReplaceChild(cx, fragment.upcast(), node)?;
641
642 if let Some(next_sibling) = next &&
645 let Some(node) = next_sibling.GetPreviousSibling()
646 {
647 Self::merge_with_the_next_text_node(cx, node);
648 }
649
650 if let Some(previous) = previous {
652 Self::merge_with_the_next_text_node(cx, previous)
653 }
654
655 Ok(())
656 }
657
658 fn Translate(&self) -> bool {
660 self.as_element().is_translate_enabled()
661 }
662
663 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
665 self.as_element().set_string_attribute(
666 cx,
667 &html5ever::local_name!("translate"),
668 match yesno {
669 true => DOMString::from("yes"),
670 false => DOMString::from("no"),
671 },
672 );
673 }
674
675 make_enumerated_getter!(
677 ContentEditable,
678 "contenteditable",
679 "true" | "false" | "plaintext-only",
680 missing => "inherit",
681 invalid => "inherit",
682 empty => "true"
683 );
684
685 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
687 let lower_value = value.to_ascii_lowercase();
688 let attr_name = &local_name!("contenteditable");
689 match lower_value.as_ref() {
690 "inherit" => {
692 self.element.remove_attribute_by_name(cx, attr_name);
693 },
694 "true" | "false" | "plaintext-only" => {
698 self.element
699 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
700 },
701 _ => return Err(Error::Syntax(None)),
703 };
704 Ok(())
705 }
706
707 fn IsContentEditable(&self) -> bool {
709 self.upcast::<Node>().is_editable_or_editing_host()
711 }
712
713 fn AttachInternals(&self, cx: &mut JSContext) -> Fallible<DomRoot<ElementInternals>> {
715 if self.element.get_is().is_some() {
717 return Err(Error::NotSupported(None));
718 }
719
720 let registry = self.owner_window().CustomElements(cx);
725 let definition = registry.lookup_definition(self.as_element().local_name(), None);
726
727 let definition = match definition {
729 Some(definition) => definition,
730 None => return Err(Error::NotSupported(None)),
731 };
732
733 if definition.disable_internals {
735 return Err(Error::NotSupported(None));
736 }
737
738 let internals = self.element.ensure_element_internals(CanGc::from_cx(cx));
740 if internals.attached() {
741 return Err(Error::NotSupported(None));
742 }
743
744 if !matches!(
747 self.element.get_custom_element_state(),
748 CustomElementState::Precustomized | CustomElementState::Custom
749 ) {
750 return Err(Error::NotSupported(None));
751 }
752
753 if self.is_form_associated_custom_element() {
754 self.element.init_state_for_internals();
755 }
756
757 internals.set_attached();
759 Ok(internals)
760 }
761
762 fn Nonce(&self) -> DOMString {
764 self.as_element().nonce_value().into()
765 }
766
767 fn SetNonce(&self, _cx: &mut JSContext, value: DOMString) {
769 self.as_element()
770 .update_nonce_internal_slot(String::from(value))
771 }
772
773 fn Autofocus(&self) -> bool {
775 self.element.has_attribute(&local_name!("autofocus"))
776 }
777
778 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
780 self.element
781 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
782 }
783
784 fn TabIndex(&self) -> i32 {
786 self.element.tab_index()
787 }
788
789 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
791 self.element
792 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
793 }
794
795 make_getter!(AccessKey, "accesskey");
797
798 make_setter!(cx, SetAccessKey, "accesskey");
800
801 fn AccessKeyLabel(&self) -> DOMString {
803 if !self.element.has_attribute(&local_name!("accesskey")) {
807 return Default::default();
808 }
809
810 let access_key_string =
811 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
812
813 #[cfg(target_os = "macos")]
814 let access_key_label = format!("⌃⌥{access_key_string}");
815 #[cfg(not(target_os = "macos"))]
816 let access_key_label = format!("Alt+Shift+{access_key_string}");
817
818 access_key_label.into()
819 }
820}
821
822fn append_text_node_to_fragment(
823 cx: &mut JSContext,
824 document: &Document,
825 fragment: &DocumentFragment,
826 text: String,
827) {
828 let text = Text::new(cx, DOMString::from(text), document);
829 fragment
830 .upcast::<Node>()
831 .AppendChild(cx, text.upcast())
832 .unwrap();
833}
834
835impl HTMLElement {
836 pub(crate) fn is_labelable_element(&self) -> bool {
838 match self.upcast::<Node>().type_id() {
839 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
840 HTMLElementTypeId::HTMLInputElement => !matches!(
841 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
842 InputType::Hidden(_)
843 ),
844 HTMLElementTypeId::HTMLButtonElement |
845 HTMLElementTypeId::HTMLMeterElement |
846 HTMLElementTypeId::HTMLOutputElement |
847 HTMLElementTypeId::HTMLProgressElement |
848 HTMLElementTypeId::HTMLSelectElement |
849 HTMLElementTypeId::HTMLTextAreaElement => true,
850 _ => self.is_form_associated_custom_element(),
851 },
852 _ => false,
853 }
854 }
855
856 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
858 if let Some(definition) = self.as_element().get_custom_element_definition() {
859 definition.is_autonomous() && definition.form_associated
860 } else {
861 false
862 }
863 }
864
865 pub(crate) fn is_listed_element(&self) -> bool {
867 match self.upcast::<Node>().type_id() {
868 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
869 HTMLElementTypeId::HTMLButtonElement |
870 HTMLElementTypeId::HTMLFieldSetElement |
871 HTMLElementTypeId::HTMLInputElement |
872 HTMLElementTypeId::HTMLObjectElement |
873 HTMLElementTypeId::HTMLOutputElement |
874 HTMLElementTypeId::HTMLSelectElement |
875 HTMLElementTypeId::HTMLTextAreaElement => true,
876 _ => self.is_form_associated_custom_element(),
877 },
878 _ => false,
879 }
880 }
881
882 pub(crate) fn is_body_element(&self) -> bool {
884 let self_node = self.upcast::<Node>();
885 self_node.GetParentNode().is_some_and(|parent| {
886 let parent_node = parent.upcast::<Node>();
887 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
888 parent_node.is::<HTMLHtmlElement>() &&
889 self_node
890 .preceding_siblings()
891 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
892 })
893 }
894
895 pub(crate) fn is_submittable_element(&self) -> bool {
897 match self.upcast::<Node>().type_id() {
898 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
899 HTMLElementTypeId::HTMLButtonElement |
900 HTMLElementTypeId::HTMLInputElement |
901 HTMLElementTypeId::HTMLSelectElement |
902 HTMLElementTypeId::HTMLTextAreaElement => true,
903 _ => self.is_form_associated_custom_element(),
904 },
905 _ => false,
906 }
907 }
908
909 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
912 let element = self.as_element();
913
914 let root_element = element.root_element();
925 let root_node = root_element.upcast::<Node>();
926 root_node
927 .traverse_preorder(ShadowIncluding::No)
928 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
929 .filter(|elem| match elem.GetControl() {
930 Some(control) => &*control == self,
931 _ => false,
932 })
933 .nth(index as usize)
934 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
935 }
936
937 pub(crate) fn labels_count(&self) -> u32 {
940 let element = self.as_element();
942 let root_element = element.root_element();
943 let root_node = root_element.upcast::<Node>();
944 root_node
945 .traverse_preorder(ShadowIncluding::No)
946 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
947 .filter(|elem| match elem.GetControl() {
948 Some(control) => &*control == self,
949 _ => false,
950 })
951 .count() as u32
952 }
953
954 pub(crate) fn directionality(&self) -> Option<String> {
958 let element_direction = &self.Dir();
959
960 if element_direction == "ltr" {
961 return Some("ltr".to_owned());
962 }
963
964 if element_direction == "rtl" {
965 return Some("rtl".to_owned());
966 }
967
968 if let Some(input) = self.downcast::<HTMLInputElement>() &&
969 matches!(*input.input_type(), InputType::Tel(_))
970 {
971 return Some("ltr".to_owned());
972 }
973
974 if element_direction == "auto" {
975 if let Some(directionality) = self
976 .downcast::<HTMLInputElement>()
977 .and_then(|input| input.auto_directionality())
978 {
979 return Some(directionality);
980 }
981
982 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
983 return Some(area.auto_directionality());
984 }
985 }
986
987 None
994 }
995
996 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
998 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
999
1000 let is_implicit_summary_element = self.is_implicit_summary_element();
1002 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
1003 return;
1004 }
1005
1006 let parent = if is_implicit_summary_element {
1008 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1009 .unwrap()
1010 } else {
1011 self.upcast::<Node>()
1012 .GetParentNode()
1013 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1014 .unwrap()
1015 };
1016
1017 parent.toggle(cx);
1020 }
1021
1022 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1024 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1027 return false;
1028 };
1029
1030 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1032 return false;
1033 };
1034
1035 details
1039 .find_corresponding_summary_element()
1040 .is_some_and(|summary| &*summary == self.upcast())
1041 }
1042
1043 fn is_implicit_summary_element(&self) -> bool {
1046 self.containing_shadow_root()
1050 .as_deref()
1051 .map(ShadowRoot::Host)
1052 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1053 }
1054
1055 fn rendered_text_fragment(
1057 &self,
1058 cx: &mut JSContext,
1059 input: DOMString,
1060 ) -> DomRoot<DocumentFragment> {
1061 let document = self.owner_document();
1063 let fragment = DocumentFragment::new(cx, &document);
1064
1065 let input = input.str();
1068 let mut position = input.chars().peekable();
1069
1070 let mut text = String::new();
1072
1073 while let Some(ch) = position.next() {
1075 match ch {
1076 '\u{000A}' | '\u{000D}' => {
1079 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1080 position.next();
1083 }
1084
1085 if !text.is_empty() {
1086 append_text_node_to_fragment(cx, &document, &fragment, text);
1087 text = String::new();
1088 }
1089
1090 let br = Element::create(
1091 cx,
1092 QualName::new(None, ns!(html), local_name!("br")),
1093 None,
1094 &document,
1095 ElementCreator::ScriptCreated,
1096 CustomElementCreationMode::Asynchronous,
1097 None,
1098 );
1099 fragment
1100 .upcast::<Node>()
1101 .AppendChild(cx, br.upcast())
1102 .unwrap();
1103 },
1104 _ => {
1105 text.push(ch);
1108 },
1109 }
1110 }
1111
1112 if !text.is_empty() {
1115 append_text_node_to_fragment(cx, &document, &fragment, text);
1116 }
1117
1118 fragment
1119 }
1120
1121 fn merge_with_the_next_text_node(cx: &mut JSContext, node: DomRoot<Node>) {
1127 if !node.is::<Text>() {
1129 return;
1130 }
1131
1132 let next = match node.GetNextSibling() {
1134 Some(next) => next,
1135 None => return,
1136 };
1137
1138 if !next.is::<Text>() {
1140 return;
1141 }
1142 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1144 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1145 node_chars
1146 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1147 .expect("Got chars from Text");
1148
1149 next.remove_self(cx);
1151 }
1152
1153 fn update_assigned_access_key(&self) {
1157 if !self.element.has_attribute(&local_name!("accesskey")) {
1159 self.owner_document()
1161 .event_handler()
1162 .unassign_access_key(self);
1163 }
1164
1165 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1167 let string_view = attribute_value.str();
1168 let values = string_view.split_html_space_characters();
1169
1170 for value in values {
1173 let mut characters = value.chars();
1176 let Some(character) = characters.next() else {
1177 continue;
1178 };
1179 if characters.count() > 0 {
1180 continue;
1181 }
1182
1183 let Some(code) = character_to_code(character) else {
1186 continue;
1187 };
1188
1189 self.owner_document()
1194 .event_handler()
1195 .assign_access_key(self, code);
1196 return;
1197 }
1198
1199 self.owner_document()
1205 .event_handler()
1206 .unassign_access_key(self);
1207 }
1208}
1209
1210impl VirtualMethods for HTMLElement {
1211 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1212 Some(self.as_element() as &dyn VirtualMethods)
1213 }
1214
1215 fn attribute_mutated(
1216 &self,
1217 cx: &mut JSContext,
1218 attr: AttrRef<'_>,
1219 mutation: AttributeMutation,
1220 ) {
1221 self.super_type()
1222 .unwrap()
1223 .attribute_mutated(cx, attr, mutation);
1224 let element = self.as_element();
1225 match (attr.local_name(), mutation) {
1226 (&local_name!("accesskey"), ..) => {
1227 self.update_assigned_access_key();
1228 },
1229 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1230 self.form_attribute_mutated(cx, mutation);
1231 },
1232 (&local_name!("disabled"), AttributeMutation::Set(..))
1234 if self.is_form_associated_custom_element() && element.enabled_state() =>
1235 {
1236 element.set_disabled_state(true);
1237 element.set_enabled_state(false);
1238 ScriptThread::enqueue_callback_reaction(
1239 cx,
1240 element,
1241 CallbackReaction::FormDisabled(true),
1242 None,
1243 );
1244 },
1245 (&local_name!("disabled"), AttributeMutation::Removed)
1248 if self.is_form_associated_custom_element() && element.disabled_state() =>
1249 {
1250 element.set_disabled_state(false);
1251 element.set_enabled_state(true);
1252 element.check_ancestors_disabled_state_for_form_control();
1253 if element.enabled_state() {
1254 ScriptThread::enqueue_callback_reaction(
1255 cx,
1256 element,
1257 CallbackReaction::FormDisabled(false),
1258 None,
1259 );
1260 }
1261 },
1262 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1263 match mutation {
1264 AttributeMutation::Set(..) => {
1265 element.set_read_write_state(true);
1266 },
1267 AttributeMutation::Removed => {
1268 element.set_read_write_state(false);
1269 },
1270 }
1271 },
1272 (&local_name!("nonce"), mutation) => match mutation {
1273 AttributeMutation::Set(..) => {
1274 let nonce = &**attr.value();
1275 element.update_nonce_internal_slot(nonce.to_owned());
1276 },
1277 AttributeMutation::Removed => {
1278 element.update_nonce_internal_slot("".to_owned());
1279 },
1280 },
1281 _ => {},
1282 }
1283 }
1284
1285 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1286 if let Some(super_type) = self.super_type() {
1287 super_type.bind_to_tree(cx, context);
1288 }
1289
1290 let element = self.as_element();
1293 if self.is_form_associated_custom_element() && element.enabled_state() {
1294 element.check_ancestors_disabled_state_for_form_control();
1295 if element.disabled_state() {
1296 ScriptThread::enqueue_callback_reaction(
1297 cx,
1298 element,
1299 CallbackReaction::FormDisabled(true),
1300 None,
1301 );
1302 }
1303 }
1304
1305 if element.has_attribute(&local_name!("accesskey")) {
1306 self.update_assigned_access_key();
1307 }
1308 }
1309
1310 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1314 let document = self.owner_document();
1316
1317 let element = self.as_element();
1327 if document
1328 .focus_handler()
1329 .focused_area()
1330 .element()
1331 .is_some_and(|focused_element| focused_element == element)
1332 {
1333 document
1334 .focus_handler()
1335 .set_focused_area(FocusableArea::Viewport);
1336 }
1337
1338 if let Some(super_type) = self.super_type() {
1343 super_type.unbind_from_tree(cx, context);
1344 }
1345
1346 if self.is_form_associated_custom_element() && element.disabled_state() {
1357 element.check_disabled_attribute();
1358 element.check_ancestors_disabled_state_for_form_control();
1359 if element.enabled_state() {
1360 ScriptThread::enqueue_callback_reaction(
1361 cx,
1362 element,
1363 CallbackReaction::FormDisabled(false),
1364 None,
1365 );
1366 }
1367 }
1368
1369 if element.has_attribute(&local_name!("accesskey")) {
1370 self.owner_document()
1371 .event_handler()
1372 .unassign_access_key(self);
1373 }
1374 }
1375
1376 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1377 if is_element_affected_by_legacy_background_presentational_hint(
1378 self.element.namespace(),
1379 self.element.local_name(),
1380 ) && attr.local_name() == &local_name!("background")
1381 {
1382 return true;
1383 }
1384
1385 self.super_type()
1386 .unwrap()
1387 .attribute_affects_presentational_hints(attr)
1388 }
1389
1390 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1391 match *name {
1392 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1393 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1394 local_name!("background")
1395 if is_element_affected_by_legacy_background_presentational_hint(
1396 self.element.namespace(),
1397 self.element.local_name(),
1398 ) =>
1399 {
1400 AttrValue::from_resolved_url(
1401 &self.owner_document().base_url().get_arc(),
1402 value.into(),
1403 )
1404 },
1405 _ => self
1406 .super_type()
1407 .unwrap()
1408 .parse_plain_attribute(name, value),
1409 }
1410 }
1411
1412 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1414 if let Some(super_type) = self.super_type() {
1418 super_type.moving_steps(cx, context);
1419 }
1420
1421 if let Some(form_control) = self.element.as_maybe_form_control() {
1425 form_control.moving_steps(cx)
1426 }
1427 }
1428}
1429
1430impl Activatable for HTMLElement {
1431 fn as_element(&self) -> &Element {
1432 &self.element
1433 }
1434
1435 fn is_instance_activatable(&self) -> bool {
1436 self.element.local_name() == &local_name!("summary")
1437 }
1438
1439 fn activation_behavior(
1441 &self,
1442 cx: &mut js::context::JSContext,
1443 _event: &Event,
1444 _target: &EventTarget,
1445 ) {
1446 self.summary_activation_behavior(cx);
1447 }
1448}
1449
1450impl FormControl for HTMLElement {
1456 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1457 debug_assert!(self.is_form_associated_custom_element());
1458 self.element
1459 .get_element_internals()
1460 .and_then(|e| e.form_owner())
1461 }
1462
1463 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1464 debug_assert!(self.is_form_associated_custom_element());
1465 self.element
1466 .ensure_element_internals(CanGc::deprecated_note())
1467 .set_form_owner(form);
1468 }
1469
1470 fn to_element(&self) -> &Element {
1471 &self.element
1472 }
1473
1474 fn is_listed(&self) -> bool {
1475 debug_assert!(self.is_form_associated_custom_element());
1476 true
1477 }
1478
1479 }