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