script/dom/
element.rs

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