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