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