1use std::default::Default;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, QualName, local_name, ns};
10use js::context::{JSContext, NoGC};
11use js::rust::HandleObject;
12use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
13use rustc_hash::FxHashSet;
14use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
15use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
16use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
17use script_bindings::dom::UnrootedDom;
18use style::attr::AttrValue;
19use stylo_dom::ElementState;
20
21use crate::dom::activation::Activatable;
22use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
23use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
24 EventHandlerNonNull, OnErrorEventHandlerNonNull,
25};
26use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
27use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
28use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
29use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
30use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
31use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
32use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
33use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
34use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
35use crate::dom::bindings::str::DOMString;
36use crate::dom::characterdata::CharacterData;
37use crate::dom::css::cssstyledeclaration::{
38 CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
39};
40use crate::dom::customelementregistry::{
41 CallbackReaction, CustomElementRegistry, CustomElementState,
42};
43use crate::dom::document::Document;
44use crate::dom::document::focus::FocusableArea;
45use crate::dom::document_event_handler::character_to_code;
46use crate::dom::documentfragment::DocumentFragment;
47use crate::dom::domstringmap::DOMStringMap;
48use crate::dom::element::attributes::storage::AttrRef;
49use crate::dom::element::{
50 AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
51 is_element_affected_by_legacy_background_presentational_hint,
52};
53use crate::dom::elementinternals::ElementInternals;
54use crate::dom::event::Event;
55use crate::dom::eventtarget::EventTarget;
56use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
57use crate::dom::html::form_controls::input_type::InputType;
58use crate::dom::html::htmlbodyelement::HTMLBodyElement;
59use crate::dom::html::htmldetailselement::HTMLDetailsElement;
60use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
61use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
62use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
63use crate::dom::html::htmllabelelement::HTMLLabelElement;
64use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
65use crate::dom::htmlformelement::FormControlElementHelpers;
66use crate::dom::iterators::ShadowIncluding;
67use crate::dom::medialist::MediaList;
68use crate::dom::node::virtualmethods::VirtualMethods;
69use crate::dom::node::{
70 BindContext, MoveContext, Node, NodeTraits, UnbindContext, from_untrusted_node_address,
71};
72use crate::dom::shadowroot::ShadowRoot;
73use crate::dom::text::Text;
74use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement};
75use crate::event_loop::script_thread::ScriptThread;
76
77#[dom_struct]
78pub(crate) struct HTMLElement {
79 element: Element,
80 style_decl: MutNullableDom<CSSStyleDeclaration>,
81 dataset: MutNullableDom<DOMStringMap>,
82}
83
84impl HTMLElement {
85 pub(crate) fn new_inherited(
86 tag_name: LocalName,
87 prefix: Option<Prefix>,
88 document: &Document,
89 ) -> HTMLElement {
90 HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
91 }
92
93 pub(crate) fn new_inherited_with_state(
94 state: ElementState,
95 tag_name: LocalName,
96 prefix: Option<Prefix>,
97 document: &Document,
98 ) -> HTMLElement {
99 HTMLElement {
100 element: Element::new_inherited_with_state(
101 state,
102 tag_name,
103 ns!(html),
104 prefix,
105 document,
106 ),
107 style_decl: Default::default(),
108 dataset: Default::default(),
109 }
110 }
111
112 pub(crate) fn new(
113 cx: &mut js::context::JSContext,
114 local_name: LocalName,
115 prefix: Option<Prefix>,
116 document: &Document,
117 proto: Option<HandleObject>,
118 ) -> DomRoot<HTMLElement> {
119 Node::reflect_node_with_proto(
120 cx,
121 Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
122 document,
123 proto,
124 )
125 }
126
127 fn is_body_or_frameset(&self) -> bool {
128 let eventtarget = self.upcast::<EventTarget>();
129 eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
130 }
131
132 pub(crate) fn get_inner_outer_text(&self) -> DOMString {
138 let node = self.upcast::<Node>();
139 let window = node.owner_window();
140 let element = self.as_element();
141
142 let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
144 if element_not_rendered {
145 return node.GetTextContent().unwrap();
146 }
147
148 window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
149 let text = window
150 .layout()
151 .query_element_inner_outer_text(node.to_trusted_node_address());
152
153 DOMString::from(text)
154 }
155
156 pub(crate) fn set_inner_text(&self, cx: &mut JSContext, input: DOMString) {
158 let fragment = self.rendered_text_fragment(cx, input);
161
162 Node::replace_all(cx, Some(fragment.upcast()), self.upcast::<Node>());
164 }
165
166 pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
168 self.element
172 .get_attribute_string_value(&local_name!("media"))
173 .is_none_or(|media| MediaList::matches_environment(&self.owner_document(), &media))
174 }
175
176 pub(crate) fn is_editing_host(&self) -> bool {
178 matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
180 }
183
184 pub(crate) fn previously_focused_element(&self, no_gc: &NoGC) -> Option<DomRoot<Element>> {
185 self.upcast::<Element>()
186 .ensure_rare_data(no_gc)
187 .previously_focused_element
188 .get()
189 }
190
191 pub(crate) fn set_previously_focused_element(&self, element: Option<&Element>, no_gc: &NoGC) {
192 self.upcast::<Element>()
193 .ensure_rare_data(no_gc)
194 .previously_focused_element
195 .set(element);
196 }
197}
198
199impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
200 fn Style(&self, cx: &mut JSContext) -> DomRoot<CSSStyleDeclaration> {
202 self.style_decl.or_init(|| {
203 let global = self.owner_window();
204 CSSStyleDeclaration::new(
205 cx,
206 &global,
207 CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
208 None,
209 CSSModificationAccess::ReadWrite,
210 )
211 })
212 }
213
214 make_getter!(Title, "title");
216 make_setter!(SetTitle, "title");
218
219 make_getter!(Lang, "lang");
221 make_setter!(SetLang, "lang");
223
224 make_enumerated_getter!(
226 Dir,
227 "dir",
228 "ltr" | "rtl" | "auto",
229 missing => "",
230 invalid => ""
231 );
232
233 make_setter!(SetDir, "dir");
235
236 make_bool_getter!(Hidden, "hidden");
238 make_bool_setter!(SetHidden, "hidden");
240
241 global_event_handlers!(NoOnload);
243
244 fn Dataset(&self, cx: &mut JSContext) -> DomRoot<DOMStringMap> {
246 self.dataset.or_init(|| DOMStringMap::new(cx, self))
247 }
248
249 fn GetOnerror(&self, cx: &mut JSContext) -> Option<Rc<OnErrorEventHandlerNonNull>> {
251 if self.is_body_or_frameset() {
252 let document = self.owner_document();
253 if document.has_browsing_context() {
254 document.window().GetOnerror(cx)
255 } else {
256 None
257 }
258 } else {
259 self.upcast::<EventTarget>()
260 .get_event_handler_common(cx, "error")
261 }
262 }
263
264 fn SetOnerror(&self, cx: &mut JSContext, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
266 if self.is_body_or_frameset() {
267 let document = self.owner_document();
268 if document.has_browsing_context() {
269 document.window().SetOnerror(cx, listener)
270 }
271 } else {
272 self.upcast::<EventTarget>()
274 .set_error_event_handler(cx, "error", listener)
275 }
276 }
277
278 fn GetOnload(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
280 if self.is_body_or_frameset() {
281 let document = self.owner_document();
282 if document.has_browsing_context() {
283 document.window().GetOnload(cx)
284 } else {
285 None
286 }
287 } else {
288 self.upcast::<EventTarget>()
289 .get_event_handler_common(cx, "load")
290 }
291 }
292
293 fn SetOnload(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
295 if self.is_body_or_frameset() {
296 let document = self.owner_document();
297 if document.has_browsing_context() {
298 document.window().SetOnload(cx, listener)
299 }
300 } else {
301 self.upcast::<EventTarget>()
302 .set_event_handler_common(cx, "load", listener)
303 }
304 }
305
306 fn GetOnblur(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
308 if self.is_body_or_frameset() {
309 let document = self.owner_document();
310 if document.has_browsing_context() {
311 document.window().GetOnblur(cx)
312 } else {
313 None
314 }
315 } else {
316 self.upcast::<EventTarget>()
317 .get_event_handler_common(cx, "blur")
318 }
319 }
320
321 fn SetOnblur(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
323 if self.is_body_or_frameset() {
324 let document = self.owner_document();
325 if document.has_browsing_context() {
326 document.window().SetOnblur(cx, listener)
327 }
328 } else {
329 self.upcast::<EventTarget>()
330 .set_event_handler_common(cx, "blur", listener)
331 }
332 }
333
334 fn GetOnfocus(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
336 if self.is_body_or_frameset() {
337 let document = self.owner_document();
338 if document.has_browsing_context() {
339 document.window().GetOnfocus(cx)
340 } else {
341 None
342 }
343 } else {
344 self.upcast::<EventTarget>()
345 .get_event_handler_common(cx, "focus")
346 }
347 }
348
349 fn SetOnfocus(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
351 if self.is_body_or_frameset() {
352 let document = self.owner_document();
353 if document.has_browsing_context() {
354 document.window().SetOnfocus(cx, listener)
355 }
356 } else {
357 self.upcast::<EventTarget>()
358 .set_event_handler_common(cx, "focus", listener)
359 }
360 }
361
362 fn GetOnresize(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
364 if self.is_body_or_frameset() {
365 let document = self.owner_document();
366 if document.has_browsing_context() {
367 document.window().GetOnresize(cx)
368 } else {
369 None
370 }
371 } else {
372 self.upcast::<EventTarget>()
373 .get_event_handler_common(cx, "resize")
374 }
375 }
376
377 fn SetOnresize(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
379 if self.is_body_or_frameset() {
380 let document = self.owner_document();
381 if document.has_browsing_context() {
382 document.window().SetOnresize(cx, listener)
383 }
384 } else {
385 self.upcast::<EventTarget>()
386 .set_event_handler_common(cx, "resize", listener)
387 }
388 }
389
390 fn GetOnscroll(&self, cx: &mut JSContext) -> Option<Rc<EventHandlerNonNull>> {
392 if self.is_body_or_frameset() {
393 let document = self.owner_document();
394 if document.has_browsing_context() {
395 document.window().GetOnscroll(cx)
396 } else {
397 None
398 }
399 } else {
400 self.upcast::<EventTarget>()
401 .get_event_handler_common(cx, "scroll")
402 }
403 }
404
405 fn SetOnscroll(&self, cx: &mut JSContext, listener: Option<Rc<EventHandlerNonNull>>) {
407 if self.is_body_or_frameset() {
408 let document = self.owner_document();
409 if document.has_browsing_context() {
410 document.window().SetOnscroll(cx, listener)
411 }
412 } else {
413 self.upcast::<EventTarget>()
414 .set_event_handler_common(cx, "scroll", listener)
415 }
416 }
417
418 fn Itemtypes(&self) -> Option<Vec<DOMString>> {
420 let atoms = self
421 .element
422 .get_tokenlist_attribute(&local_name!("itemtype"));
423
424 if atoms.is_empty() {
425 return None;
426 }
427
428 Some(
429 FxHashSet::from_iter(
430 atoms
431 .iter()
432 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
433 )
434 .into_iter()
435 .collect(),
436 )
437 }
438
439 fn PropertyNames(&self) -> Option<Vec<DOMString>> {
441 let atoms = self
442 .element
443 .get_tokenlist_attribute(&local_name!("itemprop"));
444
445 if atoms.is_empty() {
446 return None;
447 }
448
449 Some(
450 FxHashSet::from_iter(
451 atoms
452 .iter()
453 .map(|attr_value| DOMString::from(String::from(attr_value.trim()))),
454 )
455 .into_iter()
456 .collect(),
457 )
458 }
459
460 fn Click(&self, cx: &mut JSContext) {
462 let element = self.as_element();
463 if element.disabled_state() {
464 return;
465 }
466 if element.click_in_progress() {
467 return;
468 }
469 element.set_click_in_progress(true);
470
471 self.upcast::<Node>()
472 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
473 element.set_click_in_progress(false);
474 }
475
476 fn Focus(&self, cx: &mut JSContext, options: &FocusOptions) {
478 if !self.upcast::<Node>().run_the_focusing_steps(cx, None) {
483 return;
487 }
488
489 if !options.preventScroll {
496 let scroll_axis = ScrollAxisState {
497 position: ScrollLogicalPosition::Center,
498 requirement: ScrollRequirement::IfNotVisible,
499 };
500 self.upcast::<Element>().scroll_into_view_with_options(
501 cx,
502 ScrollBehavior::Smooth,
503 scroll_axis,
504 scroll_axis,
505 None,
506 None,
507 );
508 }
509 }
510
511 fn Blur(&self, cx: &mut JSContext) {
513 if !self.as_element().focus_state() {
516 return;
517 }
518 self.owner_document()
520 .focus_handler()
521 .focus(cx, &FocusableArea::Viewport);
522 }
523
524 #[expect(unsafe_code)]
526 fn ScrollParent(&self) -> Option<DomRoot<Element>> {
527 self.owner_window()
528 .scroll_container_query(
529 Some(self.upcast()),
530 ScrollContainerQueryFlags::ForScrollParent,
531 )
532 .and_then(|response| match response {
533 ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
534 ScrollContainerResponse::Element(parent_node_address, _) => {
535 let node = unsafe { from_untrusted_node_address(parent_node_address) };
536 DomRoot::downcast(node)
537 },
538 })
539 }
540
541 fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
543 if self.is::<HTMLBodyElement>() || self.element.is_root() {
544 return None;
545 }
546
547 let node = self.upcast::<Node>();
548 let window = self.owner_window();
549 let (element, _) = window.offset_parent_query(node);
550
551 element
552 }
553
554 fn OffsetTop(&self) -> i32 {
556 if self.is_body_element() {
557 return 0;
558 }
559
560 let node = self.upcast::<Node>();
561 let window = self.owner_window();
562 let (_, rect) = window.offset_parent_query(node);
563
564 rect.origin.y.to_nearest_px()
565 }
566
567 fn OffsetLeft(&self) -> i32 {
569 if self.is_body_element() {
570 return 0;
571 }
572
573 let node = self.upcast::<Node>();
574 let window = self.owner_window();
575 let (_, rect) = window.offset_parent_query(node);
576
577 rect.origin.x.to_nearest_px()
578 }
579
580 fn OffsetWidth(&self) -> i32 {
582 let node = self.upcast::<Node>();
583 let window = self.owner_window();
584 let (_, rect) = window.offset_parent_query(node);
585
586 rect.size.width.to_nearest_px()
587 }
588
589 fn OffsetHeight(&self) -> i32 {
591 let node = self.upcast::<Node>();
592 let window = self.owner_window();
593 let (_, rect) = window.offset_parent_query(node);
594
595 rect.size.height.to_nearest_px()
596 }
597
598 fn InnerText(&self) -> DOMString {
600 self.get_inner_outer_text()
601 }
602
603 fn SetInnerText(&self, cx: &mut JSContext, input: DOMString) {
605 self.set_inner_text(cx, input)
606 }
607
608 fn GetOuterText(&self) -> Fallible<DOMString> {
610 Ok(self.get_inner_outer_text())
611 }
612
613 fn SetOuterText(&self, cx: &mut JSContext, input: DOMString) -> Fallible<()> {
615 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
617 return Err(Error::NoModificationAllowed(Some(
618 "Cannot modify HTML element as its parent element is null".into(),
619 )));
620 };
621
622 let node = self.upcast::<Node>();
623 let document = self.owner_document();
624
625 let next = node.GetNextSibling();
627
628 let previous = node.GetPreviousSibling();
630
631 let fragment = self.rendered_text_fragment(cx, input);
634
635 if fragment.upcast::<Node>().children_count() == 0 {
638 let text_node = Text::new(cx, DOMString::from("".to_owned()), &document);
639
640 fragment
641 .upcast::<Node>()
642 .AppendChild(cx, text_node.upcast())?;
643 }
644
645 parent.ReplaceChild(cx, fragment.upcast(), node)?;
647
648 if let Some(next_sibling) = next &&
651 let Some(node) = next_sibling.GetPreviousSibling()
652 {
653 Self::merge_with_the_next_text_node(cx, &node);
654 }
655
656 if let Some(previous) = previous {
658 Self::merge_with_the_next_text_node(cx, &previous)
659 }
660
661 Ok(())
662 }
663
664 fn Translate(&self) -> bool {
666 self.as_element().is_translate_enabled()
667 }
668
669 fn SetTranslate(&self, cx: &mut JSContext, yesno: bool) {
671 self.as_element().set_string_attribute(
672 cx,
673 &html5ever::local_name!("translate"),
674 match yesno {
675 true => DOMString::from("yes"),
676 false => DOMString::from("no"),
677 },
678 );
679 }
680
681 make_enumerated_getter!(
683 ContentEditable,
684 "contenteditable",
685 "true" | "false" | "plaintext-only",
686 missing => "inherit",
687 invalid => "inherit",
688 empty => "true"
689 );
690
691 fn SetContentEditable(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
693 let lower_value = value.to_ascii_lowercase();
694 let attr_name = &local_name!("contenteditable");
695 match lower_value.as_ref() {
696 "inherit" => {
698 self.element.remove_attribute_by_name(cx, attr_name);
699 },
700 "true" | "false" | "plaintext-only" => {
704 self.element
705 .set_attribute(cx, attr_name, AttrValue::String(lower_value));
706 },
707 _ => {
709 return Err(Error::Syntax(Some(
710 "Invalid attribute for HTML element".into(),
711 )));
712 },
713 };
714 Ok(())
715 }
716
717 fn IsContentEditable(&self) -> bool {
719 self.upcast::<Node>().is_editable_or_editing_host()
721 }
722
723 fn AttachInternals(&self, cx: &mut JSContext) -> Fallible<DomRoot<ElementInternals>> {
725 if self.element.get_is().is_some() {
727 return Err(Error::NotSupported(Some(
728 "Local name of HTML element must not be set".into(),
729 )));
730 }
731
732 let lookup_registry = {
734 let registry = self.as_element().custom_element_registry();
737 if registry
738 .as_ref()
739 .is_some_and(|registry| registry.is_scoped())
740 {
741 registry
742 } else {
743 self.upcast::<Node>().owner_doc().custom_element_registry()
744 }
745 };
746 let definition = CustomElementRegistry::lookup_custom_element_definition(
747 lookup_registry.as_deref(),
748 self.upcast::<Element>().namespace(),
749 self.as_element().local_name(),
750 None,
751 );
752
753 let definition = match definition {
755 Some(definition) => definition,
756 None => {
757 return Err(Error::NotSupported(Some(
758 "HTML element defintion is not defined".into(),
759 )));
760 },
761 };
762
763 if definition.disable_internals {
765 return Err(Error::NotSupported(Some(
766 "HTML element defintion's `disable_internals` must be set to true".into(),
767 )));
768 }
769
770 let internals = self.element.ensure_element_internals(cx);
772 if internals.attached() {
773 return Err(Error::NotSupported(Some(
774 "HTML element's attached internals are null".into(),
775 )));
776 }
777
778 if !matches!(
781 self.element.get_custom_element_state(),
782 CustomElementState::Precustomized | CustomElementState::Custom
783 ) {
784 return Err(Error::NotSupported(Some(
785 "Custom element state must either be precustomized, or custom".into(),
786 )));
787 }
788
789 if self.is_form_associated_custom_element() {
790 self.element.init_state_for_internals();
791 }
792
793 internals.set_attached();
795 Ok(internals)
796 }
797
798 fn Nonce(&self) -> DOMString {
800 self.as_element().nonce_value().into()
801 }
802
803 fn SetNonce(&self, cx: &mut JSContext, value: DOMString) {
805 self.as_element()
806 .update_nonce_internal_slot(String::from(value), cx.no_gc())
807 }
808
809 fn Autofocus(&self) -> bool {
811 self.element.has_attribute(&local_name!("autofocus"))
812 }
813
814 fn SetAutofocus(&self, cx: &mut JSContext, autofocus: bool) {
816 self.element
817 .set_bool_attribute(cx, &local_name!("autofocus"), autofocus);
818 }
819
820 fn TabIndex(&self) -> i32 {
822 self.element.tab_index()
823 }
824
825 fn SetTabIndex(&self, cx: &mut JSContext, tab_index: i32) {
827 self.element
828 .set_attribute(cx, &local_name!("tabindex"), tab_index.into());
829 }
830
831 make_getter!(AccessKey, "accesskey");
833
834 make_setter!(SetAccessKey, "accesskey");
836
837 fn AccessKeyLabel(&self) -> DOMString {
839 if !self.element.has_attribute(&local_name!("accesskey")) {
843 return Default::default();
844 }
845
846 let access_key_string =
847 String::from(self.element.get_string_attribute(&local_name!("accesskey")));
848
849 #[cfg(target_os = "macos")]
850 let access_key_label = format!("⌃⌥{access_key_string}");
851 #[cfg(not(target_os = "macos"))]
852 let access_key_label = format!("Alt+Shift+{access_key_string}");
853
854 access_key_label.into()
855 }
856}
857
858fn append_text_node_to_fragment(
859 cx: &mut JSContext,
860 document: &Document,
861 fragment: &DocumentFragment,
862 text: String,
863) {
864 let text = Text::new(cx, DOMString::from(text), document);
865 fragment
866 .upcast::<Node>()
867 .AppendChild(cx, text.upcast())
868 .unwrap();
869}
870
871impl HTMLElement {
872 pub(crate) fn is_labelable_element(&self) -> bool {
874 match self.upcast::<Node>().type_id() {
875 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
876 HTMLElementTypeId::HTMLInputElement => !matches!(
877 *self.downcast::<HTMLInputElement>().unwrap().input_type(),
878 InputType::Hidden(_)
879 ),
880 HTMLElementTypeId::HTMLButtonElement |
881 HTMLElementTypeId::HTMLMeterElement |
882 HTMLElementTypeId::HTMLOutputElement |
883 HTMLElementTypeId::HTMLProgressElement |
884 HTMLElementTypeId::HTMLSelectElement |
885 HTMLElementTypeId::HTMLTextAreaElement => true,
886 _ => self.is_form_associated_custom_element(),
887 },
888 _ => false,
889 }
890 }
891
892 pub(crate) fn is_form_associated_custom_element(&self) -> bool {
894 if let Some(definition) = self.as_element().get_custom_element_definition() {
895 definition.is_autonomous() && definition.form_associated
896 } else {
897 false
898 }
899 }
900
901 pub(crate) fn is_listed_element(&self) -> bool {
903 match self.upcast::<Node>().type_id() {
904 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
905 HTMLElementTypeId::HTMLButtonElement |
906 HTMLElementTypeId::HTMLFieldSetElement |
907 HTMLElementTypeId::HTMLInputElement |
908 HTMLElementTypeId::HTMLObjectElement |
909 HTMLElementTypeId::HTMLOutputElement |
910 HTMLElementTypeId::HTMLSelectElement |
911 HTMLElementTypeId::HTMLTextAreaElement => true,
912 _ => self.is_form_associated_custom_element(),
913 },
914 _ => false,
915 }
916 }
917
918 pub(crate) fn is_body_element(&self) -> bool {
920 let self_node = self.upcast::<Node>();
921 self_node.GetParentNode().is_some_and(|parent| {
922 let parent_node = parent.upcast::<Node>();
923 (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
924 parent_node.is::<HTMLHtmlElement>() &&
925 self_node
926 .preceding_siblings()
927 .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
928 })
929 }
930
931 pub(crate) fn is_submittable_element(&self) -> bool {
933 match self.upcast::<Node>().type_id() {
934 NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
935 HTMLElementTypeId::HTMLButtonElement |
936 HTMLElementTypeId::HTMLInputElement |
937 HTMLElementTypeId::HTMLSelectElement |
938 HTMLElementTypeId::HTMLTextAreaElement => true,
939 _ => self.is_form_associated_custom_element(),
940 },
941 _ => false,
942 }
943 }
944
945 pub(crate) fn label_at<'a>(
948 &self,
949 no_gc: &'a NoGC,
950 index: u32,
951 ) -> Option<UnrootedDom<'a, Node>> {
952 let element = self.as_element();
953
954 let root_element = element.root_element();
965 let root_node = root_element.upcast::<Node>();
966 root_node
967 .traverse_preorder_non_rooting(no_gc, ShadowIncluding::No)
968 .filter_map(UnrootedDom::downcast::<HTMLLabelElement>)
969 .filter(|elem| match elem.GetControl() {
970 Some(control) => &*control == self,
971 _ => false,
972 })
973 .nth(index as usize)
974 .map(UnrootedDom::upcast)
975 }
976
977 pub(crate) fn labels_count(&self) -> u32 {
980 let element = self.as_element();
982 let root_element = element.root_element();
983 let root_node = root_element.upcast::<Node>();
984 root_node
985 .traverse_preorder(ShadowIncluding::No)
986 .filter_map(DomRoot::downcast::<HTMLLabelElement>)
987 .filter(|elem| match elem.GetControl() {
988 Some(control) => &*control == self,
989 _ => false,
990 })
991 .count() as u32
992 }
993
994 pub(crate) fn directionality(&self) -> Option<String> {
998 let element_direction = &self.Dir();
999
1000 if element_direction == "ltr" {
1001 return Some("ltr".to_owned());
1002 }
1003
1004 if element_direction == "rtl" {
1005 return Some("rtl".to_owned());
1006 }
1007
1008 if let Some(input) = self.downcast::<HTMLInputElement>() &&
1009 matches!(*input.input_type(), InputType::Tel(_))
1010 {
1011 return Some("ltr".to_owned());
1012 }
1013
1014 if element_direction == "auto" {
1015 if let Some(directionality) = self
1016 .downcast::<HTMLInputElement>()
1017 .and_then(|input| input.auto_directionality())
1018 {
1019 return Some(directionality);
1020 }
1021
1022 if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
1023 return Some(area.auto_directionality());
1024 }
1025 }
1026
1027 None
1034 }
1035
1036 pub(crate) fn summary_activation_behavior(&self, cx: &mut js::context::JSContext) {
1038 debug_assert!(self.as_element().local_name() == &local_name!("summary"));
1039
1040 let is_implicit_summary_element = self.is_implicit_summary_element();
1042 if !is_implicit_summary_element && !self.is_a_summary_for_its_parent_details() {
1043 return;
1044 }
1045
1046 let parent = if is_implicit_summary_element {
1048 DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
1049 .unwrap()
1050 } else {
1051 self.upcast::<Node>()
1052 .GetParentNode()
1053 .and_then(DomRoot::downcast::<HTMLDetailsElement>)
1054 .unwrap()
1055 };
1056
1057 parent.toggle(cx);
1060 }
1061
1062 pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
1064 let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1067 return false;
1068 };
1069
1070 let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1072 return false;
1073 };
1074
1075 details
1079 .find_corresponding_summary_element()
1080 .is_some_and(|summary| &*summary == self.upcast())
1081 }
1082
1083 fn is_implicit_summary_element(&self) -> bool {
1086 self.containing_shadow_root()
1090 .as_deref()
1091 .map(ShadowRoot::Host)
1092 .is_some_and(|host| host.is::<HTMLDetailsElement>())
1093 }
1094
1095 fn rendered_text_fragment(
1097 &self,
1098 cx: &mut JSContext,
1099 input: DOMString,
1100 ) -> DomRoot<DocumentFragment> {
1101 let document = self.owner_document();
1103 let fragment = DocumentFragment::new(cx, &document);
1104
1105 let input = input.str();
1108 let mut position = input.chars().peekable();
1109
1110 let mut text = String::new();
1112
1113 while let Some(ch) = position.next() {
1115 match ch {
1116 '\u{000A}' | '\u{000D}' => {
1119 if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1120 position.next();
1123 }
1124
1125 if !text.is_empty() {
1126 append_text_node_to_fragment(cx, &document, &fragment, text);
1127 text = String::new();
1128 }
1129
1130 let br = Element::create(
1131 cx,
1132 QualName::new(None, ns!(html), local_name!("br")),
1133 None,
1134 &document,
1135 ElementCreator::ScriptCreated,
1136 CustomElementCreationMode::Asynchronous,
1137 None,
1138 );
1139 fragment
1140 .upcast::<Node>()
1141 .AppendChild(cx, br.upcast())
1142 .unwrap();
1143 },
1144 _ => {
1145 text.push(ch);
1148 },
1149 }
1150 }
1151
1152 if !text.is_empty() {
1155 append_text_node_to_fragment(cx, &document, &fragment, text);
1156 }
1157
1158 fragment
1159 }
1160
1161 fn merge_with_the_next_text_node(cx: &mut JSContext, node: &Node) {
1167 if !node.is::<Text>() {
1169 return;
1170 }
1171
1172 let next = match node.GetNextSibling() {
1174 Some(next) => next,
1175 None => return,
1176 };
1177
1178 if !next.is::<Text>() {
1180 return;
1181 }
1182 let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1184 let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1185 node_chars
1186 .ReplaceData(cx, node_chars.Length(), 0, next_chars.Data())
1187 .expect("Got chars from Text");
1188
1189 next.remove_self(cx);
1191 }
1192
1193 fn update_assigned_access_key(&self) {
1197 if !self.element.has_attribute(&local_name!("accesskey")) {
1199 self.owner_document()
1201 .event_handler()
1202 .unassign_access_key(self);
1203 }
1204
1205 let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1207 let string_view = attribute_value.str();
1208 let values = string_view.split_html_space_characters();
1209
1210 for value in values {
1213 let mut characters = value.chars();
1216 let Some(character) = characters.next() else {
1217 continue;
1218 };
1219 if characters.count() > 0 {
1220 continue;
1221 }
1222
1223 let Some(code) = character_to_code(character) else {
1226 continue;
1227 };
1228
1229 self.owner_document()
1234 .event_handler()
1235 .assign_access_key(self, code);
1236 return;
1237 }
1238
1239 self.owner_document()
1245 .event_handler()
1246 .unassign_access_key(self);
1247 }
1248}
1249
1250impl VirtualMethods for HTMLElement {
1251 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1252 Some(self.as_element() as &dyn VirtualMethods)
1253 }
1254
1255 fn attribute_mutated(
1256 &self,
1257 cx: &mut JSContext,
1258 attr: AttrRef<'_>,
1259 mutation: AttributeMutation,
1260 ) {
1261 self.super_type()
1262 .unwrap()
1263 .attribute_mutated(cx, attr, mutation);
1264 let element = self.as_element();
1265 match (attr.local_name(), mutation) {
1266 (&local_name!("accesskey"), ..) => {
1267 self.update_assigned_access_key();
1268 },
1269 (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1270 self.form_attribute_mutated(cx, mutation);
1271 },
1272 (&local_name!("disabled"), AttributeMutation::Set(..))
1274 if self.is_form_associated_custom_element() && element.enabled_state() =>
1275 {
1276 element.set_disabled_state(true);
1277 element.set_enabled_state(false);
1278 ScriptThread::enqueue_callback_reaction(
1279 cx,
1280 element,
1281 CallbackReaction::FormDisabled(true),
1282 None,
1283 );
1284 },
1285 (&local_name!("disabled"), AttributeMutation::Removed)
1288 if self.is_form_associated_custom_element() && element.disabled_state() =>
1289 {
1290 element.set_disabled_state(false);
1291 element.set_enabled_state(true);
1292 element.check_ancestors_disabled_state_for_form_control();
1293 if element.enabled_state() {
1294 ScriptThread::enqueue_callback_reaction(
1295 cx,
1296 element,
1297 CallbackReaction::FormDisabled(false),
1298 None,
1299 );
1300 }
1301 },
1302 (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1303 match mutation {
1304 AttributeMutation::Set(..) => {
1305 element.set_read_write_state(true);
1306 },
1307 AttributeMutation::Removed => {
1308 element.set_read_write_state(false);
1309 },
1310 }
1311 },
1312 (&local_name!("nonce"), mutation) => match mutation {
1313 AttributeMutation::Set(..) => {
1314 let nonce = &**attr.value();
1315 element.update_nonce_internal_slot(nonce.to_owned(), cx.no_gc());
1316 },
1317 AttributeMutation::Removed => {
1318 element.update_nonce_internal_slot("".to_owned(), cx.no_gc());
1319 },
1320 },
1321 _ => {},
1322 }
1323 }
1324
1325 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1326 if let Some(super_type) = self.super_type() {
1327 super_type.bind_to_tree(cx, context);
1328 }
1329
1330 let element = self.as_element();
1333 if self.is_form_associated_custom_element() && element.enabled_state() {
1334 element.check_ancestors_disabled_state_for_form_control();
1335 if element.disabled_state() {
1336 ScriptThread::enqueue_callback_reaction(
1337 cx,
1338 element,
1339 CallbackReaction::FormDisabled(true),
1340 None,
1341 );
1342 }
1343 }
1344
1345 if element.has_attribute(&local_name!("accesskey")) {
1346 self.update_assigned_access_key();
1347 }
1348 }
1349
1350 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1354 let document = self.owner_document();
1356
1357 let element = self.as_element();
1367 if document
1368 .focus_handler()
1369 .focused_area()
1370 .element()
1371 .is_some_and(|focused_element| focused_element == element)
1372 {
1373 document
1374 .focus_handler()
1375 .set_focused_area(FocusableArea::Viewport);
1376 }
1377
1378 if let Some(super_type) = self.super_type() {
1383 super_type.unbind_from_tree(cx, context);
1384 }
1385
1386 if self.is_form_associated_custom_element() && element.disabled_state() {
1397 element.check_disabled_attribute();
1398 element.check_ancestors_disabled_state_for_form_control();
1399 if element.enabled_state() {
1400 ScriptThread::enqueue_callback_reaction(
1401 cx,
1402 element,
1403 CallbackReaction::FormDisabled(false),
1404 None,
1405 );
1406 }
1407 }
1408
1409 if element.has_attribute(&local_name!("accesskey")) {
1410 self.owner_document()
1411 .event_handler()
1412 .unassign_access_key(self);
1413 }
1414 }
1415
1416 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1417 if is_element_affected_by_legacy_background_presentational_hint(
1418 self.element.namespace(),
1419 self.element.local_name(),
1420 ) && attr.local_name() == &local_name!("background")
1421 {
1422 return true;
1423 }
1424
1425 self.super_type()
1426 .unwrap()
1427 .attribute_affects_presentational_hints(attr)
1428 }
1429
1430 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1431 match *name {
1432 local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1433 local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1434 local_name!("background")
1435 if is_element_affected_by_legacy_background_presentational_hint(
1436 self.element.namespace(),
1437 self.element.local_name(),
1438 ) =>
1439 {
1440 AttrValue::from_resolved_url(
1441 &self.owner_document().base_url().get_arc(),
1442 value.into(),
1443 )
1444 },
1445 _ => self
1446 .super_type()
1447 .unwrap()
1448 .parse_plain_attribute(name, value),
1449 }
1450 }
1451
1452 fn moving_steps(&self, cx: &mut JSContext, context: &MoveContext) {
1454 if let Some(super_type) = self.super_type() {
1458 super_type.moving_steps(cx, context);
1459 }
1460
1461 if let Some(form_control) = self.element.as_maybe_form_control() {
1465 form_control.moving_steps(cx)
1466 }
1467 }
1468}
1469
1470impl Activatable for HTMLElement {
1471 fn as_element(&self) -> &Element {
1472 &self.element
1473 }
1474
1475 fn is_instance_activatable(&self) -> bool {
1476 self.element.local_name() == &local_name!("summary")
1477 }
1478
1479 fn activation_behavior(
1481 &self,
1482 cx: &mut js::context::JSContext,
1483 _event: &Event,
1484 _target: &EventTarget,
1485 ) {
1486 self.summary_activation_behavior(cx);
1487 }
1488}
1489
1490impl FormControl for HTMLElement {
1496 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1497 debug_assert!(self.is_form_associated_custom_element());
1498 self.element
1499 .get_element_internals()
1500 .and_then(|e| e.form_owner())
1501 }
1502
1503 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>) {
1504 debug_assert!(self.is_form_associated_custom_element());
1505 self.element
1506 .ensure_element_internals(cx)
1507 .set_form_owner(form);
1508 }
1509
1510 fn to_html_element(&self) -> &HTMLElement {
1511 self
1512 }
1513}