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