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