style/sharing/
checks.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//! Different checks done during the style sharing process in order to determine
6//! quickly whether it's worth to share style, and whether two different
7//! elements can indeed share the same style.
8
9use crate::bloom::StyleBloom;
10use crate::context::SharedStyleContext;
11use crate::dom::TElement;
12use crate::sharing::{StyleSharingCandidate, StyleSharingTarget};
13use selectors::matching::SelectorCaches;
14
15/// Determines whether a target and a candidate have compatible parents for
16/// sharing.
17pub fn parents_allow_sharing<E>(
18    target: &mut StyleSharingTarget<E>,
19    candidate: &mut StyleSharingCandidate<E>,
20) -> bool
21where
22    E: TElement,
23{
24    // If the identity of the parent style isn't equal, we can't share. We check
25    // this first, because the result is cached.
26    if target.parent_style_identity() != candidate.parent_style_identity() {
27        return false;
28    }
29
30    // Siblings can always share.
31    let parent = target.inheritance_parent().unwrap();
32    let candidate_parent = candidate.element.inheritance_parent().unwrap();
33    if parent == candidate_parent {
34        return true;
35    }
36
37    // If a parent element was already styled and we traversed past it without
38    // restyling it, that may be because our clever invalidation logic was able
39    // to prove that the styles of that element would remain unchanged despite
40    // changes to the id or class attributes. However, style sharing relies in
41    // the strong guarantee that all the classes and ids up the respective parent
42    // chains are identical. As such, if we skipped styling for one (or both) of
43    // the parents on this traversal, we can't share styles across cousins.
44    //
45    // This is a somewhat conservative check. We could tighten it by having the
46    // invalidation logic explicitly flag elements for which it ellided styling.
47    let parent_data = parent.borrow_data().unwrap();
48    let candidate_parent_data = candidate_parent.borrow_data().unwrap();
49    if !parent_data.safe_for_cousin_sharing() || !candidate_parent_data.safe_for_cousin_sharing() {
50        return false;
51    }
52
53    true
54}
55
56/// Whether two elements have the same style attribute.
57///
58/// First checks pointer identity (fast path), then falls back to value comparison.
59pub fn have_same_style_attribute<E>(
60    target: &mut StyleSharingTarget<E>,
61    candidate: &mut StyleSharingCandidate<E>,
62    shared_context: &SharedStyleContext,
63) -> bool
64where
65    E: TElement,
66{
67    match (target.style_attribute(), candidate.style_attribute()) {
68        (None, None) => true,
69        (Some(_), None) | (None, Some(_)) => false,
70        (Some(a), Some(b)) => {
71            if std::ptr::eq(&*a, &*b) {
72                return true;
73            }
74            let guard = shared_context.guards.author;
75            *a.read_with(guard) == *b.read_with(guard)
76        },
77    }
78}
79
80/// Whether two elements have the same same presentational attributes.
81pub fn have_same_presentational_hints<E>(
82    target: &mut StyleSharingTarget<E>,
83    candidate: &mut StyleSharingCandidate<E>,
84) -> bool
85where
86    E: TElement,
87{
88    target.pres_hints() == candidate.pres_hints()
89}
90
91/// Whether a given element has the same class attribute as a given candidate.
92///
93/// We don't try to share style across elements with different class attributes.
94pub fn have_same_class<E>(
95    target: &mut StyleSharingTarget<E>,
96    candidate: &mut StyleSharingCandidate<E>,
97) -> bool
98where
99    E: TElement,
100{
101    target.class_list() == candidate.class_list()
102}
103
104/// Whether a given element has the same part attribute as a given candidate.
105///
106/// We don't try to share style across elements with different part attributes.
107pub fn have_same_parts<E>(
108    target: &mut StyleSharingTarget<E>,
109    candidate: &mut StyleSharingCandidate<E>,
110) -> bool
111where
112    E: TElement,
113{
114    target.part_list() == candidate.part_list()
115}
116
117/// Whether a given element and a candidate match the same set of "revalidation"
118/// selectors.
119///
120/// Revalidation selectors are those that depend on the DOM structure, like
121/// :first-child, etc, or on attributes that we don't check off-hand (pretty
122/// much every attribute selector except `id` and `class`.
123#[inline]
124pub fn revalidate<E>(
125    target: &mut StyleSharingTarget<E>,
126    candidate: &mut StyleSharingCandidate<E>,
127    shared_context: &SharedStyleContext,
128    bloom: &StyleBloom<E>,
129    selector_caches: &mut SelectorCaches,
130) -> bool
131where
132    E: TElement,
133{
134    let stylist = &shared_context.stylist;
135
136    let for_element = target.revalidation_match_results(stylist, bloom, selector_caches);
137
138    let for_candidate = candidate.revalidation_match_results(stylist, bloom, selector_caches);
139
140    for_element == for_candidate
141}
142
143/// Whether the given element and a candidate have the same values for the the
144/// attributes used in an `attr()` function.
145#[inline]
146pub fn have_same_referenced_attrs<E>(
147    target: &StyleSharingTarget<E>,
148    candidate: &StyleSharingCandidate<E>,
149) -> bool
150where
151    E: TElement,
152{
153    // The candidate must be styled in order to be in the cache.
154    let borrowed_data = candidate.element.borrow_data().unwrap();
155    let attrs_used = borrowed_data.styles.primary().attribute_references.as_ref();
156
157    let Some(attrs_used) = attrs_used else {
158        return true;
159    };
160
161    attrs_used.iter().all(|(name, namespaces)| {
162        namespaces.iter().all(|namespace| {
163            target.get_attr(name, namespace) == candidate.get_attr(name, namespace)
164        })
165    })
166}
167
168/// Whether a given element and a candidate share a set of scope activations
169/// for revalidation.
170#[inline]
171pub fn revalidate_scope<E>(
172    target: &mut StyleSharingTarget<E>,
173    candidate: &mut StyleSharingCandidate<E>,
174    shared_context: &SharedStyleContext,
175    selector_caches: &mut SelectorCaches,
176) -> bool
177where
178    E: TElement,
179{
180    let stylist = &shared_context.stylist;
181    let for_element = target.scope_revalidation_results(stylist, selector_caches);
182    let for_candidate = candidate.scope_revalidation_results(stylist, selector_caches);
183
184    for_element == for_candidate
185}
186
187/// Checks whether we might have rules for either of the two ids.
188#[inline]
189pub fn may_match_different_id_rules<E>(
190    shared_context: &SharedStyleContext,
191    element: E,
192    candidate: E,
193) -> bool
194where
195    E: TElement,
196{
197    let element_id = element.id();
198    let candidate_id = candidate.id();
199
200    if element_id == candidate_id {
201        return false;
202    }
203
204    let stylist = &shared_context.stylist;
205
206    let may_have_rules_for_element = match element_id {
207        Some(id) => stylist.may_have_rules_for_id(id, element),
208        None => false,
209    };
210
211    if may_have_rules_for_element {
212        return true;
213    }
214
215    match candidate_id {
216        Some(id) => stylist.may_have_rules_for_id(id, candidate),
217        None => false,
218    }
219}