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