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, ScrollContainerQueryType, 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        let mut item_attr_values = HashSet::new();
381        for attr_value in &atoms {
382            item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
383        }
384
385        Some(item_attr_values.into_iter().collect())
386    }
387
388    // https://html.spec.whatwg.org/multipage/#names:-the-itemprop-attribute
389    fn PropertyNames(&self) -> Option<Vec<DOMString>> {
390        let atoms = self
391            .element
392            .get_tokenlist_attribute(&local_name!("itemprop"));
393
394        if atoms.is_empty() {
395            return None;
396        }
397
398        let mut item_attr_values = HashSet::new();
399        for attr_value in &atoms {
400            item_attr_values.insert(DOMString::from(String::from(attr_value.trim())));
401        }
402
403        Some(item_attr_values.into_iter().collect())
404    }
405
406    /// <https://html.spec.whatwg.org/multipage/#dom-click>
407    fn Click(&self, can_gc: CanGc) {
408        let element = self.as_element();
409        if element.disabled_state() {
410            return;
411        }
412        if element.click_in_progress() {
413            return;
414        }
415        element.set_click_in_progress(true);
416
417        self.upcast::<Node>()
418            .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
419        element.set_click_in_progress(false);
420    }
421
422    /// <https://html.spec.whatwg.org/multipage/#dom-focus>
423    fn Focus(&self, options: &FocusOptions, can_gc: CanGc) {
424        // TODO: Mark the element as locked for focus and run the focusing steps.
425        // <https://html.spec.whatwg.org/multipage/#focusing-steps>
426        let document = self.owner_document();
427        document.request_focus_with_options(
428            Some(self.upcast()),
429            FocusInitiator::Local,
430            FocusOptions {
431                preventScroll: options.preventScroll,
432            },
433            can_gc,
434        );
435    }
436
437    // https://html.spec.whatwg.org/multipage/#dom-blur
438    fn Blur(&self, can_gc: CanGc) {
439        // TODO: Run the unfocusing steps. Focus the top-level document, not
440        //       the current document.
441        if !self.as_element().focus_state() {
442            return;
443        }
444        // https://html.spec.whatwg.org/multipage/#unfocusing-steps
445        let document = self.owner_document();
446        document.request_focus(None, FocusInitiator::Local, can_gc);
447    }
448
449    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>
450    #[allow(unsafe_code)]
451    fn GetScrollParent(&self) -> Option<DomRoot<Element>> {
452        self.owner_window()
453            .scroll_container_query(self.upcast(), ScrollContainerQueryType::ForScrollParent)
454            .and_then(|response| match response {
455                ScrollContainerResponse::Viewport => self.owner_document().GetScrollingElement(),
456                ScrollContainerResponse::Element(parent_node_address) => {
457                    let node = unsafe { from_untrusted_node_address(parent_node_address) };
458                    DomRoot::downcast(node)
459                },
460            })
461    }
462
463    /// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent>
464    fn GetOffsetParent(&self) -> Option<DomRoot<Element>> {
465        if self.is::<HTMLBodyElement>() || self.upcast::<Element>().is_root() {
466            return None;
467        }
468
469        let node = self.upcast::<Node>();
470        let window = self.owner_window();
471        let (element, _) = window.offset_parent_query(node);
472
473        element
474    }
475
476    // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsettop
477    fn OffsetTop(&self) -> i32 {
478        if self.is_body_element() {
479            return 0;
480        }
481
482        let node = self.upcast::<Node>();
483        let window = self.owner_window();
484        let (_, rect) = window.offset_parent_query(node);
485
486        rect.origin.y.to_nearest_px()
487    }
488
489    // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetleft
490    fn OffsetLeft(&self) -> i32 {
491        if self.is_body_element() {
492            return 0;
493        }
494
495        let node = self.upcast::<Node>();
496        let window = self.owner_window();
497        let (_, rect) = window.offset_parent_query(node);
498
499        rect.origin.x.to_nearest_px()
500    }
501
502    // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetwidth
503    fn OffsetWidth(&self) -> i32 {
504        let node = self.upcast::<Node>();
505        let window = self.owner_window();
506        let (_, rect) = window.offset_parent_query(node);
507
508        rect.size.width.to_nearest_px()
509    }
510
511    // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetheight
512    fn OffsetHeight(&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.height.to_nearest_px()
518    }
519
520    /// <https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute>
521    fn InnerText(&self) -> DOMString {
522        self.get_inner_outer_text()
523    }
524
525    /// <https://html.spec.whatwg.org/multipage/#set-the-inner-text-steps>
526    fn SetInnerText(&self, input: DOMString, can_gc: CanGc) {
527        self.set_inner_text(input, can_gc)
528    }
529
530    /// <https://html.spec.whatwg.org/multipage/#dom-outertext>
531    fn GetOuterText(&self) -> Fallible<DOMString> {
532        Ok(self.get_inner_outer_text())
533    }
534
535    /// <https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute:dom-outertext-2>
536    fn SetOuterText(&self, input: DOMString, can_gc: CanGc) -> Fallible<()> {
537        // Step 1: If this's parent is null, then throw a "NoModificationAllowedError" DOMException.
538        let Some(parent) = self.upcast::<Node>().GetParentNode() else {
539            return Err(Error::NoModificationAllowed);
540        };
541
542        let node = self.upcast::<Node>();
543        let document = self.owner_document();
544
545        // Step 2: Let next be this's next sibling.
546        let next = node.GetNextSibling();
547
548        // Step 3: Let previous be this's previous sibling.
549        let previous = node.GetPreviousSibling();
550
551        // Step 4: Let fragment be the rendered text fragment for the given value given this's node
552        // document.
553        let fragment = self.rendered_text_fragment(input, can_gc);
554
555        // Step 5: If fragment has no children, then append a new Text node whose data is the empty
556        // string and node document is this's node document to fragment.
557        if fragment.upcast::<Node>().children_count() == 0 {
558            let text_node = Text::new(DOMString::from("".to_owned()), &document, can_gc);
559
560            fragment
561                .upcast::<Node>()
562                .AppendChild(text_node.upcast(), can_gc)?;
563        }
564
565        // Step 6: Replace this with fragment within this's parent.
566        parent.ReplaceChild(fragment.upcast(), node, can_gc)?;
567
568        // Step 7: If next is non-null and next's previous sibling is a Text node, then merge with
569        // the next text node given next's previous sibling.
570        if let Some(next_sibling) = next {
571            if let Some(node) = next_sibling.GetPreviousSibling() {
572                Self::merge_with_the_next_text_node(node, can_gc);
573            }
574        }
575
576        // Step 8: If previous is a Text node, then merge with the next text node given previous.
577        if let Some(previous) = previous {
578            Self::merge_with_the_next_text_node(previous, can_gc)
579        }
580
581        Ok(())
582    }
583
584    // https://html.spec.whatwg.org/multipage/#dom-translate
585    fn Translate(&self) -> bool {
586        self.as_element().is_translate_enabled()
587    }
588
589    // https://html.spec.whatwg.org/multipage/#dom-translate
590    fn SetTranslate(&self, yesno: bool, can_gc: CanGc) {
591        self.as_element().set_string_attribute(
592            &html5ever::local_name!("translate"),
593            match yesno {
594                true => DOMString::from("yes"),
595                false => DOMString::from("no"),
596            },
597            can_gc,
598        );
599    }
600
601    // https://html.spec.whatwg.org/multipage/#dom-contenteditable
602    fn ContentEditable(&self) -> DOMString {
603        // TODO: https://github.com/servo/servo/issues/12776
604        self.as_element()
605            .get_attribute(&ns!(), &local_name!("contenteditable"))
606            .map(|attr| DOMString::from(&**attr.value()))
607            .unwrap_or_else(|| DOMString::from("inherit"))
608    }
609
610    // https://html.spec.whatwg.org/multipage/#dom-contenteditable
611    fn SetContentEditable(&self, _: DOMString) {
612        // TODO: https://github.com/servo/servo/issues/12776
613        warn!("The contentEditable attribute is not implemented yet");
614    }
615
616    // https://html.spec.whatwg.org/multipage/#dom-contenteditable
617    fn IsContentEditable(&self) -> bool {
618        // TODO: https://github.com/servo/servo/issues/12776
619        false
620    }
621
622    /// <https://html.spec.whatwg.org/multipage#dom-attachinternals>
623    fn AttachInternals(&self, can_gc: CanGc) -> Fallible<DomRoot<ElementInternals>> {
624        let element = self.as_element();
625        // Step 1: If this's is value is not null, then throw a "NotSupportedError" DOMException
626        if element.get_is().is_some() {
627            return Err(Error::NotSupported);
628        }
629
630        // Step 2: Let definition be the result of looking up a custom element definition
631        // Note: the element can pass this check without yet being a custom
632        // element, as long as there is a registered definition
633        // that could upgrade it to one later.
634        let registry = self.owner_window().CustomElements();
635        let definition = registry.lookup_definition(self.as_element().local_name(), None);
636
637        // Step 3: If definition is null, then throw an "NotSupportedError" DOMException
638        let definition = match definition {
639            Some(definition) => definition,
640            None => return Err(Error::NotSupported),
641        };
642
643        // Step 4: If definition's disable internals is true, then throw a "NotSupportedError" DOMException
644        if definition.disable_internals {
645            return Err(Error::NotSupported);
646        }
647
648        // Step 5: If this's attached internals is non-null, then throw an "NotSupportedError" DOMException
649        let internals = element.ensure_element_internals(can_gc);
650        if internals.attached() {
651            return Err(Error::NotSupported);
652        }
653
654        // Step 6: If this's custom element state is not "precustomized" or "custom",
655        // then throw a "NotSupportedError" DOMException.
656        if !matches!(
657            element.get_custom_element_state(),
658            CustomElementState::Precustomized | CustomElementState::Custom
659        ) {
660            return Err(Error::NotSupported);
661        }
662
663        if self.is_form_associated_custom_element() {
664            element.init_state_for_internals();
665        }
666
667        // Step 6-7: Set this's attached internals to a new ElementInternals instance
668        internals.set_attached();
669        Ok(internals)
670    }
671
672    // https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce
673    fn Nonce(&self) -> DOMString {
674        self.as_element().nonce_value().into()
675    }
676
677    // https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce
678    fn SetNonce(&self, value: DOMString) {
679        self.as_element()
680            .update_nonce_internal_slot(value.to_string())
681    }
682
683    // https://html.spec.whatwg.org/multipage/#dom-fe-autofocus
684    fn Autofocus(&self) -> bool {
685        self.element.has_attribute(&local_name!("autofocus"))
686    }
687
688    // https://html.spec.whatwg.org/multipage/#dom-fe-autofocus
689    fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
690        self.element
691            .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
692    }
693}
694
695fn append_text_node_to_fragment(
696    document: &Document,
697    fragment: &DocumentFragment,
698    text: String,
699    can_gc: CanGc,
700) {
701    let text = Text::new(DOMString::from(text), document, can_gc);
702    fragment
703        .upcast::<Node>()
704        .AppendChild(text.upcast(), can_gc)
705        .unwrap();
706}
707
708// https://html.spec.whatwg.org/multipage/#attr-data-*
709
710static DATA_PREFIX: &str = "data-";
711static DATA_HYPHEN_SEPARATOR: char = '\x2d';
712
713fn to_snake_case(name: DOMString) -> DOMString {
714    let mut attr_name = String::with_capacity(name.len() + DATA_PREFIX.len());
715    attr_name.push_str(DATA_PREFIX);
716    for ch in name.chars() {
717        if ch.is_ascii_uppercase() {
718            attr_name.push(DATA_HYPHEN_SEPARATOR);
719            attr_name.push(ch.to_ascii_lowercase());
720        } else {
721            attr_name.push(ch);
722        }
723    }
724    DOMString::from(attr_name)
725}
726
727// https://html.spec.whatwg.org/multipage/#attr-data-*
728// if this attribute is in snake case with a data- prefix,
729// this function returns a name converted to camel case
730// without the data prefix.
731
732fn to_camel_case(name: &str) -> Option<DOMString> {
733    if !name.starts_with(DATA_PREFIX) {
734        return None;
735    }
736    let name = &name[5..];
737    let has_uppercase = name.chars().any(|curr_char| curr_char.is_ascii_uppercase());
738    if has_uppercase {
739        return None;
740    }
741    let mut result = String::with_capacity(name.len().saturating_sub(DATA_PREFIX.len()));
742    let mut name_chars = name.chars();
743    while let Some(curr_char) = name_chars.next() {
744        // check for hyphen followed by character
745        if curr_char == DATA_HYPHEN_SEPARATOR {
746            if let Some(next_char) = name_chars.next() {
747                if next_char.is_ascii_lowercase() {
748                    result.push(next_char.to_ascii_uppercase());
749                } else {
750                    result.push(curr_char);
751                    result.push(next_char);
752                }
753            } else {
754                result.push(curr_char);
755            }
756        } else {
757            result.push(curr_char);
758        }
759    }
760    Some(DOMString::from(result))
761}
762
763impl HTMLElement {
764    pub(crate) fn set_custom_attr(
765        &self,
766        name: DOMString,
767        value: DOMString,
768        can_gc: CanGc,
769    ) -> ErrorResult {
770        if name
771            .chars()
772            .skip_while(|&ch| ch != '\u{2d}')
773            .nth(1)
774            .is_some_and(|ch| ch.is_ascii_lowercase())
775        {
776            return Err(Error::Syntax(None));
777        }
778        self.as_element()
779            .set_custom_attribute(to_snake_case(name), value, can_gc)
780    }
781
782    pub(crate) fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> {
783        // FIXME(ajeffrey): Convert directly from DOMString to LocalName
784        let local_name = LocalName::from(to_snake_case(local_name));
785        self.as_element()
786            .get_attribute(&ns!(), &local_name)
787            .map(|attr| {
788                DOMString::from(&**attr.value()) // FIXME(ajeffrey): Convert directly from AttrValue to DOMString
789            })
790    }
791
792    pub(crate) fn delete_custom_attr(&self, local_name: DOMString, can_gc: CanGc) {
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            .remove_attribute(&ns!(), &local_name, can_gc);
797    }
798
799    /// <https://html.spec.whatwg.org/multipage/#category-label>
800    pub(crate) fn is_labelable_element(&self) -> bool {
801        match self.upcast::<Node>().type_id() {
802            NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
803                HTMLElementTypeId::HTMLInputElement => {
804                    self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden
805                },
806                HTMLElementTypeId::HTMLButtonElement |
807                HTMLElementTypeId::HTMLMeterElement |
808                HTMLElementTypeId::HTMLOutputElement |
809                HTMLElementTypeId::HTMLProgressElement |
810                HTMLElementTypeId::HTMLSelectElement |
811                HTMLElementTypeId::HTMLTextAreaElement => true,
812                _ => self.is_form_associated_custom_element(),
813            },
814            _ => false,
815        }
816    }
817
818    /// <https://html.spec.whatwg.org/multipage/#form-associated-custom-element>
819    pub(crate) fn is_form_associated_custom_element(&self) -> bool {
820        if let Some(definition) = self.as_element().get_custom_element_definition() {
821            definition.is_autonomous() && definition.form_associated
822        } else {
823            false
824        }
825    }
826
827    /// <https://html.spec.whatwg.org/multipage/#category-listed>
828    pub(crate) fn is_listed_element(&self) -> bool {
829        match self.upcast::<Node>().type_id() {
830            NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
831                HTMLElementTypeId::HTMLButtonElement |
832                HTMLElementTypeId::HTMLFieldSetElement |
833                HTMLElementTypeId::HTMLInputElement |
834                HTMLElementTypeId::HTMLObjectElement |
835                HTMLElementTypeId::HTMLOutputElement |
836                HTMLElementTypeId::HTMLSelectElement |
837                HTMLElementTypeId::HTMLTextAreaElement => true,
838                _ => self.is_form_associated_custom_element(),
839            },
840            _ => false,
841        }
842    }
843
844    /// <https://html.spec.whatwg.org/multipage/#the-body-element-2>
845    pub(crate) fn is_body_element(&self) -> bool {
846        let self_node = self.upcast::<Node>();
847        self_node.GetParentNode().is_some_and(|parent| {
848            let parent_node = parent.upcast::<Node>();
849            (self_node.is::<HTMLBodyElement>() || self_node.is::<HTMLFrameSetElement>()) &&
850                parent_node.is::<HTMLHtmlElement>() &&
851                self_node
852                    .preceding_siblings()
853                    .all(|n| !n.is::<HTMLBodyElement>() && !n.is::<HTMLFrameSetElement>())
854        })
855    }
856
857    /// <https://html.spec.whatwg.org/multipage/#category-submit>
858    pub(crate) fn is_submittable_element(&self) -> bool {
859        match self.upcast::<Node>().type_id() {
860            NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
861                HTMLElementTypeId::HTMLButtonElement |
862                HTMLElementTypeId::HTMLInputElement |
863                HTMLElementTypeId::HTMLSelectElement |
864                HTMLElementTypeId::HTMLTextAreaElement => true,
865                _ => self.is_form_associated_custom_element(),
866            },
867            _ => false,
868        }
869    }
870
871    pub(crate) fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> {
872        let element = self.as_element();
873        element
874            .attrs()
875            .iter()
876            .filter_map(|attr| {
877                let raw_name = attr.local_name();
878                to_camel_case(raw_name)
879            })
880            .collect()
881    }
882
883    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
884    // This gets the nth label in tree order.
885    pub(crate) fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
886        let element = self.as_element();
887
888        // Traverse entire tree for <label> elements that have
889        // this as their control.
890        // There is room for performance optimization, as we don't need
891        // the actual result of GetControl, only whether the result
892        // would match self.
893        // (Even more room for performance optimization: do what
894        // nodelist ChildrenList does and keep a mutation-aware cursor
895        // around; this may be hard since labels need to keep working
896        // even as they get detached into a subtree and reattached to
897        // a document.)
898        let root_element = element.root_element();
899        let root_node = root_element.upcast::<Node>();
900        root_node
901            .traverse_preorder(ShadowIncluding::No)
902            .filter_map(DomRoot::downcast::<HTMLLabelElement>)
903            .filter(|elem| match elem.GetControl() {
904                Some(control) => &*control == self,
905                _ => false,
906            })
907            .nth(index as usize)
908            .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
909    }
910
911    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
912    // This counts the labels of the element, to support NodeList::Length
913    pub(crate) fn labels_count(&self) -> u32 {
914        // see label_at comments about performance
915        let element = self.as_element();
916        let root_element = element.root_element();
917        let root_node = root_element.upcast::<Node>();
918        root_node
919            .traverse_preorder(ShadowIncluding::No)
920            .filter_map(DomRoot::downcast::<HTMLLabelElement>)
921            .filter(|elem| match elem.GetControl() {
922                Some(control) => &*control == self,
923                _ => false,
924            })
925            .count() as u32
926    }
927
928    // https://html.spec.whatwg.org/multipage/#the-directionality.
929    // returns Some if can infer direction by itself or from child nodes
930    // returns None if requires to go up to parent
931    pub(crate) fn directionality(&self) -> Option<String> {
932        let element_direction: &str = &self.Dir();
933
934        if element_direction == "ltr" {
935            return Some("ltr".to_owned());
936        }
937
938        if element_direction == "rtl" {
939            return Some("rtl".to_owned());
940        }
941
942        if let Some(input) = self.downcast::<HTMLInputElement>() {
943            if input.input_type() == InputType::Tel {
944                return Some("ltr".to_owned());
945            }
946        }
947
948        if element_direction == "auto" {
949            if let Some(directionality) = self
950                .downcast::<HTMLInputElement>()
951                .and_then(|input| input.auto_directionality())
952            {
953                return Some(directionality);
954            }
955
956            if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
957                return Some(area.auto_directionality());
958            }
959        }
960
961        // TODO(NeverHappened): Implement condition
962        // If the element's dir attribute is in the auto state OR
963        // If the element is a bdi element and the dir attribute is not in a defined state
964        // (i.e. it is not present or has an invalid value)
965        // Requires bdi element implementation (https://html.spec.whatwg.org/multipage/#the-bdi-element)
966
967        None
968    }
969
970    // https://html.spec.whatwg.org/multipage/#the-summary-element:activation-behaviour
971    pub(crate) fn summary_activation_behavior(&self) {
972        debug_assert!(self.as_element().local_name() == &local_name!("summary"));
973
974        // Step 1. If this summary element is not the summary for its parent details, then return.
975        if !self.is_a_summary_for_its_parent_details() {
976            return;
977        }
978
979        // Step 2. Let parent be this summary element's parent.
980        let parent = if self.is_implicit_summary_element() {
981            DomRoot::downcast::<HTMLDetailsElement>(self.containing_shadow_root().unwrap().Host())
982                .unwrap()
983        } else {
984            self.upcast::<Node>()
985                .GetParentNode()
986                .and_then(DomRoot::downcast::<HTMLDetailsElement>)
987                .unwrap()
988        };
989
990        // Step 3. If the open attribute is present on parent, then remove it.
991        // Otherwise, set parent's open attribute to the empty string.
992        parent.toggle();
993    }
994
995    /// <https://html.spec.whatwg.org/multipage/#summary-for-its-parent-details>
996    fn is_a_summary_for_its_parent_details(&self) -> bool {
997        if self.is_implicit_summary_element() {
998            return true;
999        }
1000
1001        // Step 1. If this summary element has no parent, then return false.
1002        // Step 2. Let parent be this summary element's parent.
1003        let Some(parent) = self.upcast::<Node>().GetParentNode() else {
1004            return false;
1005        };
1006
1007        // Step 3. If parent is not a details element, then return false.
1008        let Some(details) = parent.downcast::<HTMLDetailsElement>() else {
1009            return false;
1010        };
1011
1012        // Step 4. If parent's first summary element child is not this summary
1013        // element, then return false.
1014        // Step 5. Return true.
1015        details
1016            .find_corresponding_summary_element()
1017            .is_some_and(|summary| &*summary == self.upcast())
1018    }
1019
1020    /// Whether or not this is an implicitly generated `<summary>`
1021    /// element for a UA `<details>` shadow tree
1022    fn is_implicit_summary_element(&self) -> bool {
1023        // Note that non-implicit summary elements are not actually inside
1024        // the UA shadow tree, they're only assigned to a slot inside it.
1025        // Therefore they don't cause false positives here
1026        self.containing_shadow_root()
1027            .as_deref()
1028            .map(ShadowRoot::Host)
1029            .is_some_and(|host| host.is::<HTMLDetailsElement>())
1030    }
1031
1032    /// <https://html.spec.whatwg.org/multipage/#rendered-text-fragment>
1033    fn rendered_text_fragment(&self, input: DOMString, can_gc: CanGc) -> DomRoot<DocumentFragment> {
1034        // Step 1: Let fragment be a new DocumentFragment whose node document is document.
1035        let document = self.owner_document();
1036        let fragment = DocumentFragment::new(&document, can_gc);
1037
1038        // Step 2: Let position be a position variable for input, initially pointing at the start
1039        // of input.
1040        let mut position = input.chars().peekable();
1041
1042        // Step 3: Let text be the empty string.
1043        let mut text = String::new();
1044
1045        // Step 4
1046        while let Some(ch) = position.next() {
1047            match ch {
1048                // While position is not past the end of input, and the code point at position is
1049                // either U+000A LF or U+000D CR:
1050                '\u{000A}' | '\u{000D}' => {
1051                    if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') {
1052                        // a \r\n pair should only generate one <br>,
1053                        // so just skip the \r.
1054                        position.next();
1055                    }
1056
1057                    if !text.is_empty() {
1058                        append_text_node_to_fragment(&document, &fragment, text, can_gc);
1059                        text = String::new();
1060                    }
1061
1062                    let br = Element::create(
1063                        QualName::new(None, ns!(html), local_name!("br")),
1064                        None,
1065                        &document,
1066                        ElementCreator::ScriptCreated,
1067                        CustomElementCreationMode::Asynchronous,
1068                        None,
1069                        can_gc,
1070                    );
1071                    fragment
1072                        .upcast::<Node>()
1073                        .AppendChild(br.upcast(), can_gc)
1074                        .unwrap();
1075                },
1076                _ => {
1077                    // Collect a sequence of code points that are not U+000A LF or U+000D CR from
1078                    // input given position, and set text to the result.
1079                    text.push(ch);
1080                },
1081            }
1082        }
1083
1084        // If text is not the empty string, then append a new Text node whose data is text and node
1085        // document is document to fragment.
1086        if !text.is_empty() {
1087            append_text_node_to_fragment(&document, &fragment, text, can_gc);
1088        }
1089
1090        fragment
1091    }
1092
1093    /// Checks whether a given [`DomRoot<Node>`] and its next sibling are
1094    /// of type [`Text`], and if so merges them into a single [`Text`]
1095    /// node.
1096    ///
1097    /// <https://html.spec.whatwg.org/multipage/#merge-with-the-next-text-node>
1098    fn merge_with_the_next_text_node(node: DomRoot<Node>, can_gc: CanGc) {
1099        // Make sure node is a Text node
1100        if !node.is::<Text>() {
1101            return;
1102        }
1103
1104        // Step 1: Let next be node's next sibling.
1105        let next = match node.GetNextSibling() {
1106            Some(next) => next,
1107            None => return,
1108        };
1109
1110        // Step 2: If next is not a Text node, then return.
1111        if !next.is::<Text>() {
1112            return;
1113        }
1114        // Step 3: Replace data with node, node's data's length, 0, and next's data.
1115        let node_chars = node.downcast::<CharacterData>().expect("Node is Text");
1116        let next_chars = next.downcast::<CharacterData>().expect("Next node is Text");
1117        node_chars
1118            .ReplaceData(node_chars.Length(), 0, next_chars.Data())
1119            .expect("Got chars from Text");
1120
1121        // Step 4:Remove next.
1122        next.remove_self(can_gc);
1123    }
1124}
1125
1126impl VirtualMethods for HTMLElement {
1127    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1128        Some(self.as_element() as &dyn VirtualMethods)
1129    }
1130
1131    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1132        self.super_type()
1133            .unwrap()
1134            .attribute_mutated(attr, mutation, can_gc);
1135        let element = self.as_element();
1136        match (attr.local_name(), mutation) {
1137            // https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3
1138            (name, mutation)
1139                if name.starts_with("on") && EventTarget::is_content_event_handler(name) =>
1140            {
1141                let evtarget = self.upcast::<EventTarget>();
1142                let event_name = &name[2..];
1143                match mutation {
1144                    // https://html.spec.whatwg.org/multipage/#activate-an-event-handler
1145                    AttributeMutation::Set(_) => {
1146                        let source = &**attr.value();
1147                        let source_line = 1; // TODO(#9604) get current JS execution line
1148                        evtarget.set_event_handler_uncompiled(
1149                            self.owner_window().get_url(),
1150                            source_line,
1151                            event_name,
1152                            source,
1153                        );
1154                    },
1155                    // https://html.spec.whatwg.org/multipage/#deactivate-an-event-handler
1156                    AttributeMutation::Removed => {
1157                        evtarget.set_event_handler_common::<EventHandlerNonNull>(event_name, None);
1158                    },
1159                }
1160            },
1161
1162            (&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
1163                self.form_attribute_mutated(mutation, can_gc);
1164            },
1165            // Adding a "disabled" attribute disables an enabled form element.
1166            (&local_name!("disabled"), AttributeMutation::Set(_))
1167                if self.is_form_associated_custom_element() && element.enabled_state() =>
1168            {
1169                element.set_disabled_state(true);
1170                element.set_enabled_state(false);
1171                ScriptThread::enqueue_callback_reaction(
1172                    element,
1173                    CallbackReaction::FormDisabled(true),
1174                    None,
1175                );
1176            },
1177            // Removing the "disabled" attribute may enable a disabled
1178            // form element, but a fieldset ancestor may keep it disabled.
1179            (&local_name!("disabled"), AttributeMutation::Removed)
1180                if self.is_form_associated_custom_element() && element.disabled_state() =>
1181            {
1182                element.set_disabled_state(false);
1183                element.set_enabled_state(true);
1184                element.check_ancestors_disabled_state_for_form_control();
1185                if element.enabled_state() {
1186                    ScriptThread::enqueue_callback_reaction(
1187                        element,
1188                        CallbackReaction::FormDisabled(false),
1189                        None,
1190                    );
1191                }
1192            },
1193            (&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
1194                match mutation {
1195                    AttributeMutation::Set(_) => {
1196                        element.set_read_write_state(true);
1197                    },
1198                    AttributeMutation::Removed => {
1199                        element.set_read_write_state(false);
1200                    },
1201                }
1202            },
1203            (&local_name!("nonce"), mutation) => match mutation {
1204                AttributeMutation::Set(_) => {
1205                    let nonce = &**attr.value();
1206                    element.update_nonce_internal_slot(nonce.to_owned());
1207                },
1208                AttributeMutation::Removed => {
1209                    element.update_nonce_internal_slot("".to_owned());
1210                },
1211            },
1212            _ => {},
1213        }
1214    }
1215
1216    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1217        if let Some(super_type) = self.super_type() {
1218            super_type.bind_to_tree(context, can_gc);
1219        }
1220        let element = self.as_element();
1221        element.update_sequentially_focusable_status(can_gc);
1222
1223        // Binding to a tree can disable a form control if one of the new
1224        // ancestors is a fieldset.
1225        if self.is_form_associated_custom_element() && element.enabled_state() {
1226            element.check_ancestors_disabled_state_for_form_control();
1227            if element.disabled_state() {
1228                ScriptThread::enqueue_callback_reaction(
1229                    element,
1230                    CallbackReaction::FormDisabled(true),
1231                    None,
1232                );
1233            }
1234        }
1235    }
1236
1237    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1238        if let Some(super_type) = self.super_type() {
1239            super_type.unbind_from_tree(context, can_gc);
1240        }
1241
1242        // Unbinding from a tree might enable a form control, if a
1243        // fieldset ancestor is the only reason it was disabled.
1244        // (The fact that it's enabled doesn't do much while it's
1245        // disconnected, but it is an observable fact to keep track of.)
1246        let element = self.as_element();
1247        if self.is_form_associated_custom_element() && element.disabled_state() {
1248            element.check_disabled_attribute();
1249            element.check_ancestors_disabled_state_for_form_control();
1250            if element.enabled_state() {
1251                ScriptThread::enqueue_callback_reaction(
1252                    element,
1253                    CallbackReaction::FormDisabled(false),
1254                    None,
1255                );
1256            }
1257        }
1258    }
1259
1260    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1261        match *name {
1262            local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
1263            local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()),
1264            _ => self
1265                .super_type()
1266                .unwrap()
1267                .parse_plain_attribute(name, value),
1268        }
1269    }
1270}
1271
1272impl Activatable for HTMLElement {
1273    fn as_element(&self) -> &Element {
1274        self.upcast::<Element>()
1275    }
1276
1277    fn is_instance_activatable(&self) -> bool {
1278        self.as_element().local_name() == &local_name!("summary")
1279    }
1280
1281    // Basically used to make the HTMLSummaryElement activatable (which has no IDL definition)
1282    fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
1283        self.summary_activation_behavior();
1284    }
1285}
1286
1287// Form-associated custom elements are the same interface type as
1288// normal HTMLElements, so HTMLElement needs to have the FormControl trait
1289// even though it's usually more specific trait implementations, like the
1290// HTMLInputElement one, that we really want. (Alternately we could put
1291// the FormControl trait on ElementInternals, but that raises lifetime issues.)
1292impl FormControl for HTMLElement {
1293    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1294        debug_assert!(self.is_form_associated_custom_element());
1295        self.as_element()
1296            .get_element_internals()
1297            .and_then(|e| e.form_owner())
1298    }
1299
1300    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
1301        debug_assert!(self.is_form_associated_custom_element());
1302        self.as_element()
1303            .ensure_element_internals(CanGc::note())
1304            .set_form_owner(form);
1305    }
1306
1307    fn to_element(&self) -> &Element {
1308        self.as_element()
1309    }
1310
1311    fn is_listed(&self) -> bool {
1312        debug_assert!(self.is_form_associated_custom_element());
1313        true
1314    }
1315
1316    // TODO satisfies_constraints traits
1317}