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