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