Skip to main content

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