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