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