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