script/dom/html/
htmlelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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::rust::HandleObject;
12use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
13use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
14use style::attr::AttrValue;
15use stylo_dom::ElementState;
16
17use crate::dom::activation::Activatable;
18use crate::dom::attr::Attr;
19use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
20use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
21    EventHandlerNonNull, OnErrorEventHandlerNonNull,
22};
23use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
24use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
25use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
26use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
27use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
28use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
29use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
30use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
31use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
32use crate::dom::bindings::str::DOMString;
33use crate::dom::characterdata::CharacterData;
34use crate::dom::css::cssstyledeclaration::{
35    CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
36};
37use crate::dom::customelementregistry::{CallbackReaction, CustomElementState};
38use crate::dom::document::{Document, FocusInitiator};
39use crate::dom::documentfragment::DocumentFragment;
40use crate::dom::domstringmap::DOMStringMap;
41use crate::dom::element::{
42    AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
43    is_element_affected_by_legacy_background_presentational_hint,
44};
45use crate::dom::elementinternals::ElementInternals;
46use crate::dom::event::Event;
47use crate::dom::eventtarget::EventTarget;
48use crate::dom::html::htmlbodyelement::HTMLBodyElement;
49use crate::dom::html::htmldetailselement::HTMLDetailsElement;
50use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
51use crate::dom::html::htmlframesetelement::HTMLFrameSetElement;
52use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
53use crate::dom::html::htmlinputelement::{HTMLInputElement, InputType};
54use crate::dom::html::htmllabelelement::HTMLLabelElement;
55use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
56use crate::dom::htmlformelement::FormControlElementHelpers;
57use crate::dom::medialist::MediaList;
58use crate::dom::node::{
59    BindContext, MoveContext, Node, NodeTraits, ShadowIncluding, UnbindContext,
60    from_untrusted_node_address,
61};
62use crate::dom::shadowroot::ShadowRoot;
63use crate::dom::text::Text;
64use crate::dom::virtualmethods::VirtualMethods;
65use crate::script_runtime::CanGc;
66use crate::script_thread::ScriptThread;
67
68#[dom_struct]
69pub(crate) struct HTMLElement {
70    element: Element,
71    style_decl: MutNullableDom<CSSStyleDeclaration>,
72    dataset: MutNullableDom<DOMStringMap>,
73}
74
75impl HTMLElement {
76    pub(crate) fn new_inherited(
77        tag_name: LocalName,
78        prefix: Option<Prefix>,
79        document: &Document,
80    ) -> HTMLElement {
81        HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
82    }
83
84    pub(crate) fn new_inherited_with_state(
85        state: ElementState,
86        tag_name: LocalName,
87        prefix: Option<Prefix>,
88        document: &Document,
89    ) -> HTMLElement {
90        HTMLElement {
91            element: Element::new_inherited_with_state(
92                state,
93                tag_name,
94                ns!(html),
95                prefix,
96                document,
97            ),
98            style_decl: Default::default(),
99            dataset: Default::default(),
100        }
101    }
102
103    pub(crate) fn new(
104        local_name: LocalName,
105        prefix: Option<Prefix>,
106        document: &Document,
107        proto: Option<HandleObject>,
108        can_gc: CanGc,
109    ) -> DomRoot<HTMLElement> {
110        Node::reflect_node_with_proto(
111            Box::new(HTMLElement::new_inherited(local_name, prefix, document)),
112            document,
113            proto,
114            can_gc,
115        )
116    }
117
118    fn is_body_or_frameset(&self) -> bool {
119        let eventtarget = self.upcast::<EventTarget>();
120        eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
121    }
122
123    /// Calls into the layout engine to generate a plain text representation
124    /// of a [`HTMLElement`] as specified when getting the `.innerText` or
125    /// `.outerText` in JavaScript.`
126    ///
127    /// <https://html.spec.whatwg.org/multipage/#get-the-text-steps>
128    pub(crate) fn get_inner_outer_text(&self) -> DOMString {
129        let node = self.upcast::<Node>();
130        let window = node.owner_window();
131        let element = self.as_element();
132
133        // Step 1.
134        let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
135        if element_not_rendered {
136            return node.GetTextContent().unwrap();
137        }
138
139        window.layout_reflow(QueryMsg::ElementInnerOuterTextQuery);
140        let text = window
141            .layout()
142            .query_element_inner_outer_text(node.to_trusted_node_address());
143
144        DOMString::from(text)
145    }
146
147    /// <https://html.spec.whatwg.org/multipage/#set-the-inner-text-steps>
148    pub(crate) fn set_inner_text(&self, input: DOMString, can_gc: CanGc) {
149        // Step 1: Let fragment be the rendered text fragment for value given element's node
150        // document.
151        let fragment = self.rendered_text_fragment(input, can_gc);
152
153        // Step 2: Replace all with fragment within element.
154        Node::replace_all(Some(fragment.upcast()), self.upcast::<Node>(), can_gc);
155    }
156
157    /// <https://html.spec.whatwg.org/multipage/#matches-the-environment>
158    pub(crate) fn media_attribute_matches_media_environment(&self) -> bool {
159        // A string matches the environment of the user if it is the empty string,
160        // a string consisting of only ASCII whitespace, or is a media query list that
161        // matches the user's environment according to the definitions given in Media Queries. [MQ]
162        self.upcast::<Element>()
163            .get_attribute(&local_name!("media"))
164            .is_none_or(|media| {
165                MediaList::matches_environment(&self.owner_document(), &media.value())
166            })
167    }
168
169    /// <https://html.spec.whatwg.org/multipage/#editing-host>
170    pub(crate) fn is_editing_host(&self) -> bool {
171        // > An editing host is either an HTML element with its contenteditable attribute in the true state or plaintext-only state,
172        matches!(&*self.ContentEditable().str(), "true" | "plaintext-only")
173        // > or a child HTML element of a Document whose design mode enabled is true.
174        // TODO
175    }
176}
177
178impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
179    /// <https://html.spec.whatwg.org/multipage/#the-style-attribute>
180    fn Style(&self, can_gc: CanGc) -> DomRoot<CSSStyleDeclaration> {
181        self.style_decl.or_init(|| {
182            let global = self.owner_window();
183            CSSStyleDeclaration::new(
184                &global,
185                CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
186                None,
187                CSSModificationAccess::ReadWrite,
188                can_gc,
189            )
190        })
191    }
192
193    // https://html.spec.whatwg.org/multipage/#attr-title
194    make_getter!(Title, "title");
195    // https://html.spec.whatwg.org/multipage/#attr-title
196    make_setter!(SetTitle, "title");
197
198    // https://html.spec.whatwg.org/multipage/#attr-lang
199    make_getter!(Lang, "lang");
200    // https://html.spec.whatwg.org/multipage/#attr-lang
201    make_setter!(SetLang, "lang");
202
203    // https://html.spec.whatwg.org/multipage/#the-dir-attribute
204    make_enumerated_getter!(
205        Dir,
206        "dir",
207        "ltr" | "rtl" | "auto",
208        missing => "",
209        invalid => ""
210    );
211
212    // https://html.spec.whatwg.org/multipage/#the-dir-attribute
213    make_setter!(SetDir, "dir");
214
215    // https://html.spec.whatwg.org/multipage/#dom-hidden
216    make_bool_getter!(Hidden, "hidden");
217    // https://html.spec.whatwg.org/multipage/#dom-hidden
218    make_bool_setter!(SetHidden, "hidden");
219
220    // https://html.spec.whatwg.org/multipage/#globaleventhandlers
221    global_event_handlers!(NoOnload);
222
223    /// <https://html.spec.whatwg.org/multipage/#dom-dataset>
224    fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> {
225        self.dataset.or_init(|| DOMStringMap::new(self, can_gc))
226    }
227
228    /// <https://html.spec.whatwg.org/multipage/#handler-onerror>
229    fn GetOnerror(&self, can_gc: CanGc) -> Option<Rc<OnErrorEventHandlerNonNull>> {
230        if self.is_body_or_frameset() {
231            let document = self.owner_document();
232            if document.has_browsing_context() {
233                document.window().GetOnerror()
234            } else {
235                None
236            }
237        } else {
238            self.upcast::<EventTarget>()
239                .get_event_handler_common("error", can_gc)
240        }
241    }
242
243    /// <https://html.spec.whatwg.org/multipage/#handler-onerror>
244    fn SetOnerror(&self, listener: Option<Rc<OnErrorEventHandlerNonNull>>) {
245        if self.is_body_or_frameset() {
246            let document = self.owner_document();
247            if document.has_browsing_context() {
248                document.window().SetOnerror(listener)
249            }
250        } else {
251            // special setter for error
252            self.upcast::<EventTarget>()
253                .set_error_event_handler("error", listener)
254        }
255    }
256
257    /// <https://html.spec.whatwg.org/multipage/#handler-onload>
258    fn GetOnload(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
259        if self.is_body_or_frameset() {
260            let document = self.owner_document();
261            if document.has_browsing_context() {
262                document.window().GetOnload()
263            } else {
264                None
265            }
266        } else {
267            self.upcast::<EventTarget>()
268                .get_event_handler_common("load", can_gc)
269        }
270    }
271
272    /// <https://html.spec.whatwg.org/multipage/#handler-onload>
273    fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) {
274        if self.is_body_or_frameset() {
275            let document = self.owner_document();
276            if document.has_browsing_context() {
277                document.window().SetOnload(listener)
278            }
279        } else {
280            self.upcast::<EventTarget>()
281                .set_event_handler_common("load", listener)
282        }
283    }
284
285    /// <https://html.spec.whatwg.org/multipage/#handler-onblur>
286    fn GetOnblur(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
287        if self.is_body_or_frameset() {
288            let document = self.owner_document();
289            if document.has_browsing_context() {
290                document.window().GetOnblur()
291            } else {
292                None
293            }
294        } else {
295            self.upcast::<EventTarget>()
296                .get_event_handler_common("blur", can_gc)
297        }
298    }
299
300    /// <https://html.spec.whatwg.org/multipage/#handler-onblur>
301    fn SetOnblur(&self, listener: Option<Rc<EventHandlerNonNull>>) {
302        if self.is_body_or_frameset() {
303            let document = self.owner_document();
304            if document.has_browsing_context() {
305                document.window().SetOnblur(listener)
306            }
307        } else {
308            self.upcast::<EventTarget>()
309                .set_event_handler_common("blur", listener)
310        }
311    }
312
313    /// <https://html.spec.whatwg.org/multipage/#handler-onfocus>
314    fn GetOnfocus(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
315        if self.is_body_or_frameset() {
316            let document = self.owner_document();
317            if document.has_browsing_context() {
318                document.window().GetOnfocus()
319            } else {
320                None
321            }
322        } else {
323            self.upcast::<EventTarget>()
324                .get_event_handler_common("focus", can_gc)
325        }
326    }
327
328    /// <https://html.spec.whatwg.org/multipage/#handler-onfocus>
329    fn SetOnfocus(&self, listener: Option<Rc<EventHandlerNonNull>>) {
330        if self.is_body_or_frameset() {
331            let document = self.owner_document();
332            if document.has_browsing_context() {
333                document.window().SetOnfocus(listener)
334            }
335        } else {
336            self.upcast::<EventTarget>()
337                .set_event_handler_common("focus", listener)
338        }
339    }
340
341    /// <https://html.spec.whatwg.org/multipage/#handler-onresize>
342    fn GetOnresize(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
343        if self.is_body_or_frameset() {
344            let document = self.owner_document();
345            if document.has_browsing_context() {
346                document.window().GetOnresize()
347            } else {
348                None
349            }
350        } else {
351            self.upcast::<EventTarget>()
352                .get_event_handler_common("resize", can_gc)
353        }
354    }
355
356    /// <https://html.spec.whatwg.org/multipage/#handler-onresize>
357    fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) {
358        if self.is_body_or_frameset() {
359            let document = self.owner_document();
360            if document.has_browsing_context() {
361                document.window().SetOnresize(listener)
362            }
363        } else {
364            self.upcast::<EventTarget>()
365                .set_event_handler_common("resize", listener)
366        }
367    }
368
369    /// <https://html.spec.whatwg.org/multipage/#handler-onscroll>
370    fn GetOnscroll(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
371        if self.is_body_or_frameset() {
372            let document = self.owner_document();
373            if document.has_browsing_context() {
374                document.window().GetOnscroll()
375            } else {
376                None
377            }
378        } else {
379            self.upcast::<EventTarget>()
380                .get_event_handler_common("scroll", can_gc)
381        }
382    }
383
384    /// <https://html.spec.whatwg.org/multipage/#handler-onscroll>
385    fn SetOnscroll(&self, listener: Option<Rc<EventHandlerNonNull>>) {
386        if self.is_body_or_frameset() {
387            let document = self.owner_document();
388            if document.has_browsing_context() {
389                document.window().SetOnscroll(listener)
390            }
391        } else {
392            self.upcast::<EventTarget>()
393                .set_event_handler_common("scroll", listener)
394        }
395    }
396
397    /// <https://html.spec.whatwg.org/multipage/#attr-itemtype>
398    fn Itemtypes(&self) -> Option<Vec<DOMString>> {
399        let atoms = self
400            .element
401            .get_tokenlist_attribute(&local_name!("itemtype"));
402
403        if atoms.is_empty() {
404            return None;
405        }
406
407        #[expect(clippy::mutable_key_type)]
408        // See `impl Hash for DOMString`.
409        let mut item_attr_values = HashSet::new();
410        for attr_value in &atoms {
411            item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
412        }
413
414        Some(item_attr_values.into_iter().collect())
415    }
416
417    /// <https://html.spec.whatwg.org/multipage/#names:-the-itemprop-attribute>
418    fn PropertyNames(&self) -> Option<Vec<DOMString>> {
419        let atoms = self
420            .element
421            .get_tokenlist_attribute(&local_name!("itemprop"));
422
423        if atoms.is_empty() {
424            return None;
425        }
426
427        #[expect(clippy::mutable_key_type)]
428        // See `impl Hash for DOMString`.
429        let mut item_attr_values = HashSet::new();
430        for attr_value in &atoms {
431            item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
432        }
433
434        Some(item_attr_values.into_iter().collect())
435    }
436
437    /// <https://html.spec.whatwg.org/multipage/#dom-click>
438    fn Click(&self, can_gc: CanGc) {
439        let element = self.as_element();
440        if element.disabled_state() {
441            return;
442        }
443        if element.click_in_progress() {
444            return;
445        }
446        element.set_click_in_progress(true);
447
448        self.upcast::<Node>()
449            .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
450        element.set_click_in_progress(false);
451    }
452
453    /// <https://html.spec.whatwg.org/multipage/#dom-focus>
454    fn Focus(&self, options: &FocusOptions, can_gc: CanGc) {
455        // TODO: Mark the element as locked for focus and run the focusing steps.
456        // <https://html.spec.whatwg.org/multipage/#focusing-steps>
457        let document = self.owner_document();
458        document.request_focus_with_options(
459            Some(self.upcast()),
460            FocusInitiator::Script,
461            FocusOptions {
462                preventScroll: options.preventScroll,
463            },
464            can_gc,
465        );
466    }
467
468    /// <https://html.spec.whatwg.org/multipage/#dom-blur>
469    fn Blur(&self, can_gc: CanGc) {
470        // TODO: Run the unfocusing steps. Focus the top-level document, not
471        //       the current document.
472        if !self.as_element().focus_state() {
473            return;
474        }
475        // https://html.spec.whatwg.org/multipage/#unfocusing-steps
476        let document = self.owner_document();
477        document.request_focus(None, FocusInitiator::Script, can_gc);
478    }
479
480    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>
481    #[expect(unsafe_code)]
482    fn ScrollParent(&self) -> Option<DomRoot<Element>> {
483        self.owner_window()
484            .scroll_container_query(
485                Some(self.upcast()),
486                ScrollContainerQueryFlags::ForScrollParent,
487            )
488            .and_then(|response| match response {
489                ScrollContainerResponse::Viewport(_) => self.owner_document().GetScrollingElement(),
490                ScrollContainerResponse::Element(parent_node_address, _) => {
491                    let node = unsafe { from_untrusted_node_address(parent_node_address) };
492                    DomRoot::downcast(node)
493                },
494            })
495    }
496
497    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent>
498    fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
499        if self.is::<HTMLBodyElement>() || self.upcast::<Element>().is_root() {
500            return None;
501        }
502
503        let node = self.upcast::<Node>();
504        let window = self.owner_window();
505        let (element, _) = window.offset_parent_query(node);
506
507        element
508    }
509
510    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsettop>
511    fn OffsetTop(&self) -> i32 {
512        if self.is_body_element() {
513            return 0;
514        }
515
516        let node = self.upcast::<Node>();
517        let window = self.owner_window();
518        let (_, rect) = window.offset_parent_query(node);
519
520        rect.origin.y.to_nearest_px()
521    }
522
523    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetleft>
524    fn OffsetLeft(&self) -> i32 {
525        if self.is_body_element() {
526            return 0;
527        }
528
529        let node = self.upcast::<Node>();
530        let window = self.owner_window();
531        let (_, rect) = window.offset_parent_query(node);
532
533        rect.origin.x.to_nearest_px()
534    }
535
536    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetwidth>
537    fn OffsetWidth(&self) -> i32 {
538        let node = self.upcast::<Node>();
539        let window = self.owner_window();
540        let (_, rect) = window.offset_parent_query(node);
541
542        rect.size.width.to_nearest_px()
543    }
544
545    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetheight>
546    fn OffsetHeight(&self) -> i32 {
547        let node = self.upcast::<Node>();
548        let window = self.owner_window();
549        let (_, rect) = window.offset_parent_query(node);
550
551        rect.size.height.to_nearest_px()
552    }
553
554    /// <https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute>
555    fn InnerText(&self) -> DOMString {
556        self.get_inner_outer_text()
557    }
558
559    /// <https://html.spec.whatwg.org/multipage/#set-the-inner-text-steps>
560    fn SetInnerText(&self, input: DOMString, can_gc: CanGc) {
561        self.set_inner_text(input, can_gc)
562    }
563
564    /// <https://html.spec.whatwg.org/multipage/#dom-outertext>
565    fn GetOuterText(&self) -> Fallible<DOMString> {
566        Ok(self.get_inner_outer_text())
567    }
568
569    /// <https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute:dom-outertext-2>
570    fn SetOuterText(&self, input: DOMString, can_gc: CanGc) -> Fallible<()> {
571        // Step 1: If this's parent is null, then throw a "NoModificationAllowedError" DOMException.
572        let Some(parent) = self.upcast::<Node>().GetParentNode() else {
573            return Err(Error::NoModificationAllowed(None));
574        };
575
576        let node = self.upcast::<Node>();
577        let document = self.owner_document();
578
579        // Step 2: Let next be this's next sibling.
580        let next = node.GetNextSibling();
581
582        // Step 3: Let previous be this's previous sibling.
583        let previous = node.GetPreviousSibling();
584
585        // Step 4: Let fragment be the rendered text fragment for the given value given this's node
586        // document.
587        let fragment = self.rendered_text_fragment(input, can_gc);
588
589        // Step 5: If fragment has no children, then append a new Text node whose data is the empty
590        // string and node document is this's node document to fragment.
591        if fragment.upcast::<Node>().children_count() == 0 {
592            let text_node = Text::new(DOMString::from("".to_owned()), &document, can_gc);
593
594            fragment
595                .upcast::<Node>()
596                .AppendChild(text_node.upcast(), can_gc)?;
597        }
598
599        // Step 6: Replace this with fragment within this's parent.
600        parent.ReplaceChild(fragment.upcast(), node, can_gc)?;
601
602        // Step 7: If next is non-null and next's previous sibling is a Text node, then merge with
603        // the next text node given next's previous sibling.
604        if let Some(next_sibling) = next {
605            if let Some(node) = next_sibling.GetPreviousSibling() {
606                Self::merge_with_the_next_text_node(node, can_gc);
607            }
608        }
609
610        // Step 8: If previous is a Text node, then merge with the next text node given previous.
611        if let Some(previous) = previous {
612            Self::merge_with_the_next_text_node(previous, can_gc)
613        }
614
615        Ok(())
616    }
617
618    /// <https://html.spec.whatwg.org/multipage/#dom-translate>
619    fn Translate(&self) -> bool {
620        self.as_element().is_translate_enabled()
621    }
622
623    /// <https://html.spec.whatwg.org/multipage/#dom-translate>
624    fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
625        self.as_element().set_string_attribute(
626            &html5ever::local_name!("translate"),
627            match yesno {
628                true => DOMString::from("yes"),
629                false => DOMString::from("no"),
630            },
631            can_gc,
632        );
633    }
634
635    // https://html.spec.whatwg.org/multipage/#dom-contenteditable
636    make_enumerated_getter!(
637        ContentEditable,
638        "contenteditable",
639        "true" | "false" | "plaintext-only",
640        missing => "inherit",
641        invalid => "inherit",
642        empty => "true"
643    );
644
645    /// <https://html.spec.whatwg.org/multipage/#dom-contenteditable>
646    fn SetContentEditable(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
647        let lower_value = value.to_ascii_lowercase();
648        let element = self.upcast::<Element>();
649        let attr_name = &local_name!("contenteditable");
650        match lower_value.as_ref() {
651            // > On setting, if the new value is an ASCII case-insensitive match for the string "inherit", then the content attribute must be removed,
652            "inherit" => {
653                element.remove_attribute_by_name(attr_name, can_gc);
654            },
655            // > if the new value is an ASCII case-insensitive match for the string "true", then the content attribute must be set to the string "true",
656            // > if the new value is an ASCII case-insensitive match for the string "plaintext-only", then the content attribute must be set to the string "plaintext-only",
657            // > if the new value is an ASCII case-insensitive match for the string "false", then the content attribute must be set to the string "false",
658            "true" | "false" | "plaintext-only" => {
659                element.set_attribute(attr_name, AttrValue::String(lower_value), can_gc);
660            },
661            // > and otherwise the attribute setter must throw a "SyntaxError" DOMException.
662            _ => return Err(Error::Syntax(None)),
663        };
664        Ok(())
665    }
666
667    /// <https://html.spec.whatwg.org/multipage/#dom-iscontenteditable>
668    fn IsContentEditable(&self) -> bool {
669        // > The isContentEditable IDL attribute, on getting, must return true if the element is either an editing host or editable, and false otherwise.
670        self.upcast::<Node>().is_editable_or_editing_host()
671    }
672
673    /// <https://html.spec.whatwg.org/multipage#dom-attachinternals>
674    fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
675        let element = self.as_element();
676        // Step 1: If this's is value is not null, then throw a "NotSupportedError" DOMException
677        if element.get_is().is_some() {
678            return Err(Error::NotSupported(None));
679        }
680
681        // Step 2: Let definition be the result of looking up a custom element definition
682        // Note: the element can pass this check without yet being a custom
683        // element, as long as there is a registered definition
684        // that could upgrade it to one later.
685        let registry = self.owner_window().CustomElements();
686        let definition = registry.lookup_definition(self.as_element().local_name(), None);
687
688        // Step 3: If definition is null, then throw an "NotSupportedError" DOMException
689        let definition = match definition {
690            Some(definition) => definition,
691            None => return Err(Error::NotSupported(None)),
692        };
693
694        // Step 4: If definition's disable internals is true, then throw a "NotSupportedError" DOMException
695        if definition.disable_internals {
696            return Err(Error::NotSupported(None));
697        }
698
699        // Step 5: If this's attached internals is non-null, then throw an "NotSupportedError" DOMException
700        let internals = element.ensure_element_internals(can_gc);
701        if internals.attached() {
702            return Err(Error::NotSupported(None));
703        }
704
705        // Step 6: If this's custom element state is not "precustomized" or "custom",
706        // then throw a "NotSupportedError" DOMException.
707        if !matches!(
708            element.get_custom_element_state(),
709            CustomElementState::Precustomized | CustomElementState::Custom
710        ) {
711            return Err(Error::NotSupported(None));
712        }
713
714        if self.is_form_associated_custom_element() {
715            element.init_state_for_internals();
716        }
717
718        // Step 6-7: Set this's attached internals to a new ElementInternals instance
719        internals.set_attached();
720        Ok(internals)
721    }
722
723    /// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
724    fn Nonce(&self) -> DOMString {
725        self.as_element().nonce_value().into()
726    }
727
728    /// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
729    fn SetNonce(&self, value: DOMString) {
730        self.as_element()
731            .update_nonce_internal_slot(value.to_string())
732    }
733
734    /// <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
735    fn Autofocus(&self) -> bool {
736        self.element.has_attribute(&local_name!("autofocus"))
737    }
738
739    /// <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
740    fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
741        self.element
742            .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
743    }
744
745    /// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
746    fn TabIndex(&self) -> i32 {
747        self.element.tab_index()
748    }
749
750    /// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
751    fn SetTabIndex(&self, tab_index: i32, can_gc: CanGc) {
752        self.element
753            .set_int_attribute(&local_name!("tabindex"), tab_index, can_gc);
754    }
755
756    // https://html.spec.whatwg.org/multipage/#dom-accesskey
757    make_getter!(AccessKey, "accesskey");
758
759    // https://html.spec.whatwg.org/multipage/#dom-accesskey
760    make_setter!(SetAccessKey, "accesskey");
761
762    /// <https://html.spec.whatwg.org/multipage/#dom-accesskeylabel>
763    fn AccessKeyLabel(&self) -> DOMString {
764        // The accessKeyLabel IDL attribute must return a string that represents the element's
765        // assigned access key, if any. If the element does not have one, then the IDL attribute
766        // must return the empty string.
767        if !self.element.has_attribute(&local_name!("accesskey")) {
768            return Default::default();
769        }
770
771        let access_key_string = self
772            .element
773            .get_string_attribute(&local_name!("accesskey"))
774            .to_string();
775
776        #[cfg(target_os = "macos")]
777        let access_key_label = format!("⌃⌥{access_key_string}");
778        #[cfg(not(target_os = "macos"))]
779        let access_key_label = format!("Alt+Shift+{access_key_string}");
780
781        access_key_label.into()
782    }
783}
784
785fn append_text_node_to_fragment(
786    document: &Document,
787    fragment: &DocumentFragment,
788    text: String,
789    can_gc: CanGc,
790) {
791    let text = Text::new(DOMString::from(text), document, can_gc);
792    fragment
793        .upcast::<Node>()
794        .AppendChild(text.upcast(), can_gc)
795        .unwrap();
796}
797
798impl HTMLElement {
799    /// <https://html.spec.whatwg.org/multipage/#category-label>
800    pub(crate) fn is_labelable_element(&self) -> bool {
801        match self.upcast::<Node>().type_id() {
802            NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
803                HTMLElementTypeId::HTMLInputElement => {
804                    self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
805                },
806                HTMLElementTypeId::HTMLButtonElement |
807                HTMLElementTypeId::HTMLMeterElement |
808                HTMLElementTypeId::HTMLOutputElement |
809                HTMLElementTypeId::HTMLProgressElement |
810                HTMLElementTypeId::HTMLSelectElement |
811                HTMLElementTypeId::HTMLTextAreaElement => true,
812                _ => self.is_form_associated_custom_element(),
813            },
814            _ => false,
815        }
816    }
817
818    /// <https://html.spec.whatwg.org/multipage/#form-associated-custom-element>
819    pub(crate) fn is_form_associated_custom_element(&self) -> bool {
820        if let Some(definition) = self.as_element().get_custom_element_definition() {
821            definition.is_autonomous() && definition.form_associated
822        } else {
823            false
824        }
825    }
826
827    /// <https://html.spec.whatwg.org/multipage/#category-listed>
828    pub(crate) fn is_listed_element(&self) -> bool {
829        match self.upcast::<Node>().type_id() {
830            NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
831                HTMLElementTypeId::HTMLButtonElement |
832                HTMLElementTypeId::HTMLFieldSetElement |
833                HTMLElementTypeId::HTMLInputElement |
834                HTMLElementTypeId::HTMLObjectElement |
835                HTMLElementTypeId::HTMLOutputElement |
836                HTMLElementTypeId::HTMLSelectElement |
837                HTMLElementTypeId::HTMLTextAreaElement => true,
838                _ => self.is_form_associated_custom_element(),
839            },
840            _ => false,
841        }
842    }
843
844    /// <https://html.spec.whatwg.org/multipage/#the-body-element-2>
845    pub(crate) fn is_body_element(&self) -> bool {
846        let self_node = self.upcast::<Node>();
847        self_node.GetParentNode().is_some_and(|parent| {
848            let parent_node = parent.upcast::<Node>();
849            (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
850                parent_node.is::<HTMLHtmlElement>() &&
851                self_node
852                    .preceding_siblings()
853                    .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
854        })
855    }
856
857    /// <https://html.spec.whatwg.org/multipage/#category-submit>
858    pub(crate) fn is_submittable_element(&self) -> bool {
859        match self.upcast::<Node>().type_id() {
860            NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
861                HTMLElementTypeId::HTMLButtonElement |
862                HTMLElementTypeId::HTMLInputElement |
863                HTMLElementTypeId::HTMLSelectElement |
864                HTMLElementTypeId::HTMLTextAreaElement => true,
865                _ => self.is_form_associated_custom_element(),
866            },
867            _ => false,
868        }
869    }
870
871    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
872    // This gets the nth label in tree order.
873    pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
874        let element = self.as_element();
875
876        // Traverse entire tree for <label> elements that have
877        // this as their control.
878        // There is room for performance optimization, as we don't need
879        // the actual result of GetControl, only whether the result
880        // would match self.
881        // (Even more room for performance optimization: do what
882        // nodelist ChildrenList does and keep a mutation-aware cursor
883        // around; this may be hard since labels need to keep working
884        // even as they get detached into a subtree and reattached to
885        // a document.)
886        let root_element = element.root_element();
887        let root_node = root_element.upcast::<Node>();
888        root_node
889            .traverse_preorder(ShadowIncluding::No)
890            .filter_map(DomRoot::downcast::<HTMLLabelElement>)
891            .filter(|elem| match elem.GetControl() {
892                Some(control) => &*control == self,
893                _ => false,
894            })
895            .nth(index as usize)
896            .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
897    }
898
899    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
900    // This counts the labels of the element, to support NodeList::Length
901    pub(crate) fn labels_count(&self) -> u32 {
902        // see label_at comments about performance
903        let element = self.as_element();
904        let root_element = element.root_element();
905        let root_node = root_element.upcast::<Node>();
906        root_node
907            .traverse_preorder(ShadowIncluding::No)
908            .filter_map(DomRoot::downcast::<HTMLLabelElement>)
909            .filter(|elem| match elem.GetControl() {
910                Some(control) => &*control == self,
911                _ => false,
912            })
913            .count() as u32
914    }
915
916    // https://html.spec.whatwg.org/multipage/#the-directionality.
917    // returns Some if can infer direction by itself or from child nodes
918    // returns None if requires to go up to parent
919    pub(crate) fn directionality(&self) -> Option<String> {
920        let element_direction = &self.Dir();
921
922        if element_direction == "ltr" {
923            return Some("ltr".to_owned());
924        }
925
926        if element_direction == "rtl" {
927            return Some("rtl".to_owned());
928        }
929
930        if let Some(input) = self.downcast::<HTMLInputElement>() {
931            if input.input_type() == InputType::Tel {
932                return Some("ltr".to_owned());
933            }
934        }
935
936        if element_direction == "auto" {
937            if let Some(directionality) = self
938                .downcast::<HTMLInputElement>()
939                .and_then(|input| input.auto_directionality())
940            {
941                return Some(directionality);
942            }
943
944            if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
945                return Some(area.auto_directionality());
946            }
947        }
948
949        // TODO(NeverHappened): Implement condition
950        // If the element's dir attribute is in the auto state OR
951        // If the element is a bdi element and the dir attribute is not in a defined state
952        // (i.e. it is not present or has an invalid value)
953        // Requires bdi element implementation (https://html.spec.whatwg.org/multipage/#the-bdi-element)
954
955        None
956    }
957
958    // https://html.spec.whatwg.org/multipage/#the-summary-element:activation-behaviour
959    pub(crate) fn summary_activation_behavior(&self) {
960        debug_assert!(self.as_element().local_name() == &local_name!("summary"));
961
962        // Step 1. If this summary element is not the summary for its parent details, then return.
963        if !self.is_a_summary_for_its_parent_details() {
964            return;
965        }
966
967        // Step 2. Let parent be this summary element's parent.
968        let parent = if self.is_implicit_summary_element() {
969            DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
970                .unwrap()
971        } else {
972            self.upcast::<Node>()
973                .GetParentNode()
974                .and_then(DomRoot::downcast::<HTMLDetailsElement>)
975                .unwrap()
976        };
977
978        // Step 3. If the open attribute is present on parent, then remove it.
979        // Otherwise, set parent's open attribute to the empty string.
980        parent.toggle();
981    }
982
983    /// <https://html.spec.whatwg.org/multipage/#summary-for-its-parent-details>
984    pub(crate) fn is_a_summary_for_its_parent_details(&self) -> bool {
985        if self.is_implicit_summary_element() {
986            return true;
987        }
988
989        // Step 1. If this summary element has no parent, then return false.
990        // Step 2. Let parent be this summary element's parent.
991        let Some(parent) = self.upcast::<Node>().GetParentNode() else {
992            return false;
993        };
994
995        // Step 3. If parent is not a details element, then return false.
996        let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
997            return false;
998        };
999
1000        // Step 4. If parent's first summary element child is not this summary
1001        // element, then return false.
1002        // Step 5. Return true.
1003        details
1004            .find_corresponding_summary_element()
1005            .is_some_and(|summary| &*summary == self.upcast())
1006    }
1007
1008    /// Whether or not this is an implicitly generated `<summary>`
1009    /// element for a UA `<details>` shadow tree
1010    fn is_implicit_summary_element(&self) -> bool {
1011        // Note that non-implicit summary elements are not actually inside
1012        // the UA shadow tree, they're only assigned to a slot inside it.
1013        // Therefore they don't cause false positives here
1014        self.containing_shadow_root()
1015            .as_deref()
1016            .map(ShadowRoot::Host)
1017            .is_some_and(|host| host.is::<HTMLDetailsElement>())
1018    }
1019
1020    /// <https://html.spec.whatwg.org/multipage/#rendered-text-fragment>
1021    fn rendered_text_fragment(&self, input: DOMString, can_gc: CanGc) -> DomRoot<DocumentFragment> {
1022        // Step 1: Let fragment be a new DocumentFragment whose node document is document.
1023        let document = self.owner_document();
1024        let fragment = DocumentFragment::new(&document, can_gc);
1025
1026        // Step 2: Let position be a position variable for input, initially pointing at the start
1027        // of input.
1028        let input = input.str();
1029        let mut position = input.chars().peekable();
1030
1031        // Step 3: Let text be the empty string.
1032        let mut text = String::new();
1033
1034        // Step 4
1035        while let Some(ch) = position.next() {
1036            match ch {
1037                // While position is not past the end of input, and the code point at position is
1038                // either U+000A LF or U+000D CR:
1039                '\u{000A}' | '\u{000D}' => {
1040                    if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1041                        // a \r\n pair should only generate one <br>,
1042                        // so just skip the \r.
1043                        position.next();
1044                    }
1045
1046                    if !text.is_empty() {
1047                        append_text_node_to_fragment(&document, &fragment, text, can_gc);
1048                        text = String::new();
1049                    }
1050
1051                    let br = Element::create(
1052                        QualName::new(None, ns!(html), local_name!("br")),
1053                        None,
1054                        &document,
1055                        ElementCreator::ScriptCreated,
1056                        CustomElementCreationMode::Asynchronous,
1057                        None,
1058                        can_gc,
1059                    );
1060                    fragment
1061                        .upcast::<Node>()
1062                        .AppendChild(br.upcast(), can_gc)
1063                        .unwrap();
1064                },
1065                _ => {
1066                    // Collect a sequence of code points that are not U+000A LF or U+000D CR from
1067                    // input given position, and set text to the result.
1068                    text.push(ch);
1069                },
1070            }
1071        }
1072
1073        // If text is not the empty string, then append a new Text node whose data is text and node
1074        // document is document to fragment.
1075        if !text.is_empty() {
1076            append_text_node_to_fragment(&document, &fragment, text, can_gc);
1077        }
1078
1079        fragment
1080    }
1081
1082    /// Checks whether a given [`DomRoot<Node>`] and its next sibling are
1083    /// of type [`Text`], and if so merges them into a single [`Text`]
1084    /// node.
1085    ///
1086    /// <https://html.spec.whatwg.org/multipage/#merge-with-the-next-text-node>
1087    fn merge_with_the_next_text_node(node: DomRoot<Node>, can_gc: CanGc) {
1088        // Make sure node is a Text node
1089        if !node.is::<Text>() {
1090            return;
1091        }
1092
1093        // Step 1: Let next be node's next sibling.
1094        let next = match node.GetNextSibling() {
1095            Some(next) => next,
1096            None => return,
1097        };
1098
1099        // Step 2: If next is not a Text node, then return.
1100        if !next.is::<Text>() {
1101            return;
1102        }
1103        // Step 3: Replace data with node, node's data's length, 0, and next's data.
1104        let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1105        let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1106        node_chars
1107            .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1108            .expect("Got chars from Text");
1109
1110        // Step 4:Remove next.
1111        next.remove_self(can_gc);
1112    }
1113
1114    /// <https://html.spec.whatwg.org/multipage/#keyboard-shortcuts-processing-model>
1115    /// > Whenever an element's accesskey attribute is set, changed, or removed, the user agent must
1116    /// > update the element's assigned access key by running the following steps:
1117    fn update_assigned_access_key(&self) {
1118        // 1. If the element has no accesskey attribute, then skip to the fallback step below.
1119        if !self.element.has_attribute(&local_name!("accesskey")) {
1120            // This is the same as steps 4 and 5 below.
1121            self.owner_document()
1122                .event_handler()
1123                .unassign_access_key(self);
1124        }
1125
1126        // 2. Otherwise, split the attribute's value on ASCII whitespace, and let keys be the resulting tokens.
1127        let attribute_value = self.element.get_string_attribute(&local_name!("accesskey"));
1128        let string_view = attribute_value.str();
1129        let values = string_view.split_html_space_characters();
1130
1131        // 3. For each value in keys in turn, in the order the tokens appeared in the attribute's
1132        //    value, run the following substeps:
1133        for value in values {
1134            // 1. If the value is not a string exactly one code point in length, then skip the
1135            //    remainder of these steps for this value.
1136            let mut characters = value.chars();
1137            let Some(character) = characters.next() else {
1138                continue;
1139            };
1140            if characters.count() > 0 {
1141                continue;
1142            }
1143
1144            // 2. If the value does not correspond to a key on the system's keyboard, then skip the
1145            //    remainder of these steps for this value.
1146            //    TODO: This is just a heuristic for whether or not the character is on the keyboard.
1147            //    We should do better here, but as we don't know anything about the keyboard hardware,
1148            //    it's quite difficult at the moment.
1149            if !character.is_ascii_graphic() {
1150                continue;
1151            }
1152
1153            // 3. If the user agent can find a mix of zero or more modifier keys that, combined with
1154            //    the key that corresponds to the value given in the attribute, can be used as the
1155            //    access key, then the user agent may assign that combination of keys as the element's
1156            //    assigned access key and return.
1157            self.owner_document()
1158                .event_handler()
1159                .assign_access_key(self, character);
1160            return;
1161        }
1162
1163        // 4. Fallback: Optionally, the user agent may assign a key combination of its choosing as
1164        //    the element's assigned access key and then return.
1165        // We do not do this.
1166
1167        // 5. If this step is reached, the element has no assigned access key.
1168        self.owner_document()
1169            .event_handler()
1170            .unassign_access_key(self);
1171    }
1172}
1173
1174impl VirtualMethods for HTMLElement {
1175    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1176        Some(self.as_element() as &dyn VirtualMethods)
1177    }
1178
1179    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1180        self.super_type()
1181            .unwrap()
1182            .attribute_mutated(attr, mutation, can_gc);
1183        let element = self.as_element();
1184        match (attr.local_name(), mutation) {
1185            // https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3
1186            (name, mutation)
1187                if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1188            {
1189                let evtarget = self.upcast::<EventTarget>();
1190                let event_name = &name[2..];
1191                match mutation {
1192                    // https://html.spec.whatwg.org/multipage/#activate-an-event-handler
1193                    AttributeMutation::Set(..) => {
1194                        let source = &**attr.value();
1195                        let source_line = 1; // TODO(#9604) get current JS execution line
1196                        evtarget.set_event_handler_uncompiled(
1197                            self.owner_window().get_url(),
1198                            source_line,
1199                            event_name,
1200                            source,
1201                        );
1202                    },
1203                    // https://html.spec.whatwg.org/multipage/#deactivate-an-event-handler
1204                    AttributeMutation::Removed => {
1205                        evtarget.set_event_handler_common::<EventHandlerNonNull>(event_name, None);
1206                    },
1207                }
1208            },
1209
1210            (&local_name!("accesskey"), ..) => {
1211                self.update_assigned_access_key();
1212            },
1213            (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1214                self.form_attribute_mutated(mutation, can_gc);
1215            },
1216            // Adding a "disabled" attribute disables an enabled form element.
1217            (&local_name!("disabled"), AttributeMutation::Set(..))
1218                if self.is_form_associated_custom_element() && element.enabled_state() =>
1219            {
1220                element.set_disabled_state(true);
1221                element.set_enabled_state(false);
1222                ScriptThread::enqueue_callback_reaction(
1223                    element,
1224                    CallbackReaction::FormDisabled(true),
1225                    None,
1226                );
1227            },
1228            // Removing the "disabled" attribute may enable a disabled
1229            // form element, but a fieldset ancestor may keep it disabled.
1230            (&local_name!("disabled"), AttributeMutation::Removed)
1231                if self.is_form_associated_custom_element() && element.disabled_state() =>
1232            {
1233                element.set_disabled_state(false);
1234                element.set_enabled_state(true);
1235                element.check_ancestors_disabled_state_for_form_control();
1236                if element.enabled_state() {
1237                    ScriptThread::enqueue_callback_reaction(
1238                        element,
1239                        CallbackReaction::FormDisabled(false),
1240                        None,
1241                    );
1242                }
1243            },
1244            (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1245                match mutation {
1246                    AttributeMutation::Set(..) => {
1247                        element.set_read_write_state(true);
1248                    },
1249                    AttributeMutation::Removed => {
1250                        element.set_read_write_state(false);
1251                    },
1252                }
1253            },
1254            (&local_name!("nonce"), mutation) => match mutation {
1255                AttributeMutation::Set(..) => {
1256                    let nonce = &**attr.value();
1257                    element.update_nonce_internal_slot(nonce.to_owned());
1258                },
1259                AttributeMutation::Removed => {
1260                    element.update_nonce_internal_slot("".to_owned());
1261                },
1262            },
1263            _ => {},
1264        }
1265    }
1266
1267    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1268        if let Some(super_type) = self.super_type() {
1269            super_type.bind_to_tree(context, can_gc);
1270        }
1271
1272        // Binding to a tree can disable a form control if one of the new
1273        // ancestors is a fieldset.
1274        let element = self.as_element();
1275        if self.is_form_associated_custom_element() && element.enabled_state() {
1276            element.check_ancestors_disabled_state_for_form_control();
1277            if element.disabled_state() {
1278                ScriptThread::enqueue_callback_reaction(
1279                    element,
1280                    CallbackReaction::FormDisabled(true),
1281                    None,
1282                );
1283            }
1284        }
1285
1286        if element.has_attribute(&local_name!("accesskey")) {
1287            self.update_assigned_access_key();
1288        }
1289    }
1290
1291    /// <https://html.spec.whatwg.org/multipage#dom-trees:concept-node-remove-ext>
1292    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1293        // 1. Let document be removedNode's node document.
1294        let document = self.owner_document();
1295
1296        // 2. If document's focused area is removedNode, then set document's focused area to
1297        // document's viewport, and set document's relevant global object's navigation API's focus
1298        // changed during ongoing navigation to false.
1299        //
1300        // TODO: Should this also happen for non-HTML elements such as SVG elements?
1301        let element = self.as_element();
1302        if document
1303            .get_focused_element()
1304            .is_some_and(|focused_element| &*focused_element == element)
1305        {
1306            document.request_focus(None, FocusInitiator::Script, can_gc);
1307        }
1308
1309        // 3. If removedNode is an element whose namespace is the HTML namespace, and this standard
1310        // defines HTML element removing steps for removedNode's local name, then run the
1311        // corresponding HTML element removing steps given removedNode, isSubtreeRoot, and
1312        // oldAncestor.
1313        if let Some(super_type) = self.super_type() {
1314            super_type.unbind_from_tree(context, can_gc);
1315        }
1316
1317        // 4. If removedNode is a form-associated element with a non-null form owner and removedNode
1318        // and its form owner are no longer in the same tree, then reset the form owner of
1319        // removedNode.
1320        //
1321        // Unbinding from a tree might enable a form control, if a
1322        // fieldset ancestor is the only reason it was disabled.
1323        // (The fact that it's enabled doesn't do much while it's
1324        // disconnected, but it is an observable fact to keep track of.)
1325        //
1326        // TODO: This should likely just call reset on form owner.
1327        if self.is_form_associated_custom_element() && element.disabled_state() {
1328            element.check_disabled_attribute();
1329            element.check_ancestors_disabled_state_for_form_control();
1330            if element.enabled_state() {
1331                ScriptThread::enqueue_callback_reaction(
1332                    element,
1333                    CallbackReaction::FormDisabled(false),
1334                    None,
1335                );
1336            }
1337        }
1338
1339        if element.has_attribute(&local_name!("accesskey")) {
1340            self.owner_document()
1341                .event_handler()
1342                .unassign_access_key(self);
1343        }
1344    }
1345
1346    fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1347        let element = self.upcast::<Element>();
1348        if is_element_affected_by_legacy_background_presentational_hint(
1349            element.namespace(),
1350            element.local_name(),
1351        ) && attr.local_name() == &local_name!("background")
1352        {
1353            return true;
1354        }
1355
1356        self.super_type()
1357            .unwrap()
1358            .attribute_affects_presentational_hints(attr)
1359    }
1360
1361    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1362        let element = self.upcast::<Element>();
1363        match *name {
1364            local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1365            local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1366            local_name!("background")
1367                if is_element_affected_by_legacy_background_presentational_hint(
1368                    element.namespace(),
1369                    element.local_name(),
1370                ) =>
1371            {
1372                AttrValue::from_resolved_url(
1373                    &self.owner_document().base_url().get_arc(),
1374                    value.into(),
1375                )
1376            },
1377            _ => self
1378                .super_type()
1379                .unwrap()
1380                .parse_plain_attribute(name, value),
1381        }
1382    }
1383
1384    /// <https://html.spec.whatwg.org/multipage/#dom-trees:html-element-moving-steps>
1385    fn moving_steps(&self, context: &MoveContext, can_gc: CanGc) {
1386        // Step 1. If movedNode is an element whose namespace is the HTML namespace, and this
1387        // standard defines HTML element moving steps for movedNode's local name, then run the
1388        // corresponding HTML element moving steps given movedNode.
1389        if let Some(super_type) = self.super_type() {
1390            super_type.moving_steps(context, can_gc);
1391        }
1392
1393        // Step 2. If movedNode is a form-associated element with a non-null form owner and
1394        // movedNode and its form owner are no longer in the same tree, then reset the form owner of
1395        // movedNode.
1396        if let Some(form_control) = self.upcast::<Element>().as_maybe_form_control() {
1397            form_control.moving_steps(can_gc)
1398        }
1399    }
1400}
1401
1402impl Activatable for HTMLElement {
1403    fn as_element(&self) -> &Element {
1404        self.upcast::<Element>()
1405    }
1406
1407    fn is_instance_activatable(&self) -> bool {
1408        self.as_element().local_name() == &local_name!("summary")
1409    }
1410
1411    // Basically used to make the HTMLSummaryElement activatable (which has no IDL definition)
1412    fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
1413        self.summary_activation_behavior();
1414    }
1415}
1416
1417// Form-associated custom elements are the same interface type as
1418// normal HTMLElements, so HTMLElement needs to have the FormControl trait
1419// even though it's usually more specific trait implementations, like the
1420// HTMLInputElement one, that we really want. (Alternately we could put
1421// the FormControl trait on ElementInternals, but that raises lifetime issues.)
1422impl FormControl for HTMLElement {
1423    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1424        debug_assert!(self.is_form_associated_custom_element());
1425        self.as_element()
1426            .get_element_internals()
1427            .and_then(|e| e.form_owner())
1428    }
1429
1430    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1431        debug_assert!(self.is_form_associated_custom_element());
1432        self.as_element()
1433            .ensure_element_internals(CanGc::note())
1434            .set_form_owner(form);
1435    }
1436
1437    fn to_element(&self) -> &Element {
1438        self.as_element()
1439    }
1440
1441    fn is_listed(&self) -> bool {
1442        debug_assert!(self.is_form_associated_custom_element());
1443        true
1444    }
1445
1446    // TODO satisfies_constraints traits
1447}