Skip to main content

script/dom/element/
element.rs

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