Skip to main content

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