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