Skip to main content

script/layout_dom/
servo_dangerous_style_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#![expect(unsafe_code)]
6#![deny(missing_docs)]
7
8use std::hash::Hash;
9use std::slice;
10use std::sync::atomic::Ordering;
11
12use embedder_traits::UntrustedNodeAddress;
13use euclid::default::Size2D;
14use html5ever::{LocalName, Namespace, local_name, ns};
15use js::jsapi::JSObject;
16use layout_api::{DangerousStyleElement, LayoutDamage, LayoutNode};
17use script_bindings::root::DomRoot;
18use selectors::Element as _;
19use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
20use selectors::bloom::{BLOOM_HASH_MASK, BloomFilter};
21use selectors::matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode};
22use selectors::sink::Push;
23use servo_arc::{Arc, ArcBorrow};
24use style::CaseSensitivityExt;
25use style::animation::AnimationSetKey;
26use style::applicable_declarations::ApplicableDeclarationBlock;
27use style::attr::AttrValue;
28use style::bloom::each_relevant_element_hash;
29use style::context::SharedStyleContext;
30use style::data::{ElementDataMut, ElementDataRef};
31use style::dom::{LayoutIterator, TDocument, TElement, TNode, TShadowRoot};
32use style::properties::{ComputedValues, PropertyDeclarationBlock};
33use style::selector_parser::{
34    AttrValue as SelectorAttrValue, Lang, NonTSPseudoClass, PseudoElement, RestyleDamage,
35    SelectorImpl, extended_filtering,
36};
37use style::shared_lock::Locked as StyleLocked;
38use style::stylesheets::scope_rule::ImplicitScopeRoot;
39use style::values::computed::{Display, Image};
40use style::values::generics::counters::{Content, ContentItem, GenericContentItems};
41use style::values::specified::align::AlignFlags;
42use style::values::specified::box_::{DisplayInside, DisplayOutside};
43use style::values::{AtomIdent, AtomString};
44use stylo_atoms::Atom;
45use stylo_dom::ElementState;
46
47use crate::dom::bindings::inheritance::{
48    CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId, NodeTypeId,
49};
50use crate::dom::bindings::root::LayoutDom;
51use crate::dom::element::Element;
52use crate::dom::html::htmlslotelement::HTMLSlotElement;
53use crate::dom::node::{Node, NodeFlags};
54use crate::layout_dom::{
55    DOMDescendantIterator, ServoDangerousStyleNode, ServoDangerousStyleShadowRoot,
56    ServoLayoutDomTypeBundle, ServoLayoutElement, ServoLayoutNode,
57};
58
59/// A wrapper around [`LayoutDom<_, Element>`] to be used with `stylo` and `selectors`.
60///
61/// Note: This should only be used for `stylo` or `selectors interaction.
62#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
63pub struct ServoDangerousStyleElement<'dom> {
64    pub(crate) element: LayoutDom<'dom, Element>,
65}
66
67unsafe impl Send for ServoDangerousStyleElement<'_> {}
68unsafe impl Sync for ServoDangerousStyleElement<'_> {}
69
70impl<'dom> ServoDangerousStyleElement<'dom> {
71    pub(crate) fn rooted(self) -> DomRoot<Element> {
72        DomRoot::from_ref(unsafe { self.element.as_ref() })
73    }
74}
75
76impl<'dom> From<LayoutDom<'dom, Element>> for ServoDangerousStyleElement<'dom> {
77    fn from(element: LayoutDom<'dom, Element>) -> Self {
78        Self { element }
79    }
80}
81
82impl<'dom> DangerousStyleElement<'dom> for ServoDangerousStyleElement<'dom> {
83    type ConcreteTypeBundle = ServoLayoutDomTypeBundle<'dom>;
84
85    fn layout_element(&self) -> ServoLayoutElement<'dom> {
86        self.element.into()
87    }
88}
89
90impl<'dom> style::dom::TElement for ServoDangerousStyleElement<'dom> {
91    type ConcreteNode = ServoDangerousStyleNode<'dom>;
92    type TraversalChildrenIterator = DOMDescendantIterator<'dom>;
93
94    fn as_node(&self) -> ServoDangerousStyleNode<'dom> {
95        self.element.upcast().into()
96    }
97
98    fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator> {
99        let iterator = if self.slotted_nodes().is_empty() {
100            let children = if let Some(shadow_root) = self.shadow_root() {
101                shadow_root.as_node().dom_children()
102            } else {
103                self.as_node().dom_children()
104            };
105            DOMDescendantIterator::Children(children)
106        } else {
107            DOMDescendantIterator::Slottables {
108                slot: *self,
109                index: 0,
110            }
111        };
112
113        LayoutIterator(iterator)
114    }
115
116    fn traversal_parent(&self) -> Option<Self> {
117        self.as_node().traversal_parent()
118    }
119
120    fn inheritance_parent(&self) -> Option<Self> {
121        if self.is_pseudo_element() {
122            // The inheritance parent of an implemented pseudo-element should be the
123            // originating element, except if `is_element_backed()` is true, then it should
124            // be the flat tree parent. Note `is_element_backed()` differs from the CSS term.
125            // At the current time, `is_element_backed()` is always false in Servo.
126            //
127            // FIXME: handle the cases of element-backed pseudo-elements.
128            return self.pseudo_element_originating_element();
129        }
130
131        self.traversal_parent()
132    }
133
134    fn is_html_element(&self) -> bool {
135        self.element.is_html_element()
136    }
137
138    fn is_mathml_element(&self) -> bool {
139        *self.element.namespace() == ns!(mathml)
140    }
141
142    fn is_svg_element(&self) -> bool {
143        *self.element.namespace() == ns!(svg)
144    }
145
146    fn has_part_attr(&self) -> bool {
147        self.element
148            .get_attr_for_layout(&ns!(), &local_name!("part"))
149            .is_some()
150    }
151
152    fn exports_any_part(&self) -> bool {
153        self.element
154            .get_attr_for_layout(&ns!(), &local_name!("exportparts"))
155            .is_some()
156    }
157
158    fn style_attribute(&self) -> Option<ArcBorrow<'_, StyleLocked<PropertyDeclarationBlock>>> {
159        unsafe {
160            (*self.element.style_attribute())
161                .as_ref()
162                .map(|x| x.borrow_arc())
163        }
164    }
165
166    fn may_have_animations(&self) -> bool {
167        true
168    }
169
170    fn animation_rule(
171        &self,
172        context: &SharedStyleContext,
173    ) -> Option<Arc<StyleLocked<PropertyDeclarationBlock>>> {
174        let node = self.as_node();
175        let document = node.owner_doc();
176        context.animations.get_animation_declarations(
177            &AnimationSetKey::new_for_non_pseudo(node.opaque()),
178            context.current_time_for_animations,
179            &document.shared_style_locks().author,
180        )
181    }
182
183    fn transition_rule(
184        &self,
185        context: &SharedStyleContext,
186    ) -> Option<Arc<StyleLocked<PropertyDeclarationBlock>>> {
187        let node = self.as_node();
188        let document = node.owner_doc();
189        context.animations.get_transition_declarations(
190            &AnimationSetKey::new_for_non_pseudo(node.opaque()),
191            context.current_time_for_animations,
192            &document.shared_style_locks().author,
193        )
194    }
195
196    fn state(&self) -> ElementState {
197        self.element.get_state_for_layout()
198    }
199
200    #[inline]
201    fn id(&self) -> Option<&Atom> {
202        unsafe { (*self.element.id_attribute()).as_ref() }
203    }
204
205    #[inline(always)]
206    fn each_class<F>(&self, mut callback: F)
207    where
208        F: FnMut(&AtomIdent),
209    {
210        if let Some(classes) = self.element.get_classes_for_layout() {
211            for class in classes {
212                callback(AtomIdent::cast(class))
213            }
214        }
215    }
216
217    #[inline(always)]
218    fn each_attr_name<F>(&self, mut callback: F)
219    where
220        F: FnMut(&style::LocalName),
221    {
222        self.element
223            .each_attr_name_for_layout(|name| callback(style::values::GenericAtomIdent::cast(name)))
224    }
225
226    fn each_part<F>(&self, mut callback: F)
227    where
228        F: FnMut(&AtomIdent),
229    {
230        if let Some(parts) = self.element.get_parts_for_layout() {
231            for part in parts {
232                callback(AtomIdent::cast(part))
233            }
234        }
235    }
236
237    fn each_exported_part<F>(&self, name: &AtomIdent, callback: F)
238    where
239        F: FnMut(&AtomIdent),
240    {
241        let Some(exported_parts) = self
242            .element
243            .get_attr_for_layout(&ns!(), &local_name!("exportparts"))
244        else {
245            return;
246        };
247        exported_parts
248            .as_shadow_parts()
249            .for_each_exported_part(AtomIdent::cast(name), callback);
250    }
251
252    fn has_dirty_descendants(&self) -> bool {
253        unsafe {
254            self.as_node()
255                .node
256                .get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS)
257        }
258    }
259
260    fn has_snapshot(&self) -> bool {
261        unsafe { self.as_node().node.get_flag(NodeFlags::HAS_SNAPSHOT) }
262    }
263
264    fn handled_snapshot(&self) -> bool {
265        unsafe { self.as_node().node.get_flag(NodeFlags::HANDLED_SNAPSHOT) }
266    }
267
268    unsafe fn set_handled_snapshot(&self) {
269        unsafe {
270            self.as_node()
271                .node
272                .set_flag(NodeFlags::HANDLED_SNAPSHOT, true);
273        }
274    }
275
276    unsafe fn set_dirty_descendants(&self) {
277        let node = self.as_node();
278        unsafe {
279            debug_assert!(node.node.get_flag(NodeFlags::IS_CONNECTED));
280            node.node.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true)
281        }
282    }
283
284    unsafe fn unset_dirty_descendants(&self) {
285        unsafe {
286            self.as_node()
287                .node
288                .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false)
289        }
290    }
291
292    /// Whether this element should match user and content rules.
293    /// We would like to match rules from the same tree in all cases and optimize computation.
294    /// UA Widget is an exception since we could have a pseudo element selector inside it.
295    #[inline]
296    fn matches_user_and_content_rules(&self) -> bool {
297        !self.as_node().node.is_in_ua_widget()
298    }
299
300    /// Returns the pseudo-element implemented by this element, if any. In other words,
301    /// the element will match the specified pseudo element throughout the style computation.
302    #[inline]
303    fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
304        self.as_node().node.implemented_pseudo_element()
305    }
306
307    fn store_children_to_process(&self, n: isize) {
308        let data = self.element.style_data().unwrap();
309        data.parallel
310            .children_to_process
311            .store(n, Ordering::Relaxed);
312    }
313
314    fn did_process_child(&self) -> isize {
315        let data = self.element.style_data().unwrap();
316        let old_value = data
317            .parallel
318            .children_to_process
319            .fetch_sub(1, Ordering::Relaxed);
320        debug_assert!(old_value >= 1);
321        old_value - 1
322    }
323
324    unsafe fn clear_data(&self) {
325        unsafe { self.element.clear_style_data() };
326        unsafe { self.as_node().node.clear_layout_data() }
327    }
328
329    unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
330        unsafe { self.element.initialize_style_data() };
331        self.mutate_data().unwrap()
332    }
333
334    /// Whether there is an ElementData container.
335    fn has_data(&self) -> bool {
336        self.element.style_data().is_some()
337    }
338
339    /// Immutably borrows the ElementData.
340    fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
341        self.element
342            .style_data()
343            .map(|data| data.element_data.borrow())
344    }
345
346    /// Mutably borrows the ElementData.
347    fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
348        self.element
349            .style_data()
350            .map(|data| data.element_data.borrow_mut())
351    }
352
353    fn skip_item_display_fixup(&self) -> bool {
354        false
355    }
356
357    fn has_animations(&self, context: &SharedStyleContext) -> bool {
358        // This is not used for pseudo elements currently so we can pass None.
359        self.has_css_animations(context, /* pseudo_element = */ None) ||
360            self.has_css_transitions(context, /* pseudo_element = */ None)
361    }
362
363    fn has_css_animations(
364        &self,
365        context: &SharedStyleContext,
366        pseudo_element: Option<PseudoElement>,
367    ) -> bool {
368        let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
369        context.animations.has_active_animations(&key)
370    }
371
372    fn has_css_transitions(
373        &self,
374        context: &SharedStyleContext,
375        pseudo_element: Option<PseudoElement>,
376    ) -> bool {
377        let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
378        context.animations.has_active_transitions(&key)
379    }
380
381    #[inline]
382    fn lang_attr(&self) -> Option<SelectorAttrValue> {
383        self.element
384            .get_attr_for_layout(&ns!(xml), &local_name!("lang"))
385            .or_else(|| {
386                self.element
387                    .get_attr_for_layout(&ns!(), &local_name!("lang"))
388            })
389            .map(|v| SelectorAttrValue::from(v as &str))
390    }
391
392    fn match_element_lang(
393        &self,
394        override_lang: Option<Option<SelectorAttrValue>>,
395        value: &Lang,
396    ) -> bool {
397        // Servo supports :lang() from CSS Selectors 4, which can take a comma-
398        // separated list of language tags in the pseudo-class, and which
399        // performs RFC 4647 extended filtering matching on them.
400        //
401        // FIXME(heycam): This is wrong, since extended_filtering accepts
402        // a string containing commas (separating each language tag in
403        // a list) but the pseudo-class instead should be parsing and
404        // storing separate <ident> or <string>s for each language tag.
405        //
406        // FIXME(heycam): Look at `element`'s document's Content-Language
407        // HTTP header for language tags to match `value` against.  To
408        // do this, we should make `get_lang_for_layout` return an Option,
409        // so we can decide when to fall back to the Content-Language check.
410        let element_lang = match override_lang {
411            Some(Some(lang)) => lang,
412            Some(None) => AtomString::default(),
413            None => self.element.get_lang_for_layout(),
414        };
415        extended_filtering(&element_lang, value)
416    }
417
418    fn is_html_document_body_element(&self) -> bool {
419        self.element.is_body_element_of_html_element_root()
420    }
421
422    fn synthesize_presentational_hints_for_legacy_attributes<V>(
423        &self,
424        _visited_handling: VisitedHandlingMode,
425        hints: &mut V,
426    ) where
427        V: Push<ApplicableDeclarationBlock>,
428    {
429        self.element
430            .synthesize_presentational_hints_for_legacy_attributes(hints);
431    }
432
433    /// The shadow root this element is a host of.
434    fn shadow_root(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
435        self.element.get_shadow_root_for_layout().map(Into::into)
436    }
437
438    /// The shadow root which roots the subtree this element is contained in.
439    fn containing_shadow(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
440        self.element
441            .upcast()
442            .containing_shadow_root_for_layout()
443            .map(Into::into)
444    }
445
446    fn local_name(&self) -> &LocalName {
447        self.element.local_name()
448    }
449
450    fn namespace(&self) -> &Namespace {
451        self.element.namespace()
452    }
453
454    fn query_container_size(&self, _display: &Display) -> Size2D<Option<app_units::Au>> {
455        todo!();
456    }
457
458    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
459        self.element.get_selector_flags().contains(flags)
460    }
461
462    fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
463        self.element
464            .get_selector_flags()
465            .intersection(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
466    }
467
468    fn each_custom_state<F>(&self, callback: F)
469    where
470        F: FnMut(&AtomIdent),
471    {
472        self.element.each_custom_state_for_layout(callback);
473    }
474
475    /// Returns the implicit scope root for given sheet index and host.
476    fn implicit_scope_for_sheet_in_shadow_root(
477        opaque_host: ::selectors::OpaqueElement,
478        sheet_index: usize,
479    ) -> Option<ImplicitScopeRoot> {
480        // As long as this "unopaqued" element does not escape this function, we're not leaking
481        // potentially-mutable elements from opaque elements.
482        let host = unsafe {
483            let ptr = opaque_host.as_const_ptr::<JSObject>();
484            let untrusted_address = UntrustedNodeAddress::from_id(ptr as usize);
485            let node = Node::from_untrusted_node_address(untrusted_address);
486            let trusted_address = node.to_trusted_node_address();
487            let servo_layout_node = ServoLayoutNode::new(&trusted_address);
488            servo_layout_node.as_element().unwrap()
489        };
490        host.shadow_root()?.implicit_scope_for_sheet(sheet_index)
491    }
492
493    fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
494        let Some(slot_element) = self.element.downcast::<HTMLSlotElement>() else {
495            return &[];
496        };
497        let assigned_nodes = slot_element.unsafe_get().assigned_nodes();
498
499        // SAFETY:
500        // Self::ConcreteNode (aka ServoDangerousStyleNode) and Slottable are guaranteed to
501        // have the same layout and alignment as ptr::NonNull<T>. Lifetimes are not an issue
502        // because the slottables are being kept alive by the slot element.
503        unsafe {
504            slice::from_raw_parts(
505                assigned_nodes.as_ptr() as *const ServoDangerousStyleNode,
506                assigned_nodes.len(),
507            )
508        }
509    }
510
511    fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
512        let box_tree_needs_rebuild = || {
513            let old_box = old.get_box();
514            let new_box = new.get_box();
515
516            if old_box.display != new_box.display ||
517                old_box.float != new_box.float ||
518                old_box.position != new_box.position
519            {
520                return true;
521            }
522
523            if new_box.position.is_absolutely_positioned() &&
524                old_box.original_display != new_box.original_display
525            {
526                // The original display only affects the static position, which is only used
527                // when both insets in some axis are auto.
528                // <https://drafts.csswg.org/css-position/#resolving-insets>
529                let position = new.get_position();
530                if (position.top.is_auto() && position.bottom.is_auto()) ||
531                    (position.left.is_auto() && position.right.is_auto())
532                {
533                    return true;
534                }
535            }
536
537            if old.get_font() != new.get_font() {
538                return true;
539            }
540
541            if old.get_position().order != new.get_position().order {
542                return true;
543            }
544
545            // Only consider changes to the `quotes` attribute if they actually apply to this
546            // style (if it is a pseudo-element that supports it).
547            if matches!(
548                new.pseudo(),
549                Some(PseudoElement::Before | PseudoElement::After | PseudoElement::Marker),
550            ) && old.get_list().quotes != new.get_list().quotes
551            {
552                return true;
553            }
554
555            // NOTE: This should be kept in sync with the checks in `impl
556            // StyleExt::establishes_block_formatting_context` for `ComputedValues` in
557            // `components/layout/style_ext.rs`.
558            if new_box.display.outside() == DisplayOutside::Block &&
559                new_box.display.inside() == DisplayInside::Flow
560            {
561                let alignment_establishes_new_block_formatting_context =
562                    |style: &ComputedValues| {
563                        style.get_position().align_content.primary() != AlignFlags::NORMAL
564                    };
565
566                let old_column = old.get_column();
567                let new_column = new.get_column();
568                if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable() ||
569                    old_column.is_multicol() != new_column.is_multicol() ||
570                    old_column.column_span != new_column.column_span ||
571                    alignment_establishes_new_block_formatting_context(old) !=
572                        alignment_establishes_new_block_formatting_context(new)
573                {
574                    return true;
575                }
576            }
577
578            if old_box.display.is_list_item() {
579                let old_list = old.get_list();
580                let new_list = new.get_list();
581                if old_list.list_style_position != new_list.list_style_position ||
582                    old_list.list_style_image != new_list.list_style_image ||
583                    (new_list.list_style_image == Image::None &&
584                        old_list.list_style_type != new_list.list_style_type)
585                {
586                    return true;
587                }
588            }
589
590            if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
591                return true;
592            }
593
594            // If we're not a pseudo, `content` can still cause layout damage if its value is
595            // <content-replacement> (a.k.a. a single <image>).
596            fn replacement<Image>(content: &Content<Image>) -> Option<&Image> {
597                match content {
598                    Content::Items(GenericContentItems { items, .. }) => match items.as_slice() {
599                        [ContentItem::Image(image)] => Some(image),
600                        _ => None,
601                    },
602                    _ => None,
603                }
604            }
605            if replacement(&old.get_counters().content) != replacement(&new.get_counters().content)
606            {
607                return true;
608            }
609
610            false
611        };
612
613        let text_shaping_needs_recollect = || {
614            if old.clone_direction() != new.clone_direction() ||
615                old.clone_unicode_bidi() != new.clone_unicode_bidi()
616            {
617                return true;
618            }
619
620            let old_text = old.get_inherited_text().clone();
621            let new_text = new.get_inherited_text().clone();
622            if old_text.white_space_collapse != new_text.white_space_collapse ||
623                old_text.text_transform != new_text.text_transform ||
624                old_text.word_break != new_text.word_break ||
625                old_text.overflow_wrap != new_text.overflow_wrap ||
626                old_text.letter_spacing != new_text.letter_spacing ||
627                old_text.word_spacing != new_text.word_spacing ||
628                old_text.text_rendering != new_text.text_rendering
629            {
630                return true;
631            }
632
633            false
634        };
635
636        if box_tree_needs_rebuild() {
637            RestyleDamage::from_bits_retain(LayoutDamage::BoxDamage.bits())
638        } else if text_shaping_needs_recollect() {
639            RestyleDamage::from_bits_retain(LayoutDamage::DescendantHasBoxDamage.bits())
640        } else {
641            // This element needs to be laid out again, but does not have any damage to
642            // its box. In the future, we will distinguish between types of damage to the
643            // fragment as well.
644            RestyleDamage::RELAYOUT
645        }
646    }
647
648    fn get_attr(&self, attr: &style::LocalName, namespace: &style::Namespace) -> Option<String> {
649        // All attribute names on HTML elements in HTML docs match ASCII-case-insensitively.
650        // See note in https://html.spec.whatwg.org/multipage/#custom-data-attribute
651        if self.is_html_element_in_html_document() {
652            let attr = &style::LocalName::new(attr.to_ascii_lowercase());
653            self.element.get_attr_val_for_layout(namespace, attr)
654        } else {
655            self.element.get_attr_val_for_layout(namespace, attr)
656        }
657        .map(Into::into)
658    }
659}
660
661impl<'dom> ::selectors::Element for ServoDangerousStyleElement<'dom> {
662    type Impl = SelectorImpl;
663
664    fn opaque(&self) -> ::selectors::OpaqueElement {
665        ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) })
666    }
667
668    fn parent_element(&self) -> Option<Self> {
669        self.as_node().parent_node()?.as_element()
670    }
671
672    fn parent_node_is_shadow_root(&self) -> bool {
673        self.as_node().parent_node().is_some_and(|parent_node| {
674            parent_node.node.type_id_for_layout() ==
675                NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot)
676        })
677    }
678
679    fn containing_shadow_host(&self) -> Option<Self> {
680        self.containing_shadow()
681            .as_ref()
682            .map(ServoDangerousStyleShadowRoot::host)
683    }
684
685    #[inline]
686    fn is_pseudo_element(&self) -> bool {
687        self.implemented_pseudo_element().is_some()
688    }
689
690    #[inline]
691    fn pseudo_element_originating_element(&self) -> Option<Self> {
692        debug_assert!(self.is_pseudo_element());
693        debug_assert!(!self.matches_user_and_content_rules());
694        self.containing_shadow_host()
695    }
696
697    fn prev_sibling_element(&self) -> Option<Self> {
698        let mut node = self.as_node();
699        while let Some(sibling) = node.prev_sibling() {
700            if let Some(element) = sibling.as_element() {
701                return Some(element);
702            }
703            node = sibling;
704        }
705        None
706    }
707
708    fn next_sibling_element(&self) -> Option<ServoDangerousStyleElement<'dom>> {
709        let mut node = self.as_node();
710        while let Some(sibling) = node.next_sibling() {
711            if let Some(element) = sibling.as_element() {
712                return Some(element);
713            }
714            node = sibling;
715        }
716        None
717    }
718
719    fn first_element_child(&self) -> Option<Self> {
720        self.as_node()
721            .dom_children()
722            .find_map(|child| child.as_element())
723    }
724
725    fn attr_matches(
726        &self,
727        ns: &NamespaceConstraint<&style::Namespace>,
728        local_name: &style::LocalName,
729        operation: &AttrSelectorOperation<&AtomString>,
730    ) -> bool {
731        match *ns {
732            NamespaceConstraint::Specific(ns) => self
733                .element
734                .get_attr_for_layout(ns, local_name)
735                .is_some_and(|value| value.eval_selector(operation)),
736            NamespaceConstraint::Any => self
737                .element
738                .get_attr_vals_for_layout(local_name)
739                .any(|value| value.eval_selector(operation)),
740        }
741    }
742
743    fn is_root(&self) -> bool {
744        self.element.is_root()
745    }
746
747    fn is_empty(&self) -> bool {
748        self.as_node()
749            .dom_children()
750            .all(|node| match node.node.type_id_for_layout() {
751                NodeTypeId::Element(..) => false,
752                NodeTypeId::CharacterData(CharacterDataTypeId::Text(..)) => {
753                    node.node.downcast().unwrap().data_for_layout().is_empty()
754                },
755                _ => true,
756            })
757    }
758
759    #[inline]
760    fn has_local_name(&self, name: &LocalName) -> bool {
761        self.element.local_name() == name
762    }
763
764    #[inline]
765    fn has_namespace(&self, ns: &Namespace) -> bool {
766        self.element.namespace() == ns
767    }
768
769    #[inline]
770    fn is_same_type(&self, other: &Self) -> bool {
771        self.element.local_name() == other.element.local_name() &&
772            self.element.namespace() == other.element.namespace()
773    }
774
775    fn match_non_ts_pseudo_class(
776        &self,
777        pseudo_class: &NonTSPseudoClass,
778        _: &mut MatchingContext<Self::Impl>,
779    ) -> bool {
780        match *pseudo_class {
781            // https://github.com/servo/servo/issues/8718
782            NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
783            NonTSPseudoClass::Visited => false,
784
785            NonTSPseudoClass::CustomState(ref state) => self.has_custom_state(&state.0),
786            NonTSPseudoClass::Lang(ref lang) => self.match_element_lang(None, lang),
787
788            NonTSPseudoClass::ServoNonZeroBorder => !matches!(
789                self.element
790                    .get_attr_for_layout(&ns!(), &local_name!("border")),
791                None | Some(&AttrValue::UInt(_, 0))
792            ),
793            NonTSPseudoClass::ReadOnly => !self
794                .element
795                .get_state_for_layout()
796                .contains(NonTSPseudoClass::ReadWrite.state_flag()),
797
798            NonTSPseudoClass::Active |
799            NonTSPseudoClass::Autofill |
800            NonTSPseudoClass::Checked |
801            NonTSPseudoClass::Default |
802            NonTSPseudoClass::Defined |
803            NonTSPseudoClass::Disabled |
804            NonTSPseudoClass::Enabled |
805            NonTSPseudoClass::Focus |
806            NonTSPseudoClass::FocusVisible |
807            NonTSPseudoClass::FocusWithin |
808            NonTSPseudoClass::Fullscreen |
809            NonTSPseudoClass::Hover |
810            NonTSPseudoClass::InRange |
811            NonTSPseudoClass::Indeterminate |
812            NonTSPseudoClass::Invalid |
813            NonTSPseudoClass::Modal |
814            NonTSPseudoClass::MozMeterOptimum |
815            NonTSPseudoClass::MozMeterSubOptimum |
816            NonTSPseudoClass::MozMeterSubSubOptimum |
817            NonTSPseudoClass::Open |
818            NonTSPseudoClass::Optional |
819            NonTSPseudoClass::OutOfRange |
820            NonTSPseudoClass::PlaceholderShown |
821            NonTSPseudoClass::PopoverOpen |
822            NonTSPseudoClass::ReadWrite |
823            NonTSPseudoClass::Required |
824            NonTSPseudoClass::Target |
825            NonTSPseudoClass::UserInvalid |
826            NonTSPseudoClass::UserValid |
827            NonTSPseudoClass::Valid => self
828                .element
829                .get_state_for_layout()
830                .contains(pseudo_class.state_flag()),
831        }
832    }
833
834    fn match_pseudo_element(
835        &self,
836        pseudo: &PseudoElement,
837        _context: &mut MatchingContext<Self::Impl>,
838    ) -> bool {
839        self.implemented_pseudo_element() == Some(*pseudo)
840    }
841
842    #[inline]
843    fn is_link(&self) -> bool {
844        match self.as_node().node.type_id_for_layout() {
845            // https://html.spec.whatwg.org/multipage/#selector-link
846            NodeTypeId::Element(ElementTypeId::HTMLElement(
847                HTMLElementTypeId::HTMLAnchorElement,
848            )) |
849            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) |
850            NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => {
851                self.element
852                    .get_attr_val_for_layout(&ns!(), &local_name!("href"))
853                    .is_some()
854            },
855            _ => false,
856        }
857    }
858
859    #[inline]
860    fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
861        unsafe {
862            (*self.element.id_attribute())
863                .as_ref()
864                .is_some_and(|atom| case_sensitivity.eq_atom(atom, id))
865        }
866    }
867
868    #[inline]
869    fn is_part(&self, name: &AtomIdent) -> bool {
870        self.element.has_class_or_part_for_layout(
871            name,
872            &local_name!("part"),
873            CaseSensitivity::CaseSensitive,
874        )
875    }
876
877    fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
878        self.element
879            .get_attr_for_layout(&ns!(), &local_name!("exportparts"))?
880            .as_shadow_parts()
881            .imported_part(name)
882            .map(|import| AtomIdent::new(import.clone()))
883    }
884
885    #[inline]
886    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
887        self.element
888            .has_class_or_part_for_layout(name, &local_name!("class"), case_sensitivity)
889    }
890
891    fn is_html_slot_element(&self) -> bool {
892        self.element.is::<HTMLSlotElement>()
893    }
894
895    fn assigned_slot(&self) -> Option<Self> {
896        Some(
897            self.element
898                .upcast::<Node>()
899                .assigned_slot_for_layout()?
900                .upcast()
901                .into(),
902        )
903    }
904
905    fn is_html_element_in_html_document(&self) -> bool {
906        self.element.is_html_element() && self.as_node().owner_doc().is_html_document()
907    }
908
909    fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
910        // Handle flags that apply to the element.
911        let self_flags = flags.for_self();
912        if !self_flags.is_empty() {
913            self.element.insert_selector_flags(flags);
914        }
915
916        // Handle flags that apply to the parent.
917        let parent_flags = flags.for_parent();
918        if !parent_flags.is_empty() &&
919            let Some(p) = self.as_node().parent_element()
920        {
921            p.element.insert_selector_flags(flags);
922        }
923    }
924
925    fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool {
926        each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
927        true
928    }
929
930    fn has_custom_state(&self, name: &AtomIdent) -> bool {
931        let mut has_state = false;
932        self.element
933            .each_custom_state_for_layout(|state| has_state |= state == name);
934
935        has_state
936    }
937}