1use std::default::Default;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, QualName, local_name, ns};
10use js::context::JSContext;
11use js::rust::HandleObject;
12use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
13use rustc_hash::FxHashSet;
14use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
15use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
16use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
17use 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::{
40 CallbackReaction, CustomElementRegistry, CustomElementState,
41};
42use crate::dom::document::Document;
43use crate::dom::document::focus::FocusableArea;
44use crate::dom::document_event_handler::character_to_code;
45use crate::dom::documentfragment::DocumentFragment;
46use crate::dom::domstringmap::DOMStringMap;
47use crate::dom::element::attributes::storage::AttrRef;
48use crate::dom::element::{
49 AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
50 is_element_affected_by_legacy_background_presentational_hint,
51};
52use crate::dom::elementinternals::ElementInternals;
53use crate::dom::event::Event;
54use crate::dom::eventtarget::EventTarget;
55use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
56use crate::dom::html::form_controls::input_type::InputType;
57use crate::dom::html::htmlbodyelement::HTMLBodyElement;
58use crate::dom::html::htmldetailselement::HTMLDetailsElement;
59use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
60use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
61use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
62use crate::dom::html::htmllabelelement::HTMLLabelElement;
63use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
64use crate::dom::htmlformelement::FormControlElementHelpers;
65use crate::dom::iterators::ShadowIncluding;
66use crate::dom::medialist::MediaList;
67use crate::dom::node::virtualmethods::VirtualMethods;
68use crate::dom::node::{
69 BindContext, MoveContext, Node, NodeTraits, UnbindContext, from_untrusted_node_address,
70};
71use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement};
72use crate::dom::shadowroot::ShadowRoot;
73use crate::dom::text::Text;
74use crate::script_thread::ScriptThread;
75
76#[dom_struct]
77pub(crate) struct HTMLElement {
78 element: Element,
79 style_decl: MutNullableDom<CSSStyleDeclaration>,
80 dataset: MutNullableDom<DOMStringMap>,
81}
82
83impl HTMLElement {
84 pub(crate) fn new_inherited(
85 tag_name: LocalName,
86 prefix: Option<Prefix>,
87 document: &Document,
88 ) -> HTMLElement {
89 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
90 }
91
92 pub(crate) fn new_inherited_with_state(
93 state: ElementState,
94 tag_name: LocalName,
95 prefix: Option<Prefix>,
96 document: &Document,
97 ) -> HTMLElement {
98 HTMLElement {
99 element: Element::new_inherited_with_state(
100 state,
101 tag_name,
102 ns!(html),
103 prefix,
104 document,
105 ),
106 style_decl: Default::default(),
107 dataset: Default::default(),
108 }
109 }
110
111 pub(crate) fn new(
112 cx: &mut js::context::JSContext,
113 local_name: LocalName,
114 prefix: Option<Prefix>,
115 document: &Document,
116 proto: Option<HandleObject>,
117 ) -> DomRoot<HTMLElement> {
118 Node::reflect_node_with_proto(
119 cx,
120 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
121 document,
122 proto,
123 )
124 }
125
126 fn is_body_or_frameset(&self) -> bool {
127 let eventtarget = self.upcast::<EventTarget>();
128 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
129 }
130
131 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
137 let node = self.upcast::<Node>();
138 let window = node.owner_window();
139 let element = self.as_element();
140
141 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
143 if element_not_rendered {
144 return node.GetTextContent().unwrap();
145 }
146
147 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
148 let text = window
149 .layout()
150 .query_element_inner_outer_text(node.to_trusted_node_address());
151
152 DOMString::from(text)
153 }
154
155 pub(crate) fn set_inner_text(&self, cx: &mut JSContext, input: DOMString) {
157 let fragment = self.rendered_text_fragment(cx, input);
160
161 Node::replace_all(cx, Some(fragment.upcast()), self.upcast::<Node>());
163 }
164
165 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
167 self.element
171 .get_attribute_string_value(&local_name!("media"))
172 .is_none_or(|media| MediaList::matches_environment(&self.owner_document(), &media))
173 }
174
175 pub(crate) fn is_editing_host(&self) -> bool {
177 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
179 }
182
183 pub(crate) fn previously_focused_element(&self) -> Option<DomRoot<Element>> {
184 self.upcast::<Element>()
185 .ensure_rare_data()
186 .previously_focused_element
187 .get()
188 }
189
190 pub(crate) fn set_previously_focused_element(&self, element: Option<&Element>) {
191 self.upcast::<Element>()
192 .ensure_rare_data()
193 .previously_focused_element
194 .set(element);
195 }
196}
197
198impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
199 fn Style(&self, cx: &mut JSContext) -> DomRoot<CSSStyleDeclaration> {
201 self.style_decl.or_init(|| {
202 let global = self.owner_window();
203 CSSStyleDeclaration::new(
204 cx,
205 &global,
206 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
207 None,
208 CSSModificationAccess::ReadWrite,
209 )
210 })
211 }
212
213 make_getter!(Title, "title");
215 make_setter!(SetTitle, "title");
217
218 make_getter!(Lang, "lang");
220 make_setter!(SetLang, "lang");
222
223 make_enumerated_getter!(
225 Dir,
226 "dir",
227 "ltr" | "rtl" | "auto",
228 missing => "",
229 invalid => ""
230 );
231
232 make_setter!(SetDir, "dir");
234
235 make_bool_getter!(Hidden, "hidden");
237 make_bool_setter!(SetHidden, "hidden");
239
240 global_event_handlers!(NoOnload);
242
243 fn Dataset(&self, cx: &mut JSContext) -> DomRoot<DOMStringMap> {
245 self.dataset.or_init(|| DOMStringMap::new(cx, self))
246 }
247
248 fn GetOnerror(&self, cx: &mut JSContext) -> Option<Rc<OnErrorEventHandlerNonNull>> {
250 if self.is_body_or_frameset() {
251 let document = self.owner_document();
252 if document.has_browsing_context() {
253 document.window().GetOnerror(cx)
254 } else {
255 None
256 }
257 } else {
258 self.upcast::<EventTarget>()
259 .get_event_handler_common(cx, "error")
260 }
261 }
262
263 fn SetOnerror(&self, cx: &mut JSContext, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
265 if self.is_body_or_frameset() {
266 let document = self.owner_document();
267 if document.has_browsing_context() {
268 document.window().SetOnerror(cx, listener)
269 }
270 } else {
271 self.upcast::<EventTarget>()
273 .set_error_event_handler(cx, "error", listener)
274 }
275 }
276
277 fn GetOnload(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
279 if self.is_body_or_frameset() {
280 let document = self.owner_document();
281 if document.has_browsing_context() {
282 document.window().GetOnload(cx)
283 } else {
284 None
285 }
286 } else {
287 self.upcast::<EventTarget>()
288 .get_event_handler_common(cx, "load")
289 }
290 }
291
292 fn SetOnload(&self, cx: &mut JSContext, listener: 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().SetOnload(cx, listener)
298 }
299 } else {
300 self.upcast::<EventTarget>()
301 .set_event_handler_common(cx, "load", listener)
302 }
303 }
304
305 fn GetOnblur(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
307 if self.is_body_or_frameset() {
308 let document = self.owner_document();
309 if document.has_browsing_context() {
310 document.window().GetOnblur(cx)
311 } else {
312 None
313 }
314 } else {
315 self.upcast::<EventTarget>()
316 .get_event_handler_common(cx, "blur")
317 }
318 }
319
320 fn SetOnblur(&self, cx: &mut JSContext, listener: 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().SetOnblur(cx, listener)
326 }
327 } else {
328 self.upcast::<EventTarget>()
329 .set_event_handler_common(cx, "blur", listener)
330 }
331 }
332
333 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
335 if self.is_body_or_frameset() {
336 let document = self.owner_document();
337 if document.has_browsing_context() {
338 document.window().GetOnfocus(cx)
339 } else {
340 None
341 }
342 } else {
343 self.upcast::<EventTarget>()
344 .get_event_handler_common(cx, "focus")
345 }
346 }
347
348 fn SetOnfocus(&self, cx: &mut JSContext, listener: 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().SetOnfocus(cx, listener)
354 }
355 } else {
356 self.upcast::<EventTarget>()
357 .set_event_handler_common(cx, "focus", listener)
358 }
359 }
360
361 fn GetOnresize(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
363 if self.is_body_or_frameset() {
364 let document = self.owner_document();
365 if document.has_browsing_context() {
366 document.window().GetOnresize(cx)
367 } else {
368 None
369 }
370 } else {
371 self.upcast::<EventTarget>()
372 .get_event_handler_common(cx, "resize")
373 }
374 }
375
376 fn SetOnresize(&self, cx: &mut JSContext, listener: 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().SetOnresize(cx, listener)
382 }
383 } else {
384 self.upcast::<EventTarget>()
385 .set_event_handler_common(cx, "resize", listener)
386 }
387 }
388
389 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
391 if self.is_body_or_frameset() {
392 let document = self.owner_document();
393 if document.has_browsing_context() {
394 document.window().GetOnscroll(cx)
395 } else {
396 None
397 }
398 } else {
399 self.upcast::<EventTarget>()
400 .get_event_handler_common(cx, "scroll")
401 }
402 }
403
404 fn SetOnscroll(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
406 if self.is_body_or_frameset() {
407 let document = self.owner_document();
408 if document.has_browsing_context() {
409 document.window().SetOnscroll(cx, listener)
410 }
411 } else {
412 self.upcast::<EventTarget>()
413 .set_event_handler_common(cx, "scroll", listener)
414 }
415 }
416
417 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
419 let atoms = self
420 .element
421 .get_tokenlist_attribute(&local_name!("itemtype"));
422
423 if atoms.is_empty() {
424 return None;
425 }
426
427 Some(
428 FxHashSet::from_iter(
429 atoms
430 .iter()
431 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
432 )
433 .into_iter()
434 .collect(),
435 )
436 }
437
438 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
440 let atoms = self
441 .element
442 .get_tokenlist_attribute(&local_name!("itemprop"));
443
444 if atoms.is_empty() {
445 return None;
446 }
447
448 Some(
449 FxHashSet::from_iter(
450 atoms
451 .iter()
452 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
453 )
454 .into_iter()
455 .collect(),
456 )
457 }
458
459 fn Click(&self, cx: &mut JSContext) {
461 let element = self.as_element();
462 if element.disabled_state() {
463 return;
464 }
465 if element.click_in_progress() {
466 return;
467 }
468 element.set_click_in_progress(true);
469
470 self.upcast::<Node>()
471 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
472 element.set_click_in_progress(false);
473 }
474
475 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
477 if !self.upcast::<Node>().run_the_focusing_steps(cx, None) {
482 return;
486 }
487
488 if !options.preventScroll {
495 let scroll_axis = ScrollAxisState {
496 position: ScrollLogicalPosition::Center,
497 requirement: ScrollRequirement::IfNotVisible,
498 };
499 self.upcast::<Element>().scroll_into_view_with_options(
500 cx,
501 ScrollBehavior::Smooth,
502 scroll_axis,
503 scroll_axis,
504 None,
505 None,
506 );
507 }
508 }
509
510 fn Blur(&self, cx: &mut JSContext) {
512 if !self.as_element().focus_state() {
515 return;
516 }
517 self.owner_document()
519 .focus_handler()
520 .focus(cx, FocusableArea::Viewport);
521 }
522
523 #[expect(unsafe_code)]
525 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
526 self.owner_window()
527 .scroll_container_query(
528 Some(self.upcast()),
529 ScrollContainerQueryFlags::ForScrollParent,
530 )
531 .and_then(|response| match response {
532 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
533 ScrollContainerResponse::Element(parent_node_address, _) => {
534 let node = unsafe { from_untrusted_node_address(parent_node_address) };
535 DomRoot::downcast(node)
536 },
537 })
538 }
539
540 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
542 if self.is::<HTMLBodyElement>() || self.element.is_root() {
543 return None;
544 }
545
546 let node = self.upcast::<Node>();
547 let window = self.owner_window();
548 let (element, _) = window.offset_parent_query(node);
549
550 element
551 }
552
553 fn OffsetTop(&self) -> i32 {
555 if self.is_body_element() {
556 return 0;
557 }
558
559 let node = self.upcast::<Node>();
560 let window = self.owner_window();
561 let (_, rect) = window.offset_parent_query(node);
562
563 rect.origin.y.to_nearest_px()
564 }
565
566 fn OffsetLeft(&self) -> i32 {
568 if self.is_body_element() {
569 return 0;
570 }
571
572 let node = self.upcast::<Node>();
573 let window = self.owner_window();
574 let (_, rect) = window.offset_parent_query(node);
575
576 rect.origin.x.to_nearest_px()
577 }
578
579 fn OffsetWidth(&self) -> i32 {
581 let node = self.upcast::<Node>();
582 let window = self.owner_window();
583 let (_, rect) = window.offset_parent_query(node);
584
585 rect.size.width.to_nearest_px()
586 }
587
588 fn OffsetHeight(&self) -> i32 {
590 let node = self.upcast::<Node>();
591 let window = self.owner_window();
592 let (_, rect) = window.offset_parent_query(node);
593
594 rect.size.height.to_nearest_px()
595 }
596
597 fn InnerText(&self) -> DOMString {
599 self.get_inner_outer_text()
600 }
601
602 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
604 self.set_inner_text(cx, input)
605 }
606
607 fn GetOuterText(&self) -> Fallible<DOMString> {
609 Ok(self.get_inner_outer_text())
610 }
611
612 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
614 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
616 return Err(Error::NoModificationAllowed(None));
617 };
618
619 let node = self.upcast::<Node>();
620 let document = self.owner_document();
621
622 let next = node.GetNextSibling();
624
625 let previous = node.GetPreviousSibling();
627
628 let fragment = self.rendered_text_fragment(cx, input);
631
632 if fragment.upcast::<Node>().children_count() == 0 {
635 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
636
637 fragment
638 .upcast::<Node>()
639 .AppendChild(cx, text_node.upcast())?;
640 }
641
642 parent.ReplaceChild(cx, fragment.upcast(), node)?;
644
645 if let Some(next_sibling) = next &&
648 let Some(node) = next_sibling.GetPreviousSibling()
649 {
650 Self::merge_with_the_next_text_node(cx, &node);
651 }
652
653 if let Some(previous) = previous {
655 Self::merge_with_the_next_text_node(cx, &previous)
656 }
657
658 Ok(())
659 }
660
661 fn Translate(&self) -> bool {
663 self.as_element().is_translate_enabled()
664 }
665
666 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
668 self.as_element().set_string_attribute(
669 cx,
670 &html5ever::local_name!("translate"),
671 match yesno {
672 true => DOMString::from("yes"),
673 false => DOMString::from("no"),
674 },
675 );
676 }
677
678 make_enumerated_getter!(
680 ContentEditable,
681 "contenteditable",
682 "true" | "false" | "plaintext-only",
683 missing => "inherit",
684 invalid => "inherit",
685 empty => "true"
686 );
687
688 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
690 let lower_value = value.to_ascii_lowercase();
691 let attr_name = &local_name!("contenteditable");
692 match lower_value.as_ref() {
693 "inherit" => {
695 self.element.remove_attribute_by_name(cx, attr_name);
696 },
697 "true" | "false" | "plaintext-only" => {
701 self.element
702 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
703 },
704 _ => return Err(Error::Syntax(None)),
706 };
707 Ok(())
708 }
709
710 fn IsContentEditable(&self) -> bool {
712 self.upcast::<Node>().is_editable_or_editing_host()
714 }
715
716 fn AttachInternals(&self, cx: &mut JSContext) -> Fallible<DomRoot<ElementInternals>> {
718 if self.element.get_is().is_some() {
720 return Err(Error::NotSupported(None));
721 }
722
723 let lookup_registry = {
725 let registry = self.as_element().custom_element_registry();
728 if registry
729 .as_ref()
730 .is_some_and(|registry| registry.is_scoped())
731 {
732 registry
733 } else {
734 self.upcast::<Node>().owner_doc().custom_element_registry()
735 }
736 };
737 let definition = CustomElementRegistry::lookup_custom_element_definition(
738 lookup_registry.as_deref(),
739 self.upcast::<Element>().namespace(),
740 self.as_element().local_name(),
741 None,
742 );
743
744 let definition = match definition {
746 Some(definition) => definition,
747 None => return Err(Error::NotSupported(None)),
748 };
749
750 if definition.disable_internals {
752 return Err(Error::NotSupported(None));
753 }
754
755 let internals = self.element.ensure_element_internals(cx);
757 if internals.attached() {
758 return Err(Error::NotSupported(None));
759 }
760
761 if !matches!(
764 self.element.get_custom_element_state(),
765 CustomElementState::Precustomized | CustomElementState::Custom
766 ) {
767 return Err(Error::NotSupported(None));
768 }
769
770 if self.is_form_associated_custom_element() {
771 self.element.init_state_for_internals();
772 }
773
774 internals.set_attached();
776 Ok(internals)
777 }
778
779 fn Nonce(&self) -> DOMString {
781 self.as_element().nonce_value().into()
782 }
783
784 fn SetNonce(&self, _cx: &mut JSContext, value: DOMString) {
786 self.as_element()
787 .update_nonce_internal_slot(String::from(value))
788 }
789
790 fn Autofocus(&self) -> bool {
792 self.element.has_attribute(&local_name!("autofocus"))
793 }
794
795 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
797 self.element
798 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
799 }
800
801 fn TabIndex(&self) -> i32 {
803 self.element.tab_index()
804 }
805
806 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
808 self.element
809 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
810 }
811
812 make_getter!(AccessKey, "accesskey");
814
815 make_setter!(SetAccessKey, "accesskey");
817
818 fn AccessKeyLabel(&self) -> DOMString {
820 if !self.element.has_attribute(&local_name!("accesskey")) {
824 return Default::default();
825 }
826
827 let access_key_string =
828 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
829
830 #[cfg(target_os = "macos")]
831 let access_key_label = format!("⌃⌥{access_key_string}");
832 #[cfg(not(target_os = "macos"))]
833 let access_key_label = format!("Alt+Shift+{access_key_string}");
834
835 access_key_label.into()
836 }
837}
838
839fn append_text_node_to_fragment(
840 cx: &mut JSContext,
841 document: &Document,
842 fragment: &DocumentFragment,
843 text: String,
844) {
845 let text = Text::new(cx, DOMString::from(text), document);
846 fragment
847 .upcast::<Node>()
848 .AppendChild(cx, text.upcast())
849 .unwrap();
850}
851
852impl HTMLElement {
853 pub(crate) fn is_labelable_element(&self) -> bool {
855 match self.upcast::<Node>().type_id() {
856 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
857 HTMLElementTypeId::HTMLInputElement => !matches!(
858 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
859 InputType::Hidden(_)
860 ),
861 HTMLElementTypeId::HTMLButtonElement |
862 HTMLElementTypeId::HTMLMeterElement |
863 HTMLElementTypeId::HTMLOutputElement |
864 HTMLElementTypeId::HTMLProgressElement |
865 HTMLElementTypeId::HTMLSelectElement |
866 HTMLElementTypeId::HTMLTextAreaElement => true,
867 _ => self.is_form_associated_custom_element(),
868 },
869 _ => false,
870 }
871 }
872
873 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
875 if let Some(definition) = self.as_element().get_custom_element_definition() {
876 definition.is_autonomous() && definition.form_associated
877 } else {
878 false
879 }
880 }
881
882 pub(crate) fn is_listed_element(&self) -> bool {
884 match self.upcast::<Node>().type_id() {
885 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
886 HTMLElementTypeId::HTMLButtonElement |
887 HTMLElementTypeId::HTMLFieldSetElement |
888 HTMLElementTypeId::HTMLInputElement |
889 HTMLElementTypeId::HTMLObjectElement |
890 HTMLElementTypeId::HTMLOutputElement |
891 HTMLElementTypeId::HTMLSelectElement |
892 HTMLElementTypeId::HTMLTextAreaElement => true,
893 _ => self.is_form_associated_custom_element(),
894 },
895 _ => false,
896 }
897 }
898
899 pub(crate) fn is_body_element(&self) -> bool {
901 let self_node = self.upcast::<Node>();
902 self_node.GetParentNode().is_some_and(|parent| {
903 let parent_node = parent.upcast::<Node>();
904 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
905 parent_node.is::<HTMLHtmlElement>() &&
906 self_node
907 .preceding_siblings()
908 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
909 })
910 }
911
912 pub(crate) fn is_submittable_element(&self) -> bool {
914 match self.upcast::<Node>().type_id() {
915 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
916 HTMLElementTypeId::HTMLButtonElement |
917 HTMLElementTypeId::HTMLInputElement |
918 HTMLElementTypeId::HTMLSelectElement |
919 HTMLElementTypeId::HTMLTextAreaElement => true,
920 _ => self.is_form_associated_custom_element(),
921 },
922 _ => false,
923 }
924 }
925
926 pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
929 let element = self.as_element();
930
931 let root_element = element.root_element();
942 let root_node = root_element.upcast::<Node>();
943 root_node
944 .traverse_preorder(ShadowIncluding::No)
945 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
946 .filter(|elem| match elem.GetControl() {
947 Some(control) => &*control == self,
948 _ => false,
949 })
950 .nth(index as usize)
951 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
952 }
953
954 pub(crate) fn labels_count(&self) -> u32 {
957 let element = self.as_element();
959 let root_element = element.root_element();
960 let root_node = root_element.upcast::<Node>();
961 root_node
962 .traverse_preorder(ShadowIncluding::No)
963 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
964 .filter(|elem| match elem.GetControl() {
965 Some(control) => &*control == self,
966 _ => false,
967 })
968 .count() as u32
969 }
970
971 pub(crate) fn directionality(&self) -> Option<String> {
975 let element_direction = &self.Dir();
976
977 if element_direction == "ltr" {
978 return Some("ltr".to_owned());
979 }
980
981 if element_direction == "rtl" {
982 return Some("rtl".to_owned());
983 }
984
985 if let Some(input) = self.downcast::<HTMLInputElement>() &&
986 matches!(*input.input_type(), InputType::Tel(_))
987 {
988 return Some("ltr".to_owned());
989 }
990
991 if element_direction == "auto" {
992 if let Some(directionality) = self
993 .downcast::<HTMLInputElement>()
994 .and_then(|input| input.auto_directionality())
995 {
996 return Some(directionality);
997 }
998
999 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
1000 return Some(area.auto_directionality());
1001 }
1002 }
1003
1004 None
1011 }
1012
1013 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
1015 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
1016
1017 let is_implicit_summary_element = self.is_implicit_summary_element();
1019 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
1020 return;
1021 }
1022
1023 let parent = if is_implicit_summary_element {
1025 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1026 .unwrap()
1027 } else {
1028 self.upcast::<Node>()
1029 .GetParentNode()
1030 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1031 .unwrap()
1032 };
1033
1034 parent.toggle(cx);
1037 }
1038
1039 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1041 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1044 return false;
1045 };
1046
1047 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1049 return false;
1050 };
1051
1052 details
1056 .find_corresponding_summary_element()
1057 .is_some_and(|summary| &*summary == self.upcast())
1058 }
1059
1060 fn is_implicit_summary_element(&self) -> bool {
1063 self.containing_shadow_root()
1067 .as_deref()
1068 .map(ShadowRoot::Host)
1069 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1070 }
1071
1072 fn rendered_text_fragment(
1074 &self,
1075 cx: &mut JSContext,
1076 input: DOMString,
1077 ) -> DomRoot<DocumentFragment> {
1078 let document = self.owner_document();
1080 let fragment = DocumentFragment::new(cx, &document);
1081
1082 let input = input.str();
1085 let mut position = input.chars().peekable();
1086
1087 let mut text = String::new();
1089
1090 while let Some(ch) = position.next() {
1092 match ch {
1093 '\u{000A}' | '\u{000D}' => {
1096 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1097 position.next();
1100 }
1101
1102 if !text.is_empty() {
1103 append_text_node_to_fragment(cx, &document, &fragment, text);
1104 text = String::new();
1105 }
1106
1107 let br = Element::create(
1108 cx,
1109 QualName::new(None, ns!(html), local_name!("br")),
1110 None,
1111 &document,
1112 ElementCreator::ScriptCreated,
1113 CustomElementCreationMode::Asynchronous,
1114 None,
1115 );
1116 fragment
1117 .upcast::<Node>()
1118 .AppendChild(cx, br.upcast())
1119 .unwrap();
1120 },
1121 _ => {
1122 text.push(ch);
1125 },
1126 }
1127 }
1128
1129 if !text.is_empty() {
1132 append_text_node_to_fragment(cx, &document, &fragment, text);
1133 }
1134
1135 fragment
1136 }
1137
1138 fn merge_with_the_next_text_node(cx: &mut JSContext, node: &Node) {
1144 if !node.is::<Text>() {
1146 return;
1147 }
1148
1149 let next = match node.GetNextSibling() {
1151 Some(next) => next,
1152 None => return,
1153 };
1154
1155 if !next.is::<Text>() {
1157 return;
1158 }
1159 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1161 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1162 node_chars
1163 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1164 .expect("Got chars from Text");
1165
1166 next.remove_self(cx);
1168 }
1169
1170 fn update_assigned_access_key(&self) {
1174 if !self.element.has_attribute(&local_name!("accesskey")) {
1176 self.owner_document()
1178 .event_handler()
1179 .unassign_access_key(self);
1180 }
1181
1182 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1184 let string_view = attribute_value.str();
1185 let values = string_view.split_html_space_characters();
1186
1187 for value in values {
1190 let mut characters = value.chars();
1193 let Some(character) = characters.next() else {
1194 continue;
1195 };
1196 if characters.count() > 0 {
1197 continue;
1198 }
1199
1200 let Some(code) = character_to_code(character) else {
1203 continue;
1204 };
1205
1206 self.owner_document()
1211 .event_handler()
1212 .assign_access_key(self, code);
1213 return;
1214 }
1215
1216 self.owner_document()
1222 .event_handler()
1223 .unassign_access_key(self);
1224 }
1225}
1226
1227impl VirtualMethods for HTMLElement {
1228 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1229 Some(self.as_element() as &dyn VirtualMethods)
1230 }
1231
1232 fn attribute_mutated(
1233 &self,
1234 cx: &mut JSContext,
1235 attr: AttrRef<'_>,
1236 mutation: AttributeMutation,
1237 ) {
1238 self.super_type()
1239 .unwrap()
1240 .attribute_mutated(cx, attr, mutation);
1241 let element = self.as_element();
1242 match (attr.local_name(), mutation) {
1243 (&local_name!("accesskey"), ..) => {
1244 self.update_assigned_access_key();
1245 },
1246 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1247 self.form_attribute_mutated(cx, mutation);
1248 },
1249 (&local_name!("disabled"), AttributeMutation::Set(..))
1251 if self.is_form_associated_custom_element() && element.enabled_state() =>
1252 {
1253 element.set_disabled_state(true);
1254 element.set_enabled_state(false);
1255 ScriptThread::enqueue_callback_reaction(
1256 cx,
1257 element,
1258 CallbackReaction::FormDisabled(true),
1259 None,
1260 );
1261 },
1262 (&local_name!("disabled"), AttributeMutation::Removed)
1265 if self.is_form_associated_custom_element() && element.disabled_state() =>
1266 {
1267 element.set_disabled_state(false);
1268 element.set_enabled_state(true);
1269 element.check_ancestors_disabled_state_for_form_control();
1270 if element.enabled_state() {
1271 ScriptThread::enqueue_callback_reaction(
1272 cx,
1273 element,
1274 CallbackReaction::FormDisabled(false),
1275 None,
1276 );
1277 }
1278 },
1279 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1280 match mutation {
1281 AttributeMutation::Set(..) => {
1282 element.set_read_write_state(true);
1283 },
1284 AttributeMutation::Removed => {
1285 element.set_read_write_state(false);
1286 },
1287 }
1288 },
1289 (&local_name!("nonce"), mutation) => match mutation {
1290 AttributeMutation::Set(..) => {
1291 let nonce = &**attr.value();
1292 element.update_nonce_internal_slot(nonce.to_owned());
1293 },
1294 AttributeMutation::Removed => {
1295 element.update_nonce_internal_slot("".to_owned());
1296 },
1297 },
1298 _ => {},
1299 }
1300 }
1301
1302 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1303 if let Some(super_type) = self.super_type() {
1304 super_type.bind_to_tree(cx, context);
1305 }
1306
1307 let element = self.as_element();
1310 if self.is_form_associated_custom_element() && element.enabled_state() {
1311 element.check_ancestors_disabled_state_for_form_control();
1312 if element.disabled_state() {
1313 ScriptThread::enqueue_callback_reaction(
1314 cx,
1315 element,
1316 CallbackReaction::FormDisabled(true),
1317 None,
1318 );
1319 }
1320 }
1321
1322 if element.has_attribute(&local_name!("accesskey")) {
1323 self.update_assigned_access_key();
1324 }
1325 }
1326
1327 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1331 let document = self.owner_document();
1333
1334 let element = self.as_element();
1344 if document
1345 .focus_handler()
1346 .focused_area()
1347 .element()
1348 .is_some_and(|focused_element| focused_element == element)
1349 {
1350 document
1351 .focus_handler()
1352 .set_focused_area(FocusableArea::Viewport);
1353 }
1354
1355 if let Some(super_type) = self.super_type() {
1360 super_type.unbind_from_tree(cx, context);
1361 }
1362
1363 if self.is_form_associated_custom_element() && element.disabled_state() {
1374 element.check_disabled_attribute();
1375 element.check_ancestors_disabled_state_for_form_control();
1376 if element.enabled_state() {
1377 ScriptThread::enqueue_callback_reaction(
1378 cx,
1379 element,
1380 CallbackReaction::FormDisabled(false),
1381 None,
1382 );
1383 }
1384 }
1385
1386 if element.has_attribute(&local_name!("accesskey")) {
1387 self.owner_document()
1388 .event_handler()
1389 .unassign_access_key(self);
1390 }
1391 }
1392
1393 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1394 if is_element_affected_by_legacy_background_presentational_hint(
1395 self.element.namespace(),
1396 self.element.local_name(),
1397 ) && attr.local_name() == &local_name!("background")
1398 {
1399 return true;
1400 }
1401
1402 self.super_type()
1403 .unwrap()
1404 .attribute_affects_presentational_hints(attr)
1405 }
1406
1407 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1408 match *name {
1409 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1410 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1411 local_name!("background")
1412 if is_element_affected_by_legacy_background_presentational_hint(
1413 self.element.namespace(),
1414 self.element.local_name(),
1415 ) =>
1416 {
1417 AttrValue::from_resolved_url(
1418 &self.owner_document().base_url().get_arc(),
1419 value.into(),
1420 )
1421 },
1422 _ => self
1423 .super_type()
1424 .unwrap()
1425 .parse_plain_attribute(name, value),
1426 }
1427 }
1428
1429 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1431 if let Some(super_type) = self.super_type() {
1435 super_type.moving_steps(cx, context);
1436 }
1437
1438 if let Some(form_control) = self.element.as_maybe_form_control() {
1442 form_control.moving_steps(cx)
1443 }
1444 }
1445}
1446
1447impl Activatable for HTMLElement {
1448 fn as_element(&self) -> &Element {
1449 &self.element
1450 }
1451
1452 fn is_instance_activatable(&self) -> bool {
1453 self.element.local_name() == &local_name!("summary")
1454 }
1455
1456 fn activation_behavior(
1458 &self,
1459 cx: &mut js::context::JSContext,
1460 _event: &Event,
1461 _target: &EventTarget,
1462 ) {
1463 self.summary_activation_behavior(cx);
1464 }
1465}
1466
1467impl FormControl for HTMLElement {
1473 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1474 debug_assert!(self.is_form_associated_custom_element());
1475 self.element
1476 .get_element_internals()
1477 .and_then(|e| e.form_owner())
1478 }
1479
1480 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>) {
1481 debug_assert!(self.is_form_associated_custom_element());
1482 self.element
1483 .ensure_element_internals(cx)
1484 .set_form_owner(form);
1485 }
1486
1487 fn to_html_element(&self) -> &HTMLElement {
1488 self
1489 }
1490}