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