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
162        .iter()
163        .all(|name| target.get_attr(name) == candidate.get_attr(name))
164}
165
166/// Whether a given element and a candidate share a set of scope activations
167/// for revalidation.
168#[inline]
169pub fn revalidate_scope<E>(
170    target: &mut StyleSharingTarget<E>,
171    candidate: &mut StyleSharingCandidate<E>,
172    shared_context: &SharedStyleContext,
173    selector_caches: &mut SelectorCaches,
174) -> bool
175where
176    E: TElement,
177{
178    let stylist = &shared_context.stylist;
179    let for_element = target.scope_revalidation_results(stylist, selector_caches);
180    let for_candidate = candidate.scope_revalidation_results(stylist, selector_caches);
181
182    for_element == for_candidate
183}
184
185/// Checks whether we might have rules for either of the two ids.
186#[inline]
187pub fn may_match_different_id_rules<E>(
188    shared_context: &SharedStyleContext,
189    element: E,
190    candidate: E,
191) -> bool
192where
193    E: TElement,
194{
195    let element_id = element.id();
196    let candidate_id = candidate.id();
197
198    if element_id == candidate_id {
199        return false;
200    }
201
202    let stylist = &shared_context.stylist;
203
204    let may_have_rules_for_element = match element_id {
205        Some(id) => stylist.may_have_rules_for_id(id, element),
206        None => false,
207    };
208
209    if may_have_rules_for_element {
210        return true;
211    }
212
213    match candidate_id {
214        Some(id) => stylist.may_have_rules_for_id(id, candidate),
215        None => false,
216    }
217}