script/dom/
element.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
5//! Element nodes.
6
7use std::borrow::Cow;
8use std::cell::{Cell, LazyCell};
9use std::default::Default;
10use std::ops::Deref;
11use std::rc::Rc;
12use std::str::FromStr;
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::{fmt, mem};
15
16use app_units::Au;
17use bitflags::bitflags;
18use cssparser::match_ignore_ascii_case;
19use devtools_traits::{AttrInfo, DomMutation, ScriptToDevtoolsControlMsg};
20use dom_struct::dom_struct;
21use euclid::Rect;
22use html5ever::serialize::TraversalScope;
23use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
24use html5ever::{LocalName, Namespace, Prefix, QualName, local_name, namespace_prefix, ns};
25use js::context::JSContext;
26use js::jsapi::{Heap, JSAutoRealm};
27use js::jsval::JSVal;
28use js::rust::HandleObject;
29use layout_api::{LayoutDamage, ScrollContainerQueryFlags};
30use net_traits::ReferrerPolicy;
31use net_traits::request::CorsSettings;
32use selectors::Element as SelectorsElement;
33use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
34use selectors::bloom::{BLOOM_HASH_MASK, BloomFilter};
35use selectors::matching::{ElementSelectorFlags, MatchingContext};
36use selectors::sink::Push;
37use servo_arc::Arc;
38use style::applicable_declarations::ApplicableDeclarationBlock;
39use style::attr::{AttrValue, LengthOrPercentageOrAuto};
40use style::context::QuirksMode;
41use style::invalidation::element::restyle_hints::RestyleHint;
42use style::properties::longhands::{
43    self, background_image, border_spacing, font_family, font_size,
44};
45use style::properties::{
46    ComputedValues, Importance, PropertyDeclaration, PropertyDeclarationBlock,
47    parse_style_attribute,
48};
49use style::rule_tree::CascadeLevel;
50use style::selector_parser::{
51    NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser, Snapshot,
52    extended_filtering,
53};
54use style::shared_lock::Locked;
55use style::stylesheets::layer_rule::LayerOrder;
56use style::stylesheets::{CssRuleType, UrlExtraData};
57use style::values::computed::Overflow;
58use style::values::generics::NonNegative;
59use style::values::generics::position::PreferredRatio;
60use style::values::generics::ratio::Ratio;
61use style::values::{AtomIdent, AtomString, CSSFloat, computed, specified};
62use style::{ArcSlice, CaseSensitivityExt, dom_apis, thread_state};
63use style_traits::CSSPixel;
64use stylo_atoms::Atom;
65use stylo_dom::ElementState;
66use xml5ever::serialize::TraversalScope::{
67    ChildrenOnly as XmlChildrenOnly, IncludeNode as XmlIncludeNode,
68};
69
70use crate::conversions::Convert;
71use crate::dom::activation::Activatable;
72use crate::dom::attr::{Attr, AttrHelpersForLayout, is_relevant_attribute};
73use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut};
74use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
75use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
76use crate::dom::bindings::codegen::Bindings::ElementBinding::{
77    ElementMethods, GetHTMLOptions, ScrollIntoViewContainer, ScrollLogicalPosition, ShadowRootInit,
78};
79use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
80use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
81use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
82use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
83use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
84    ShadowRootMethods, ShadowRootMode, SlotAssignmentMode,
85};
86use crate::dom::bindings::codegen::Bindings::WindowBinding::{
87    ScrollBehavior, ScrollToOptions, WindowMethods,
88};
89use crate::dom::bindings::codegen::UnionTypes::{
90    BooleanOrScrollIntoViewOptions, NodeOrString, TrustedHTMLOrNullIsEmptyString,
91    TrustedHTMLOrString,
92    TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString,
93    TrustedScriptURLOrUSVString,
94};
95use crate::dom::bindings::conversions::DerivedFrom;
96use crate::dom::bindings::domname::{
97    self, is_valid_attribute_local_name, namespace_from_domstring,
98};
99use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
100use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
101use crate::dom::bindings::num::Finite;
102use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
103use crate::dom::bindings::reflector::DomObject;
104use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayout};
105use crate::dom::bindings::str::{DOMString, USVString};
106use crate::dom::characterdata::CharacterData;
107use crate::dom::create::create_element;
108use crate::dom::csp::{CspReporting, InlineCheckType, SourcePosition};
109use crate::dom::customelementregistry::{
110    CallbackReaction, CustomElementDefinition, CustomElementReaction, CustomElementRegistry,
111    CustomElementState, is_valid_custom_element_name,
112};
113use crate::dom::document::{Document, LayoutDocumentHelpers};
114use crate::dom::documentfragment::DocumentFragment;
115use crate::dom::domrect::DOMRect;
116use crate::dom::domrectlist::DOMRectList;
117use crate::dom::domtokenlist::DOMTokenList;
118use crate::dom::elementinternals::ElementInternals;
119use crate::dom::event::{EventBubbles, EventCancelable, EventComposed};
120use crate::dom::eventtarget::EventTarget;
121use crate::dom::globalscope::GlobalScope;
122use crate::dom::html::htmlanchorelement::HTMLAnchorElement;
123use crate::dom::html::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers};
124use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
125use crate::dom::html::htmlcollection::HTMLCollection;
126use crate::dom::html::htmlelement::HTMLElement;
127use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
128use crate::dom::html::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers};
129use crate::dom::html::htmlformelement::FormControlElementHelpers;
130use crate::dom::html::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers, SizePresentationalHint};
131use crate::dom::html::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods};
132use crate::dom::html::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
133use crate::dom::html::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
134use crate::dom::html::htmllabelelement::HTMLLabelElement;
135use crate::dom::html::htmllegendelement::HTMLLegendElement;
136use crate::dom::html::htmllinkelement::HTMLLinkElement;
137use crate::dom::html::htmlobjectelement::HTMLObjectElement;
138use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
139use crate::dom::html::htmloutputelement::HTMLOutputElement;
140use crate::dom::html::htmlscriptelement::HTMLScriptElement;
141use crate::dom::html::htmlselectelement::HTMLSelectElement;
142use crate::dom::html::htmlslotelement::{HTMLSlotElement, Slottable};
143use crate::dom::html::htmlstyleelement::HTMLStyleElement;
144use crate::dom::html::htmltablecellelement::{
145    HTMLTableCellElement, HTMLTableCellElementLayoutHelpers,
146};
147use crate::dom::html::htmltablecolelement::{
148    HTMLTableColElement, HTMLTableColElementLayoutHelpers,
149};
150use crate::dom::html::htmltableelement::{HTMLTableElement, HTMLTableElementLayoutHelpers};
151use crate::dom::html::htmltablerowelement::{
152    HTMLTableRowElement, HTMLTableRowElementLayoutHelpers,
153};
154use crate::dom::html::htmltablesectionelement::{
155    HTMLTableSectionElement, HTMLTableSectionElementLayoutHelpers,
156};
157use crate::dom::html::htmltemplateelement::HTMLTemplateElement;
158use crate::dom::html::htmltextareaelement::{
159    HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers,
160};
161use crate::dom::html::htmlvideoelement::{HTMLVideoElement, LayoutHTMLVideoElementHelpers};
162use crate::dom::intersectionobserver::{IntersectionObserver, IntersectionObserverRegistration};
163use crate::dom::mutationobserver::{Mutation, MutationObserver};
164use crate::dom::namednodemap::NamedNodeMap;
165use crate::dom::node::{
166    BindContext, ChildrenMutation, CloneChildrenFlag, IsShadowTree, LayoutNodeHelpers, Node,
167    NodeDamage, NodeFlags, NodeTraits, ShadowIncluding, UnbindContext,
168};
169use crate::dom::nodelist::NodeList;
170use crate::dom::promise::Promise;
171use crate::dom::range::Range;
172use crate::dom::raredata::ElementRareData;
173use crate::dom::scrolling_box::{ScrollAxisState, ScrollingBox};
174use crate::dom::servoparser::ServoParser;
175use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot};
176use crate::dom::text::Text;
177use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
178use crate::dom::trustedtypes::trustedtypepolicyfactory::TrustedTypePolicyFactory;
179use crate::dom::validation::Validatable;
180use crate::dom::validitystate::ValidationFlags;
181use crate::dom::virtualmethods::{VirtualMethods, vtable_for};
182use crate::script_runtime::CanGc;
183use crate::script_thread::ScriptThread;
184use crate::stylesheet_loader::StylesheetOwner;
185use crate::task::TaskOnce;
186
187// TODO: Update focus state when the top-level browsing context gains or loses system focus,
188// and when the element enters or leaves a browsing context container.
189// https://html.spec.whatwg.org/multipage/#selector-focus
190
191/// <https://dom.spec.whatwg.org/#element>
192#[dom_struct]
193pub struct Element {
194    node: Node,
195    #[no_trace]
196    local_name: LocalName,
197    tag_name: TagName,
198    #[no_trace]
199    namespace: Namespace,
200    #[no_trace]
201    prefix: DomRefCell<Option<Prefix>>,
202    attrs: DomRefCell<Vec<Dom<Attr>>>,
203    #[no_trace]
204    id_attribute: DomRefCell<Option<Atom>>,
205    /// <https://dom.spec.whatwg.org/#concept-element-is-value>
206    #[no_trace]
207    is: DomRefCell<Option<LocalName>>,
208    #[conditional_malloc_size_of]
209    #[no_trace]
210    style_attribute: DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>,
211    attr_list: MutNullableDom<NamedNodeMap>,
212    class_list: MutNullableDom<DOMTokenList>,
213    #[no_trace]
214    state: Cell<ElementState>,
215    /// These flags are set by the style system to indicate the that certain
216    /// operations may require restyling this element or its descendants.
217    selector_flags: AtomicUsize,
218    rare_data: DomRefCell<Option<Box<ElementRareData>>>,
219}
220
221impl fmt::Debug for Element {
222    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223        write!(f, "<{}", self.local_name)?;
224        if let Some(ref id) = *self.id_attribute.borrow() {
225            write!(f, " id={}", id)?;
226        }
227        write!(f, ">")
228    }
229}
230
231#[derive(MallocSizeOf, PartialEq)]
232pub(crate) enum ElementCreator {
233    ParserCreated(u64),
234    ScriptCreated,
235}
236
237pub(crate) enum CustomElementCreationMode {
238    Synchronous,
239    Asynchronous,
240}
241
242impl ElementCreator {
243    pub(crate) fn is_parser_created(&self) -> bool {
244        match *self {
245            ElementCreator::ParserCreated(_) => true,
246            ElementCreator::ScriptCreated => false,
247        }
248    }
249    pub(crate) fn return_line_number(&self) -> u64 {
250        match *self {
251            ElementCreator::ParserCreated(l) => l,
252            ElementCreator::ScriptCreated => 1,
253        }
254    }
255}
256
257pub(crate) enum AdjacentPosition {
258    BeforeBegin,
259    AfterEnd,
260    AfterBegin,
261    BeforeEnd,
262}
263
264impl FromStr for AdjacentPosition {
265    type Err = Error;
266
267    fn from_str(position: &str) -> Result<Self, Self::Err> {
268        match_ignore_ascii_case! { position,
269            "beforebegin" => Ok(AdjacentPosition::BeforeBegin),
270            "afterbegin"  => Ok(AdjacentPosition::AfterBegin),
271            "beforeend"   => Ok(AdjacentPosition::BeforeEnd),
272            "afterend"    => Ok(AdjacentPosition::AfterEnd),
273            _             => Err(Error::Syntax(None))
274        }
275    }
276}
277
278//
279// Element methods
280//
281impl Element {
282    pub(crate) fn create(
283        name: QualName,
284        is: Option<LocalName>,
285        document: &Document,
286        creator: ElementCreator,
287        mode: CustomElementCreationMode,
288        proto: Option<HandleObject>,
289        can_gc: CanGc,
290    ) -> DomRoot<Element> {
291        create_element(name, is, document, creator, mode, proto, can_gc)
292    }
293
294    pub(crate) fn new_inherited(
295        local_name: LocalName,
296        namespace: Namespace,
297        prefix: Option<Prefix>,
298        document: &Document,
299    ) -> Element {
300        Element::new_inherited_with_state(
301            ElementState::empty(),
302            local_name,
303            namespace,
304            prefix,
305            document,
306        )
307    }
308
309    pub(crate) fn new_inherited_with_state(
310        state: ElementState,
311        local_name: LocalName,
312        namespace: Namespace,
313        prefix: Option<Prefix>,
314        document: &Document,
315    ) -> Element {
316        Element {
317            node: Node::new_inherited(document),
318            local_name,
319            tag_name: TagName::new(),
320            namespace,
321            prefix: DomRefCell::new(prefix),
322            attrs: DomRefCell::new(vec![]),
323            id_attribute: DomRefCell::new(None),
324            is: DomRefCell::new(None),
325            style_attribute: DomRefCell::new(None),
326            attr_list: Default::default(),
327            class_list: Default::default(),
328            state: Cell::new(state),
329            selector_flags: Default::default(),
330            rare_data: Default::default(),
331        }
332    }
333
334    pub(crate) fn new(
335        local_name: LocalName,
336        namespace: Namespace,
337        prefix: Option<Prefix>,
338        document: &Document,
339        proto: Option<HandleObject>,
340        can_gc: CanGc,
341    ) -> DomRoot<Element> {
342        Node::reflect_node_with_proto(
343            Box::new(Element::new_inherited(
344                local_name, namespace, prefix, document,
345            )),
346            document,
347            proto,
348            can_gc,
349        )
350    }
351
352    fn rare_data(&self) -> Ref<'_, Option<Box<ElementRareData>>> {
353        self.rare_data.borrow()
354    }
355
356    fn rare_data_mut(&self) -> RefMut<'_, Option<Box<ElementRareData>>> {
357        self.rare_data.borrow_mut()
358    }
359
360    fn ensure_rare_data(&self) -> RefMut<'_, Box<ElementRareData>> {
361        let mut rare_data = self.rare_data.borrow_mut();
362        if rare_data.is_none() {
363            *rare_data = Some(Default::default());
364        }
365        RefMut::map(rare_data, |rare_data| rare_data.as_mut().unwrap())
366    }
367
368    pub(crate) fn restyle(&self, damage: NodeDamage) {
369        let doc = self.node.owner_doc();
370        let mut restyle = doc.ensure_pending_restyle(self);
371
372        // FIXME(bholley): I think we should probably only do this for
373        // NodeStyleDamaged, but I'm preserving existing behavior.
374        restyle.hint.insert(RestyleHint::RESTYLE_SELF);
375
376        match damage {
377            NodeDamage::Style => {},
378            NodeDamage::ContentOrHeritage => {
379                doc.note_node_with_dirty_descendants(self.upcast());
380                restyle
381                    .damage
382                    .insert(LayoutDamage::descendant_has_box_damage());
383            },
384            NodeDamage::Other => {
385                doc.note_node_with_dirty_descendants(self.upcast());
386                restyle.damage.insert(RestyleDamage::reconstruct());
387            },
388        }
389    }
390
391    pub(crate) fn set_is(&self, is: LocalName) {
392        *self.is.borrow_mut() = Some(is);
393    }
394
395    /// <https://dom.spec.whatwg.org/#concept-element-is-value>
396    pub(crate) fn get_is(&self) -> Option<LocalName> {
397        self.is.borrow().clone()
398    }
399
400    /// This is a performance optimization. `Element::create` can simply call
401    /// `element.set_custom_element_state(CustomElementState::Uncustomized)` to initialize
402    /// uncustomized, built-in elements with the right state, which currently just means that the
403    /// `DEFINED` state should be `true` for styling. However `set_custom_element_state` has a high
404    /// performance cost and it is unnecessary if the element is being created as an uncustomized
405    /// built-in element.
406    ///
407    /// See <https://github.com/servo/servo/issues/37745> for more details.
408    pub(crate) fn set_initial_custom_element_state_to_uncustomized(&self) {
409        let mut state = self.state.get();
410        state.insert(ElementState::DEFINED);
411        self.state.set(state);
412    }
413
414    /// <https://dom.spec.whatwg.org/#concept-element-custom-element-state>
415    pub(crate) fn set_custom_element_state(&self, state: CustomElementState) {
416        // no need to inflate rare data for uncustomized
417        if state != CustomElementState::Uncustomized {
418            self.ensure_rare_data().custom_element_state = state;
419        }
420
421        let in_defined_state = matches!(
422            state,
423            CustomElementState::Uncustomized | CustomElementState::Custom
424        );
425        self.set_state(ElementState::DEFINED, in_defined_state)
426    }
427
428    pub(crate) fn get_custom_element_state(&self) -> CustomElementState {
429        if let Some(rare_data) = self.rare_data().as_ref() {
430            return rare_data.custom_element_state;
431        }
432        CustomElementState::Uncustomized
433    }
434
435    /// <https://dom.spec.whatwg.org/#concept-element-custom>
436    pub(crate) fn is_custom(&self) -> bool {
437        self.get_custom_element_state() == CustomElementState::Custom
438    }
439
440    pub(crate) fn set_custom_element_definition(&self, definition: Rc<CustomElementDefinition>) {
441        self.ensure_rare_data().custom_element_definition = Some(definition);
442    }
443
444    pub(crate) fn get_custom_element_definition(&self) -> Option<Rc<CustomElementDefinition>> {
445        self.rare_data().as_ref()?.custom_element_definition.clone()
446    }
447
448    pub(crate) fn clear_custom_element_definition(&self) {
449        self.ensure_rare_data().custom_element_definition = None;
450    }
451
452    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
453    pub(crate) fn push_callback_reaction(&self, function: Rc<Function>, args: Box<[Heap<JSVal>]>) {
454        self.ensure_rare_data()
455            .custom_element_reaction_queue
456            .push(CustomElementReaction::Callback(function, args));
457    }
458
459    pub(crate) fn push_upgrade_reaction(&self, definition: Rc<CustomElementDefinition>) {
460        self.ensure_rare_data()
461            .custom_element_reaction_queue
462            .push(CustomElementReaction::Upgrade(definition));
463    }
464
465    pub(crate) fn clear_reaction_queue(&self) {
466        if let Some(ref mut rare_data) = *self.rare_data_mut() {
467            rare_data.custom_element_reaction_queue.clear();
468        }
469    }
470
471    pub(crate) fn invoke_reactions(&self, can_gc: CanGc) {
472        loop {
473            rooted_vec!(let mut reactions);
474            match *self.rare_data_mut() {
475                Some(ref mut data) => {
476                    mem::swap(&mut *reactions, &mut data.custom_element_reaction_queue)
477                },
478                None => break,
479            };
480
481            if reactions.is_empty() {
482                break;
483            }
484
485            for reaction in reactions.iter() {
486                reaction.invoke(self, can_gc);
487            }
488
489            reactions.clear();
490        }
491    }
492
493    /// style will be `None` for elements in a `display: none` subtree. otherwise, the element has a
494    /// layout box iff it doesn't have `display: none`.
495    pub(crate) fn style(&self) -> Option<Arc<ComputedValues>> {
496        self.upcast::<Node>().style()
497    }
498
499    // https://drafts.csswg.org/cssom-view/#css-layout-box
500    pub(crate) fn has_css_layout_box(&self) -> bool {
501        self.style()
502            .is_some_and(|s| !s.get_box().clone_display().is_none())
503    }
504
505    /// <https://drafts.csswg.org/cssom-view/#potentially-scrollable>
506    pub(crate) fn is_potentially_scrollable_body(&self) -> bool {
507        self.is_potentially_scrollable_body_shared_logic(false)
508    }
509
510    /// <https://drafts.csswg.org/cssom-view/#potentially-scrollable>
511    pub(crate) fn is_potentially_scrollable_body_for_scrolling_element(&self) -> bool {
512        self.is_potentially_scrollable_body_shared_logic(true)
513    }
514
515    /// <https://drafts.csswg.org/cssom-view/#potentially-scrollable>
516    fn is_potentially_scrollable_body_shared_logic(
517        &self,
518        treat_overflow_clip_on_parent_as_hidden: bool,
519    ) -> bool {
520        let node = self.upcast::<Node>();
521        debug_assert!(
522            node.owner_doc().GetBody().as_deref() == self.downcast::<HTMLElement>(),
523            "Called is_potentially_scrollable_body on element that is not the <body>"
524        );
525
526        // "An element body (which will be the body element) is potentially
527        // scrollable if all of the following conditions are true:
528        //  - body has an associated box."
529        if !self.has_css_layout_box() {
530            return false;
531        }
532
533        // " - body’s parent element’s computed value of the overflow-x or
534        //     overflow-y properties is neither visible nor clip."
535        if let Some(parent) = node.GetParentElement() {
536            if let Some(style) = parent.style() {
537                let mut overflow_x = style.get_box().clone_overflow_x();
538                let mut overflow_y = style.get_box().clone_overflow_y();
539
540                // This fulfills the 'treat parent element overflow:clip as overflow:hidden' stipulation
541                // from the document.scrollingElement specification.
542                if treat_overflow_clip_on_parent_as_hidden {
543                    if overflow_x == Overflow::Clip {
544                        overflow_x = Overflow::Hidden;
545                    }
546                    if overflow_y == Overflow::Clip {
547                        overflow_y = Overflow::Hidden;
548                    }
549                }
550
551                if !overflow_x.is_scrollable() && !overflow_y.is_scrollable() {
552                    return false;
553                }
554            };
555        }
556
557        // " - body’s computed value of the overflow-x or overflow-y properties
558        //     is neither visible nor clip."
559        if let Some(style) = self.style() {
560            if !style.get_box().clone_overflow_x().is_scrollable() &&
561                !style.get_box().clone_overflow_y().is_scrollable()
562            {
563                return false;
564            }
565        };
566
567        true
568    }
569
570    /// Whether this element is styled such that it establishes a scroll container.
571    /// <https://www.w3.org/TR/css-overflow-3/#scroll-container>
572    pub(crate) fn establishes_scroll_container(&self) -> bool {
573        // The CSS computed value has made sure that either both axes are scrollable or none are scrollable.
574        self.upcast::<Node>()
575            .effective_overflow()
576            .is_some_and(|overflow| overflow.establishes_scroll_container())
577    }
578
579    pub(crate) fn has_overflow(&self) -> bool {
580        self.ScrollHeight() > self.ClientHeight() || self.ScrollWidth() > self.ClientWidth()
581    }
582
583    /// Whether or not this element has a scrolling box according to
584    /// <https://drafts.csswg.org/cssom-view/#scrolling-box>.
585    ///
586    /// This is true if:
587    ///  1. The element has a layout box.
588    ///  2. The style specifies that overflow should be scrollable (`auto`, `hidden` or `scroll`).
589    ///  3. The fragment actually has content that overflows the box.
590    fn has_scrolling_box(&self) -> bool {
591        self.has_css_layout_box() && self.establishes_scroll_container() && self.has_overflow()
592    }
593
594    pub(crate) fn shadow_root(&self) -> Option<DomRoot<ShadowRoot>> {
595        self.rare_data()
596            .as_ref()?
597            .shadow_root
598            .as_ref()
599            .map(|sr| DomRoot::from_ref(&**sr))
600    }
601
602    pub(crate) fn is_shadow_host(&self) -> bool {
603        self.shadow_root().is_some()
604    }
605
606    /// <https://dom.spec.whatwg.org/#dom-element-attachshadow>
607    #[allow(clippy::too_many_arguments)]
608    pub(crate) fn attach_shadow(
609        &self,
610        is_ua_widget: IsUserAgentWidget,
611        mode: ShadowRootMode,
612        clonable: bool,
613        serializable: bool,
614        delegates_focus: bool,
615        slot_assignment_mode: SlotAssignmentMode,
616        can_gc: CanGc,
617    ) -> Fallible<DomRoot<ShadowRoot>> {
618        // Step 1. If element’s namespace is not the HTML namespace,
619        // then throw a "NotSupportedError" DOMException.
620        if self.namespace != ns!(html) {
621            return Err(Error::NotSupported(Some(
622                "Cannot attach shadow roots to elements with non-HTML namespaces".to_owned(),
623            )));
624        }
625
626        // Step 2. If element’s local name is not a valid shadow host name,
627        // then throw a "NotSupportedError" DOMException.
628        if !is_valid_shadow_host_name(self.local_name()) {
629            // UA shadow roots may be attached to anything
630            if is_ua_widget != IsUserAgentWidget::Yes {
631                let error_message = format!(
632                    "Cannot attach shadow roots to <{}> elements",
633                    *self.local_name()
634                );
635                return Err(Error::NotSupported(Some(error_message)));
636            }
637        }
638
639        // Step 3. If element’s local name is a valid custom element name,
640        // or element’s is value is non-null
641        if is_valid_custom_element_name(self.local_name()) || self.get_is().is_some() {
642            // Step 3.1. Let definition be the result of looking up a custom element definition
643            // given element’s node document, its namespace, its local name, and its is value.
644
645            let definition = self.get_custom_element_definition();
646            // Step 3.2. If definition is not null and definition’s disable shadow
647            //  is true, then throw a "NotSupportedError" DOMException.
648            if definition.is_some_and(|definition| definition.disable_shadow) {
649                let error_message = format!(
650                    "The custom element constructor of <{}> disabled attachment of shadow roots",
651                    self.local_name()
652                );
653                return Err(Error::NotSupported(Some(error_message)));
654            }
655        }
656
657        // Step 4. If element is a shadow host:
658        // Step 4.1. Let currentShadowRoot be element’s shadow root.
659        if let Some(current_shadow_root) = self.shadow_root() {
660            // Step 4.2. If currentShadowRoot’s declarative is false
661            // or currentShadowRoot’s mode is not mode
662            // then throw a "NotSupportedError" DOMException.
663            if !current_shadow_root.is_declarative() ||
664                current_shadow_root.shadow_root_mode() != mode
665            {
666                return Err(Error::NotSupported(Some(
667                    "Cannot attach a second shadow root to the same element".into(),
668                )));
669            }
670
671            // Step 4.3.1. Remove all of currentShadowRoot’s children, in tree order.
672            for child in current_shadow_root.upcast::<Node>().children() {
673                child.remove_self(can_gc);
674            }
675
676            // Step 4.3.2. Set currentShadowRoot’s declarative to false.
677            current_shadow_root.set_declarative(false);
678
679            // Step 4.3.3. Return
680            return Ok(current_shadow_root);
681        }
682
683        // Step 5. Let shadow be a new shadow root whose node document
684        // is element’s node document, host is element, and mode is mode
685        //
686        // Step 8. Set shadow’s slot assignment to slotAssignment
687        //
688        // Step 10. Set shadow’s clonable to clonable
689        let shadow_root = ShadowRoot::new(
690            self,
691            &self.node.owner_doc(),
692            mode,
693            slot_assignment_mode,
694            clonable,
695            is_ua_widget,
696            can_gc,
697        );
698
699        // This is not in the specification, but this is where we ensure that the
700        // non-shadow-tree children of `self` no longer have layout boxes as they are no
701        // longer in the flat tree.
702        let node = self.upcast::<Node>();
703        node.remove_layout_boxes_from_subtree();
704
705        // Step 6. Set shadow's delegates focus to delegatesFocus
706        shadow_root.set_delegates_focus(delegates_focus);
707
708        // Step 7. If element’s custom element state is "precustomized" or "custom",
709        // then set shadow’s available to element internals to true.
710        if matches!(
711            self.get_custom_element_state(),
712            CustomElementState::Precustomized | CustomElementState::Custom
713        ) {
714            shadow_root.set_available_to_element_internals(true);
715        }
716
717        // Step 9. Set shadow's declarative to false
718        shadow_root.set_declarative(false);
719
720        // Step 11. Set shadow's serializable to serializable
721        shadow_root.set_serializable(serializable);
722
723        // Step 12. Set element’s shadow root to shadow
724        self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root));
725        shadow_root
726            .upcast::<Node>()
727            .set_containing_shadow_root(Some(&shadow_root));
728
729        let bind_context = BindContext::new(self.upcast(), IsShadowTree::Yes);
730        shadow_root.bind_to_tree(&bind_context, can_gc);
731
732        node.dirty(NodeDamage::Other);
733
734        Ok(shadow_root)
735    }
736
737    /// Attach a UA widget shadow root with its default parameters.
738    /// Additionally mark ShadowRoot to use styling configuration for a UA widget.
739    ///
740    /// The general trait of these elements is that it would hide the implementation.
741    /// Thus, we would make it inaccessible (i.e., closed mode, not cloneable, and
742    /// not serializable).
743    ///
744    /// With UA shadow root element being assumed as one element, any focus should
745    /// be delegated to its host.
746    ///
747    // TODO: Ideally, all of the UA shadow root should use UA widget styling, but
748    //       some of the UA widget implemented prior to the implementation of Gecko's
749    //       UA widget matching might need some tweaking.
750    // FIXME: We are yet to implement more complex focusing with that is necessary
751    //        for delegate focus, and we are using workarounds for that right now.
752    pub(crate) fn attach_ua_shadow_root(
753        &self,
754        use_ua_widget_styling: bool,
755        can_gc: CanGc,
756    ) -> DomRoot<ShadowRoot> {
757        let root = self
758            .attach_shadow(
759                IsUserAgentWidget::Yes,
760                ShadowRootMode::Closed,
761                false,
762                false,
763                false,
764                SlotAssignmentMode::Manual,
765                can_gc,
766            )
767            .expect("Attaching UA shadow root failed");
768
769        root.upcast::<Node>()
770            .set_in_ua_widget(use_ua_widget_styling);
771        root
772    }
773
774    // https://html.spec.whatwg.org/multipage/#translation-mode
775    pub(crate) fn is_translate_enabled(&self) -> bool {
776        let name = &local_name!("translate");
777        if self.has_attribute(name) {
778            let attribute = self.get_string_attribute(name);
779            match_ignore_ascii_case! { &*attribute.str(),
780                "yes" | "" => return true,
781                "no" => return false,
782                _ => {},
783            }
784        }
785        if let Some(parent) = self.upcast::<Node>().GetParentNode() {
786            if let Some(elem) = parent.downcast::<Element>() {
787                return elem.is_translate_enabled();
788            }
789        }
790        true
791    }
792
793    // https://html.spec.whatwg.org/multipage/#the-directionality
794    pub(crate) fn directionality(&self) -> String {
795        self.downcast::<HTMLElement>()
796            .and_then(|html_element| html_element.directionality())
797            .unwrap_or_else(|| {
798                let node = self.upcast::<Node>();
799                node.parent_directionality()
800            })
801    }
802
803    pub(crate) fn is_root(&self) -> bool {
804        match self.node.GetParentNode() {
805            None => false,
806            Some(node) => node.is::<Document>(),
807        }
808    }
809
810    /// Return all IntersectionObserverRegistration for this element.
811    /// Lazily initialize the raredata if it does not exist.
812    pub(crate) fn registered_intersection_observers_mut(
813        &self,
814    ) -> RefMut<'_, Vec<IntersectionObserverRegistration>> {
815        RefMut::map(self.ensure_rare_data(), |rare_data| {
816            &mut rare_data.registered_intersection_observers
817        })
818    }
819
820    pub(crate) fn registered_intersection_observers(
821        &self,
822    ) -> Option<Ref<'_, Vec<IntersectionObserverRegistration>>> {
823        let rare_data: Ref<'_, _> = self.rare_data.borrow();
824
825        if rare_data.is_none() {
826            return None;
827        }
828        Some(Ref::map(rare_data, |rare_data| {
829            &rare_data
830                .as_ref()
831                .unwrap()
832                .registered_intersection_observers
833        }))
834    }
835
836    pub(crate) fn get_intersection_observer_registration(
837        &self,
838        observer: &IntersectionObserver,
839    ) -> Option<Ref<'_, IntersectionObserverRegistration>> {
840        if let Some(registrations) = self.registered_intersection_observers() {
841            registrations
842                .iter()
843                .position(|reg_obs| reg_obs.observer == observer)
844                .map(|index| Ref::map(registrations, |registrations| &registrations[index]))
845        } else {
846            None
847        }
848    }
849
850    /// Add a new IntersectionObserverRegistration with initial value to the element.
851    pub(crate) fn add_initial_intersection_observer_registration(
852        &self,
853        observer: &IntersectionObserver,
854    ) {
855        self.ensure_rare_data()
856            .registered_intersection_observers
857            .push(IntersectionObserverRegistration::new_initial(observer));
858    }
859
860    /// Removes a certain IntersectionObserver.
861    pub(crate) fn remove_intersection_observer(&self, observer: &IntersectionObserver) {
862        self.ensure_rare_data()
863            .registered_intersection_observers
864            .retain(|reg_obs| *reg_obs.observer != *observer)
865    }
866
867    /// Get the [`ScrollingBox`] that contains this element, if one does. `position:
868    /// fixed` elements do not have a containing [`ScrollingBox`].
869    pub(crate) fn scrolling_box(&self, flags: ScrollContainerQueryFlags) -> Option<ScrollingBox> {
870        self.owner_window()
871            .scrolling_box_query(Some(self.upcast()), flags)
872    }
873
874    /// <https://drafts.csswg.org/cssom-view/#scroll-a-target-into-view>
875    pub(crate) fn scroll_into_view_with_options(
876        &self,
877        behavior: ScrollBehavior,
878        block: ScrollAxisState,
879        inline: ScrollAxisState,
880        container: Option<&Element>,
881        inner_target_rect: Option<Rect<Au, CSSPixel>>,
882    ) {
883        let get_target_rect = || match inner_target_rect {
884            None => self.upcast::<Node>().border_box().unwrap_or_default(),
885            Some(inner_target_rect) => inner_target_rect.translate(
886                self.upcast::<Node>()
887                    .content_box()
888                    .unwrap_or_default()
889                    .origin
890                    .to_vector(),
891            ),
892        };
893
894        // Step 1: For each ancestor element or viewport that establishes a scrolling box `scrolling
895        // box`, in order of innermost to outermost scrolling box, run these substeps:
896        let mut parent_scrolling_box = self.scrolling_box(ScrollContainerQueryFlags::empty());
897        while let Some(scrolling_box) = parent_scrolling_box {
898            parent_scrolling_box = scrolling_box.parent();
899
900            // Step 1.1: If the Document associated with `target` is not same origin with the
901            // Document associated with the element or viewport associated with `scrolling box`,
902            // terminate these steps.
903            //
904            // TODO: Handle this. We currently do not chain up to parent Documents.
905
906            // Step 1.2 Let `position` be the scroll position resulting from running the steps to
907            // determine the scroll-into-view position of `target` with `behavior` as the scroll
908            // behavior, `block` as the block flow position, `inline` as the inline base direction
909            // position and `scrolling box` as the scrolling box.
910            let position =
911                scrolling_box.determine_scroll_into_view_position(block, inline, get_target_rect());
912
913            // Step 1.3: If `position` is not the same as `scrolling box`’s current scroll position, or
914            // `scrolling box` has an ongoing smooth scroll,
915            //
916            // TODO: Handle smooth scrolling.
917            if position != scrolling_box.scroll_position() {
918                //  ↪ If `scrolling box` is associated with an element
919                //    Perform a scroll of the element’s scrolling box to `position`,
920                //    with the `element` as the associated element and `behavior` as the
921                //    scroll behavior.
922                //  ↪ If `scrolling box` is associated with a viewport
923                //    Step 1: Let `document` be the viewport’s associated Document.
924                //    Step 2: Let `root element` be document’s root element, if there is one, or
925                //    null otherwise.
926                //    Step 3: Perform a scroll of the viewport to `position`, with `root element`
927                //    as the associated element and `behavior` as the scroll behavior.
928                scrolling_box.scroll_to(position, behavior);
929            }
930
931            // Step 1.4: If `container` is not null and either `scrolling box` is a shadow-including
932            // inclusive ancestor of `container` or is a viewport whose document is a shadow-including
933            // inclusive ancestor of `container`, abort the rest of these steps.
934            if container.is_some_and(|container| {
935                let container_node = container.upcast::<Node>();
936                scrolling_box
937                    .node()
938                    .is_shadow_including_inclusive_ancestor_of(container_node)
939            }) {
940                return;
941            }
942        }
943
944        let window_proxy = self.owner_window().window_proxy();
945        let Some(frame_element) = window_proxy.frame_element() else {
946            return;
947        };
948
949        let inner_target_rect = Some(get_target_rect());
950        let parent_window = frame_element.owner_window();
951        let cx = GlobalScope::get_cx();
952        let _ac = JSAutoRealm::new(*cx, *parent_window.reflector().get_jsobject());
953        frame_element.scroll_into_view_with_options(
954            behavior,
955            block,
956            inline,
957            None,
958            inner_target_rect,
959        )
960    }
961
962    pub(crate) fn ensure_contenteditable_selection_range(
963        &self,
964        document: &Document,
965        can_gc: CanGc,
966    ) -> DomRoot<Range> {
967        self.ensure_rare_data()
968            .contenteditable_selection_range
969            .or_init(|| Range::new_with_doc(document, None, can_gc))
970    }
971}
972
973/// <https://dom.spec.whatwg.org/#valid-shadow-host-name>
974#[inline]
975pub(crate) fn is_valid_shadow_host_name(name: &LocalName) -> bool {
976    // > A valid shadow host name is:
977    // > - a valid custom element name
978    if is_valid_custom_element_name(name) {
979        return true;
980    }
981
982    // > - "article", "aside", "blockquote", "body", "div", "footer", "h1", "h2", "h3",
983    // >   "h4", "h5", "h6", "header", "main", "nav", "p", "section", or "span"
984    matches!(
985        name,
986        &local_name!("article") |
987            &local_name!("aside") |
988            &local_name!("blockquote") |
989            &local_name!("body") |
990            &local_name!("div") |
991            &local_name!("footer") |
992            &local_name!("h1") |
993            &local_name!("h2") |
994            &local_name!("h3") |
995            &local_name!("h4") |
996            &local_name!("h5") |
997            &local_name!("h6") |
998            &local_name!("header") |
999            &local_name!("main") |
1000            &local_name!("nav") |
1001            &local_name!("p") |
1002            &local_name!("section") |
1003            &local_name!("span")
1004    )
1005}
1006
1007#[inline]
1008pub(crate) fn get_attr_for_layout<'dom>(
1009    elem: LayoutDom<'dom, Element>,
1010    namespace: &Namespace,
1011    name: &LocalName,
1012) -> Option<&'dom AttrValue> {
1013    elem.attrs()
1014        .iter()
1015        .find(|attr| name == attr.local_name() && namespace == attr.namespace())
1016        .map(|attr| attr.value())
1017}
1018
1019pub(crate) trait LayoutElementHelpers<'dom> {
1020    fn attrs(self) -> &'dom [LayoutDom<'dom, Attr>];
1021    fn has_class_or_part_for_layout(
1022        self,
1023        name: &AtomIdent,
1024        attr_name: &LocalName,
1025        case_sensitivity: CaseSensitivity,
1026    ) -> bool;
1027    fn get_classes_for_layout(self) -> Option<&'dom [Atom]>;
1028    fn get_parts_for_layout(self) -> Option<&'dom [Atom]>;
1029
1030    fn synthesize_presentational_hints_for_legacy_attributes<V>(self, hints: &mut V)
1031    where
1032        V: Push<ApplicableDeclarationBlock>;
1033    fn get_span(self) -> Option<u32>;
1034    fn get_colspan(self) -> Option<u32>;
1035    fn get_rowspan(self) -> Option<u32>;
1036    fn is_html_element(&self) -> bool;
1037    fn id_attribute(self) -> *const Option<Atom>;
1038    fn style_attribute(self) -> *const Option<Arc<Locked<PropertyDeclarationBlock>>>;
1039    fn local_name(self) -> &'dom LocalName;
1040    fn namespace(self) -> &'dom Namespace;
1041    fn get_lang_attr_val_for_layout(self) -> Option<&'dom str>;
1042    fn get_lang_for_layout(self) -> String;
1043    fn get_state_for_layout(self) -> ElementState;
1044    fn insert_selector_flags(self, flags: ElementSelectorFlags);
1045    fn get_selector_flags(self) -> ElementSelectorFlags;
1046    /// The shadow root this element is a host of.
1047    fn get_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>>;
1048    fn get_attr_for_layout(
1049        self,
1050        namespace: &Namespace,
1051        name: &LocalName,
1052    ) -> Option<&'dom AttrValue>;
1053    fn get_attr_val_for_layout(self, namespace: &Namespace, name: &LocalName) -> Option<&'dom str>;
1054    fn get_attr_vals_for_layout(self, name: &LocalName) -> impl Iterator<Item = &'dom AttrValue>;
1055    fn each_custom_state_for_layout(self, allback: impl FnMut(&AtomIdent));
1056}
1057
1058impl LayoutDom<'_, Element> {
1059    pub(super) fn focus_state(self) -> bool {
1060        self.unsafe_get().state.get().contains(ElementState::FOCUS)
1061    }
1062}
1063
1064impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> {
1065    #[expect(unsafe_code)]
1066    #[inline]
1067    fn attrs(self) -> &'dom [LayoutDom<'dom, Attr>] {
1068        unsafe { LayoutDom::to_layout_slice(self.unsafe_get().attrs.borrow_for_layout()) }
1069    }
1070
1071    #[inline]
1072    fn has_class_or_part_for_layout(
1073        self,
1074        name: &AtomIdent,
1075        attr_name: &LocalName,
1076        case_sensitivity: CaseSensitivity,
1077    ) -> bool {
1078        get_attr_for_layout(self, &ns!(), attr_name).is_some_and(|attr| {
1079            attr.as_tokens()
1080                .iter()
1081                .any(|atom| case_sensitivity.eq_atom(atom, name))
1082        })
1083    }
1084
1085    #[inline]
1086    fn get_classes_for_layout(self) -> Option<&'dom [Atom]> {
1087        get_attr_for_layout(self, &ns!(), &local_name!("class")).map(|attr| attr.as_tokens())
1088    }
1089
1090    fn get_parts_for_layout(self) -> Option<&'dom [Atom]> {
1091        get_attr_for_layout(self, &ns!(), &local_name!("part")).map(|attr| attr.as_tokens())
1092    }
1093
1094    fn synthesize_presentational_hints_for_legacy_attributes<V>(self, hints: &mut V)
1095    where
1096        V: Push<ApplicableDeclarationBlock>,
1097    {
1098        let mut property_declaration_block = None;
1099        let mut push = |declaration| {
1100            property_declaration_block
1101                .get_or_insert_with(PropertyDeclarationBlock::default)
1102                .push(declaration, Importance::Normal);
1103        };
1104
1105        // TODO(xiaochengh): This is probably not enough. When the root element doesn't have a `lang`,
1106        // we should check the browser settings and system locale.
1107        if let Some(lang) = self.get_lang_attr_val_for_layout() {
1108            push(PropertyDeclaration::XLang(specified::XLang(Atom::from(
1109                lang.to_owned(),
1110            ))));
1111        }
1112
1113        let bgcolor = if let Some(this) = self.downcast::<HTMLBodyElement>() {
1114            this.get_background_color()
1115        } else if let Some(this) = self.downcast::<HTMLTableElement>() {
1116            this.get_background_color()
1117        } else if let Some(this) = self.downcast::<HTMLTableCellElement>() {
1118            this.get_background_color()
1119        } else if let Some(this) = self.downcast::<HTMLTableRowElement>() {
1120            this.get_background_color()
1121        } else if let Some(this) = self.downcast::<HTMLTableSectionElement>() {
1122            this.get_background_color()
1123        } else {
1124            None
1125        };
1126
1127        if let Some(color) = bgcolor {
1128            push(PropertyDeclaration::BackgroundColor(
1129                specified::Color::from_absolute_color(color),
1130            ));
1131        }
1132
1133        if is_element_affected_by_legacy_background_presentational_hint(
1134            self.namespace(),
1135            self.local_name(),
1136        ) {
1137            if let Some(url) = self
1138                .get_attr_for_layout(&ns!(), &local_name!("background"))
1139                .and_then(AttrValue::as_resolved_url)
1140                .cloned()
1141            {
1142                push(PropertyDeclaration::BackgroundImage(
1143                    background_image::SpecifiedValue(
1144                        vec![specified::Image::for_cascade(url)].into(),
1145                    ),
1146                ));
1147            }
1148        }
1149
1150        let color = if let Some(this) = self.downcast::<HTMLFontElement>() {
1151            this.get_color()
1152        } else if let Some(this) = self.downcast::<HTMLBodyElement>() {
1153            // https://html.spec.whatwg.org/multipage/#the-page:the-body-element-20
1154            this.get_color()
1155        } else if let Some(this) = self.downcast::<HTMLHRElement>() {
1156            // https://html.spec.whatwg.org/multipage/#the-hr-element-2:presentational-hints-5
1157            this.get_color()
1158        } else {
1159            None
1160        };
1161
1162        if let Some(color) = color {
1163            push(PropertyDeclaration::Color(
1164                longhands::color::SpecifiedValue(specified::Color::from_absolute_color(color)),
1165            ));
1166        }
1167
1168        let font_face = self
1169            .downcast::<HTMLFontElement>()
1170            .and_then(HTMLFontElementLayoutHelpers::get_face);
1171        if let Some(font_face) = font_face {
1172            push(PropertyDeclaration::FontFamily(
1173                font_family::SpecifiedValue::Values(computed::font::FontFamilyList {
1174                    list: ArcSlice::from_iter(
1175                        HTMLFontElement::parse_face_attribute(font_face).into_iter(),
1176                    ),
1177                }),
1178            ));
1179        }
1180
1181        let font_size = self
1182            .downcast::<HTMLFontElement>()
1183            .and_then(HTMLFontElementLayoutHelpers::get_size);
1184        if let Some(font_size) = font_size {
1185            push(PropertyDeclaration::FontSize(
1186                font_size::SpecifiedValue::from_html_size(font_size as u8),
1187            ));
1188        }
1189
1190        let cellspacing = self
1191            .downcast::<HTMLTableElement>()
1192            .and_then(HTMLTableElementLayoutHelpers::get_cellspacing);
1193        if let Some(cellspacing) = cellspacing {
1194            let width_value = specified::Length::from_px(cellspacing as f32);
1195            push(PropertyDeclaration::BorderSpacing(Box::new(
1196                border_spacing::SpecifiedValue::new(width_value.clone().into(), width_value.into()),
1197            )));
1198        }
1199
1200        // Textual input, specifically text entry and domain specific input has
1201        // a default preferred size.
1202        //
1203        // <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
1204        // <https://html.spec.whatwg.org/multipage/#the-input-element-as-domain-specific-widgets>
1205        let size = self
1206            .downcast::<HTMLInputElement>()
1207            .and_then(|input_element| {
1208                // FIXME(pcwalton): More use of atoms, please!
1209                match self.get_attr_val_for_layout(&ns!(), &local_name!("type")) {
1210                    Some("hidden") | Some("range") | Some("color") | Some("checkbox") |
1211                    Some("radio") | Some("file") | Some("submit") | Some("image") |
1212                    Some("reset") | Some("button") => None,
1213                    // Others
1214                    _ => match input_element.size_for_layout() {
1215                        0 => None,
1216                        s => Some(s as i32),
1217                    },
1218                }
1219            });
1220
1221        if let Some(size) = size {
1222            let value =
1223                specified::NoCalcLength::ServoCharacterWidth(specified::CharacterWidth(size));
1224            push(PropertyDeclaration::Width(
1225                specified::Size::LengthPercentage(NonNegative(
1226                    specified::LengthPercentage::Length(value),
1227                )),
1228            ));
1229        }
1230
1231        let width = if let Some(this) = self.downcast::<HTMLIFrameElement>() {
1232            this.get_width()
1233        } else if let Some(this) = self.downcast::<HTMLImageElement>() {
1234            this.get_width()
1235        } else if let Some(this) = self.downcast::<HTMLVideoElement>() {
1236            this.get_width()
1237        } else if let Some(this) = self.downcast::<HTMLTableElement>() {
1238            this.get_width()
1239        } else if let Some(this) = self.downcast::<HTMLTableCellElement>() {
1240            this.get_width()
1241        } else if let Some(this) = self.downcast::<HTMLTableColElement>() {
1242            this.get_width()
1243        } else if let Some(this) = self.downcast::<HTMLHRElement>() {
1244            // https://html.spec.whatwg.org/multipage/#the-hr-element-2:attr-hr-width
1245            this.get_width()
1246        } else {
1247            LengthOrPercentageOrAuto::Auto
1248        };
1249
1250        // FIXME(emilio): Use from_computed value here and below.
1251        match width {
1252            LengthOrPercentageOrAuto::Auto => {},
1253            LengthOrPercentageOrAuto::Percentage(percentage) => {
1254                let width_value = specified::Size::LengthPercentage(NonNegative(
1255                    specified::LengthPercentage::Percentage(computed::Percentage(percentage)),
1256                ));
1257                push(PropertyDeclaration::Width(width_value));
1258            },
1259            LengthOrPercentageOrAuto::Length(length) => {
1260                let width_value = specified::Size::LengthPercentage(NonNegative(
1261                    specified::LengthPercentage::Length(specified::NoCalcLength::Absolute(
1262                        specified::AbsoluteLength::Px(length.to_f32_px()),
1263                    )),
1264                ));
1265                push(PropertyDeclaration::Width(width_value));
1266            },
1267        }
1268
1269        let height = if let Some(this) = self.downcast::<HTMLIFrameElement>() {
1270            this.get_height()
1271        } else if let Some(this) = self.downcast::<HTMLImageElement>() {
1272            this.get_height()
1273        } else if let Some(this) = self.downcast::<HTMLVideoElement>() {
1274            this.get_height()
1275        } else if let Some(this) = self.downcast::<HTMLTableElement>() {
1276            this.get_height()
1277        } else if let Some(this) = self.downcast::<HTMLTableCellElement>() {
1278            this.get_height()
1279        } else if let Some(this) = self.downcast::<HTMLTableRowElement>() {
1280            this.get_height()
1281        } else if let Some(this) = self.downcast::<HTMLTableSectionElement>() {
1282            this.get_height()
1283        } else {
1284            LengthOrPercentageOrAuto::Auto
1285        };
1286
1287        match height {
1288            LengthOrPercentageOrAuto::Auto => {},
1289            LengthOrPercentageOrAuto::Percentage(percentage) => {
1290                let height_value = specified::Size::LengthPercentage(NonNegative(
1291                    specified::LengthPercentage::Percentage(computed::Percentage(percentage)),
1292                ));
1293                push(PropertyDeclaration::Height(height_value));
1294            },
1295            LengthOrPercentageOrAuto::Length(length) => {
1296                let height_value = specified::Size::LengthPercentage(NonNegative(
1297                    specified::LengthPercentage::Length(specified::NoCalcLength::Absolute(
1298                        specified::AbsoluteLength::Px(length.to_f32_px()),
1299                    )),
1300                ));
1301                push(PropertyDeclaration::Height(height_value));
1302            },
1303        }
1304
1305        // Aspect ratio when providing both width and height.
1306        // https://html.spec.whatwg.org/multipage/#attributes-for-embedded-content-and-images
1307        if self.downcast::<HTMLImageElement>().is_some() ||
1308            self.downcast::<HTMLVideoElement>().is_some()
1309        {
1310            if let LengthOrPercentageOrAuto::Length(width) = width {
1311                if let LengthOrPercentageOrAuto::Length(height) = height {
1312                    let width_value = NonNegative(specified::Number::new(width.to_f32_px()));
1313                    let height_value = NonNegative(specified::Number::new(height.to_f32_px()));
1314                    let aspect_ratio = specified::position::AspectRatio {
1315                        auto: true,
1316                        ratio: PreferredRatio::Ratio(Ratio(width_value, height_value)),
1317                    };
1318                    push(PropertyDeclaration::AspectRatio(aspect_ratio));
1319                }
1320            }
1321        }
1322
1323        let cols = self
1324            .downcast::<HTMLTextAreaElement>()
1325            .map(LayoutHTMLTextAreaElementHelpers::get_cols);
1326        if let Some(cols) = cols {
1327            let cols = cols as i32;
1328            if cols > 0 {
1329                // TODO(mttr) ServoCharacterWidth uses the size math for <input type="text">, but
1330                // the math for <textarea> is a little different since we need to take
1331                // scrollbar size into consideration (but we don't have a scrollbar yet!)
1332                //
1333                // https://html.spec.whatwg.org/multipage/#textarea-effective-width
1334                let value =
1335                    specified::NoCalcLength::ServoCharacterWidth(specified::CharacterWidth(cols));
1336                push(PropertyDeclaration::Width(
1337                    specified::Size::LengthPercentage(NonNegative(
1338                        specified::LengthPercentage::Length(value),
1339                    )),
1340                ));
1341            }
1342        }
1343
1344        let rows = self
1345            .downcast::<HTMLTextAreaElement>()
1346            .map(LayoutHTMLTextAreaElementHelpers::get_rows);
1347        if let Some(rows) = rows {
1348            let rows = rows as i32;
1349            if rows > 0 {
1350                // TODO(mttr) This should take scrollbar size into consideration.
1351                //
1352                // https://html.spec.whatwg.org/multipage/#textarea-effective-height
1353                let value = specified::NoCalcLength::FontRelative(
1354                    specified::FontRelativeLength::Em(rows as CSSFloat),
1355                );
1356                push(PropertyDeclaration::Height(
1357                    specified::Size::LengthPercentage(NonNegative(
1358                        specified::LengthPercentage::Length(value),
1359                    )),
1360                ));
1361            }
1362        }
1363
1364        let border = self
1365            .downcast::<HTMLTableElement>()
1366            .and_then(|table| table.get_border());
1367        if let Some(border) = border {
1368            let width_value = specified::BorderSideWidth::from_px(border as f32);
1369            push(PropertyDeclaration::BorderTopWidth(width_value.clone()));
1370            push(PropertyDeclaration::BorderLeftWidth(width_value.clone()));
1371            push(PropertyDeclaration::BorderBottomWidth(width_value.clone()));
1372            push(PropertyDeclaration::BorderRightWidth(width_value));
1373        }
1374
1375        if let Some(cellpadding) = self
1376            .downcast::<HTMLTableCellElement>()
1377            .and_then(|this| this.get_table())
1378            .and_then(|table| table.get_cellpadding())
1379        {
1380            let cellpadding = NonNegative(specified::LengthPercentage::Length(
1381                specified::NoCalcLength::from_px(cellpadding as f32),
1382            ));
1383            push(PropertyDeclaration::PaddingTop(cellpadding.clone()));
1384            push(PropertyDeclaration::PaddingLeft(cellpadding.clone()));
1385            push(PropertyDeclaration::PaddingBottom(cellpadding.clone()));
1386            push(PropertyDeclaration::PaddingRight(cellpadding));
1387        }
1388
1389        // https://html.spec.whatwg.org/multipage/#the-hr-element-2
1390        if let Some(size_info) = self
1391            .downcast::<HTMLHRElement>()
1392            .and_then(|hr_element| hr_element.get_size_info())
1393        {
1394            match size_info {
1395                SizePresentationalHint::SetHeightTo(height) => {
1396                    push(PropertyDeclaration::Height(height));
1397                },
1398                SizePresentationalHint::SetAllBorderWidthValuesTo(border_width) => {
1399                    push(PropertyDeclaration::BorderLeftWidth(border_width.clone()));
1400                    push(PropertyDeclaration::BorderRightWidth(border_width.clone()));
1401                    push(PropertyDeclaration::BorderTopWidth(border_width.clone()));
1402                    push(PropertyDeclaration::BorderBottomWidth(border_width));
1403                },
1404                SizePresentationalHint::SetBottomBorderWidthToZero => {
1405                    push(PropertyDeclaration::BorderBottomWidth(
1406                        specified::border::BorderSideWidth::from_px(0.),
1407                    ));
1408                },
1409            }
1410        }
1411
1412        let Some(property_declaration_block) = property_declaration_block else {
1413            return;
1414        };
1415
1416        let document = self.upcast::<Node>().owner_doc_for_layout();
1417        let shared_lock = document.style_shared_lock();
1418        hints.push(ApplicableDeclarationBlock::from_declarations(
1419            Arc::new(shared_lock.wrap(property_declaration_block)),
1420            CascadeLevel::PresHints,
1421            LayerOrder::root(),
1422        ));
1423    }
1424
1425    fn get_span(self) -> Option<u32> {
1426        // Don't panic since `display` can cause this to be called on arbitrary elements.
1427        self.downcast::<HTMLTableColElement>()
1428            .and_then(|element| element.get_span())
1429    }
1430
1431    fn get_colspan(self) -> Option<u32> {
1432        // Don't panic since `display` can cause this to be called on arbitrary elements.
1433        self.downcast::<HTMLTableCellElement>()
1434            .and_then(|element| element.get_colspan())
1435    }
1436
1437    fn get_rowspan(self) -> Option<u32> {
1438        // Don't panic since `display` can cause this to be called on arbitrary elements.
1439        self.downcast::<HTMLTableCellElement>()
1440            .and_then(|element| element.get_rowspan())
1441    }
1442
1443    #[inline]
1444    fn is_html_element(&self) -> bool {
1445        *self.namespace() == ns!(html)
1446    }
1447
1448    #[expect(unsafe_code)]
1449    fn id_attribute(self) -> *const Option<Atom> {
1450        unsafe { (self.unsafe_get()).id_attribute.borrow_for_layout() }
1451    }
1452
1453    #[expect(unsafe_code)]
1454    fn style_attribute(self) -> *const Option<Arc<Locked<PropertyDeclarationBlock>>> {
1455        unsafe { (self.unsafe_get()).style_attribute.borrow_for_layout() }
1456    }
1457
1458    fn local_name(self) -> &'dom LocalName {
1459        &(self.unsafe_get()).local_name
1460    }
1461
1462    fn namespace(self) -> &'dom Namespace {
1463        &(self.unsafe_get()).namespace
1464    }
1465
1466    fn get_lang_attr_val_for_layout(self) -> Option<&'dom str> {
1467        if let Some(attr) = self.get_attr_val_for_layout(&ns!(xml), &local_name!("lang")) {
1468            return Some(attr);
1469        }
1470        if let Some(attr) = self.get_attr_val_for_layout(&ns!(), &local_name!("lang")) {
1471            return Some(attr);
1472        }
1473        None
1474    }
1475
1476    fn get_lang_for_layout(self) -> String {
1477        let mut current_node = Some(self.upcast::<Node>());
1478        while let Some(node) = current_node {
1479            current_node = node.composed_parent_node_ref();
1480            match node.downcast::<Element>() {
1481                Some(elem) => {
1482                    if let Some(attr) = elem.get_lang_attr_val_for_layout() {
1483                        return attr.to_owned();
1484                    }
1485                },
1486                None => continue,
1487            }
1488        }
1489        // TODO: Check meta tags for a pragma-set default language
1490        // TODO: Check HTTP Content-Language header
1491        String::new()
1492    }
1493
1494    #[inline]
1495    fn get_state_for_layout(self) -> ElementState {
1496        (self.unsafe_get()).state.get()
1497    }
1498
1499    #[inline]
1500    fn insert_selector_flags(self, flags: ElementSelectorFlags) {
1501        debug_assert!(thread_state::get().is_layout());
1502        self.unsafe_get().insert_selector_flags(flags);
1503    }
1504
1505    #[inline]
1506    fn get_selector_flags(self) -> ElementSelectorFlags {
1507        self.unsafe_get().get_selector_flags()
1508    }
1509
1510    #[inline]
1511    #[expect(unsafe_code)]
1512    fn get_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>> {
1513        unsafe {
1514            self.unsafe_get()
1515                .rare_data
1516                .borrow_for_layout()
1517                .as_ref()?
1518                .shadow_root
1519                .as_ref()
1520                .map(|sr| sr.to_layout())
1521        }
1522    }
1523
1524    #[inline]
1525    fn get_attr_for_layout(
1526        self,
1527        namespace: &Namespace,
1528        name: &LocalName,
1529    ) -> Option<&'dom AttrValue> {
1530        get_attr_for_layout(self, namespace, name)
1531    }
1532
1533    #[inline]
1534    fn get_attr_val_for_layout(self, namespace: &Namespace, name: &LocalName) -> Option<&'dom str> {
1535        get_attr_for_layout(self, namespace, name).map(|attr| &**attr)
1536    }
1537
1538    #[inline]
1539    fn get_attr_vals_for_layout(self, name: &LocalName) -> impl Iterator<Item = &'dom AttrValue> {
1540        self.attrs().iter().filter_map(move |attr| {
1541            if name == attr.local_name() {
1542                Some(attr.value())
1543            } else {
1544                None
1545            }
1546        })
1547    }
1548
1549    #[expect(unsafe_code)]
1550    fn each_custom_state_for_layout(self, mut callback: impl FnMut(&AtomIdent)) {
1551        let rare_data = unsafe { self.unsafe_get().rare_data.borrow_for_layout() };
1552        let Some(rare_data) = rare_data.as_ref() else {
1553            return;
1554        };
1555        let Some(element_internals) = rare_data.element_internals.as_ref() else {
1556            return;
1557        };
1558
1559        let element_internals = unsafe { element_internals.to_layout() };
1560        if let Some(states) = element_internals.unsafe_get().custom_states_for_layout() {
1561            for state in unsafe { states.unsafe_get().set_for_layout().iter() } {
1562                // FIXME: This creates new atoms whenever it is called, which is not optimal.
1563                callback(&AtomIdent::from(&*state.str()));
1564            }
1565        }
1566    }
1567}
1568
1569impl Element {
1570    pub(crate) fn is_html_element(&self) -> bool {
1571        self.namespace == ns!(html)
1572    }
1573
1574    pub(crate) fn html_element_in_html_document(&self) -> bool {
1575        self.is_html_element() && self.upcast::<Node>().is_in_html_doc()
1576    }
1577
1578    pub(crate) fn local_name(&self) -> &LocalName {
1579        &self.local_name
1580    }
1581
1582    pub(crate) fn parsed_name(&self, mut name: DOMString) -> LocalName {
1583        if self.html_element_in_html_document() {
1584            name.make_ascii_lowercase();
1585        }
1586        LocalName::from(name)
1587    }
1588
1589    pub(crate) fn namespace(&self) -> &Namespace {
1590        &self.namespace
1591    }
1592
1593    pub(crate) fn prefix(&self) -> Ref<'_, Option<Prefix>> {
1594        self.prefix.borrow()
1595    }
1596
1597    pub(crate) fn set_prefix(&self, prefix: Option<Prefix>) {
1598        *self.prefix.borrow_mut() = prefix;
1599    }
1600
1601    pub(crate) fn set_custom_element_registry(
1602        &self,
1603        registry: Option<DomRoot<CustomElementRegistry>>,
1604    ) {
1605        self.ensure_rare_data().custom_element_registry = registry.as_deref().map(Dom::from_ref);
1606    }
1607
1608    pub(crate) fn custom_element_registry(&self) -> Option<DomRoot<CustomElementRegistry>> {
1609        self.rare_data()
1610            .as_ref()?
1611            .custom_element_registry
1612            .as_deref()
1613            .map(DomRoot::from_ref)
1614    }
1615
1616    pub(crate) fn attrs(&self) -> Ref<'_, [Dom<Attr>]> {
1617        Ref::map(self.attrs.borrow(), |attrs| &**attrs)
1618    }
1619
1620    /// Element branch of <https://dom.spec.whatwg.org/#locate-a-namespace>
1621    pub(crate) fn locate_namespace(&self, prefix: Option<DOMString>) -> Namespace {
1622        let namespace_prefix = prefix.clone().map(|s| Prefix::from(&*s.str()));
1623
1624        // Step 1. If prefix is "xml", then return the XML namespace.
1625        if namespace_prefix == Some(namespace_prefix!("xml")) {
1626            return ns!(xml);
1627        }
1628
1629        // Step 2. If prefix is "xmlns", then return the XMLNS namespace.
1630        if namespace_prefix == Some(namespace_prefix!("xmlns")) {
1631            return ns!(xmlns);
1632        }
1633
1634        let prefix = prefix.map(LocalName::from);
1635
1636        let inclusive_ancestor_elements = self
1637            .upcast::<Node>()
1638            .inclusive_ancestors(ShadowIncluding::No)
1639            .filter_map(DomRoot::downcast::<Self>);
1640
1641        // Step 5. If its parent element is null, then return null.
1642        // Step 6. Return the result of running locate a namespace on its parent element using prefix.
1643        for element in inclusive_ancestor_elements {
1644            // Step 3. If its namespace is non-null and its namespace prefix is prefix, then return namespace.
1645            if element.namespace() != &ns!() &&
1646                element.prefix().as_ref().map(|p| &**p) == prefix.as_deref()
1647            {
1648                return element.namespace().clone();
1649            }
1650
1651            // Step 4. If it has an attribute whose namespace is the XMLNS namespace, namespace prefix
1652            // is "xmlns", and local name is prefix, or if prefix is null and it has an attribute
1653            // whose namespace is the XMLNS namespace, namespace prefix is null, and local name is
1654            // "xmlns", then return its value if it is not the empty string, and null otherwise.
1655            let attr = Ref::filter_map(self.attrs(), |attrs| {
1656                attrs.iter().find(|attr| {
1657                    if attr.namespace() != &ns!(xmlns) {
1658                        return false;
1659                    }
1660                    match (attr.prefix(), prefix.as_ref()) {
1661                        (Some(&namespace_prefix!("xmlns")), Some(prefix)) => {
1662                            attr.local_name() == prefix
1663                        },
1664                        (None, None) => attr.local_name() == &local_name!("xmlns"),
1665                        _ => false,
1666                    }
1667                })
1668            })
1669            .ok();
1670
1671            if let Some(attr) = attr {
1672                return (**attr.value()).into();
1673            }
1674        }
1675
1676        ns!()
1677    }
1678
1679    pub(crate) fn name_attribute(&self) -> Option<Atom> {
1680        self.rare_data().as_ref()?.name_attribute.clone()
1681    }
1682
1683    pub(crate) fn style_attribute(
1684        &self,
1685    ) -> &DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>> {
1686        &self.style_attribute
1687    }
1688
1689    pub(crate) fn summarize(&self) -> Vec<AttrInfo> {
1690        self.attrs
1691            .borrow()
1692            .iter()
1693            .map(|attr| attr.summarize())
1694            .collect()
1695    }
1696
1697    pub(crate) fn is_void(&self) -> bool {
1698        if self.namespace != ns!(html) {
1699            return false;
1700        }
1701        match self.local_name {
1702            /* List of void elements from
1703            https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm */
1704            local_name!("area") |
1705            local_name!("base") |
1706            local_name!("basefont") |
1707            local_name!("bgsound") |
1708            local_name!("br") |
1709            local_name!("col") |
1710            local_name!("embed") |
1711            local_name!("frame") |
1712            local_name!("hr") |
1713            local_name!("img") |
1714            local_name!("input") |
1715            local_name!("keygen") |
1716            local_name!("link") |
1717            local_name!("meta") |
1718            local_name!("param") |
1719            local_name!("source") |
1720            local_name!("track") |
1721            local_name!("wbr") => true,
1722            _ => false,
1723        }
1724    }
1725
1726    pub(crate) fn root_element(&self) -> DomRoot<Element> {
1727        if self.node.is_in_a_document_tree() {
1728            self.upcast::<Node>()
1729                .owner_doc()
1730                .GetDocumentElement()
1731                .unwrap()
1732        } else {
1733            self.upcast::<Node>()
1734                .inclusive_ancestors(ShadowIncluding::No)
1735                .filter_map(DomRoot::downcast)
1736                .last()
1737                .expect("We know inclusive_ancestors will return `self` which is an element")
1738        }
1739    }
1740
1741    // https://dom.spec.whatwg.org/#locate-a-namespace-prefix
1742    pub(crate) fn lookup_prefix(&self, namespace: Namespace) -> Option<DOMString> {
1743        for node in self
1744            .upcast::<Node>()
1745            .inclusive_ancestors(ShadowIncluding::No)
1746        {
1747            let element = node.downcast::<Element>()?;
1748            // Step 1.
1749            if *element.namespace() == namespace {
1750                if let Some(prefix) = element.GetPrefix() {
1751                    return Some(prefix);
1752                }
1753            }
1754
1755            // Step 2.
1756            for attr in element.attrs.borrow().iter() {
1757                if attr.prefix() == Some(&namespace_prefix!("xmlns")) &&
1758                    **attr.value() == *namespace
1759                {
1760                    return Some(attr.LocalName());
1761                }
1762            }
1763        }
1764        None
1765    }
1766
1767    /// <https://dom.spec.whatwg.org/#document-element>
1768    pub(crate) fn is_document_element(&self) -> bool {
1769        if let Some(document_element) = self.owner_document().GetDocumentElement() {
1770            *document_element == *self
1771        } else {
1772            false
1773        }
1774    }
1775
1776    /// <https://html.spec.whatwg.org/multipage/#dom-document-activeelement>
1777    pub(crate) fn is_active_element(&self) -> bool {
1778        if let Some(active_element) = self.owner_document().GetActiveElement() {
1779            *active_element == *self
1780        } else {
1781            false
1782        }
1783    }
1784
1785    fn is_editing_host(&self) -> bool {
1786        self.downcast::<HTMLElement>()
1787            .is_some_and(|element| element.IsContentEditable())
1788    }
1789
1790    /// <https://html.spec.whatwg.org/multipage/#focusable-area>
1791    ///
1792    /// The list of focusable areas at this point in the specification is both incomplete and leaves
1793    /// a lot up to the user agent. In addition, the specifications for "click focusable" and
1794    /// "sequentially focusable" are written in a way that they are subsets of all focusable areas.
1795    /// In order to avoid having to first determine whether an element is a focusable area and then
1796    /// work backwards to figure out what kind it is, this function attempts to classify the
1797    /// different types of focusable areas ahead of time so that the logic is useful for answering
1798    /// both "Is this element a focusable area?" and "Is this element click (or sequentially)
1799    /// focusable."
1800    fn focusable_area_kind(&self) -> FocusableAreaKind {
1801        // Do not allow unrendered, disconnected, or disabled nodes to be focusable areas ever.
1802        let node: &Node = self.upcast();
1803        if !node.is_connected() || !self.has_css_layout_box() || self.is_actually_disabled() {
1804            return Default::default();
1805        }
1806
1807        // > Elements that meet all the following criteria:
1808        // > the element's tabindex value is non-null, or the element is determined by the user agent to be focusable;
1809        // > the element is either not a shadow host, or has a shadow root whose delegates focus is false;
1810        // TODO: Handle this.
1811        // > the element is not actually disabled;
1812        // Note: Checked above
1813        // > the element is not inert;
1814        // TODO: Handle this.
1815        // > the element is either being rendered, delegating its rendering to its children, or
1816        // > being used as relevant canvas fallback content.
1817        // Note: Checked above
1818        // TODO: Handle fallback canvas content.
1819        match self.explicitly_set_tab_index() {
1820            // From <https://html.spec.whatwg.org/multipage/#tabindex-ordered-focus-navigation-scope>:
1821            // > A tabindex-ordered focus navigation scope is a list of focusable areas and focus
1822            // > navigation scope owners. Every focus navigation scope owner owner has tabindex-ordered
1823            // > focus navigation scope, whose contents are determined as follows:
1824            // >  - It contains all elements in owner's focus navigation scope that are themselves focus
1825            // >    navigation scope owners, except the elements whose tabindex value is a negative integer.
1826            // >  - It contains all of the focusable areas whose DOM anchor is an element in owner's focus
1827            // >    navigation scope, except the focusable areas whose tabindex value is a negative integer.
1828            Some(tab_index) if tab_index < 0 => return FocusableAreaKind::Click,
1829            Some(_) => return FocusableAreaKind::Click | FocusableAreaKind::Sequential,
1830            None => {},
1831        }
1832
1833        // From <https://html.spec.whatwg.org/multipage/#tabindex-value>
1834        // > If the value is null
1835        // > ...
1836        // > Modulo platform conventions, it is suggested that the following elements should be
1837        // > considered as focusable areas and be sequentially focusable:
1838        let is_focusable_area_due_to_type = match node.type_id() {
1839            // >  - a elements that have an href attribute
1840            NodeTypeId::Element(ElementTypeId::HTMLElement(
1841                HTMLElementTypeId::HTMLAnchorElement,
1842            )) => self.has_attribute(&local_name!("href")),
1843
1844            // >  - input elements whose type attribute are not in the Hidden state
1845            // >  - button elements
1846            // >  - select elements
1847            // >  - textarea elements
1848            // >  - Navigable containers
1849            //
1850            // Note: the `hidden` attribute is checked above for all elements.
1851            NodeTypeId::Element(ElementTypeId::HTMLElement(
1852                HTMLElementTypeId::HTMLInputElement |
1853                HTMLElementTypeId::HTMLButtonElement |
1854                HTMLElementTypeId::HTMLSelectElement |
1855                HTMLElementTypeId::HTMLTextAreaElement |
1856                HTMLElementTypeId::HTMLIFrameElement,
1857            )) => true,
1858            _ => {
1859                // >  - summary elements that are the first summary element child of a details element
1860                // >  - Editing hosts
1861                // > -  Elements with a draggable attribute set, if that would enable the user agent to allow
1862                // >    the user to begin drag operations for those elements without the use of a pointing device
1863                self.downcast::<HTMLElement>()
1864                    .is_some_and(|html_element| html_element.is_a_summary_for_its_parent_details()) ||
1865                    self.is_editing_host() ||
1866                    self.get_string_attribute(&local_name!("draggable")) == "true"
1867            },
1868        };
1869
1870        if is_focusable_area_due_to_type {
1871            return FocusableAreaKind::Click | FocusableAreaKind::Sequential;
1872        }
1873
1874        // > The scrollable regions of elements that are being rendered and are not inert.
1875        //
1876        // Note that these kind of focusable areas are only focusable via the keyboard.
1877        //
1878        // TODO: Handle inert.
1879        if self
1880            .upcast::<Node>()
1881            .effective_overflow()
1882            .is_some_and(|axes_overflow| {
1883                // This is checking whether there is an input event scrollable overflow value in
1884                // a given axis and also overflow in that same axis.
1885                (matches!(axes_overflow.x, Overflow::Auto | Overflow::Scroll) &&
1886                    self.ScrollWidth() > self.ClientWidth()) ||
1887                    (matches!(axes_overflow.y, Overflow::Auto | Overflow::Scroll) &&
1888                        self.ScrollHeight() > self.ClientHeight())
1889            })
1890        {
1891            return FocusableAreaKind::Sequential;
1892        }
1893
1894        Default::default()
1895    }
1896
1897    /// <https://html.spec.whatwg.org/multipage/#sequentially-focusable>.
1898    pub(crate) fn is_sequentially_focusable(&self) -> bool {
1899        self.focusable_area_kind()
1900            .contains(FocusableAreaKind::Sequential)
1901    }
1902
1903    /// <https://html.spec.whatwg.org/multipage/#click-focusable>
1904    pub(crate) fn is_click_focusable(&self) -> bool {
1905        self.focusable_area_kind()
1906            .contains(FocusableAreaKind::Click)
1907    }
1908
1909    /// <https://html.spec.whatwg.org/multipage/#focusable-area>
1910    pub(crate) fn is_focusable_area(&self) -> bool {
1911        !self.focusable_area_kind().is_empty()
1912    }
1913
1914    /// Returns the focusable shadow host if this is a text control inner editor.
1915    /// This is a workaround for the focus delegation of shadow DOM and should be
1916    /// used only to delegate focusable inner editor of [HTMLInputElement] and
1917    /// [HTMLTextAreaElement].
1918    pub(crate) fn find_click_focusable_shadow_host_if_necessary(&self) -> Option<DomRoot<Element>> {
1919        if self.is_click_focusable() {
1920            return Some(DomRoot::from_ref(self));
1921        }
1922
1923        if self.upcast::<Node>().implemented_pseudo_element() ==
1924            Some(PseudoElement::ServoTextControlInnerEditor)
1925        {
1926            // The containing shadow host might not be a focusable area if it is disabled.
1927            let containing_shadow_host = self
1928                .containing_shadow_root()
1929                .map(|root| root.Host())
1930                .expect("Text control inner shadow DOM should always have a shadow host.");
1931            if !containing_shadow_host.is_click_focusable() {
1932                return None;
1933            }
1934            return Some(containing_shadow_host);
1935        }
1936
1937        None
1938    }
1939
1940    pub(crate) fn is_actually_disabled(&self) -> bool {
1941        let node = self.upcast::<Node>();
1942        match node.type_id() {
1943            NodeTypeId::Element(ElementTypeId::HTMLElement(
1944                HTMLElementTypeId::HTMLButtonElement,
1945            )) |
1946            NodeTypeId::Element(ElementTypeId::HTMLElement(
1947                HTMLElementTypeId::HTMLInputElement,
1948            )) |
1949            NodeTypeId::Element(ElementTypeId::HTMLElement(
1950                HTMLElementTypeId::HTMLSelectElement,
1951            )) |
1952            NodeTypeId::Element(ElementTypeId::HTMLElement(
1953                HTMLElementTypeId::HTMLTextAreaElement,
1954            )) |
1955            NodeTypeId::Element(ElementTypeId::HTMLElement(
1956                HTMLElementTypeId::HTMLOptionElement,
1957            )) => self.disabled_state(),
1958            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => {
1959                self.downcast::<HTMLElement>()
1960                    .unwrap()
1961                    .is_form_associated_custom_element() &&
1962                    self.disabled_state()
1963            },
1964            // TODO:
1965            // an optgroup element that has a disabled attribute
1966            // a menuitem element that has a disabled attribute
1967            // a fieldset element that is a disabled fieldset
1968            _ => false,
1969        }
1970    }
1971
1972    #[allow(clippy::too_many_arguments)]
1973    pub(crate) fn push_new_attribute(
1974        &self,
1975        local_name: LocalName,
1976        value: AttrValue,
1977        name: LocalName,
1978        namespace: Namespace,
1979        prefix: Option<Prefix>,
1980        reason: AttributeMutationReason,
1981        can_gc: CanGc,
1982    ) {
1983        let attr = Attr::new(
1984            &self.node.owner_doc(),
1985            local_name,
1986            value,
1987            name,
1988            namespace,
1989            prefix,
1990            Some(self),
1991            can_gc,
1992        );
1993        self.push_attribute(&attr, reason, can_gc);
1994    }
1995
1996    /// <https://dom.spec.whatwg.org/#handle-attribute-changes>
1997    fn handle_attribute_changes(
1998        &self,
1999        attr: &Attr,
2000        old_value: Option<&AttrValue>,
2001        new_value: Option<DOMString>,
2002        reason: AttributeMutationReason,
2003        can_gc: CanGc,
2004    ) {
2005        let old_value_string = old_value.map(|old_value| DOMString::from(&**old_value));
2006        // Step 1. Queue a mutation record of "attributes" for element with attribute’s local name,
2007        // attribute’s namespace, oldValue, « », « », null, and null.
2008        let name = attr.local_name().clone();
2009        let namespace = attr.namespace().clone();
2010        let mutation = LazyCell::new(|| Mutation::Attribute {
2011            name: name.clone(),
2012            namespace: namespace.clone(),
2013            old_value: old_value_string.clone(),
2014        });
2015        MutationObserver::queue_a_mutation_record(&self.node, mutation);
2016
2017        // Avoid double borrow
2018        let has_new_value = new_value.is_some();
2019
2020        // Step 2. If element is custom, then enqueue a custom element callback reaction with element,
2021        // callback name "attributeChangedCallback", and « attribute’s local name, oldValue, newValue, attribute’s namespace ».
2022        if self.is_custom() {
2023            let reaction = CallbackReaction::AttributeChanged(
2024                attr.local_name().clone(),
2025                old_value_string,
2026                new_value,
2027                attr.namespace().clone(),
2028            );
2029            ScriptThread::enqueue_callback_reaction(self, reaction, None);
2030        }
2031
2032        // Step 3. Run the attribute change steps with element, attribute’s local name, oldValue, newValue, and attribute’s namespace.
2033        if is_relevant_attribute(attr.namespace(), attr.local_name()) {
2034            let attribute_mutation = if has_new_value {
2035                AttributeMutation::Set(old_value, reason)
2036            } else {
2037                AttributeMutation::Removed
2038            };
2039            vtable_for(self.upcast()).attribute_mutated(attr, attribute_mutation, can_gc);
2040        }
2041    }
2042
2043    /// <https://dom.spec.whatwg.org/#concept-element-attributes-change>
2044    pub(crate) fn change_attribute(&self, attr: &Attr, mut value: AttrValue, can_gc: CanGc) {
2045        // Step 1. Let oldValue be attribute’s value.
2046        //
2047        // Clone to avoid double borrow
2048        let old_value = &attr.value().clone();
2049        // Step 2. Set attribute’s value to value.
2050        self.will_mutate_attr(attr);
2051        attr.swap_value(&mut value);
2052        // Step 3. Handle attribute changes for attribute with attribute’s element, oldValue, and value.
2053        //
2054        // Put on a separate line to avoid double borrow
2055        let new_value = DOMString::from(&**attr.value());
2056        self.handle_attribute_changes(
2057            attr,
2058            Some(old_value),
2059            Some(new_value),
2060            AttributeMutationReason::Directly,
2061            can_gc,
2062        );
2063    }
2064
2065    /// <https://dom.spec.whatwg.org/#concept-element-attributes-append>
2066    pub(crate) fn push_attribute(
2067        &self,
2068        attr: &Attr,
2069        reason: AttributeMutationReason,
2070        can_gc: CanGc,
2071    ) {
2072        // Step 2. Set attribute’s element to element.
2073        //
2074        // Handled by callers of this function and asserted here.
2075        assert!(attr.GetOwnerElement().as_deref() == Some(self));
2076        // Step 3. Set attribute’s node document to element’s node document.
2077        //
2078        // Handled by callers of this function and asserted here.
2079        assert!(attr.upcast::<Node>().owner_doc() == self.node.owner_doc());
2080        // Step 1. Append attribute to element’s attribute list.
2081        self.will_mutate_attr(attr);
2082        self.attrs.borrow_mut().push(Dom::from_ref(attr));
2083        // Step 4. Handle attribute changes for attribute with element, null, and attribute’s value.
2084        //
2085        // Put on a separate line to avoid double borrow
2086        let new_value = DOMString::from(&**attr.value());
2087        self.handle_attribute_changes(attr, None, Some(new_value), reason, can_gc);
2088    }
2089
2090    /// This is the inner logic for:
2091    /// <https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace>
2092    ///
2093    /// In addition to taking a namespace argument, this version does not require the attribute
2094    /// to be lowercase ASCII, in accordance with the specification.
2095    pub(crate) fn get_attribute_with_namespace(
2096        &self,
2097        namespace: &Namespace,
2098        local_name: &LocalName,
2099    ) -> Option<DomRoot<Attr>> {
2100        self.attrs
2101            .borrow()
2102            .iter()
2103            .find(|attr| attr.local_name() == local_name && attr.namespace() == namespace)
2104            .map(|js| DomRoot::from_ref(&**js))
2105    }
2106
2107    /// This is the inner logic for:
2108    /// <https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name>
2109    ///
2110    /// Callers should convert the `LocalName` to ASCII lowercase before calling.
2111    pub(crate) fn get_attribute(&self, local_name: &LocalName) -> Option<DomRoot<Attr>> {
2112        debug_assert_eq!(
2113            *local_name,
2114            local_name.to_ascii_lowercase(),
2115            "All namespace-less attribute accesses should use a lowercase ASCII name"
2116        );
2117        self.get_attribute_with_namespace(&ns!(), local_name)
2118    }
2119
2120    /// <https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name>
2121    pub(crate) fn get_attribute_by_name(&self, name: DOMString) -> Option<DomRoot<Attr>> {
2122        let name = &self.parsed_name(name);
2123        let maybe_attribute = self
2124            .attrs
2125            .borrow()
2126            .iter()
2127            .find(|a| a.name() == name)
2128            .map(|js| DomRoot::from_ref(&**js));
2129        fn id_and_name_must_be_atoms(name: &LocalName, maybe_attr: &Option<DomRoot<Attr>>) -> bool {
2130            if *name == local_name!("id") || *name == local_name!("name") {
2131                match maybe_attr {
2132                    None => true,
2133                    Some(attr) => matches!(*attr.value(), AttrValue::Atom(_)),
2134                }
2135            } else {
2136                true
2137            }
2138        }
2139        debug_assert!(id_and_name_must_be_atoms(name, &maybe_attribute));
2140        maybe_attribute
2141    }
2142
2143    pub(crate) fn set_attribute_from_parser(
2144        &self,
2145        qname: QualName,
2146        value: DOMString,
2147        prefix: Option<Prefix>,
2148        can_gc: CanGc,
2149    ) {
2150        // Don't set if the attribute already exists, so we can handle add_attrs_if_missing
2151        if self
2152            .attrs
2153            .borrow()
2154            .iter()
2155            .any(|a| *a.local_name() == qname.local && *a.namespace() == qname.ns)
2156        {
2157            return;
2158        }
2159
2160        let name = match prefix {
2161            None => qname.local.clone(),
2162            Some(ref prefix) => {
2163                let name = format!("{}:{}", &**prefix, &*qname.local);
2164                LocalName::from(name)
2165            },
2166        };
2167        let value = self.parse_attribute(&qname.ns, &qname.local, value);
2168        self.push_new_attribute(
2169            qname.local,
2170            value,
2171            name,
2172            qname.ns,
2173            prefix,
2174            AttributeMutationReason::ByParser,
2175            can_gc,
2176        );
2177    }
2178
2179    pub(crate) fn set_attribute(&self, name: &LocalName, value: AttrValue, can_gc: CanGc) {
2180        debug_assert_eq!(
2181            *name,
2182            name.to_ascii_lowercase(),
2183            "All attribute accesses should use a lowercase ASCII name"
2184        );
2185        debug_assert!(!name.contains(':'));
2186
2187        self.set_first_matching_attribute(
2188            name.clone(),
2189            value,
2190            name.clone(),
2191            ns!(),
2192            None,
2193            |attr| attr.local_name() == name,
2194            can_gc,
2195        );
2196    }
2197
2198    pub(crate) fn set_attribute_with_namespace(
2199        &self,
2200        cx: &mut js::context::JSContext,
2201        local_name: LocalName,
2202        value: AttrValue,
2203        name: LocalName,
2204        namespace: Namespace,
2205        prefix: Option<Prefix>,
2206    ) {
2207        self.set_first_matching_attribute(
2208            local_name.clone(),
2209            value,
2210            name,
2211            namespace.clone(),
2212            prefix,
2213            |attr| *attr.local_name() == local_name && *attr.namespace() == namespace,
2214            CanGc::from_cx(cx),
2215        );
2216    }
2217
2218    /// <https://dom.spec.whatwg.org/#concept-element-attributes-set-value>
2219    #[allow(clippy::too_many_arguments)]
2220    fn set_first_matching_attribute<F>(
2221        &self,
2222        local_name: LocalName,
2223        value: AttrValue,
2224        name: LocalName,
2225        namespace: Namespace,
2226        prefix: Option<Prefix>,
2227        find: F,
2228        can_gc: CanGc,
2229    ) where
2230        F: Fn(&Attr) -> bool,
2231    {
2232        // Step 1. Let attribute be the result of getting an attribute given namespace, localName, and element.
2233        let attr = self
2234            .attrs
2235            .borrow()
2236            .iter()
2237            .find(|attr| find(attr))
2238            .map(|js| DomRoot::from_ref(&**js));
2239        if let Some(attr) = attr {
2240            // Step 3. Change attribute to value.
2241            self.will_mutate_attr(&attr);
2242            self.change_attribute(&attr, value, can_gc);
2243        } else {
2244            // Step 2. If attribute is null, create an attribute whose namespace is namespace,
2245            // namespace prefix is prefix, local name is localName, value is value,
2246            // and node document is element’s node document,
2247            // then append this attribute to element, and then return.
2248            self.push_new_attribute(
2249                local_name,
2250                value,
2251                name,
2252                namespace,
2253                prefix,
2254                AttributeMutationReason::Directly,
2255                can_gc,
2256            );
2257        };
2258    }
2259
2260    pub(crate) fn parse_attribute(
2261        &self,
2262        namespace: &Namespace,
2263        local_name: &LocalName,
2264        value: DOMString,
2265    ) -> AttrValue {
2266        if is_relevant_attribute(namespace, local_name) {
2267            vtable_for(self.upcast()).parse_plain_attribute(local_name, value)
2268        } else {
2269            AttrValue::String(value.into())
2270        }
2271    }
2272
2273    pub(crate) fn remove_attribute(
2274        &self,
2275        namespace: &Namespace,
2276        local_name: &LocalName,
2277        can_gc: CanGc,
2278    ) -> Option<DomRoot<Attr>> {
2279        self.remove_first_matching_attribute(
2280            |attr| attr.namespace() == namespace && attr.local_name() == local_name,
2281            can_gc,
2282        )
2283    }
2284
2285    pub(crate) fn remove_attribute_by_name(
2286        &self,
2287        name: &LocalName,
2288        can_gc: CanGc,
2289    ) -> Option<DomRoot<Attr>> {
2290        self.remove_first_matching_attribute(|attr| attr.name() == name, can_gc)
2291    }
2292
2293    /// <https://dom.spec.whatwg.org/#concept-element-attributes-remove>
2294    fn remove_first_matching_attribute<F>(&self, find: F, can_gc: CanGc) -> Option<DomRoot<Attr>>
2295    where
2296        F: Fn(&Attr) -> bool,
2297    {
2298        let idx = self.attrs.borrow().iter().position(|attr| find(attr));
2299        idx.map(|idx| {
2300            let attr = DomRoot::from_ref(&*(*self.attrs.borrow())[idx]);
2301
2302            // Step 2. Remove attribute from element’s attribute list.
2303            self.will_mutate_attr(&attr);
2304            self.attrs.borrow_mut().remove(idx);
2305            // Step 3. Set attribute’s element to null.
2306            attr.set_owner(None);
2307            // Step 4. Handle attribute changes for attribute with element, attribute’s value, and null.
2308            self.handle_attribute_changes(
2309                &attr,
2310                Some(&attr.value()),
2311                None,
2312                AttributeMutationReason::Directly,
2313                can_gc,
2314            );
2315
2316            attr
2317        })
2318    }
2319
2320    pub(crate) fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
2321        self.get_attribute(&local_name!("class"))
2322            .is_some_and(|attr| {
2323                attr.value()
2324                    .as_tokens()
2325                    .iter()
2326                    .any(|atom| case_sensitivity.eq_atom(name, atom))
2327            })
2328    }
2329
2330    pub(crate) fn is_part(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
2331        self.get_attribute(&LocalName::from("part"))
2332            .is_some_and(|attr| {
2333                attr.value()
2334                    .as_tokens()
2335                    .iter()
2336                    .any(|atom| case_sensitivity.eq_atom(name, atom))
2337            })
2338    }
2339
2340    pub(crate) fn set_atomic_attribute(
2341        &self,
2342        local_name: &LocalName,
2343        value: DOMString,
2344        can_gc: CanGc,
2345    ) {
2346        self.set_attribute(local_name, AttrValue::from_atomic(value.into()), can_gc);
2347    }
2348
2349    pub(crate) fn has_attribute(&self, local_name: &LocalName) -> bool {
2350        debug_assert_eq!(
2351            *local_name,
2352            local_name.to_ascii_lowercase(),
2353            "All attribute accesses should use a lowercase ASCII name"
2354        );
2355        debug_assert!(!local_name.contains(':'));
2356        self.attrs
2357            .borrow()
2358            .iter()
2359            .any(|attr| attr.local_name() == local_name && attr.namespace() == &ns!())
2360    }
2361
2362    pub(crate) fn set_bool_attribute(&self, local_name: &LocalName, value: bool, can_gc: CanGc) {
2363        if self.has_attribute(local_name) == value {
2364            return;
2365        }
2366        if value {
2367            self.set_string_attribute(local_name, DOMString::new(), can_gc);
2368        } else {
2369            self.remove_attribute(&ns!(), local_name, can_gc);
2370        }
2371    }
2372
2373    pub(crate) fn get_url_attribute(&self, local_name: &LocalName) -> USVString {
2374        let Some(attribute) = self.get_attribute(local_name) else {
2375            return Default::default();
2376        };
2377        let value = &**attribute.value();
2378        // XXXManishearth this doesn't handle `javascript:` urls properly
2379        self.owner_document()
2380            .base_url()
2381            .join(value)
2382            .map(|parsed| USVString(parsed.into_string()))
2383            .unwrap_or_else(|_| USVString(value.to_owned()))
2384    }
2385
2386    pub(crate) fn set_url_attribute(
2387        &self,
2388        local_name: &LocalName,
2389        value: USVString,
2390        can_gc: CanGc,
2391    ) {
2392        self.set_attribute(local_name, AttrValue::String(value.to_string()), can_gc);
2393    }
2394
2395    pub(crate) fn get_trusted_type_url_attribute(
2396        &self,
2397        local_name: &LocalName,
2398    ) -> TrustedScriptURLOrUSVString {
2399        let Some(attribute) = self.get_attribute(local_name) else {
2400            return TrustedScriptURLOrUSVString::USVString(USVString::default());
2401        };
2402        let value = &**attribute.value();
2403        // XXXManishearth this doesn't handle `javascript:` urls properly
2404        self.owner_document()
2405            .base_url()
2406            .join(value)
2407            .map(|parsed| TrustedScriptURLOrUSVString::USVString(USVString(parsed.into_string())))
2408            .unwrap_or_else(|_| TrustedScriptURLOrUSVString::USVString(USVString(value.to_owned())))
2409    }
2410
2411    pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString {
2412        TrustedHTMLOrString::String(self.get_string_attribute(local_name))
2413    }
2414
2415    pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString {
2416        self.get_attribute(local_name)
2417            .map(|attribute| attribute.Value())
2418            .unwrap_or_default()
2419    }
2420
2421    pub(crate) fn set_string_attribute(
2422        &self,
2423        local_name: &LocalName,
2424        value: DOMString,
2425        can_gc: CanGc,
2426    ) {
2427        self.set_attribute(local_name, AttrValue::String(value.into()), can_gc);
2428    }
2429
2430    /// Used for string attribute reflections where absence of the attribute returns `null`,
2431    /// e.g. `element.ariaLabel` returning `null` when the `aria-label` attribute is absent.
2432    fn get_nullable_string_attribute(&self, local_name: &LocalName) -> Option<DOMString> {
2433        if self.has_attribute(local_name) {
2434            Some(self.get_string_attribute(local_name))
2435        } else {
2436            None
2437        }
2438    }
2439
2440    /// Used for string attribute reflections where setting `null`/`undefined` removes the
2441    /// attribute, e.g. `element.ariaLabel = null` removing the `aria-label` attribute.
2442    fn set_nullable_string_attribute(
2443        &self,
2444        cx: &mut JSContext,
2445        local_name: &LocalName,
2446        value: Option<DOMString>,
2447    ) {
2448        match value {
2449            Some(val) => {
2450                self.set_string_attribute(local_name, val, CanGc::from_cx(cx));
2451            },
2452            None => {
2453                self.remove_attribute(&ns!(), local_name, CanGc::from_cx(cx));
2454            },
2455        }
2456    }
2457
2458    pub(crate) fn get_tokenlist_attribute(&self, local_name: &LocalName) -> Vec<Atom> {
2459        self.get_attribute(local_name)
2460            .map(|attribute| attribute.value().as_tokens().to_vec())
2461            .unwrap_or_default()
2462    }
2463
2464    pub(crate) fn set_tokenlist_attribute(
2465        &self,
2466        local_name: &LocalName,
2467        value: DOMString,
2468        can_gc: CanGc,
2469    ) {
2470        self.set_attribute(
2471            local_name,
2472            AttrValue::from_serialized_tokenlist(value.into()),
2473            can_gc,
2474        );
2475    }
2476
2477    pub(crate) fn set_atomic_tokenlist_attribute(
2478        &self,
2479        local_name: &LocalName,
2480        tokens: Vec<Atom>,
2481        can_gc: CanGc,
2482    ) {
2483        self.set_attribute(local_name, AttrValue::from_atomic_tokens(tokens), can_gc);
2484    }
2485
2486    pub(crate) fn get_int_attribute(&self, local_name: &LocalName, default: i32) -> i32 {
2487        match self.get_attribute(local_name) {
2488            Some(ref attribute) => match *attribute.value() {
2489                AttrValue::Int(_, value) => value,
2490                _ => unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute"),
2491            },
2492            None => default,
2493        }
2494    }
2495
2496    pub(crate) fn set_int_attribute(&self, local_name: &LocalName, value: i32, can_gc: CanGc) {
2497        self.set_attribute(local_name, AttrValue::Int(value.to_string(), value), can_gc);
2498    }
2499
2500    pub(crate) fn get_uint_attribute(&self, local_name: &LocalName, default: u32) -> u32 {
2501        match self.get_attribute(local_name) {
2502            Some(ref attribute) => match *attribute.value() {
2503                AttrValue::UInt(_, value) => value,
2504                _ => unreachable!("Expected an AttrValue::UInt: implement parse_plain_attribute"),
2505            },
2506            None => default,
2507        }
2508    }
2509    pub(crate) fn set_uint_attribute(&self, local_name: &LocalName, value: u32, can_gc: CanGc) {
2510        self.set_attribute(
2511            local_name,
2512            AttrValue::UInt(value.to_string(), value),
2513            can_gc,
2514        );
2515    }
2516
2517    pub(crate) fn will_mutate_attr(&self, attr: &Attr) {
2518        let node = self.upcast::<Node>();
2519        node.owner_doc().element_attr_will_change(self, attr);
2520    }
2521
2522    /// <https://html.spec.whatwg.org/multipage/#the-style-attribute>
2523    fn update_style_attribute(&self, attr: &Attr, mutation: AttributeMutation) {
2524        let doc = self.upcast::<Node>().owner_doc();
2525        // Modifying the `style` attribute might change style.
2526        *self.style_attribute.borrow_mut() = match mutation {
2527            AttributeMutation::Set(..) => {
2528                // This is the fast path we use from
2529                // CSSStyleDeclaration.
2530                //
2531                // Juggle a bit to keep the borrow checker happy
2532                // while avoiding the extra clone.
2533                let is_declaration = matches!(*attr.value(), AttrValue::Declaration(..));
2534
2535                let block = if is_declaration {
2536                    let mut value = AttrValue::String(String::new());
2537                    attr.swap_value(&mut value);
2538                    let (serialization, block) = match value {
2539                        AttrValue::Declaration(s, b) => (s, b),
2540                        _ => unreachable!(),
2541                    };
2542                    let mut value = AttrValue::String(serialization);
2543                    attr.swap_value(&mut value);
2544                    block
2545                } else {
2546                    let win = self.owner_window();
2547                    let source = &**attr.value();
2548                    let global = &self.owner_global();
2549                    // However, if the Should element's inline behavior be blocked by
2550                    // Content Security Policy? algorithm returns "Blocked" when executed
2551                    // upon the attribute's element, "style attribute", and the attribute's value,
2552                    // then the style rules defined in the attribute's value must not be applied to the element. [CSP]
2553                    if global
2554                        .get_csp_list()
2555                        .should_elements_inline_type_behavior_be_blocked(
2556                            global,
2557                            self,
2558                            InlineCheckType::StyleAttribute,
2559                            source,
2560                            doc.get_current_parser_line(),
2561                        )
2562                    {
2563                        return;
2564                    }
2565                    Arc::new(doc.style_shared_lock().wrap(parse_style_attribute(
2566                        source,
2567                        &UrlExtraData(doc.base_url().get_arc()),
2568                        Some(win.css_error_reporter()),
2569                        doc.quirks_mode(),
2570                        CssRuleType::Style,
2571                    )))
2572                };
2573
2574                Some(block)
2575            },
2576            AttributeMutation::Removed => None,
2577        };
2578    }
2579
2580    /// <https://dom.spec.whatwg.org/#concept-element-attributes-set>
2581    /// including steps of
2582    /// <https://dom.spec.whatwg.org/#concept-element-attributes-replace>
2583    fn set_attribute_node(
2584        &self,
2585        cx: &mut js::context::JSContext,
2586        attr: &Attr,
2587    ) -> Fallible<Option<DomRoot<Attr>>> {
2588        // Step 1. Let verifiedValue be the result of calling
2589        // get Trusted Types-compliant attribute value with attr’s local name,
2590        // attr’s namespace, element, and attr’s value. [TRUSTED-TYPES]
2591        let verified_value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
2592            self.namespace(),
2593            self.local_name(),
2594            attr.local_name(),
2595            Some(attr.namespace()),
2596            TrustedTypeOrString::String(attr.Value()),
2597            &self.owner_global(),
2598            CanGc::from_cx(cx),
2599        )?;
2600
2601        // Step 2. If attr’s element is neither null nor element,
2602        // throw an "InUseAttributeError" DOMException.
2603        if let Some(owner) = attr.GetOwnerElement() {
2604            if &*owner != self {
2605                return Err(Error::InUseAttribute(None));
2606            }
2607        }
2608
2609        let vtable = vtable_for(self.upcast());
2610
2611        // Step 5. Set attr’s value to verifiedValue.
2612        //
2613        // This ensures that the attribute is of the expected kind for this
2614        // specific element. This is inefficient and should probably be done
2615        // differently.
2616        attr.swap_value(
2617            &mut vtable.parse_plain_attribute(attr.local_name(), verified_value.clone()),
2618        );
2619
2620        // Step 3. Let oldAttr be the result of getting an attribute given attr’s namespace, attr’s local name, and element.
2621        let position = self.attrs.borrow().iter().position(|old_attr| {
2622            attr.namespace() == old_attr.namespace() && attr.local_name() == old_attr.local_name()
2623        });
2624
2625        let old_attr = if let Some(position) = position {
2626            let old_attr = DomRoot::from_ref(&*self.attrs.borrow()[position]);
2627
2628            // Step 4. If oldAttr is attr, return attr.
2629            if &*old_attr == attr {
2630                return Ok(Some(DomRoot::from_ref(attr)));
2631            }
2632
2633            // Step 6. If oldAttr is non-null, then replace oldAttr with attr.
2634            //
2635            // Start of steps for https://dom.spec.whatwg.org/#concept-element-attributes-replace
2636
2637            // Step 1. Let element be oldAttribute’s element.
2638            //
2639            // Skipped, as that points to self.
2640
2641            // Step 2. Replace oldAttribute by newAttribute in element’s attribute list.
2642            self.will_mutate_attr(attr);
2643            self.attrs.borrow_mut()[position] = Dom::from_ref(attr);
2644            // Step 3. Set newAttribute’s element to element.
2645            attr.set_owner(Some(self));
2646            // Step 4. Set newAttribute’s node document to element’s node document.
2647            attr.upcast::<Node>().set_owner_doc(&self.node.owner_doc());
2648            // Step 5. Set oldAttribute’s element to null.
2649            old_attr.set_owner(None);
2650            // Step 6. Handle attribute changes for oldAttribute with element, oldAttribute’s value, and newAttribute’s value.
2651            self.handle_attribute_changes(
2652                attr,
2653                Some(&old_attr.value()),
2654                Some(verified_value),
2655                AttributeMutationReason::Directly,
2656                CanGc::from_cx(cx),
2657            );
2658
2659            Some(old_attr)
2660        } else {
2661            // Step 7. Otherwise, append attr to element.
2662            attr.set_owner(Some(self));
2663            attr.upcast::<Node>().set_owner_doc(&self.node.owner_doc());
2664            self.push_attribute(attr, AttributeMutationReason::Directly, CanGc::from_cx(cx));
2665
2666            None
2667        };
2668
2669        // Step 8. Return oldAttr.
2670        Ok(old_attr)
2671    }
2672
2673    /// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
2674    pub(crate) fn update_nonce_internal_slot(&self, nonce: String) {
2675        self.ensure_rare_data().cryptographic_nonce = nonce;
2676    }
2677
2678    /// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
2679    pub(crate) fn nonce_value(&self) -> String {
2680        match self.rare_data().as_ref() {
2681            None => String::new(),
2682            Some(rare_data) => rare_data.cryptographic_nonce.clone(),
2683        }
2684    }
2685
2686    /// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
2687    pub(crate) fn update_nonce_post_connection(&self) {
2688        // Whenever an element including HTMLOrSVGElement becomes browsing-context connected,
2689        // the user agent must execute the following steps on the element:
2690        if !self.upcast::<Node>().is_connected_with_browsing_context() {
2691            return;
2692        }
2693        let global = self.owner_global();
2694        // Step 1: Let CSP list be element's shadow-including root's policy container's CSP list.
2695        let csp_list = match global.get_csp_list() {
2696            None => return,
2697            Some(csp_list) => csp_list,
2698        };
2699        // Step 2: If CSP list contains a header-delivered Content Security Policy,
2700        // and element has a nonce content attribute whose value is not the empty string, then:
2701        if !csp_list.contains_a_header_delivered_content_security_policy() ||
2702            self.get_string_attribute(&local_name!("nonce")).is_empty()
2703        {
2704            return;
2705        }
2706        // Step 2.1: Let nonce be element's [[CryptographicNonce]].
2707        let nonce = self.nonce_value();
2708        // Step 2.2: Set an attribute value for element using "nonce" and the empty string.
2709        self.set_string_attribute(&local_name!("nonce"), "".into(), CanGc::note());
2710        // Step 2.3: Set element's [[CryptographicNonce]] to nonce.
2711        self.update_nonce_internal_slot(nonce);
2712    }
2713
2714    /// <https://www.w3.org/TR/CSP/#is-element-nonceable>
2715    pub(crate) fn is_nonceable(&self) -> bool {
2716        // Step 1: If element does not have an attribute named "nonce", return "Not Nonceable".
2717        if !self.has_attribute(&local_name!("nonce")) {
2718            return false;
2719        }
2720        // Step 2: If element is a script element, then for each attribute of element’s attribute list:
2721        if self.downcast::<HTMLScriptElement>().is_some() {
2722            for attr in self.attrs().iter() {
2723                // Step 2.1: If attribute’s name contains an ASCII case-insensitive match
2724                // for "<script" or "<style", return "Not Nonceable".
2725                let attr_name = attr.name().to_ascii_lowercase();
2726                if attr_name.contains("<script") || attr_name.contains("<style") {
2727                    return false;
2728                }
2729                // Step 2.2: If attribute’s value contains an ASCII case-insensitive match
2730                // for "<script" or "<style", return "Not Nonceable".
2731                let attr_value = attr.value().to_ascii_lowercase();
2732                if attr_value.contains("<script") || attr_value.contains("<style") {
2733                    return false;
2734                }
2735            }
2736        }
2737        // Step 3: If element had a duplicate-attribute parse error during tokenization, return "Not Nonceable".
2738        // TODO(https://github.com/servo/servo/issues/4577 and https://github.com/whatwg/html/issues/3257):
2739        // Figure out how to retrieve this information from the parser
2740        // Step 4: Return "Nonceable".
2741        true
2742    }
2743
2744    // https://dom.spec.whatwg.org/#insert-adjacent
2745    pub(crate) fn insert_adjacent(
2746        &self,
2747        where_: AdjacentPosition,
2748        node: &Node,
2749        can_gc: CanGc,
2750    ) -> Fallible<Option<DomRoot<Node>>> {
2751        let self_node = self.upcast::<Node>();
2752        match where_ {
2753            AdjacentPosition::BeforeBegin => {
2754                if let Some(parent) = self_node.GetParentNode() {
2755                    Node::pre_insert(node, &parent, Some(self_node), can_gc).map(Some)
2756                } else {
2757                    Ok(None)
2758                }
2759            },
2760            AdjacentPosition::AfterBegin => Node::pre_insert(
2761                node,
2762                self_node,
2763                self_node.GetFirstChild().as_deref(),
2764                can_gc,
2765            )
2766            .map(Some),
2767            AdjacentPosition::BeforeEnd => {
2768                Node::pre_insert(node, self_node, None, can_gc).map(Some)
2769            },
2770            AdjacentPosition::AfterEnd => {
2771                if let Some(parent) = self_node.GetParentNode() {
2772                    Node::pre_insert(node, &parent, self_node.GetNextSibling().as_deref(), can_gc)
2773                        .map(Some)
2774                } else {
2775                    Ok(None)
2776                }
2777            },
2778        }
2779    }
2780
2781    /// <https://drafts.csswg.org/cssom-view/#dom-element-scroll>
2782    ///
2783    /// TODO(stevennovaryo): Need to update the scroll API to follow the spec since it is
2784    /// quite outdated.
2785    pub(crate) fn scroll(&self, x: f64, y: f64, behavior: ScrollBehavior) {
2786        // Step 1.2 or 2.3
2787        let x = if x.is_finite() { x } else { 0.0 } as f32;
2788        let y = if y.is_finite() { y } else { 0.0 } as f32;
2789
2790        let node = self.upcast::<Node>();
2791
2792        // Step 3
2793        let doc = node.owner_doc();
2794
2795        // Step 4
2796        if !doc.is_fully_active() {
2797            return;
2798        }
2799
2800        // Step 5
2801        let win = match doc.GetDefaultView() {
2802            None => return,
2803            Some(win) => win,
2804        };
2805
2806        // Step 7
2807        if *self.root_element() == *self {
2808            if doc.quirks_mode() != QuirksMode::Quirks {
2809                win.scroll(x, y, behavior);
2810            }
2811
2812            return;
2813        }
2814
2815        // Step 9
2816        if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() &&
2817            doc.quirks_mode() == QuirksMode::Quirks &&
2818            !self.is_potentially_scrollable_body()
2819        {
2820            win.scroll(x, y, behavior);
2821            return;
2822        }
2823
2824        // Step 10
2825        if !self.has_scrolling_box() {
2826            return;
2827        }
2828
2829        // Step 11
2830        win.scroll_an_element(self, x, y, behavior);
2831    }
2832
2833    /// <https://html.spec.whatwg.org/multipage/#fragment-parsing-algorithm-steps>
2834    pub(crate) fn parse_fragment(
2835        &self,
2836        markup: DOMString,
2837        cx: &mut js::context::JSContext,
2838    ) -> Fallible<DomRoot<DocumentFragment>> {
2839        // Steps 1-2.
2840        // TODO(#11995): XML case.
2841        let new_children = ServoParser::parse_html_fragment(self, markup, false, cx);
2842        // Step 3.
2843        // See https://github.com/w3c/DOM-Parsing/issues/61.
2844        let context_document = {
2845            if let Some(template) = self.downcast::<HTMLTemplateElement>() {
2846                template
2847                    .Content(CanGc::from_cx(cx))
2848                    .upcast::<Node>()
2849                    .owner_doc()
2850            } else {
2851                self.owner_document()
2852            }
2853        };
2854        let fragment = DocumentFragment::new(&context_document, CanGc::from_cx(cx));
2855        // Step 4.
2856        for child in new_children {
2857            fragment
2858                .upcast::<Node>()
2859                .AppendChild(&child, CanGc::from_cx(cx))
2860                .unwrap();
2861        }
2862        // Step 5.
2863        Ok(fragment)
2864    }
2865
2866    /// Step 4 of <https://html.spec.whatwg.org/multipage/#dom-element-insertadjacenthtml>
2867    /// and step 6. of <https://html.spec.whatwg.org/multipage/#dom-range-createcontextualfragment>
2868    pub(crate) fn fragment_parsing_context(
2869        cx: &mut JSContext,
2870        owner_doc: &Document,
2871        element: Option<&Self>,
2872    ) -> DomRoot<Self> {
2873        // If context is not an Element or all of the following are true:
2874        match element {
2875            Some(elem)
2876                // context's node document is an HTML document;
2877                // context's local name is "html"; and
2878                // context's namespace is the HTML namespace,
2879                if elem.local_name() != &local_name!("html") ||
2880                    !elem.html_element_in_html_document() =>
2881            {
2882                DomRoot::from_ref(elem)
2883            },
2884            // set context to the result of creating an element
2885            // given this's node document, "body", and the HTML namespace.
2886            _ => Element::create(
2887                QualName::new(None, ns!(html), local_name!("body")),
2888                None,
2889                owner_doc,
2890                ElementCreator::ScriptCreated,
2891                CustomElementCreationMode::Asynchronous,
2892                None,
2893                CanGc::from_cx(cx),
2894            ),
2895        }
2896    }
2897
2898    // https://fullscreen.spec.whatwg.org/#fullscreen-element-ready-check
2899    pub(crate) fn fullscreen_element_ready_check(&self) -> bool {
2900        if !self.is_connected() {
2901            return false;
2902        }
2903        self.owner_document().get_allow_fullscreen()
2904    }
2905
2906    // https://html.spec.whatwg.org/multipage/#home-subtree
2907    pub(crate) fn is_in_same_home_subtree<T>(&self, other: &T) -> bool
2908    where
2909        T: DerivedFrom<Element> + DomObject,
2910    {
2911        let other = other.upcast::<Element>();
2912        self.root_element() == other.root_element()
2913    }
2914
2915    pub(crate) fn get_id(&self) -> Option<Atom> {
2916        self.id_attribute.borrow().clone()
2917    }
2918
2919    pub(crate) fn get_name(&self) -> Option<Atom> {
2920        self.rare_data().as_ref()?.name_attribute.clone()
2921    }
2922
2923    pub(crate) fn get_element_internals(&self) -> Option<DomRoot<ElementInternals>> {
2924        self.rare_data()
2925            .as_ref()?
2926            .element_internals
2927            .as_ref()
2928            .map(|sr| DomRoot::from_ref(&**sr))
2929    }
2930
2931    pub(crate) fn ensure_element_internals(&self, can_gc: CanGc) -> DomRoot<ElementInternals> {
2932        let mut rare_data = self.ensure_rare_data();
2933        DomRoot::from_ref(rare_data.element_internals.get_or_insert_with(|| {
2934            let elem = self
2935                .downcast::<HTMLElement>()
2936                .expect("ensure_element_internals should only be called for an HTMLElement");
2937            Dom::from_ref(&*ElementInternals::new(elem, can_gc))
2938        }))
2939    }
2940
2941    pub(crate) fn outer_html(&self, can_gc: CanGc) -> Fallible<DOMString> {
2942        match self.GetOuterHTML(can_gc)? {
2943            TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(str) => Ok(str),
2944            TrustedHTMLOrNullIsEmptyString::TrustedHTML(_) => unreachable!(),
2945        }
2946    }
2947
2948    pub(crate) fn compute_source_position(&self, line_number: u32) -> SourcePosition {
2949        SourcePosition {
2950            source_file: self.owner_global().get_url().to_string(),
2951            line_number: line_number + 2,
2952            column_number: 0,
2953        }
2954    }
2955
2956    pub(crate) fn explicitly_set_tab_index(&self) -> Option<i32> {
2957        if self.has_attribute(&local_name!("tabindex")) {
2958            Some(self.get_int_attribute(&local_name!("tabindex"), 0))
2959        } else {
2960            None
2961        }
2962    }
2963
2964    /// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
2965    pub(crate) fn tab_index(&self) -> i32 {
2966        // > The tabIndex getter steps are:
2967        // > 1. Let attribute be this's tabindex attribute.
2968        // > 2. If attribute is not null:
2969        // >    1. Let parsedValue be the result of integer parsing attribute's value.
2970        // >    2. If parsedValue is not an error and is within the long range, then return parsedValue.
2971        if let Some(tab_index) = self.explicitly_set_tab_index() {
2972            return tab_index;
2973        }
2974
2975        // > 3. Return 0 if this is an a, area, button, frame, iframe, input, object, select, textarea,
2976        // > or SVG a element, or is a summary element that is a summary for its parent details;
2977        // > otherwise -1.
2978        //
2979        // Note: We do not currently support SVG `a` elements.
2980        if matches!(
2981            self.upcast::<Node>().type_id(),
2982            NodeTypeId::Element(ElementTypeId::HTMLElement(
2983                HTMLElementTypeId::HTMLAnchorElement |
2984                    HTMLElementTypeId::HTMLAreaElement |
2985                    HTMLElementTypeId::HTMLButtonElement |
2986                    HTMLElementTypeId::HTMLFrameElement |
2987                    HTMLElementTypeId::HTMLIFrameElement |
2988                    HTMLElementTypeId::HTMLInputElement |
2989                    HTMLElementTypeId::HTMLObjectElement |
2990                    HTMLElementTypeId::HTMLSelectElement |
2991                    HTMLElementTypeId::HTMLTextAreaElement
2992            ))
2993        ) {
2994            return 0;
2995        }
2996        if self
2997            .downcast::<HTMLElement>()
2998            .is_some_and(|html_element| html_element.is_a_summary_for_its_parent_details())
2999        {
3000            return 0;
3001        }
3002
3003        -1
3004    }
3005
3006    #[inline]
3007    fn insert_selector_flags(&self, flags: ElementSelectorFlags) {
3008        self.selector_flags
3009            .fetch_or(flags.bits(), Ordering::Relaxed);
3010    }
3011
3012    #[inline]
3013    fn get_selector_flags(&self) -> ElementSelectorFlags {
3014        ElementSelectorFlags::from_bits_retain(self.selector_flags.load(Ordering::Relaxed))
3015    }
3016}
3017
3018impl ElementMethods<crate::DomTypeHolder> for Element {
3019    /// <https://dom.spec.whatwg.org/#dom-element-namespaceuri>
3020    fn GetNamespaceURI(&self) -> Option<DOMString> {
3021        Node::namespace_to_string(self.namespace.clone())
3022    }
3023
3024    /// <https://dom.spec.whatwg.org/#dom-element-localname>
3025    fn LocalName(&self) -> DOMString {
3026        // FIXME(ajeffrey): Convert directly from LocalName to DOMString
3027        DOMString::from(&*self.local_name)
3028    }
3029
3030    /// <https://dom.spec.whatwg.org/#dom-element-prefix>
3031    fn GetPrefix(&self) -> Option<DOMString> {
3032        self.prefix.borrow().as_ref().map(|p| DOMString::from(&**p))
3033    }
3034
3035    /// <https://dom.spec.whatwg.org/#dom-element-tagname>
3036    fn TagName(&self) -> DOMString {
3037        let name = self.tag_name.or_init(|| {
3038            let qualified_name = match *self.prefix.borrow() {
3039                Some(ref prefix) => Cow::Owned(format!("{}:{}", &**prefix, &*self.local_name)),
3040                None => Cow::Borrowed(&*self.local_name),
3041            };
3042            if self.html_element_in_html_document() {
3043                LocalName::from(qualified_name.to_ascii_uppercase())
3044            } else {
3045                LocalName::from(qualified_name)
3046            }
3047        });
3048        DOMString::from(&*name)
3049    }
3050
3051    // https://dom.spec.whatwg.org/#dom-element-id
3052    // This always returns a string; if you'd rather see None
3053    // on a null id, call get_id
3054    fn Id(&self) -> DOMString {
3055        self.get_string_attribute(&local_name!("id"))
3056    }
3057
3058    /// <https://dom.spec.whatwg.org/#dom-element-id>
3059    fn SetId(&self, id: DOMString, can_gc: CanGc) {
3060        self.set_atomic_attribute(&local_name!("id"), id, can_gc);
3061    }
3062
3063    /// <https://dom.spec.whatwg.org/#dom-element-classname>
3064    fn ClassName(&self) -> DOMString {
3065        self.get_string_attribute(&local_name!("class"))
3066    }
3067
3068    /// <https://dom.spec.whatwg.org/#dom-element-classname>
3069    fn SetClassName(&self, class: DOMString, can_gc: CanGc) {
3070        self.set_tokenlist_attribute(&local_name!("class"), class, can_gc);
3071    }
3072
3073    /// <https://dom.spec.whatwg.org/#dom-element-classlist>
3074    fn ClassList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
3075        self.class_list
3076            .or_init(|| DOMTokenList::new(self, &local_name!("class"), None, can_gc))
3077    }
3078
3079    // https://dom.spec.whatwg.org/#dom-element-slot
3080    make_getter!(Slot, "slot");
3081
3082    // https://dom.spec.whatwg.org/#dom-element-slot
3083    make_setter!(SetSlot, "slot");
3084
3085    /// <https://dom.spec.whatwg.org/#dom-element-attributes>
3086    fn Attributes(&self, can_gc: CanGc) -> DomRoot<NamedNodeMap> {
3087        self.attr_list
3088            .or_init(|| NamedNodeMap::new(&self.owner_window(), self, can_gc))
3089    }
3090
3091    /// <https://dom.spec.whatwg.org/#dom-element-hasattributes>
3092    fn HasAttributes(&self) -> bool {
3093        !self.attrs.borrow().is_empty()
3094    }
3095
3096    /// <https://dom.spec.whatwg.org/#dom-element-getattributenames>
3097    fn GetAttributeNames(&self) -> Vec<DOMString> {
3098        self.attrs.borrow().iter().map(|attr| attr.Name()).collect()
3099    }
3100
3101    /// <https://dom.spec.whatwg.org/#dom-element-getattribute>
3102    fn GetAttribute(&self, name: DOMString) -> Option<DOMString> {
3103        self.GetAttributeNode(name).map(|s| s.Value())
3104    }
3105
3106    /// <https://dom.spec.whatwg.org/#dom-element-getattributens>
3107    fn GetAttributeNS(
3108        &self,
3109        namespace: Option<DOMString>,
3110        local_name: DOMString,
3111    ) -> Option<DOMString> {
3112        self.GetAttributeNodeNS(namespace, local_name)
3113            .map(|attr| attr.Value())
3114    }
3115
3116    /// <https://dom.spec.whatwg.org/#dom-element-getattributenode>
3117    fn GetAttributeNode(&self, name: DOMString) -> Option<DomRoot<Attr>> {
3118        self.get_attribute_by_name(name)
3119    }
3120
3121    /// <https://dom.spec.whatwg.org/#dom-element-getattributenodens>
3122    fn GetAttributeNodeNS(
3123        &self,
3124        namespace: Option<DOMString>,
3125        local_name: DOMString,
3126    ) -> Option<DomRoot<Attr>> {
3127        let namespace = &namespace_from_domstring(namespace);
3128        self.get_attribute_with_namespace(namespace, &LocalName::from(local_name))
3129    }
3130
3131    /// <https://dom.spec.whatwg.org/#dom-element-toggleattribute>
3132    fn ToggleAttribute(
3133        &self,
3134        cx: &mut js::context::JSContext,
3135        name: DOMString,
3136        force: Option<bool>,
3137    ) -> Fallible<bool> {
3138        // Step 1. If qualifiedName is not a valid attribute local name,
3139        //      then throw an "InvalidCharacterError" DOMException.
3140        if !is_valid_attribute_local_name(&name.str()) {
3141            return Err(Error::InvalidCharacter(None));
3142        }
3143
3144        // Step 3.
3145        let attribute = self.GetAttribute(name.clone());
3146
3147        // Step 2.
3148        let name = self.parsed_name(name);
3149        match attribute {
3150            // Step 4
3151            None => match force {
3152                // Step 4.1.
3153                None | Some(true) => {
3154                    self.set_first_matching_attribute(
3155                        name.clone(),
3156                        AttrValue::String(String::new()),
3157                        name.clone(),
3158                        ns!(),
3159                        None,
3160                        |attr| *attr.name() == name,
3161                        CanGc::from_cx(cx),
3162                    );
3163                    Ok(true)
3164                },
3165                // Step 4.2.
3166                Some(false) => Ok(false),
3167            },
3168            Some(_index) => match force {
3169                // Step 5.
3170                None | Some(false) => {
3171                    self.remove_attribute_by_name(&name, CanGc::from_cx(cx));
3172                    Ok(false)
3173                },
3174                // Step 6.
3175                Some(true) => Ok(true),
3176            },
3177        }
3178    }
3179
3180    /// <https://dom.spec.whatwg.org/#dom-element-setattribute>
3181    fn SetAttribute(
3182        &self,
3183        cx: &mut js::context::JSContext,
3184        name: DOMString,
3185        value: TrustedTypeOrString,
3186    ) -> ErrorResult {
3187        // Step 1. If qualifiedName does not match the Name production in XML,
3188        // then throw an "InvalidCharacterError" DOMException.
3189        if !is_valid_attribute_local_name(&name.str()) {
3190            return Err(Error::InvalidCharacter(None));
3191        }
3192
3193        // Step 2. If this is in the HTML namespace and its node document is an HTML document,
3194        // then set qualifiedName to qualifiedName in ASCII lowercase.
3195        let name = self.parsed_name(name);
3196
3197        // Step 3. Let verifiedValue be the result of calling get
3198        // Trusted Types-compliant attribute value with qualifiedName, null,
3199        // this, and value. [TRUSTED-TYPES]
3200        let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
3201            self.namespace(),
3202            self.local_name(),
3203            &name,
3204            None,
3205            value,
3206            &self.owner_global(),
3207            CanGc::from_cx(cx),
3208        )?;
3209
3210        // Step 4. Let attribute be the first attribute in this’s attribute list whose qualified name is qualifiedName, and null otherwise.
3211        // Step 5. If attribute is null, create an attribute whose local name is qualifiedName, value is verifiedValue, and node document
3212        // is this’s node document, then append this attribute to this, and then return.
3213        // Step 6. Change attribute to verifiedValue.
3214        let value = self.parse_attribute(&ns!(), &name, value);
3215        self.set_first_matching_attribute(
3216            name.clone(),
3217            value,
3218            name.clone(),
3219            ns!(),
3220            None,
3221            |attr| *attr.name() == name,
3222            CanGc::from_cx(cx),
3223        );
3224        Ok(())
3225    }
3226
3227    /// <https://dom.spec.whatwg.org/#dom-element-setattributens>
3228    fn SetAttributeNS(
3229        &self,
3230        cx: &mut js::context::JSContext,
3231        namespace: Option<DOMString>,
3232        qualified_name: DOMString,
3233        value: TrustedTypeOrString,
3234    ) -> ErrorResult {
3235        // Step 1. Let namespace, prefix, and localName be the result of passing namespace and qualifiedName to validate and extract.
3236        let (namespace, prefix, local_name) =
3237            domname::validate_and_extract(namespace, &qualified_name, domname::Context::Element)?;
3238        // Step 2. Let verifiedValue be the result of calling get
3239        // Trusted Types-compliant attribute value with localName, namespace, element, and value. [TRUSTED-TYPES]
3240        let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
3241            self.namespace(),
3242            self.local_name(),
3243            &local_name,
3244            Some(&namespace),
3245            value,
3246            &self.owner_global(),
3247            CanGc::from_cx(cx),
3248        )?;
3249        // Step 3. Set an attribute value for this using localName, verifiedValue, and also prefix and namespace.
3250        let value = self.parse_attribute(&namespace, &local_name, value);
3251        self.set_attribute_with_namespace(
3252            cx,
3253            local_name,
3254            value,
3255            LocalName::from(qualified_name),
3256            namespace,
3257            prefix,
3258        );
3259        Ok(())
3260    }
3261
3262    /// <https://dom.spec.whatwg.org/#dom-element-setattributenode>
3263    fn SetAttributeNode(
3264        &self,
3265        cx: &mut js::context::JSContext,
3266        attr: &Attr,
3267    ) -> Fallible<Option<DomRoot<Attr>>> {
3268        self.set_attribute_node(cx, attr)
3269    }
3270
3271    /// <https://dom.spec.whatwg.org/#dom-element-setattributenodens>
3272    fn SetAttributeNodeNS(
3273        &self,
3274        cx: &mut js::context::JSContext,
3275        attr: &Attr,
3276    ) -> Fallible<Option<DomRoot<Attr>>> {
3277        self.set_attribute_node(cx, attr)
3278    }
3279
3280    /// <https://dom.spec.whatwg.org/#dom-element-removeattribute>
3281    fn RemoveAttribute(&self, name: DOMString, can_gc: CanGc) {
3282        let name = self.parsed_name(name);
3283        self.remove_attribute_by_name(&name, can_gc);
3284    }
3285
3286    /// <https://dom.spec.whatwg.org/#dom-element-removeattributens>
3287    fn RemoveAttributeNS(
3288        &self,
3289        cx: &mut js::context::JSContext,
3290        namespace: Option<DOMString>,
3291        local_name: DOMString,
3292    ) {
3293        let namespace = namespace_from_domstring(namespace);
3294        let local_name = LocalName::from(local_name);
3295        self.remove_attribute(&namespace, &local_name, CanGc::from_cx(cx));
3296    }
3297
3298    /// <https://dom.spec.whatwg.org/#dom-element-removeattributenode>
3299    fn RemoveAttributeNode(
3300        &self,
3301        cx: &mut js::context::JSContext,
3302        attr: &Attr,
3303    ) -> Fallible<DomRoot<Attr>> {
3304        self.remove_first_matching_attribute(|a| a == attr, CanGc::from_cx(cx))
3305            .ok_or(Error::NotFound(None))
3306    }
3307
3308    /// <https://dom.spec.whatwg.org/#dom-element-hasattribute>
3309    fn HasAttribute(&self, name: DOMString) -> bool {
3310        self.GetAttribute(name).is_some()
3311    }
3312
3313    /// <https://dom.spec.whatwg.org/#dom-element-hasattributens>
3314    fn HasAttributeNS(&self, namespace: Option<DOMString>, local_name: DOMString) -> bool {
3315        self.GetAttributeNS(namespace, local_name).is_some()
3316    }
3317
3318    /// <https://dom.spec.whatwg.org/#dom-element-getelementsbytagname>
3319    fn GetElementsByTagName(&self, localname: DOMString, can_gc: CanGc) -> DomRoot<HTMLCollection> {
3320        let window = self.owner_window();
3321        HTMLCollection::by_qualified_name(
3322            &window,
3323            self.upcast(),
3324            LocalName::from(localname),
3325            can_gc,
3326        )
3327    }
3328
3329    /// <https://dom.spec.whatwg.org/#dom-element-getelementsbytagnamens>
3330    fn GetElementsByTagNameNS(
3331        &self,
3332        maybe_ns: Option<DOMString>,
3333        localname: DOMString,
3334        can_gc: CanGc,
3335    ) -> DomRoot<HTMLCollection> {
3336        let window = self.owner_window();
3337        HTMLCollection::by_tag_name_ns(&window, self.upcast(), localname, maybe_ns, can_gc)
3338    }
3339
3340    /// <https://dom.spec.whatwg.org/#dom-element-getelementsbyclassname>
3341    fn GetElementsByClassName(&self, classes: DOMString, can_gc: CanGc) -> DomRoot<HTMLCollection> {
3342        let window = self.owner_window();
3343        HTMLCollection::by_class_name(&window, self.upcast(), classes, can_gc)
3344    }
3345
3346    /// <https://drafts.csswg.org/cssom-view/#dom-element-getclientrects>
3347    fn GetClientRects(&self, can_gc: CanGc) -> DomRoot<DOMRectList> {
3348        let win = self.owner_window();
3349        let raw_rects = self.upcast::<Node>().border_boxes();
3350        let rects: Vec<DomRoot<DOMRect>> = raw_rects
3351            .map(|rect| {
3352                DOMRect::new(
3353                    win.upcast(),
3354                    rect.origin.x.to_f64_px(),
3355                    rect.origin.y.to_f64_px(),
3356                    rect.size.width.to_f64_px(),
3357                    rect.size.height.to_f64_px(),
3358                    can_gc,
3359                )
3360            })
3361            .collect();
3362        DOMRectList::new(&win, rects, can_gc)
3363    }
3364
3365    /// <https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect>
3366    fn GetBoundingClientRect(&self, can_gc: CanGc) -> DomRoot<DOMRect> {
3367        let win = self.owner_window();
3368        let rect = self.upcast::<Node>().border_box().unwrap_or_default();
3369        DOMRect::new(
3370            win.upcast(),
3371            rect.origin.x.to_f64_px(),
3372            rect.origin.y.to_f64_px(),
3373            rect.size.width.to_f64_px(),
3374            rect.size.height.to_f64_px(),
3375            can_gc,
3376        )
3377    }
3378
3379    /// <https://drafts.csswg.org/cssom-view/#dom-element-scroll>
3380    fn Scroll(&self, options: &ScrollToOptions) {
3381        // Step 1
3382        let left = options.left.unwrap_or(self.ScrollLeft());
3383        let top = options.top.unwrap_or(self.ScrollTop());
3384        self.scroll(left, top, options.parent.behavior);
3385    }
3386
3387    /// <https://drafts.csswg.org/cssom-view/#dom-element-scroll>
3388    fn Scroll_(&self, x: f64, y: f64) {
3389        self.scroll(x, y, ScrollBehavior::Auto);
3390    }
3391
3392    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollto>
3393    fn ScrollTo(&self, options: &ScrollToOptions) {
3394        self.Scroll(options);
3395    }
3396
3397    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollto>
3398    fn ScrollTo_(&self, x: f64, y: f64) {
3399        self.Scroll_(x, y);
3400    }
3401
3402    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollby>
3403    fn ScrollBy(&self, options: &ScrollToOptions) {
3404        // Step 2
3405        let delta_left = options.left.unwrap_or(0.0f64);
3406        let delta_top = options.top.unwrap_or(0.0f64);
3407        let left = self.ScrollLeft();
3408        let top = self.ScrollTop();
3409        self.scroll(left + delta_left, top + delta_top, options.parent.behavior);
3410    }
3411
3412    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollby>
3413    fn ScrollBy_(&self, x: f64, y: f64) {
3414        let left = self.ScrollLeft();
3415        let top = self.ScrollTop();
3416        self.scroll(left + x, top + y, ScrollBehavior::Auto);
3417    }
3418
3419    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrolltop>
3420    fn ScrollTop(&self) -> f64 {
3421        let node = self.upcast::<Node>();
3422
3423        // Step 1
3424        let doc = node.owner_doc();
3425
3426        // Step 2
3427        if !doc.is_fully_active() {
3428            return 0.0;
3429        }
3430
3431        // Step 3
3432        let win = match doc.GetDefaultView() {
3433            None => return 0.0,
3434            Some(win) => win,
3435        };
3436
3437        // Step 5
3438        if self.is_document_element() {
3439            if doc.quirks_mode() == QuirksMode::Quirks {
3440                return 0.0;
3441            }
3442
3443            // Step 6
3444            return win.ScrollY() as f64;
3445        }
3446
3447        // Step 7
3448        if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() &&
3449            doc.quirks_mode() == QuirksMode::Quirks &&
3450            !self.is_potentially_scrollable_body()
3451        {
3452            return win.ScrollY() as f64;
3453        }
3454
3455        // Step 8
3456        if !self.has_css_layout_box() {
3457            return 0.0;
3458        }
3459
3460        // Step 9
3461        let point = win.scroll_offset_query(node);
3462        point.y.abs() as f64
3463    }
3464
3465    // https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
3466    // TODO(stevennovaryo): Need to update the scroll API to follow the spec since it is quite outdated.
3467    fn SetScrollTop(&self, y_: f64) {
3468        let behavior = ScrollBehavior::Auto;
3469
3470        // Step 1, 2
3471        let y = if y_.is_finite() { y_ } else { 0.0 } as f32;
3472
3473        let node = self.upcast::<Node>();
3474
3475        // Step 3
3476        let doc = node.owner_doc();
3477
3478        // Step 4
3479        if !doc.is_fully_active() {
3480            return;
3481        }
3482
3483        // Step 5
3484        let win = match doc.GetDefaultView() {
3485            None => return,
3486            Some(win) => win,
3487        };
3488
3489        // Step 7
3490        if self.is_document_element() {
3491            if doc.quirks_mode() != QuirksMode::Quirks {
3492                win.scroll(win.ScrollX() as f32, y, behavior);
3493            }
3494
3495            return;
3496        }
3497
3498        // Step 9
3499        if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() &&
3500            doc.quirks_mode() == QuirksMode::Quirks &&
3501            !self.is_potentially_scrollable_body()
3502        {
3503            win.scroll(win.ScrollX() as f32, y, behavior);
3504            return;
3505        }
3506
3507        // Step 10
3508        if !self.has_scrolling_box() {
3509            return;
3510        }
3511
3512        // Step 11
3513        win.scroll_an_element(self, self.ScrollLeft() as f32, y, behavior);
3514    }
3515
3516    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollleft>
3517    fn ScrollLeft(&self) -> f64 {
3518        let node = self.upcast::<Node>();
3519
3520        // Step 1
3521        let doc = node.owner_doc();
3522
3523        // Step 2
3524        if !doc.is_fully_active() {
3525            return 0.0;
3526        }
3527
3528        // Step 3
3529        let win = match doc.GetDefaultView() {
3530            None => return 0.0,
3531            Some(win) => win,
3532        };
3533
3534        // Step 5
3535        if self.is_document_element() {
3536            if doc.quirks_mode() != QuirksMode::Quirks {
3537                // Step 6
3538                return win.ScrollX() as f64;
3539            }
3540
3541            return 0.0;
3542        }
3543
3544        // Step 7
3545        if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() &&
3546            doc.quirks_mode() == QuirksMode::Quirks &&
3547            !self.is_potentially_scrollable_body()
3548        {
3549            return win.ScrollX() as f64;
3550        }
3551
3552        // Step 8
3553        if !self.has_css_layout_box() {
3554            return 0.0;
3555        }
3556
3557        // Step 9
3558        let point = win.scroll_offset_query(node);
3559        point.x.abs() as f64
3560    }
3561
3562    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollleft>
3563    fn SetScrollLeft(&self, x: f64) {
3564        let behavior = ScrollBehavior::Auto;
3565
3566        // Step 1, 2
3567        let x = if x.is_finite() { x } else { 0.0 } as f32;
3568
3569        let node = self.upcast::<Node>();
3570
3571        // Step 3
3572        let doc = node.owner_doc();
3573
3574        // Step 4
3575        if !doc.is_fully_active() {
3576            return;
3577        }
3578
3579        // Step 5
3580        let win = match doc.GetDefaultView() {
3581            None => return,
3582            Some(win) => win,
3583        };
3584
3585        // Step 7
3586        if self.is_document_element() {
3587            if doc.quirks_mode() == QuirksMode::Quirks {
3588                return;
3589            }
3590
3591            win.scroll(x, win.ScrollY() as f32, behavior);
3592            return;
3593        }
3594
3595        // Step 9
3596        if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() &&
3597            doc.quirks_mode() == QuirksMode::Quirks &&
3598            !self.is_potentially_scrollable_body()
3599        {
3600            win.scroll(x, win.ScrollY() as f32, behavior);
3601            return;
3602        }
3603
3604        // Step 10
3605        if !self.has_scrolling_box() {
3606            return;
3607        }
3608
3609        // Step 11
3610        win.scroll_an_element(self, x, self.ScrollTop() as f32, behavior);
3611    }
3612
3613    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview>
3614    fn ScrollIntoView(&self, arg: BooleanOrScrollIntoViewOptions) {
3615        let (behavior, block, inline, container) = match arg {
3616            // If arg is true:
3617            BooleanOrScrollIntoViewOptions::Boolean(true) => (
3618                ScrollBehavior::Auto,           // Step 1: Let behavior be "auto".
3619                ScrollLogicalPosition::Start,   // Step 2: Let block be "start".
3620                ScrollLogicalPosition::Nearest, // Step 3: Let inline be "nearest".
3621                None,                           // Step 4: Let container be null.
3622            ),
3623            // Step 5: If arg is a ScrollIntoViewOptions dictionary, set its properties
3624            // to the corresponding values in the dictionary.
3625            BooleanOrScrollIntoViewOptions::ScrollIntoViewOptions(options) => (
3626                options.parent.behavior,
3627                options.block,
3628                options.inline,
3629                // Step 5.4: If the container dictionary member of options is "nearest",
3630                // set container to the element.
3631                if options.container == ScrollIntoViewContainer::Nearest {
3632                    Some(self)
3633                } else {
3634                    None
3635                },
3636            ),
3637            // Step 6: Otherwise, if arg is false, then set block to "end".
3638            BooleanOrScrollIntoViewOptions::Boolean(false) => (
3639                ScrollBehavior::Auto,
3640                ScrollLogicalPosition::End,
3641                ScrollLogicalPosition::Nearest,
3642                None,
3643            ),
3644        };
3645
3646        // Step 7: If the element does not have any associated box, or is not
3647        //         available to user-agent features, then return.
3648        if !self.has_css_layout_box() {
3649            return;
3650        }
3651
3652        // Step 8: Scroll the element into view with behavior, block, inline, and container.
3653        self.scroll_into_view_with_options(
3654            behavior,
3655            ScrollAxisState::new_always_scroll_position(block),
3656            ScrollAxisState::new_always_scroll_position(inline),
3657            container,
3658            None,
3659        );
3660
3661        // Step 9: Optionally perform some other action that brings the
3662        // element to the user’s attention.
3663    }
3664
3665    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollwidth>
3666    fn ScrollWidth(&self) -> i32 {
3667        self.upcast::<Node>().scroll_area().size.width
3668    }
3669
3670    /// <https://drafts.csswg.org/cssom-view/#dom-element-scrollheight>
3671    fn ScrollHeight(&self) -> i32 {
3672        self.upcast::<Node>().scroll_area().size.height
3673    }
3674
3675    /// <https://drafts.csswg.org/cssom-view/#dom-element-clienttop>
3676    fn ClientTop(&self) -> i32 {
3677        self.client_rect().origin.y
3678    }
3679
3680    /// <https://drafts.csswg.org/cssom-view/#dom-element-clientleft>
3681    fn ClientLeft(&self) -> i32 {
3682        self.client_rect().origin.x
3683    }
3684
3685    /// <https://drafts.csswg.org/cssom-view/#dom-element-clientwidth>
3686    fn ClientWidth(&self) -> i32 {
3687        self.client_rect().size.width
3688    }
3689
3690    /// <https://drafts.csswg.org/cssom-view/#dom-element-clientheight>
3691    fn ClientHeight(&self) -> i32 {
3692        self.client_rect().size.height
3693    }
3694
3695    // https://drafts.csswg.org/cssom-view/#dom-element-currentcsszoom
3696    fn CurrentCSSZoom(&self) -> Finite<f64> {
3697        let window = self.owner_window();
3698        Finite::wrap(window.current_css_zoom_query(self.upcast::<Node>()) as f64)
3699    }
3700
3701    /// <https://html.spec.whatwg.org/multipage/#dom-element-sethtmlunsafe>
3702    fn SetHTMLUnsafe(
3703        &self,
3704        cx: &mut js::context::JSContext,
3705        html: TrustedHTMLOrString,
3706    ) -> ErrorResult {
3707        // Step 1. Let compliantHTML be the result of invoking the
3708        // Get Trusted Type compliant string algorithm with TrustedHTML,
3709        // this's relevant global object, html, "Element setHTMLUnsafe", and "script".
3710        let html = TrustedHTML::get_trusted_script_compliant_string(
3711            &self.owner_global(),
3712            html,
3713            "Element setHTMLUnsafe",
3714            CanGc::from_cx(cx),
3715        )?;
3716        // Step 2. Let target be this's template contents if this is a template element; otherwise this.
3717        let target = if let Some(template) = self.downcast::<HTMLTemplateElement>() {
3718            DomRoot::upcast(template.Content(CanGc::from_cx(cx)))
3719        } else {
3720            DomRoot::from_ref(self.upcast())
3721        };
3722
3723        // Step 3. Unsafely set HTML given target, this, and compliantHTML
3724        Node::unsafely_set_html(&target, self, html, cx);
3725        Ok(())
3726    }
3727
3728    /// <https://html.spec.whatwg.org/multipage/#dom-element-gethtml>
3729    fn GetHTML(&self, options: &GetHTMLOptions, can_gc: CanGc) -> DOMString {
3730        // > Element's getHTML(options) method steps are to return the result of HTML fragment serialization
3731        // > algorithm with this, options["serializableShadowRoots"], and options["shadowRoots"].
3732        self.upcast::<Node>().html_serialize(
3733            TraversalScope::ChildrenOnly(None),
3734            options.serializableShadowRoots,
3735            options.shadowRoots.clone(),
3736            can_gc,
3737        )
3738    }
3739
3740    /// <https://html.spec.whatwg.org/multipage/#dom-element-innerhtml>
3741    fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible<TrustedHTMLOrNullIsEmptyString> {
3742        let qname = QualName::new(
3743            self.prefix().clone(),
3744            self.namespace().clone(),
3745            self.local_name().clone(),
3746        );
3747
3748        // FIXME: This should use the fragment serialization algorithm, which takes
3749        // care of distinguishing between html/xml documents
3750        let result = if self.owner_document().is_html_document() {
3751            self.upcast::<Node>()
3752                .html_serialize(ChildrenOnly(Some(qname)), false, vec![], can_gc)
3753        } else {
3754            self.upcast::<Node>()
3755                .xml_serialize(XmlChildrenOnly(Some(qname)))?
3756        };
3757
3758        Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result))
3759    }
3760
3761    /// <https://html.spec.whatwg.org/multipage/#dom-element-innerhtml>
3762    fn SetInnerHTML(
3763        &self,
3764        cx: &mut js::context::JSContext,
3765        value: TrustedHTMLOrNullIsEmptyString,
3766    ) -> ErrorResult {
3767        // Step 1: Let compliantString be the result of invoking the
3768        // Get Trusted Type compliant string algorithm with TrustedHTML,
3769        // this's relevant global object, the given value, "Element innerHTML", and "script".
3770        let value = TrustedHTML::get_trusted_script_compliant_string(
3771            &self.owner_global(),
3772            value.convert(),
3773            "Element innerHTML",
3774            CanGc::from_cx(cx),
3775        )?;
3776        // https://github.com/w3c/DOM-Parsing/issues/1
3777        let target = if let Some(template) = self.downcast::<HTMLTemplateElement>() {
3778            // Step 4: If context is a template element, then set context to
3779            // the template element's template contents (a DocumentFragment).
3780            DomRoot::upcast(template.Content(CanGc::from_cx(cx)))
3781        } else {
3782            // Step 2: Let context be this.
3783            DomRoot::from_ref(self.upcast())
3784        };
3785
3786        // Fast path for when the value is small, doesn't contain any markup and doesn't require
3787        // extra work to set innerHTML.
3788        if !self.node.has_weird_parser_insertion_mode() &&
3789            value.len() < 100 &&
3790            !value
3791                .as_bytes()
3792                .iter()
3793                .any(|c| matches!(*c, b'&' | b'\0' | b'<' | b'\r'))
3794        {
3795            return Node::SetTextContent(&target, Some(value), CanGc::from_cx(cx));
3796        }
3797
3798        // Step 3: Let fragment be the result of invoking the fragment parsing algorithm steps
3799        // with context and compliantString.
3800        let frag = self.parse_fragment(value, cx)?;
3801
3802        // Step 5: Replace all with fragment within context.
3803        Node::replace_all(Some(frag.upcast()), &target, CanGc::from_cx(cx));
3804        Ok(())
3805    }
3806
3807    /// <https://html.spec.whatwg.org/multipage/#dom-element-outerhtml>
3808    fn GetOuterHTML(&self, can_gc: CanGc) -> Fallible<TrustedHTMLOrNullIsEmptyString> {
3809        // FIXME: This should use the fragment serialization algorithm, which takes
3810        // care of distinguishing between html/xml documents
3811        let result = if self.owner_document().is_html_document() {
3812            self.upcast::<Node>()
3813                .html_serialize(IncludeNode, false, vec![], can_gc)
3814        } else {
3815            self.upcast::<Node>().xml_serialize(XmlIncludeNode)?
3816        };
3817
3818        Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result))
3819    }
3820
3821    /// <https://html.spec.whatwg.org/multipage/#dom-element-outerhtml>
3822    fn SetOuterHTML(
3823        &self,
3824        cx: &mut js::context::JSContext,
3825        value: TrustedHTMLOrNullIsEmptyString,
3826    ) -> ErrorResult {
3827        // Step 1: Let compliantString be the result of invoking the
3828        // Get Trusted Type compliant string algorithm with TrustedHTML,
3829        // this's relevant global object, the given value, "Element outerHTML", and "script".
3830        let value = TrustedHTML::get_trusted_script_compliant_string(
3831            &self.owner_global(),
3832            value.convert(),
3833            "Element outerHTML",
3834            CanGc::from_cx(cx),
3835        )?;
3836        let context_document = self.owner_document();
3837        let context_node = self.upcast::<Node>();
3838        // Step 2: Let parent be this's parent.
3839        let context_parent = match context_node.GetParentNode() {
3840            None => {
3841                // Step 3: If parent is null, return. There would be no way to
3842                // obtain a reference to the nodes created even if the remaining steps were run.
3843                return Ok(());
3844            },
3845            Some(parent) => parent,
3846        };
3847
3848        let parent = match context_parent.type_id() {
3849            // Step 4: If parent is a Document, throw a "NoModificationAllowedError" DOMException.
3850            NodeTypeId::Document(_) => return Err(Error::NoModificationAllowed(None)),
3851
3852            // Step 5: If parent is a DocumentFragment, set parent to the result of
3853            // creating an element given this's node document, "body", and the HTML namespace.
3854            NodeTypeId::DocumentFragment(_) => {
3855                let body_elem = Element::create(
3856                    QualName::new(None, ns!(html), local_name!("body")),
3857                    None,
3858                    &context_document,
3859                    ElementCreator::ScriptCreated,
3860                    CustomElementCreationMode::Synchronous,
3861                    None,
3862                    CanGc::from_cx(cx),
3863                );
3864                DomRoot::upcast(body_elem)
3865            },
3866            _ => context_node.GetParentElement().unwrap(),
3867        };
3868
3869        // Step 6: Let fragment be the result of invoking the
3870        // fragment parsing algorithm steps given parent and compliantString.
3871        let frag = parent.parse_fragment(value, cx)?;
3872        // Step 7: Replace this with fragment within this's parent.
3873        context_parent.ReplaceChild(frag.upcast(), context_node, CanGc::from_cx(cx))?;
3874        Ok(())
3875    }
3876
3877    /// <https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling>
3878    fn GetPreviousElementSibling(&self) -> Option<DomRoot<Element>> {
3879        self.upcast::<Node>()
3880            .preceding_siblings()
3881            .find_map(DomRoot::downcast)
3882    }
3883
3884    /// <https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling>
3885    fn GetNextElementSibling(&self) -> Option<DomRoot<Element>> {
3886        self.upcast::<Node>()
3887            .following_siblings()
3888            .find_map(DomRoot::downcast)
3889    }
3890
3891    /// <https://dom.spec.whatwg.org/#dom-parentnode-children>
3892    fn Children(&self, can_gc: CanGc) -> DomRoot<HTMLCollection> {
3893        let window = self.owner_window();
3894        HTMLCollection::children(&window, self.upcast(), can_gc)
3895    }
3896
3897    /// <https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild>
3898    fn GetFirstElementChild(&self) -> Option<DomRoot<Element>> {
3899        self.upcast::<Node>().child_elements().next()
3900    }
3901
3902    /// <https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild>
3903    fn GetLastElementChild(&self) -> Option<DomRoot<Element>> {
3904        self.upcast::<Node>()
3905            .rev_children()
3906            .find_map(DomRoot::downcast::<Element>)
3907    }
3908
3909    /// <https://dom.spec.whatwg.org/#dom-parentnode-childelementcount>
3910    fn ChildElementCount(&self) -> u32 {
3911        self.upcast::<Node>().child_elements().count() as u32
3912    }
3913
3914    /// <https://dom.spec.whatwg.org/#dom-parentnode-prepend>
3915    fn Prepend(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
3916        self.upcast::<Node>().prepend(nodes, can_gc)
3917    }
3918
3919    /// <https://dom.spec.whatwg.org/#dom-parentnode-append>
3920    fn Append(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
3921        self.upcast::<Node>().append(nodes, can_gc)
3922    }
3923
3924    /// <https://dom.spec.whatwg.org/#dom-parentnode-replacechildren>
3925    fn ReplaceChildren(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
3926        self.upcast::<Node>().replace_children(nodes, can_gc)
3927    }
3928
3929    /// <https://dom.spec.whatwg.org/#dom-parentnode-movebefore>
3930    fn MoveBefore(&self, node: &Node, child: Option<&Node>, can_gc: CanGc) -> ErrorResult {
3931        self.upcast::<Node>().move_before(node, child, can_gc)
3932    }
3933
3934    /// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector>
3935    fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> {
3936        let root = self.upcast::<Node>();
3937        root.query_selector(selectors)
3938    }
3939
3940    /// <https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall>
3941    fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> {
3942        let root = self.upcast::<Node>();
3943        root.query_selector_all(selectors)
3944    }
3945
3946    /// <https://dom.spec.whatwg.org/#dom-childnode-before>
3947    fn Before(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
3948        self.upcast::<Node>().before(nodes, can_gc)
3949    }
3950
3951    /// <https://dom.spec.whatwg.org/#dom-childnode-after>
3952    fn After(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
3953        self.upcast::<Node>().after(nodes, can_gc)
3954    }
3955
3956    /// <https://dom.spec.whatwg.org/#dom-childnode-replacewith>
3957    fn ReplaceWith(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
3958        self.upcast::<Node>().replace_with(nodes, can_gc)
3959    }
3960
3961    /// <https://dom.spec.whatwg.org/#dom-childnode-remove>
3962    fn Remove(&self, can_gc: CanGc) {
3963        self.upcast::<Node>().remove_self(can_gc);
3964    }
3965
3966    /// <https://dom.spec.whatwg.org/#dom-element-matches>
3967    fn Matches(&self, selectors: DOMString) -> Fallible<bool> {
3968        let doc = self.owner_document();
3969        let url = doc.url();
3970        let selectors = match SelectorParser::parse_author_origin_no_namespace(
3971            &selectors.str(),
3972            &UrlExtraData(url.get_arc()),
3973        ) {
3974            Err(_) => return Err(Error::Syntax(None)),
3975            Ok(selectors) => selectors,
3976        };
3977
3978        let quirks_mode = doc.quirks_mode();
3979        let element = DomRoot::from_ref(self);
3980
3981        Ok(dom_apis::element_matches(
3982            &SelectorWrapper::Borrowed(&element),
3983            &selectors,
3984            quirks_mode,
3985        ))
3986    }
3987
3988    /// <https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector>
3989    fn WebkitMatchesSelector(&self, selectors: DOMString) -> Fallible<bool> {
3990        self.Matches(selectors)
3991    }
3992
3993    /// <https://dom.spec.whatwg.org/#dom-element-closest>
3994    fn Closest(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> {
3995        let doc = self.owner_document();
3996        let url = doc.url();
3997        let selectors = match SelectorParser::parse_author_origin_no_namespace(
3998            &selectors.str(),
3999            &UrlExtraData(url.get_arc()),
4000        ) {
4001            Err(_) => return Err(Error::Syntax(None)),
4002            Ok(selectors) => selectors,
4003        };
4004
4005        let quirks_mode = doc.quirks_mode();
4006        Ok(dom_apis::element_closest(
4007            SelectorWrapper::Owned(DomRoot::from_ref(self)),
4008            &selectors,
4009            quirks_mode,
4010        )
4011        .map(SelectorWrapper::into_owned))
4012    }
4013
4014    /// <https://dom.spec.whatwg.org/#dom-element-insertadjacentelement>
4015    fn InsertAdjacentElement(
4016        &self,
4017        where_: DOMString,
4018        element: &Element,
4019        can_gc: CanGc,
4020    ) -> Fallible<Option<DomRoot<Element>>> {
4021        let where_ = where_.parse::<AdjacentPosition>()?;
4022        let inserted_node = self.insert_adjacent(where_, element.upcast(), can_gc)?;
4023        Ok(inserted_node.map(|node| DomRoot::downcast(node).unwrap()))
4024    }
4025
4026    /// <https://dom.spec.whatwg.org/#dom-element-insertadjacenttext>
4027    fn InsertAdjacentText(&self, where_: DOMString, data: DOMString, can_gc: CanGc) -> ErrorResult {
4028        // Step 1.
4029        let text = Text::new(data, &self.owner_document(), can_gc);
4030
4031        // Step 2.
4032        let where_ = where_.parse::<AdjacentPosition>()?;
4033        self.insert_adjacent(where_, text.upcast(), can_gc)
4034            .map(|_| ())
4035    }
4036
4037    /// <https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml>
4038    fn InsertAdjacentHTML(
4039        &self,
4040        cx: &mut js::context::JSContext,
4041        position: DOMString,
4042        text: TrustedHTMLOrString,
4043    ) -> ErrorResult {
4044        // Step 1: Let compliantString be the result of invoking the
4045        // Get Trusted Type compliant string algorithm with TrustedHTML,
4046        // this's relevant global object, string, "Element insertAdjacentHTML", and "script".
4047        let text = TrustedHTML::get_trusted_script_compliant_string(
4048            &self.owner_global(),
4049            text,
4050            "Element insertAdjacentHTML",
4051            CanGc::from_cx(cx),
4052        )?;
4053        let position = position.parse::<AdjacentPosition>()?;
4054
4055        // Step 2: Let context be null.
4056        // Step 3: Use the first matching item from this list:
4057        let context = match position {
4058            // If position is an ASCII case-insensitive match for the string "beforebegin"
4059            // If position is an ASCII case-insensitive match for the string "afterend"
4060            AdjacentPosition::BeforeBegin | AdjacentPosition::AfterEnd => {
4061                match self.upcast::<Node>().GetParentNode() {
4062                    // Step 3.2: If context is null or a Document, throw a "NoModificationAllowedError" DOMException.
4063                    Some(ref node) if node.is::<Document>() => {
4064                        return Err(Error::NoModificationAllowed(None));
4065                    },
4066                    None => return Err(Error::NoModificationAllowed(None)),
4067                    // Step 3.1: Set context to this's parent.
4068                    Some(node) => node,
4069                }
4070            },
4071            // If position is an ASCII case-insensitive match for the string "afterbegin"
4072            // If position is an ASCII case-insensitive match for the string "beforeend"
4073            AdjacentPosition::AfterBegin | AdjacentPosition::BeforeEnd => {
4074                // Set context to this.
4075                DomRoot::from_ref(self.upcast::<Node>())
4076            },
4077        };
4078
4079        // Step 4.
4080        let context = Element::fragment_parsing_context(
4081            cx,
4082            &context.owner_doc(),
4083            context.downcast::<Element>(),
4084        );
4085
4086        // Step 5: Let fragment be the result of invoking the
4087        // fragment parsing algorithm steps with context and compliantString.
4088        let fragment = context.parse_fragment(text, cx)?;
4089
4090        // Step 6.
4091        self.insert_adjacent(position, fragment.upcast(), CanGc::from_cx(cx))
4092            .map(|_| ())
4093    }
4094
4095    // check-tidy: no specs after this line
4096    fn EnterFormalActivationState(&self) -> ErrorResult {
4097        match self.as_maybe_activatable() {
4098            Some(a) => {
4099                a.enter_formal_activation_state();
4100                Ok(())
4101            },
4102            None => Err(Error::NotSupported(None)),
4103        }
4104    }
4105
4106    fn ExitFormalActivationState(&self) -> ErrorResult {
4107        match self.as_maybe_activatable() {
4108            Some(a) => {
4109                a.exit_formal_activation_state();
4110                Ok(())
4111            },
4112            None => Err(Error::NotSupported(None)),
4113        }
4114    }
4115
4116    /// <https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen>
4117    fn RequestFullscreen(&self, can_gc: CanGc) -> Rc<Promise> {
4118        let doc = self.owner_document();
4119        doc.enter_fullscreen(self, can_gc)
4120    }
4121
4122    /// <https://dom.spec.whatwg.org/#dom-element-attachshadow>
4123    fn AttachShadow(&self, init: &ShadowRootInit, can_gc: CanGc) -> Fallible<DomRoot<ShadowRoot>> {
4124        // Step 1. Run attach a shadow root with this, init["mode"], init["clonable"], init["serializable"],
4125        // init["delegatesFocus"], and init["slotAssignment"].
4126        let shadow_root = self.attach_shadow(
4127            IsUserAgentWidget::No,
4128            init.mode,
4129            init.clonable,
4130            init.serializable,
4131            init.delegatesFocus,
4132            init.slotAssignment,
4133            can_gc,
4134        )?;
4135
4136        // Step 2. Return this’s shadow root.
4137        Ok(shadow_root)
4138    }
4139
4140    /// <https://dom.spec.whatwg.org/#dom-element-shadowroot>
4141    fn GetShadowRoot(&self) -> Option<DomRoot<ShadowRoot>> {
4142        // Step 1. Let shadow be this’s shadow root.
4143        let shadow_or_none = self.shadow_root();
4144
4145        // Step 2. If shadow is null or its mode is "closed", then return null.
4146        let shadow = shadow_or_none?;
4147        if shadow.Mode() == ShadowRootMode::Closed {
4148            return None;
4149        }
4150
4151        // Step 3. Return shadow.
4152        Some(shadow)
4153    }
4154
4155    /// <https://dom.spec.whatwg.org/#dom-element-customelementregistry>
4156    fn GetCustomElementRegistry(&self) -> Option<DomRoot<CustomElementRegistry>> {
4157        // The customElementRegistry getter steps are to return this’s custom element registry.
4158        self.custom_element_registry()
4159    }
4160
4161    fn GetRole(&self) -> Option<DOMString> {
4162        self.get_nullable_string_attribute(&local_name!("role"))
4163    }
4164
4165    fn SetRole(&self, cx: &mut JSContext, value: Option<DOMString>) {
4166        self.set_nullable_string_attribute(cx, &local_name!("role"), value);
4167    }
4168
4169    fn GetAriaAtomic(&self) -> Option<DOMString> {
4170        self.get_nullable_string_attribute(&local_name!("aria-atomic"))
4171    }
4172
4173    fn SetAriaAtomic(&self, cx: &mut JSContext, value: Option<DOMString>) {
4174        self.set_nullable_string_attribute(cx, &local_name!("aria-atomic"), value);
4175    }
4176
4177    fn GetAriaAutoComplete(&self) -> Option<DOMString> {
4178        self.get_nullable_string_attribute(&local_name!("aria-autocomplete"))
4179    }
4180
4181    fn SetAriaAutoComplete(&self, cx: &mut JSContext, value: Option<DOMString>) {
4182        self.set_nullable_string_attribute(cx, &local_name!("aria-autocomplete"), value);
4183    }
4184
4185    fn GetAriaBrailleLabel(&self) -> Option<DOMString> {
4186        self.get_nullable_string_attribute(&local_name!("aria-braillelabel"))
4187    }
4188
4189    fn SetAriaBrailleLabel(&self, cx: &mut JSContext, value: Option<DOMString>) {
4190        self.set_nullable_string_attribute(cx, &local_name!("aria-braillelabel"), value);
4191    }
4192
4193    fn GetAriaBrailleRoleDescription(&self) -> Option<DOMString> {
4194        self.get_nullable_string_attribute(&local_name!("aria-brailleroledescription"))
4195    }
4196
4197    fn SetAriaBrailleRoleDescription(&self, cx: &mut JSContext, value: Option<DOMString>) {
4198        self.set_nullable_string_attribute(cx, &local_name!("aria-brailleroledescription"), value);
4199    }
4200
4201    fn GetAriaBusy(&self) -> Option<DOMString> {
4202        self.get_nullable_string_attribute(&local_name!("aria-busy"))
4203    }
4204
4205    fn SetAriaBusy(&self, cx: &mut JSContext, value: Option<DOMString>) {
4206        self.set_nullable_string_attribute(cx, &local_name!("aria-busy"), value);
4207    }
4208
4209    fn GetAriaChecked(&self) -> Option<DOMString> {
4210        self.get_nullable_string_attribute(&local_name!("aria-checked"))
4211    }
4212
4213    fn SetAriaChecked(&self, cx: &mut JSContext, value: Option<DOMString>) {
4214        self.set_nullable_string_attribute(cx, &local_name!("aria-checked"), value);
4215    }
4216
4217    fn GetAriaColCount(&self) -> Option<DOMString> {
4218        self.get_nullable_string_attribute(&local_name!("aria-colcount"))
4219    }
4220
4221    fn SetAriaColCount(&self, cx: &mut JSContext, value: Option<DOMString>) {
4222        self.set_nullable_string_attribute(cx, &local_name!("aria-colcount"), value);
4223    }
4224
4225    fn GetAriaColIndex(&self) -> Option<DOMString> {
4226        self.get_nullable_string_attribute(&local_name!("aria-colindex"))
4227    }
4228
4229    fn SetAriaColIndex(&self, cx: &mut JSContext, value: Option<DOMString>) {
4230        self.set_nullable_string_attribute(cx, &local_name!("aria-colindex"), value);
4231    }
4232
4233    fn GetAriaColIndexText(&self) -> Option<DOMString> {
4234        self.get_nullable_string_attribute(&local_name!("aria-colindextext"))
4235    }
4236
4237    fn SetAriaColIndexText(&self, cx: &mut JSContext, value: Option<DOMString>) {
4238        self.set_nullable_string_attribute(cx, &local_name!("aria-colindextext"), value);
4239    }
4240
4241    fn GetAriaColSpan(&self) -> Option<DOMString> {
4242        self.get_nullable_string_attribute(&local_name!("aria-colspan"))
4243    }
4244
4245    fn SetAriaColSpan(&self, cx: &mut JSContext, value: Option<DOMString>) {
4246        self.set_nullable_string_attribute(cx, &local_name!("aria-colspan"), value);
4247    }
4248
4249    fn GetAriaCurrent(&self) -> Option<DOMString> {
4250        self.get_nullable_string_attribute(&local_name!("aria-current"))
4251    }
4252
4253    fn SetAriaCurrent(&self, cx: &mut JSContext, value: Option<DOMString>) {
4254        self.set_nullable_string_attribute(cx, &local_name!("aria-current"), value);
4255    }
4256
4257    fn GetAriaDescription(&self) -> Option<DOMString> {
4258        self.get_nullable_string_attribute(&local_name!("aria-description"))
4259    }
4260
4261    fn SetAriaDescription(&self, cx: &mut JSContext, value: Option<DOMString>) {
4262        self.set_nullable_string_attribute(cx, &local_name!("aria-description"), value);
4263    }
4264
4265    fn GetAriaDisabled(&self) -> Option<DOMString> {
4266        self.get_nullable_string_attribute(&local_name!("aria-disabled"))
4267    }
4268
4269    fn SetAriaDisabled(&self, cx: &mut JSContext, value: Option<DOMString>) {
4270        self.set_nullable_string_attribute(cx, &local_name!("aria-disabled"), value);
4271    }
4272
4273    fn GetAriaExpanded(&self) -> Option<DOMString> {
4274        self.get_nullable_string_attribute(&local_name!("aria-expanded"))
4275    }
4276
4277    fn SetAriaExpanded(&self, cx: &mut JSContext, value: Option<DOMString>) {
4278        self.set_nullable_string_attribute(cx, &local_name!("aria-expanded"), value);
4279    }
4280
4281    fn GetAriaHasPopup(&self) -> Option<DOMString> {
4282        self.get_nullable_string_attribute(&local_name!("aria-haspopup"))
4283    }
4284
4285    fn SetAriaHasPopup(&self, cx: &mut JSContext, value: Option<DOMString>) {
4286        self.set_nullable_string_attribute(cx, &local_name!("aria-haspopup"), value);
4287    }
4288
4289    fn GetAriaHidden(&self) -> Option<DOMString> {
4290        self.get_nullable_string_attribute(&local_name!("aria-hidden"))
4291    }
4292
4293    fn SetAriaHidden(&self, cx: &mut JSContext, value: Option<DOMString>) {
4294        self.set_nullable_string_attribute(cx, &local_name!("aria-hidden"), value);
4295    }
4296
4297    fn GetAriaInvalid(&self) -> Option<DOMString> {
4298        self.get_nullable_string_attribute(&local_name!("aria-invalid"))
4299    }
4300
4301    fn SetAriaInvalid(&self, cx: &mut JSContext, value: Option<DOMString>) {
4302        self.set_nullable_string_attribute(cx, &local_name!("aria-invalid"), value);
4303    }
4304
4305    fn GetAriaKeyShortcuts(&self) -> Option<DOMString> {
4306        self.get_nullable_string_attribute(&local_name!("aria-keyshortcuts"))
4307    }
4308
4309    fn SetAriaKeyShortcuts(&self, cx: &mut JSContext, value: Option<DOMString>) {
4310        self.set_nullable_string_attribute(cx, &local_name!("aria-keyshortcuts"), value);
4311    }
4312
4313    fn GetAriaLabel(&self) -> Option<DOMString> {
4314        self.get_nullable_string_attribute(&local_name!("aria-label"))
4315    }
4316
4317    fn SetAriaLabel(&self, cx: &mut JSContext, value: Option<DOMString>) {
4318        self.set_nullable_string_attribute(cx, &local_name!("aria-label"), value);
4319    }
4320
4321    fn GetAriaLevel(&self) -> Option<DOMString> {
4322        self.get_nullable_string_attribute(&local_name!("aria-level"))
4323    }
4324
4325    fn SetAriaLevel(&self, cx: &mut JSContext, value: Option<DOMString>) {
4326        self.set_nullable_string_attribute(cx, &local_name!("aria-level"), value);
4327    }
4328
4329    fn GetAriaLive(&self) -> Option<DOMString> {
4330        self.get_nullable_string_attribute(&local_name!("aria-live"))
4331    }
4332
4333    fn SetAriaLive(&self, cx: &mut JSContext, value: Option<DOMString>) {
4334        self.set_nullable_string_attribute(cx, &local_name!("aria-live"), value);
4335    }
4336
4337    fn GetAriaModal(&self) -> Option<DOMString> {
4338        self.get_nullable_string_attribute(&local_name!("aria-modal"))
4339    }
4340
4341    fn SetAriaModal(&self, cx: &mut JSContext, value: Option<DOMString>) {
4342        self.set_nullable_string_attribute(cx, &local_name!("aria-modal"), value);
4343    }
4344
4345    fn GetAriaMultiLine(&self) -> Option<DOMString> {
4346        self.get_nullable_string_attribute(&local_name!("aria-multiline"))
4347    }
4348
4349    fn SetAriaMultiLine(&self, cx: &mut JSContext, value: Option<DOMString>) {
4350        self.set_nullable_string_attribute(cx, &local_name!("aria-multiline"), value);
4351    }
4352
4353    fn GetAriaMultiSelectable(&self) -> Option<DOMString> {
4354        self.get_nullable_string_attribute(&local_name!("aria-multiselectable"))
4355    }
4356
4357    fn SetAriaMultiSelectable(&self, cx: &mut JSContext, value: Option<DOMString>) {
4358        self.set_nullable_string_attribute(cx, &local_name!("aria-multiselectable"), value);
4359    }
4360
4361    fn GetAriaOrientation(&self) -> Option<DOMString> {
4362        self.get_nullable_string_attribute(&local_name!("aria-orientation"))
4363    }
4364
4365    fn SetAriaOrientation(&self, cx: &mut JSContext, value: Option<DOMString>) {
4366        self.set_nullable_string_attribute(cx, &local_name!("aria-orientation"), value);
4367    }
4368
4369    fn GetAriaPlaceholder(&self) -> Option<DOMString> {
4370        self.get_nullable_string_attribute(&local_name!("aria-placeholder"))
4371    }
4372
4373    fn SetAriaPlaceholder(&self, cx: &mut JSContext, value: Option<DOMString>) {
4374        self.set_nullable_string_attribute(cx, &local_name!("aria-placeholder"), value);
4375    }
4376
4377    fn GetAriaPosInSet(&self) -> Option<DOMString> {
4378        self.get_nullable_string_attribute(&local_name!("aria-posinset"))
4379    }
4380
4381    fn SetAriaPosInSet(&self, cx: &mut JSContext, value: Option<DOMString>) {
4382        self.set_nullable_string_attribute(cx, &local_name!("aria-posinset"), value);
4383    }
4384
4385    fn GetAriaPressed(&self) -> Option<DOMString> {
4386        self.get_nullable_string_attribute(&local_name!("aria-pressed"))
4387    }
4388
4389    fn SetAriaPressed(&self, cx: &mut JSContext, value: Option<DOMString>) {
4390        self.set_nullable_string_attribute(cx, &local_name!("aria-pressed"), value);
4391    }
4392
4393    fn GetAriaReadOnly(&self) -> Option<DOMString> {
4394        self.get_nullable_string_attribute(&local_name!("aria-readonly"))
4395    }
4396
4397    fn SetAriaReadOnly(&self, cx: &mut JSContext, value: Option<DOMString>) {
4398        self.set_nullable_string_attribute(cx, &local_name!("aria-readonly"), value);
4399    }
4400
4401    fn GetAriaRelevant(&self) -> Option<DOMString> {
4402        self.get_nullable_string_attribute(&local_name!("aria-relevant"))
4403    }
4404
4405    fn SetAriaRelevant(&self, cx: &mut JSContext, value: Option<DOMString>) {
4406        self.set_nullable_string_attribute(cx, &local_name!("aria-relevant"), value);
4407    }
4408
4409    fn GetAriaRequired(&self) -> Option<DOMString> {
4410        self.get_nullable_string_attribute(&local_name!("aria-required"))
4411    }
4412
4413    fn SetAriaRequired(&self, cx: &mut JSContext, value: Option<DOMString>) {
4414        self.set_nullable_string_attribute(cx, &local_name!("aria-required"), value);
4415    }
4416
4417    fn GetAriaRoleDescription(&self) -> Option<DOMString> {
4418        self.get_nullable_string_attribute(&local_name!("aria-roledescription"))
4419    }
4420
4421    fn SetAriaRoleDescription(&self, cx: &mut JSContext, value: Option<DOMString>) {
4422        self.set_nullable_string_attribute(cx, &local_name!("aria-roledescription"), value);
4423    }
4424
4425    fn GetAriaRowCount(&self) -> Option<DOMString> {
4426        self.get_nullable_string_attribute(&local_name!("aria-rowcount"))
4427    }
4428
4429    fn SetAriaRowCount(&self, cx: &mut JSContext, value: Option<DOMString>) {
4430        self.set_nullable_string_attribute(cx, &local_name!("aria-rowcount"), value);
4431    }
4432
4433    fn GetAriaRowIndex(&self) -> Option<DOMString> {
4434        self.get_nullable_string_attribute(&local_name!("aria-rowindex"))
4435    }
4436
4437    fn SetAriaRowIndex(&self, cx: &mut JSContext, value: Option<DOMString>) {
4438        self.set_nullable_string_attribute(cx, &local_name!("aria-rowindex"), value);
4439    }
4440
4441    fn GetAriaRowIndexText(&self) -> Option<DOMString> {
4442        self.get_nullable_string_attribute(&local_name!("aria-rowindextext"))
4443    }
4444
4445    fn SetAriaRowIndexText(&self, cx: &mut JSContext, value: Option<DOMString>) {
4446        self.set_nullable_string_attribute(cx, &local_name!("aria-rowindextext"), value);
4447    }
4448
4449    fn GetAriaRowSpan(&self) -> Option<DOMString> {
4450        self.get_nullable_string_attribute(&local_name!("aria-rowspan"))
4451    }
4452
4453    fn SetAriaRowSpan(&self, cx: &mut JSContext, value: Option<DOMString>) {
4454        self.set_nullable_string_attribute(cx, &local_name!("aria-rowspan"), value);
4455    }
4456
4457    fn GetAriaSelected(&self) -> Option<DOMString> {
4458        self.get_nullable_string_attribute(&local_name!("aria-selected"))
4459    }
4460
4461    fn SetAriaSelected(&self, cx: &mut JSContext, value: Option<DOMString>) {
4462        self.set_nullable_string_attribute(cx, &local_name!("aria-selected"), value);
4463    }
4464
4465    fn GetAriaSetSize(&self) -> Option<DOMString> {
4466        self.get_nullable_string_attribute(&local_name!("aria-setsize"))
4467    }
4468
4469    fn SetAriaSetSize(&self, cx: &mut JSContext, value: Option<DOMString>) {
4470        self.set_nullable_string_attribute(cx, &local_name!("aria-setsize"), value);
4471    }
4472
4473    fn GetAriaSort(&self) -> Option<DOMString> {
4474        self.get_nullable_string_attribute(&local_name!("aria-sort"))
4475    }
4476
4477    fn SetAriaSort(&self, cx: &mut JSContext, value: Option<DOMString>) {
4478        self.set_nullable_string_attribute(cx, &local_name!("aria-sort"), value);
4479    }
4480
4481    fn GetAriaValueMax(&self) -> Option<DOMString> {
4482        self.get_nullable_string_attribute(&local_name!("aria-valuemax"))
4483    }
4484
4485    fn SetAriaValueMax(&self, cx: &mut JSContext, value: Option<DOMString>) {
4486        self.set_nullable_string_attribute(cx, &local_name!("aria-valuemax"), value);
4487    }
4488
4489    fn GetAriaValueMin(&self) -> Option<DOMString> {
4490        self.get_nullable_string_attribute(&local_name!("aria-valuemin"))
4491    }
4492
4493    fn SetAriaValueMin(&self, cx: &mut JSContext, value: Option<DOMString>) {
4494        self.set_nullable_string_attribute(cx, &local_name!("aria-valuemin"), value);
4495    }
4496
4497    fn GetAriaValueNow(&self) -> Option<DOMString> {
4498        self.get_nullable_string_attribute(&local_name!("aria-valuenow"))
4499    }
4500
4501    fn SetAriaValueNow(&self, cx: &mut JSContext, value: Option<DOMString>) {
4502        self.set_nullable_string_attribute(cx, &local_name!("aria-valuenow"), value);
4503    }
4504
4505    fn GetAriaValueText(&self) -> Option<DOMString> {
4506        self.get_nullable_string_attribute(&local_name!("aria-valuetext"))
4507    }
4508
4509    fn SetAriaValueText(&self, cx: &mut JSContext, value: Option<DOMString>) {
4510        self.set_nullable_string_attribute(cx, &local_name!("aria-valuetext"), value);
4511    }
4512
4513    /// <https://dom.spec.whatwg.org/#dom-slotable-assignedslot>
4514    fn GetAssignedSlot(&self) -> Option<DomRoot<HTMLSlotElement>> {
4515        let cx = GlobalScope::get_cx();
4516
4517        // > The assignedSlot getter steps are to return the result of
4518        // > find a slot given this and with the open flag set.
4519        rooted!(in(*cx) let slottable = Slottable(Dom::from_ref(self.upcast::<Node>())));
4520        slottable.find_a_slot(true)
4521    }
4522
4523    /// <https://drafts.csswg.org/css-shadow-parts/#dom-element-part>
4524    fn Part(&self) -> DomRoot<DOMTokenList> {
4525        self.ensure_rare_data()
4526            .part
4527            .or_init(|| DOMTokenList::new(self, &local_name!("part"), None, CanGc::note()))
4528    }
4529}
4530
4531impl VirtualMethods for Element {
4532    fn super_type(&self) -> Option<&dyn VirtualMethods> {
4533        Some(self.upcast::<Node>() as &dyn VirtualMethods)
4534    }
4535
4536    fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
4537        // FIXME: This should be more fine-grained, not all elements care about these.
4538        if attr.local_name() == &local_name!("lang") {
4539            return true;
4540        }
4541
4542        self.super_type()
4543            .unwrap()
4544            .attribute_affects_presentational_hints(attr)
4545    }
4546
4547    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
4548        self.super_type()
4549            .unwrap()
4550            .attribute_mutated(attr, mutation, can_gc);
4551        let node = self.upcast::<Node>();
4552        let doc = node.owner_doc();
4553        match *attr.local_name() {
4554            local_name!("style") => self.update_style_attribute(attr, mutation),
4555            local_name!("id") => {
4556                // https://dom.spec.whatwg.org/#ref-for-concept-element-attributes-change-ext%E2%91%A2
4557                *self.id_attribute.borrow_mut() = mutation.new_value(attr).and_then(|value| {
4558                    let value = value.as_atom();
4559                    if value != &atom!("") {
4560                        // Step 2. Otherwise, if localName is id, namespace is null, then set element’s ID to value.
4561                        Some(value.clone())
4562                    } else {
4563                        // Step 1. If localName is id, namespace is null, and value is null or the empty string, then unset element’s ID.
4564                        None
4565                    }
4566                });
4567
4568                let containing_shadow_root = self.containing_shadow_root();
4569                if node.is_in_a_document_tree() || node.is_in_a_shadow_tree() {
4570                    let value = attr.value().as_atom().clone();
4571                    match mutation {
4572                        AttributeMutation::Set(old_value, _) => {
4573                            if let Some(old_value) = old_value {
4574                                let old_value = old_value.as_atom().clone();
4575                                if let Some(ref shadow_root) = containing_shadow_root {
4576                                    shadow_root.unregister_element_id(self, old_value, can_gc);
4577                                } else {
4578                                    doc.unregister_element_id(self, old_value, can_gc);
4579                                }
4580                            }
4581                            if value != atom!("") {
4582                                if let Some(ref shadow_root) = containing_shadow_root {
4583                                    shadow_root.register_element_id(self, value, can_gc);
4584                                } else {
4585                                    doc.register_element_id(self, value, can_gc);
4586                                }
4587                            }
4588                        },
4589                        AttributeMutation::Removed => {
4590                            if value != atom!("") {
4591                                if let Some(ref shadow_root) = containing_shadow_root {
4592                                    shadow_root.unregister_element_id(self, value, can_gc);
4593                                } else {
4594                                    doc.unregister_element_id(self, value, can_gc);
4595                                }
4596                            }
4597                        },
4598                    }
4599                }
4600            },
4601            local_name!("name") => {
4602                // Keep the name in rare data for fast access
4603                self.ensure_rare_data().name_attribute =
4604                    mutation.new_value(attr).and_then(|value| {
4605                        let value = value.as_atom();
4606                        if value != &atom!("") {
4607                            Some(value.clone())
4608                        } else {
4609                            None
4610                        }
4611                    });
4612                // Keep the document name_map up to date
4613                // (if we're not in shadow DOM)
4614                if node.is_connected() && node.containing_shadow_root().is_none() {
4615                    let value = attr.value().as_atom().clone();
4616                    match mutation {
4617                        AttributeMutation::Set(old_value, _) => {
4618                            if let Some(old_value) = old_value {
4619                                let old_value = old_value.as_atom().clone();
4620                                doc.unregister_element_name(self, old_value);
4621                            }
4622                            if value != atom!("") {
4623                                doc.register_element_name(self, value);
4624                            }
4625                        },
4626                        AttributeMutation::Removed => {
4627                            if value != atom!("") {
4628                                doc.unregister_element_name(self, value);
4629                            }
4630                        },
4631                    }
4632                }
4633            },
4634            local_name!("slot") => {
4635                // Update slottable data
4636                let cx = GlobalScope::get_cx();
4637
4638                rooted!(in(*cx) let slottable = Slottable(Dom::from_ref(self.upcast::<Node>())));
4639
4640                // Slottable name change steps from https://dom.spec.whatwg.org/#light-tree-slotables
4641                if let Some(assigned_slot) = slottable.assigned_slot() {
4642                    assigned_slot.assign_slottables();
4643                }
4644                slottable.assign_a_slot();
4645            },
4646            _ => {
4647                // FIXME(emilio): This is pretty dubious, and should be done in
4648                // the relevant super-classes.
4649                if attr.namespace() == &ns!() && attr.local_name() == &local_name!("src") {
4650                    node.dirty(NodeDamage::Other);
4651                }
4652            },
4653        };
4654
4655        // TODO: This should really only take into account the actual attributes that are used
4656        // for the content attribute property.
4657        if self
4658            .upcast::<Node>()
4659            .get_flag(NodeFlags::USES_ATTR_IN_CONTENT_ATTRIBUTE)
4660        {
4661            node.dirty(NodeDamage::ContentOrHeritage);
4662        }
4663
4664        // Make sure we rev the version even if we didn't dirty the node. If we
4665        // don't do this, various attribute-dependent htmlcollections (like those
4666        // generated by getElementsByClassName) might become stale.
4667        node.rev_version();
4668
4669        // Notify devtools that the DOM changed
4670        let global = self.owner_global();
4671        if global.live_devtools_updates() {
4672            if let Some(sender) = global.devtools_chan() {
4673                let pipeline_id = global.pipeline_id();
4674                if ScriptThread::devtools_want_updates_for_node(pipeline_id, self.upcast()) {
4675                    let devtools_message = ScriptToDevtoolsControlMsg::DomMutation(
4676                        pipeline_id,
4677                        DomMutation::AttributeModified {
4678                            node: self.upcast::<Node>().unique_id(pipeline_id),
4679                            attribute_name: attr.local_name().to_string(),
4680                            new_value: mutation.new_value(attr).map(|value| value.to_string()),
4681                        },
4682                    );
4683                    sender.send(devtools_message).unwrap();
4684                }
4685            }
4686        }
4687    }
4688
4689    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
4690        match *name {
4691            local_name!("id") => AttrValue::Atom(value.into()),
4692            local_name!("name") => AttrValue::Atom(value.into()),
4693            local_name!("class") | local_name!("part") => {
4694                AttrValue::from_serialized_tokenlist(value.into())
4695            },
4696            local_name!("exportparts") => AttrValue::from_shadow_parts(value.into()),
4697            local_name!("tabindex") => AttrValue::from_i32(value.into(), -1),
4698            _ => self
4699                .super_type()
4700                .unwrap()
4701                .parse_plain_attribute(name, value),
4702        }
4703    }
4704
4705    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
4706        if let Some(s) = self.super_type() {
4707            s.bind_to_tree(context, can_gc);
4708        }
4709
4710        if let Some(f) = self.as_maybe_form_control() {
4711            f.bind_form_control_to_tree(can_gc);
4712        }
4713
4714        let doc = self.owner_document();
4715
4716        if let Some(ref shadow_root) = self.shadow_root() {
4717            shadow_root.bind_to_tree(context, can_gc);
4718        }
4719
4720        if !context.is_in_tree() {
4721            return;
4722        }
4723
4724        if let Some(ref id) = *self.id_attribute.borrow() {
4725            if let Some(shadow_root) = self.containing_shadow_root() {
4726                shadow_root.register_element_id(self, id.clone(), can_gc);
4727            } else {
4728                doc.register_element_id(self, id.clone(), can_gc);
4729            }
4730        }
4731        if let Some(ref name) = self.name_attribute() {
4732            if self.containing_shadow_root().is_none() {
4733                doc.register_element_name(self, name.clone());
4734            }
4735        }
4736
4737        // This is used for layout optimization.
4738        doc.increment_dom_count();
4739    }
4740
4741    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
4742        self.super_type().unwrap().unbind_from_tree(context, can_gc);
4743
4744        if let Some(f) = self.as_maybe_form_control() {
4745            // TODO: The valid state of ancestors might be wrong if the form control element
4746            // has a fieldset ancestor, for instance: `<form><fieldset><input>`,
4747            // if `<input>` is unbound, `<form><fieldset>` should trigger a call to `update_validity()`.
4748            f.unbind_form_control_from_tree(can_gc);
4749        }
4750
4751        if !context.tree_is_in_a_document_tree && !context.tree_is_in_a_shadow_tree {
4752            return;
4753        }
4754
4755        let doc = self.owner_document();
4756
4757        let fullscreen = doc.fullscreen_element();
4758        if fullscreen.as_deref() == Some(self) {
4759            doc.exit_fullscreen(can_gc);
4760        }
4761        if let Some(ref value) = *self.id_attribute.borrow() {
4762            if let Some(ref shadow_root) = self.containing_shadow_root() {
4763                // Only unregister the element id if the node was disconnected from it's shadow root
4764                // (as opposed to the whole shadow tree being disconnected as a whole)
4765                if !self.upcast::<Node>().is_in_a_shadow_tree() {
4766                    shadow_root.unregister_element_id(self, value.clone(), can_gc);
4767                }
4768            } else {
4769                doc.unregister_element_id(self, value.clone(), can_gc);
4770            }
4771        }
4772        if let Some(ref value) = self.name_attribute() {
4773            if self.containing_shadow_root().is_none() {
4774                doc.unregister_element_name(self, value.clone());
4775            }
4776        }
4777        // This is used for layout optimization.
4778        doc.decrement_dom_count();
4779    }
4780
4781    fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
4782        if let Some(s) = self.super_type() {
4783            s.children_changed(mutation, can_gc);
4784        }
4785
4786        let flags = self.get_selector_flags();
4787        if flags.intersects(ElementSelectorFlags::HAS_SLOW_SELECTOR) {
4788            // All children of this node need to be restyled when any child changes.
4789            self.upcast::<Node>().dirty(NodeDamage::Other);
4790        } else {
4791            if flags.intersects(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
4792                if let Some(next_child) = mutation.next_child() {
4793                    for child in next_child.inclusively_following_siblings() {
4794                        if child.is::<Element>() {
4795                            child.dirty(NodeDamage::Other);
4796                        }
4797                    }
4798                }
4799            }
4800            if flags.intersects(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) {
4801                if let Some(child) = mutation.modified_edge_element() {
4802                    child.dirty(NodeDamage::Other);
4803                }
4804            }
4805        }
4806    }
4807
4808    fn adopting_steps(&self, old_doc: &Document, can_gc: CanGc) {
4809        self.super_type().unwrap().adopting_steps(old_doc, can_gc);
4810
4811        if self.owner_document().is_html_document() != old_doc.is_html_document() {
4812            self.tag_name.clear();
4813        }
4814    }
4815
4816    fn post_connection_steps(&self, cx: &mut js::context::JSContext) {
4817        if let Some(s) = self.super_type() {
4818            s.post_connection_steps(cx);
4819        }
4820
4821        self.update_nonce_post_connection();
4822    }
4823
4824    /// <https://html.spec.whatwg.org/multipage/#nonce-attributes%3Aconcept-node-clone-ext>
4825    fn cloning_steps(
4826        &self,
4827        cx: &mut JSContext,
4828        copy: &Node,
4829        maybe_doc: Option<&Document>,
4830        clone_children: CloneChildrenFlag,
4831    ) {
4832        if let Some(s) = self.super_type() {
4833            s.cloning_steps(cx, copy, maybe_doc, clone_children);
4834        }
4835        let elem = copy.downcast::<Element>().unwrap();
4836        if let Some(rare_data) = self.rare_data().as_ref() {
4837            elem.update_nonce_internal_slot(rare_data.cryptographic_nonce.clone());
4838        }
4839    }
4840}
4841
4842#[derive(Clone, PartialEq)]
4843/// A type that wraps a DomRoot value so we can implement the SelectorsElement
4844/// trait without violating the orphan rule. Since the trait assumes that the
4845/// return type and self type of various methods is the same type that it is
4846/// implemented against, we need to be able to represent multiple ownership styles.
4847pub enum SelectorWrapper<'a> {
4848    Borrowed(&'a DomRoot<Element>),
4849    Owned(DomRoot<Element>),
4850}
4851
4852impl fmt::Debug for SelectorWrapper<'_> {
4853    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
4854        self.deref().fmt(f)
4855    }
4856}
4857
4858impl Deref for SelectorWrapper<'_> {
4859    type Target = DomRoot<Element>;
4860
4861    fn deref(&self) -> &Self::Target {
4862        match self {
4863            SelectorWrapper::Owned(r) => r,
4864            SelectorWrapper::Borrowed(r) => r,
4865        }
4866    }
4867}
4868
4869impl SelectorWrapper<'_> {
4870    fn into_owned(self) -> DomRoot<Element> {
4871        match self {
4872            SelectorWrapper::Owned(r) => r,
4873            SelectorWrapper::Borrowed(r) => r.clone(),
4874        }
4875    }
4876}
4877
4878impl SelectorsElement for SelectorWrapper<'_> {
4879    type Impl = SelectorImpl;
4880
4881    #[expect(unsafe_code)]
4882    fn opaque(&self) -> ::selectors::OpaqueElement {
4883        ::selectors::OpaqueElement::new(unsafe { &*self.reflector().get_jsobject().get() })
4884    }
4885
4886    fn parent_element(&self) -> Option<Self> {
4887        self.upcast::<Node>()
4888            .GetParentElement()
4889            .map(SelectorWrapper::Owned)
4890    }
4891
4892    fn parent_node_is_shadow_root(&self) -> bool {
4893        match self.upcast::<Node>().GetParentNode() {
4894            None => false,
4895            Some(node) => node.is::<ShadowRoot>(),
4896        }
4897    }
4898
4899    fn containing_shadow_host(&self) -> Option<Self> {
4900        self.containing_shadow_root()
4901            .map(|shadow_root| shadow_root.Host())
4902            .map(SelectorWrapper::Owned)
4903    }
4904
4905    fn is_pseudo_element(&self) -> bool {
4906        false
4907    }
4908
4909    fn match_pseudo_element(
4910        &self,
4911        _pseudo: &PseudoElement,
4912        _context: &mut MatchingContext<Self::Impl>,
4913    ) -> bool {
4914        false
4915    }
4916
4917    fn prev_sibling_element(&self) -> Option<Self> {
4918        self.node
4919            .preceding_siblings()
4920            .find_map(DomRoot::downcast)
4921            .map(SelectorWrapper::Owned)
4922    }
4923
4924    fn next_sibling_element(&self) -> Option<Self> {
4925        self.node
4926            .following_siblings()
4927            .find_map(DomRoot::downcast)
4928            .map(SelectorWrapper::Owned)
4929    }
4930
4931    fn first_element_child(&self) -> Option<Self> {
4932        self.GetFirstElementChild().map(SelectorWrapper::Owned)
4933    }
4934
4935    fn attr_matches(
4936        &self,
4937        ns: &NamespaceConstraint<&style::Namespace>,
4938        local_name: &style::LocalName,
4939        operation: &AttrSelectorOperation<&AtomString>,
4940    ) -> bool {
4941        match *ns {
4942            NamespaceConstraint::Specific(ns) => self
4943                .get_attribute_with_namespace(ns, local_name)
4944                .is_some_and(|attr| attr.value().eval_selector(operation)),
4945            NamespaceConstraint::Any => self.attrs.borrow().iter().any(|attr| {
4946                *attr.local_name() == **local_name && attr.value().eval_selector(operation)
4947            }),
4948        }
4949    }
4950
4951    fn is_root(&self) -> bool {
4952        Element::is_root(self)
4953    }
4954
4955    fn is_empty(&self) -> bool {
4956        self.node.children().all(|node| {
4957            !node.is::<Element>() &&
4958                match node.downcast::<Text>() {
4959                    None => true,
4960                    Some(text) => text.upcast::<CharacterData>().data().is_empty(),
4961                }
4962        })
4963    }
4964
4965    fn has_local_name(&self, local_name: &LocalName) -> bool {
4966        Element::local_name(self) == local_name
4967    }
4968
4969    fn has_namespace(&self, ns: &Namespace) -> bool {
4970        Element::namespace(self) == ns
4971    }
4972
4973    fn is_same_type(&self, other: &Self) -> bool {
4974        Element::local_name(self) == Element::local_name(other) &&
4975            Element::namespace(self) == Element::namespace(other)
4976    }
4977
4978    fn match_non_ts_pseudo_class(
4979        &self,
4980        pseudo_class: &NonTSPseudoClass,
4981        _: &mut MatchingContext<Self::Impl>,
4982    ) -> bool {
4983        match *pseudo_class {
4984            // https://github.com/servo/servo/issues/8718
4985            NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
4986            NonTSPseudoClass::Visited => false,
4987
4988            NonTSPseudoClass::ServoNonZeroBorder => match self.downcast::<HTMLTableElement>() {
4989                None => false,
4990                Some(this) => match this.get_border() {
4991                    None | Some(0) => false,
4992                    Some(_) => true,
4993                },
4994            },
4995
4996            NonTSPseudoClass::CustomState(ref state) => self.has_custom_state(&state.0),
4997
4998            // FIXME(heycam): This is wrong, since extended_filtering accepts
4999            // a string containing commas (separating each language tag in
5000            // a list) but the pseudo-class instead should be parsing and
5001            // storing separate <ident> or <string>s for each language tag.
5002            NonTSPseudoClass::Lang(ref lang) => {
5003                extended_filtering(&self.upcast::<Node>().get_lang().unwrap_or_default(), lang)
5004            },
5005
5006            NonTSPseudoClass::ReadOnly => {
5007                !Element::state(self).contains(NonTSPseudoClass::ReadWrite.state_flag())
5008            },
5009
5010            NonTSPseudoClass::Active |
5011            NonTSPseudoClass::Autofill |
5012            NonTSPseudoClass::Checked |
5013            NonTSPseudoClass::Default |
5014            NonTSPseudoClass::Defined |
5015            NonTSPseudoClass::Disabled |
5016            NonTSPseudoClass::Enabled |
5017            NonTSPseudoClass::Focus |
5018            NonTSPseudoClass::FocusVisible |
5019            NonTSPseudoClass::FocusWithin |
5020            NonTSPseudoClass::Fullscreen |
5021            NonTSPseudoClass::Hover |
5022            NonTSPseudoClass::InRange |
5023            NonTSPseudoClass::Indeterminate |
5024            NonTSPseudoClass::Invalid |
5025            NonTSPseudoClass::Modal |
5026            NonTSPseudoClass::MozMeterOptimum |
5027            NonTSPseudoClass::MozMeterSubOptimum |
5028            NonTSPseudoClass::MozMeterSubSubOptimum |
5029            NonTSPseudoClass::Open |
5030            NonTSPseudoClass::Optional |
5031            NonTSPseudoClass::OutOfRange |
5032            NonTSPseudoClass::PlaceholderShown |
5033            NonTSPseudoClass::PopoverOpen |
5034            NonTSPseudoClass::ReadWrite |
5035            NonTSPseudoClass::Required |
5036            NonTSPseudoClass::Target |
5037            NonTSPseudoClass::UserInvalid |
5038            NonTSPseudoClass::UserValid |
5039            NonTSPseudoClass::Valid => Element::state(self).contains(pseudo_class.state_flag()),
5040        }
5041    }
5042
5043    fn is_link(&self) -> bool {
5044        // FIXME: This is HTML only.
5045        let node = self.upcast::<Node>();
5046        match node.type_id() {
5047            // https://html.spec.whatwg.org/multipage/#selector-link
5048            NodeTypeId::Element(ElementTypeId::HTMLElement(
5049                HTMLElementTypeId::HTMLAnchorElement,
5050            )) |
5051            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) |
5052            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => {
5053                self.has_attribute(&local_name!("href"))
5054            },
5055            _ => false,
5056        }
5057    }
5058
5059    fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
5060        self.id_attribute
5061            .borrow()
5062            .as_ref()
5063            .is_some_and(|atom| case_sensitivity.eq_atom(id, atom))
5064    }
5065
5066    fn is_part(&self, name: &AtomIdent) -> bool {
5067        Element::is_part(self, name, CaseSensitivity::CaseSensitive)
5068    }
5069
5070    fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
5071        None
5072    }
5073
5074    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
5075        Element::has_class(self, name, case_sensitivity)
5076    }
5077
5078    fn is_html_element_in_html_document(&self) -> bool {
5079        self.html_element_in_html_document()
5080    }
5081
5082    fn is_html_slot_element(&self) -> bool {
5083        self.is_html_element() && self.local_name() == &local_name!("slot")
5084    }
5085
5086    fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
5087        // Handle flags that apply to the element.
5088        let self_flags = flags.for_self();
5089        if !self_flags.is_empty() {
5090            #[expect(unsafe_code)]
5091            unsafe {
5092                Dom::from_ref(&***self)
5093                    .to_layout()
5094                    .insert_selector_flags(self_flags);
5095            }
5096        }
5097
5098        // Handle flags that apply to the parent.
5099        let parent_flags = flags.for_parent();
5100        if !parent_flags.is_empty() {
5101            if let Some(p) = self.parent_element() {
5102                #[expect(unsafe_code)]
5103                unsafe {
5104                    Dom::from_ref(&**p)
5105                        .to_layout()
5106                        .insert_selector_flags(parent_flags);
5107                }
5108            }
5109        }
5110    }
5111
5112    fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool {
5113        let mut f = |hash| filter.insert_hash(hash & BLOOM_HASH_MASK);
5114
5115        // We can't use style::bloom::each_relevant_element_hash(*self, f)
5116        // since DomRoot<Element> doesn't have the TElement trait.
5117        f(Element::local_name(self).get_hash());
5118        f(Element::namespace(self).get_hash());
5119
5120        if let Some(ref id) = *self.id_attribute.borrow() {
5121            f(id.get_hash());
5122        }
5123
5124        if let Some(attr) = self.get_attribute(&local_name!("class")) {
5125            for class in attr.value().as_tokens() {
5126                f(AtomIdent::cast(class).get_hash());
5127            }
5128        }
5129
5130        for attr in self.attrs.borrow().iter() {
5131            let name = style::values::GenericAtomIdent::cast(attr.local_name());
5132            if !style::bloom::is_attr_name_excluded_from_filter(name) {
5133                f(name.get_hash());
5134            }
5135        }
5136
5137        true
5138    }
5139
5140    fn has_custom_state(&self, name: &AtomIdent) -> bool {
5141        let mut has_state = false;
5142        self.each_custom_state(|state| has_state |= state == name);
5143
5144        has_state
5145    }
5146}
5147
5148impl Element {
5149    fn each_custom_state<F>(&self, callback: F)
5150    where
5151        F: FnMut(&AtomIdent),
5152    {
5153        self.get_element_internals()
5154            .and_then(|internals| internals.custom_states())
5155            .inspect(|states| states.for_each_state(callback));
5156    }
5157
5158    pub(crate) fn client_rect(&self) -> Rect<i32, CSSPixel> {
5159        let doc = self.node.owner_doc();
5160
5161        if let Some(rect) = self
5162            .rare_data()
5163            .as_ref()
5164            .and_then(|data| data.client_rect.as_ref())
5165            .and_then(|rect| rect.get().ok())
5166        {
5167            if doc.restyle_reason().is_empty() {
5168                return rect;
5169            }
5170        }
5171
5172        let mut rect = self.upcast::<Node>().client_rect();
5173        let in_quirks_mode = doc.quirks_mode() == QuirksMode::Quirks;
5174
5175        if (in_quirks_mode && doc.GetBody().as_deref() == self.downcast::<HTMLElement>()) ||
5176            (!in_quirks_mode && self.is_document_element())
5177        {
5178            rect.size = doc.window().viewport_details().size.round().to_i32();
5179        }
5180
5181        self.ensure_rare_data().client_rect = Some(self.owner_window().cache_layout_value(rect));
5182        rect
5183    }
5184
5185    pub(crate) fn as_maybe_activatable(&self) -> Option<&dyn Activatable> {
5186        let element = match self.upcast::<Node>().type_id() {
5187            NodeTypeId::Element(ElementTypeId::HTMLElement(
5188                HTMLElementTypeId::HTMLInputElement,
5189            )) => {
5190                let element = self.downcast::<HTMLInputElement>().unwrap();
5191                Some(element as &dyn Activatable)
5192            },
5193            NodeTypeId::Element(ElementTypeId::HTMLElement(
5194                HTMLElementTypeId::HTMLButtonElement,
5195            )) => {
5196                let element = self.downcast::<HTMLButtonElement>().unwrap();
5197                Some(element as &dyn Activatable)
5198            },
5199            NodeTypeId::Element(ElementTypeId::HTMLElement(
5200                HTMLElementTypeId::HTMLAnchorElement,
5201            )) => {
5202                let element = self.downcast::<HTMLAnchorElement>().unwrap();
5203                Some(element as &dyn Activatable)
5204            },
5205            NodeTypeId::Element(ElementTypeId::HTMLElement(
5206                HTMLElementTypeId::HTMLLabelElement,
5207            )) => {
5208                let element = self.downcast::<HTMLLabelElement>().unwrap();
5209                Some(element as &dyn Activatable)
5210            },
5211            NodeTypeId::Element(ElementTypeId::HTMLElement(
5212                HTMLElementTypeId::HTMLSelectElement,
5213            )) => {
5214                let element = self.downcast::<HTMLSelectElement>().unwrap();
5215                Some(element as &dyn Activatable)
5216            },
5217            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => {
5218                let element = self.downcast::<HTMLElement>().unwrap();
5219                Some(element as &dyn Activatable)
5220            },
5221            _ => None,
5222        };
5223        element.and_then(|elem| {
5224            if elem.is_instance_activatable() {
5225                Some(elem)
5226            } else {
5227                None
5228            }
5229        })
5230    }
5231
5232    pub(crate) fn as_stylesheet_owner(&self) -> Option<&dyn StylesheetOwner> {
5233        if let Some(s) = self.downcast::<HTMLStyleElement>() {
5234            return Some(s as &dyn StylesheetOwner);
5235        }
5236
5237        if let Some(l) = self.downcast::<HTMLLinkElement>() {
5238            return Some(l as &dyn StylesheetOwner);
5239        }
5240
5241        None
5242    }
5243
5244    // https://html.spec.whatwg.org/multipage/#category-submit
5245    pub(crate) fn as_maybe_validatable(&self) -> Option<&dyn Validatable> {
5246        match self.upcast::<Node>().type_id() {
5247            NodeTypeId::Element(ElementTypeId::HTMLElement(
5248                HTMLElementTypeId::HTMLInputElement,
5249            )) => {
5250                let element = self.downcast::<HTMLInputElement>().unwrap();
5251                Some(element as &dyn Validatable)
5252            },
5253            NodeTypeId::Element(ElementTypeId::HTMLElement(
5254                HTMLElementTypeId::HTMLButtonElement,
5255            )) => {
5256                let element = self.downcast::<HTMLButtonElement>().unwrap();
5257                Some(element as &dyn Validatable)
5258            },
5259            NodeTypeId::Element(ElementTypeId::HTMLElement(
5260                HTMLElementTypeId::HTMLObjectElement,
5261            )) => {
5262                let element = self.downcast::<HTMLObjectElement>().unwrap();
5263                Some(element as &dyn Validatable)
5264            },
5265            NodeTypeId::Element(ElementTypeId::HTMLElement(
5266                HTMLElementTypeId::HTMLSelectElement,
5267            )) => {
5268                let element = self.downcast::<HTMLSelectElement>().unwrap();
5269                Some(element as &dyn Validatable)
5270            },
5271            NodeTypeId::Element(ElementTypeId::HTMLElement(
5272                HTMLElementTypeId::HTMLTextAreaElement,
5273            )) => {
5274                let element = self.downcast::<HTMLTextAreaElement>().unwrap();
5275                Some(element as &dyn Validatable)
5276            },
5277            NodeTypeId::Element(ElementTypeId::HTMLElement(
5278                HTMLElementTypeId::HTMLFieldSetElement,
5279            )) => {
5280                let element = self.downcast::<HTMLFieldSetElement>().unwrap();
5281                Some(element as &dyn Validatable)
5282            },
5283            NodeTypeId::Element(ElementTypeId::HTMLElement(
5284                HTMLElementTypeId::HTMLOutputElement,
5285            )) => {
5286                let element = self.downcast::<HTMLOutputElement>().unwrap();
5287                Some(element as &dyn Validatable)
5288            },
5289            _ => None,
5290        }
5291    }
5292
5293    pub(crate) fn is_invalid(&self, needs_update: bool, can_gc: CanGc) -> bool {
5294        if let Some(validatable) = self.as_maybe_validatable() {
5295            if needs_update {
5296                validatable
5297                    .validity_state(can_gc)
5298                    .perform_validation_and_update(ValidationFlags::all(), can_gc);
5299            }
5300            return validatable.is_instance_validatable() &&
5301                !validatable.satisfies_constraints(can_gc);
5302        }
5303
5304        if let Some(internals) = self.get_element_internals() {
5305            return internals.is_invalid(can_gc);
5306        }
5307        false
5308    }
5309
5310    pub(crate) fn is_instance_validatable(&self) -> bool {
5311        if let Some(validatable) = self.as_maybe_validatable() {
5312            return validatable.is_instance_validatable();
5313        }
5314        if let Some(internals) = self.get_element_internals() {
5315            return internals.is_instance_validatable();
5316        }
5317        false
5318    }
5319
5320    pub(crate) fn init_state_for_internals(&self) {
5321        self.set_enabled_state(true);
5322        self.set_state(ElementState::VALID, true);
5323        self.set_state(ElementState::INVALID, false);
5324    }
5325
5326    pub(crate) fn click_in_progress(&self) -> bool {
5327        self.upcast::<Node>().get_flag(NodeFlags::CLICK_IN_PROGRESS)
5328    }
5329
5330    pub(crate) fn set_click_in_progress(&self, click: bool) {
5331        self.upcast::<Node>()
5332            .set_flag(NodeFlags::CLICK_IN_PROGRESS, click)
5333    }
5334
5335    // https://html.spec.whatwg.org/multipage/#nearest-activatable-element
5336    pub(crate) fn nearest_activable_element(&self) -> Option<DomRoot<Element>> {
5337        match self.as_maybe_activatable() {
5338            Some(el) => Some(DomRoot::from_ref(el.as_element())),
5339            None => {
5340                let node = self.upcast::<Node>();
5341                for node in node.ancestors() {
5342                    if let Some(node) = node.downcast::<Element>() {
5343                        if node.as_maybe_activatable().is_some() {
5344                            return Some(DomRoot::from_ref(node));
5345                        }
5346                    }
5347                }
5348                None
5349            },
5350        }
5351    }
5352
5353    pub fn state(&self) -> ElementState {
5354        self.state.get()
5355    }
5356
5357    pub(crate) fn set_state(&self, which: ElementState, value: bool) {
5358        let mut state = self.state.get();
5359        let previous_state = state;
5360        if value {
5361            state.insert(which);
5362        } else {
5363            state.remove(which);
5364        }
5365
5366        if previous_state == state {
5367            // Nothing to do
5368            return;
5369        }
5370
5371        // Add a pending restyle for this node which captures a snapshot of the state
5372        // before the change.
5373        {
5374            let document = self.owner_document();
5375            let mut entry = document.ensure_pending_restyle(self);
5376            if entry.snapshot.is_none() {
5377                entry.snapshot = Some(Snapshot::new());
5378            }
5379            let snapshot = entry.snapshot.as_mut().unwrap();
5380            if snapshot.state.is_none() {
5381                snapshot.state = Some(self.state());
5382            }
5383        }
5384
5385        self.state.set(state);
5386    }
5387
5388    /// <https://html.spec.whatwg.org/multipage/#concept-selector-active>
5389    pub(crate) fn set_active_state(&self, value: bool) {
5390        self.set_state(ElementState::ACTIVE, value);
5391
5392        if let Some(parent) = self.upcast::<Node>().GetParentElement() {
5393            parent.set_active_state(value);
5394        }
5395    }
5396
5397    pub(crate) fn focus_state(&self) -> bool {
5398        self.state.get().contains(ElementState::FOCUS)
5399    }
5400
5401    pub(crate) fn set_focus_state(&self, value: bool) {
5402        self.set_state(ElementState::FOCUS, value);
5403    }
5404
5405    pub(crate) fn hover_state(&self) -> bool {
5406        self.state.get().contains(ElementState::HOVER)
5407    }
5408
5409    pub(crate) fn set_hover_state(&self, value: bool) {
5410        self.set_state(ElementState::HOVER, value);
5411    }
5412
5413    pub(crate) fn enabled_state(&self) -> bool {
5414        self.state.get().contains(ElementState::ENABLED)
5415    }
5416
5417    pub(crate) fn set_enabled_state(&self, value: bool) {
5418        self.set_state(ElementState::ENABLED, value)
5419    }
5420
5421    pub(crate) fn disabled_state(&self) -> bool {
5422        self.state.get().contains(ElementState::DISABLED)
5423    }
5424
5425    pub(crate) fn set_disabled_state(&self, value: bool) {
5426        self.set_state(ElementState::DISABLED, value)
5427    }
5428
5429    pub(crate) fn read_write_state(&self) -> bool {
5430        self.state.get().contains(ElementState::READWRITE)
5431    }
5432
5433    pub(crate) fn set_read_write_state(&self, value: bool) {
5434        self.set_state(ElementState::READWRITE, value)
5435    }
5436
5437    pub(crate) fn open_state(&self) -> bool {
5438        self.state.get().contains(ElementState::OPEN)
5439    }
5440
5441    pub(crate) fn set_open_state(&self, value: bool) {
5442        self.set_state(ElementState::OPEN, value);
5443    }
5444
5445    pub(crate) fn placeholder_shown_state(&self) -> bool {
5446        self.state.get().contains(ElementState::PLACEHOLDER_SHOWN)
5447    }
5448
5449    pub(crate) fn set_placeholder_shown_state(&self, value: bool) {
5450        self.set_state(ElementState::PLACEHOLDER_SHOWN, value);
5451    }
5452
5453    pub(crate) fn set_modal_state(&self, value: bool) {
5454        self.set_state(ElementState::MODAL, value);
5455    }
5456
5457    pub(crate) fn set_target_state(&self, value: bool) {
5458        self.set_state(ElementState::URLTARGET, value)
5459    }
5460
5461    pub(crate) fn set_fullscreen_state(&self, value: bool) {
5462        self.set_state(ElementState::FULLSCREEN, value)
5463    }
5464
5465    /// <https://dom.spec.whatwg.org/#connected>
5466    pub(crate) fn is_connected(&self) -> bool {
5467        self.upcast::<Node>().is_connected()
5468    }
5469
5470    // https://html.spec.whatwg.org/multipage/#cannot-navigate
5471    pub(crate) fn cannot_navigate(&self) -> bool {
5472        let document = self.owner_document();
5473
5474        // Step 1.
5475        !document.is_fully_active() ||
5476            (
5477                // Step 2.
5478                !self.is::<HTMLAnchorElement>() && !self.is_connected()
5479            )
5480    }
5481}
5482
5483impl Element {
5484    pub(crate) fn check_ancestors_disabled_state_for_form_control(&self) {
5485        let node = self.upcast::<Node>();
5486        if self.disabled_state() {
5487            return;
5488        }
5489        for ancestor in node.ancestors() {
5490            if !ancestor.is::<HTMLFieldSetElement>() {
5491                continue;
5492            }
5493            if !ancestor.downcast::<Element>().unwrap().disabled_state() {
5494                continue;
5495            }
5496            if ancestor.is_parent_of(node) {
5497                self.set_disabled_state(true);
5498                self.set_enabled_state(false);
5499                return;
5500            }
5501            if let Some(ref legend) = ancestor.children().find(|n| n.is::<HTMLLegendElement>()) {
5502                // XXXabinader: should we save previous ancestor to avoid this iteration?
5503                if node.ancestors().any(|ancestor| ancestor == *legend) {
5504                    continue;
5505                }
5506            }
5507            self.set_disabled_state(true);
5508            self.set_enabled_state(false);
5509            return;
5510        }
5511    }
5512
5513    pub(crate) fn check_parent_disabled_state_for_option(&self) {
5514        if self.disabled_state() {
5515            return;
5516        }
5517        let node = self.upcast::<Node>();
5518        if let Some(ref parent) = node.GetParentNode() {
5519            if parent.is::<HTMLOptGroupElement>() &&
5520                parent.downcast::<Element>().unwrap().disabled_state()
5521            {
5522                self.set_disabled_state(true);
5523                self.set_enabled_state(false);
5524            }
5525        }
5526    }
5527
5528    pub(crate) fn check_disabled_attribute(&self) {
5529        let has_disabled_attrib = self.has_attribute(&local_name!("disabled"));
5530        self.set_disabled_state(has_disabled_attrib);
5531        self.set_enabled_state(!has_disabled_attrib);
5532    }
5533
5534    pub(crate) fn update_read_write_state_from_readonly_attribute(&self) {
5535        let has_readonly_attribute = self.has_attribute(&local_name!("readonly"));
5536        self.set_read_write_state(has_readonly_attribute);
5537    }
5538}
5539
5540#[derive(Clone, Copy, PartialEq)]
5541pub(crate) enum AttributeMutationReason {
5542    ByCloning,
5543    ByParser,
5544    Directly,
5545}
5546
5547#[derive(Clone, Copy)]
5548pub(crate) enum AttributeMutation<'a> {
5549    /// The attribute is set, keep track of old value.
5550    /// <https://dom.spec.whatwg.org/#attribute-is-set>
5551    Set(Option<&'a AttrValue>, AttributeMutationReason),
5552
5553    /// The attribute is removed.
5554    /// <https://dom.spec.whatwg.org/#attribute-is-removed>
5555    Removed,
5556}
5557
5558impl AttributeMutation<'_> {
5559    pub(crate) fn is_removal(&self) -> bool {
5560        match *self {
5561            AttributeMutation::Removed => true,
5562            AttributeMutation::Set(..) => false,
5563        }
5564    }
5565
5566    pub(crate) fn new_value<'b>(&self, attr: &'b Attr) -> Option<Ref<'b, AttrValue>> {
5567        match *self {
5568            AttributeMutation::Set(..) => Some(attr.value()),
5569            AttributeMutation::Removed => None,
5570        }
5571    }
5572}
5573
5574/// A holder for an element's "tag name", which will be lazily
5575/// resolved and cached. Should be reset when the document
5576/// owner changes.
5577#[derive(JSTraceable, MallocSizeOf)]
5578struct TagName {
5579    #[no_trace]
5580    ptr: DomRefCell<Option<LocalName>>,
5581}
5582
5583impl TagName {
5584    fn new() -> TagName {
5585        TagName {
5586            ptr: DomRefCell::new(None),
5587        }
5588    }
5589
5590    /// Retrieve a copy of the current inner value. If it is `None`, it is
5591    /// initialized with the result of `cb` first.
5592    fn or_init<F>(&self, cb: F) -> LocalName
5593    where
5594        F: FnOnce() -> LocalName,
5595    {
5596        match &mut *self.ptr.borrow_mut() {
5597            &mut Some(ref name) => name.clone(),
5598            ptr => {
5599                let name = cb();
5600                *ptr = Some(name.clone());
5601                name
5602            },
5603        }
5604    }
5605
5606    /// Clear the cached tag name, so that it will be re-calculated the
5607    /// next time that `or_init()` is called.
5608    fn clear(&self) {
5609        *self.ptr.borrow_mut() = None;
5610    }
5611}
5612
5613pub(crate) struct ElementPerformFullscreenEnter {
5614    element: Trusted<Element>,
5615    document: Trusted<Document>,
5616    promise: TrustedPromise,
5617    error: bool,
5618}
5619
5620impl ElementPerformFullscreenEnter {
5621    pub(crate) fn new(
5622        element: Trusted<Element>,
5623        document: Trusted<Document>,
5624        promise: TrustedPromise,
5625        error: bool,
5626    ) -> Box<ElementPerformFullscreenEnter> {
5627        Box::new(ElementPerformFullscreenEnter {
5628            element,
5629            document,
5630            promise,
5631            error,
5632        })
5633    }
5634}
5635
5636impl TaskOnce for ElementPerformFullscreenEnter {
5637    /// Step 9-14 of <https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen>
5638    fn run_once(self, cx: &mut js::context::JSContext) {
5639        let element = self.element.root();
5640        let promise = self.promise.root();
5641        let document = element.owner_document();
5642
5643        // Step 9
5644        // > If any of the following conditions are false, then set error to true:
5645        // > - This’s node document is pendingDoc.
5646        // > - The fullscreen element ready check for this returns true.
5647        // Step 10
5648        // > If error is true:
5649        // > - Append (fullscreenerror, this) to pendingDoc’s list of pending fullscreen events.
5650        // > - Reject promise with a TypeError exception and terminate these steps.
5651        if self.document.root() != document ||
5652            !element.fullscreen_element_ready_check() ||
5653            self.error
5654        {
5655            // TODO(#31866): we should queue this and fire them in update the rendering.
5656            document
5657                .upcast::<EventTarget>()
5658                .fire_event(atom!("fullscreenerror"), CanGc::from_cx(cx));
5659            promise.reject_error(
5660                Error::Type(c"fullscreen is not connected".to_owned()),
5661                CanGc::from_cx(cx),
5662            );
5663            return;
5664        }
5665
5666        // TODO(#42067): Implement step 11-13
5667        // The following operations is based on the old version of the specs.
5668        element.set_fullscreen_state(true);
5669        document.set_fullscreen_element(Some(&element));
5670        document.upcast::<EventTarget>().fire_event_with_params(
5671            atom!("fullscreenchange"),
5672            EventBubbles::Bubbles,
5673            EventCancelable::NotCancelable,
5674            EventComposed::Composed,
5675            CanGc::from_cx(cx),
5676        );
5677
5678        // Step 14.
5679        // > Resolve promise with undefined.
5680        promise.resolve_native(&(), CanGc::from_cx(cx));
5681    }
5682}
5683
5684pub(crate) struct ElementPerformFullscreenExit {
5685    element: Trusted<Element>,
5686    promise: TrustedPromise,
5687}
5688
5689impl ElementPerformFullscreenExit {
5690    pub(crate) fn new(
5691        element: Trusted<Element>,
5692        promise: TrustedPromise,
5693    ) -> Box<ElementPerformFullscreenExit> {
5694        Box::new(ElementPerformFullscreenExit { element, promise })
5695    }
5696}
5697
5698impl TaskOnce for ElementPerformFullscreenExit {
5699    /// Step 9-16 of <https://fullscreen.spec.whatwg.org/#exit-fullscreen>
5700    fn run_once(self, cx: &mut js::context::JSContext) {
5701        let element = self.element.root();
5702        let document = element.owner_document();
5703        // Step 9.
5704        // > Run the fully unlock the screen orientation steps with doc.
5705        // TODO: Need to implement ScreenOrientation API first
5706
5707        // TODO(#42067): Implement step 10-15
5708        // The following operations is based on the old version of the specs.
5709        element.set_fullscreen_state(false);
5710        document.set_fullscreen_element(None);
5711        document.upcast::<EventTarget>().fire_event_with_params(
5712            atom!("fullscreenchange"),
5713            EventBubbles::Bubbles,
5714            EventCancelable::NotCancelable,
5715            EventComposed::Composed,
5716            CanGc::from_cx(cx),
5717        );
5718
5719        // Step 16
5720        // > Resolve promise with undefined.
5721        self.promise.root().resolve_native(&(), CanGc::from_cx(cx));
5722    }
5723}
5724
5725/// <https://html.spec.whatwg.org/multipage/#cors-settings-attribute>
5726pub(crate) fn reflect_cross_origin_attribute(element: &Element) -> Option<DOMString> {
5727    element
5728        .get_attribute(&local_name!("crossorigin"))
5729        .map(|attribute| {
5730            let value = attribute.value().to_ascii_lowercase();
5731            if value == "anonymous" || value == "use-credentials" {
5732                DOMString::from(value)
5733            } else {
5734                DOMString::from("anonymous")
5735            }
5736        })
5737}
5738
5739pub(crate) fn set_cross_origin_attribute(
5740    cx: &mut JSContext,
5741    element: &Element,
5742    value: Option<DOMString>,
5743) {
5744    match value {
5745        Some(val) => {
5746            element.set_string_attribute(&local_name!("crossorigin"), val, CanGc::from_cx(cx))
5747        },
5748        None => {
5749            element.remove_attribute(&ns!(), &local_name!("crossorigin"), CanGc::from_cx(cx));
5750        },
5751    }
5752}
5753
5754/// <https://html.spec.whatwg.org/multipage/#referrer-policy-attribute>
5755pub(crate) fn reflect_referrer_policy_attribute(element: &Element) -> DOMString {
5756    element
5757        .get_attribute(&local_name!("referrerpolicy"))
5758        .map(|attribute| {
5759            let value = attribute.value().to_ascii_lowercase();
5760            if value == "no-referrer" ||
5761                value == "no-referrer-when-downgrade" ||
5762                value == "same-origin" ||
5763                value == "origin" ||
5764                value == "strict-origin" ||
5765                value == "origin-when-cross-origin" ||
5766                value == "strict-origin-when-cross-origin" ||
5767                value == "unsafe-url"
5768            {
5769                DOMString::from(value)
5770            } else {
5771                DOMString::new()
5772            }
5773        })
5774        .unwrap_or_default()
5775}
5776
5777pub(crate) fn referrer_policy_for_element(element: &Element) -> ReferrerPolicy {
5778    element
5779        .get_attribute(&local_name!("referrerpolicy"))
5780        .map(|attribute| ReferrerPolicy::from(&**attribute.value()))
5781        .unwrap_or(element.owner_document().get_referrer_policy())
5782}
5783
5784pub(crate) fn cors_setting_for_element(element: &Element) -> Option<CorsSettings> {
5785    element
5786        .get_attribute(&local_name!("crossorigin"))
5787        .map(|attribute| CorsSettings::from_enumerated_attribute(&attribute.value()))
5788}
5789
5790pub(crate) fn is_element_affected_by_legacy_background_presentational_hint(
5791    namespace: &Namespace,
5792    local_name: &LocalName,
5793) -> bool {
5794    *namespace == ns!(html) &&
5795        matches!(
5796            *local_name,
5797            local_name!("body") |
5798                local_name!("table") |
5799                local_name!("thead") |
5800                local_name!("tbody") |
5801                local_name!("tfoot") |
5802                local_name!("tr") |
5803                local_name!("td") |
5804                local_name!("th")
5805        )
5806}
5807
5808/// What kind of focusable area an [`Element`] is.
5809#[derive(Clone, Copy, Debug, Default)]
5810pub(crate) struct FocusableAreaKind(u8);
5811
5812bitflags! {
5813    impl FocusableAreaKind: u8 {
5814        /// <https://html.spec.whatwg.org/multipage/#click-focusable>
5815        ///
5816        /// > A focusable area is said to be click focusable if the user agent determines that it is
5817        /// > click focusable. User agents should consider focusable areas with non-null tabindex values
5818        /// > to be click focusable.
5819        const Click = 1 << 0;
5820        /// <https://html.spec.whatwg.org/multipage/#sequentially-focusable>.
5821        ///
5822        /// > A focusable area is said to be sequentially focusable if it is included in its
5823        /// > Document's sequential focus navigation order and the user agent determines that it is
5824        /// > sequentially focusable.
5825        const Sequential = 1 << 1;
5826    }
5827}