script/dom/element/
element.rs

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