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