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