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