aboutsummaryrefslogtreecommitdiffstats
path: root/components/style/sharing/mod.rs
diff options
context:
space:
mode:
authorDelan Azabani <dazabani@igalia.com>2024-02-27 23:39:06 +0800
committerGitHub <noreply@github.com>2024-02-27 15:39:06 +0000
commitfaf754dfa655f0b9a28f62bc47a78fbf78ebcaf4 (patch)
tree4725e1446680d036797b1fc258733ae6b2c9f354 /components/style/sharing/mod.rs
parentb07505417e629bbb081be9683630f2d7a5f50544 (diff)
downloadservo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.tar.gz
servo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.zip
Move Stylo to its own repo (#31350)
* Remove packages that were moved to external repo * Add workspace dependencies pointing to 2023-06-14 branch * Fix servo-tidy.toml errors * Update commit to include #31346 * Update commit to include servo/stylo#2 * Move css-properties.json lookup to target/doc/stylo * Remove dependency on vendored mako in favour of pypi dependency This also removes etc/ci/generate_workflow.py, which has been unused since at least 9e71bd6a7010d6e5723831696ae0ebe26b47682f. * Add temporary code to debug Windows test failures * Fix failures on Windows due to custom target dir * Update commit to include servo/stylo#3 * Fix license in tests/unit/style/build.rs * Document how to build with local Stylo in Cargo.toml
Diffstat (limited to 'components/style/sharing/mod.rs')
-rw-r--r--components/style/sharing/mod.rs910
1 files changed, 0 insertions, 910 deletions
diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs
deleted file mode 100644
index 9e5b9603d77..00000000000
--- a/components/style/sharing/mod.rs
+++ /dev/null
@@ -1,910 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-
-//! Code related to the style sharing cache, an optimization that allows similar
-//! nodes to share style without having to run selector matching twice.
-//!
-//! The basic setup is as follows. We have an LRU cache of style sharing
-//! candidates. When we try to style a target element, we first check whether
-//! we can quickly determine that styles match something in this cache, and if
-//! so we just use the cached style information. This check is done with a
-//! StyleBloom filter set up for the target element, which may not be a correct
-//! state for the cached candidate element if they're cousins instead of
-//! siblings.
-//!
-//! The complicated part is determining that styles match. This is subject to
-//! the following constraints:
-//!
-//! 1) The target and candidate must be inheriting the same styles.
-//! 2) The target and candidate must have exactly the same rules matching them.
-//! 3) The target and candidate must have exactly the same non-selector-based
-//! style information (inline styles, presentation hints).
-//! 4) The target and candidate must have exactly the same rules matching their
-//! pseudo-elements, because an element's style data points to the style
-//! data for its pseudo-elements.
-//!
-//! These constraints are satisfied in the following ways:
-//!
-//! * We check that the parents of the target and the candidate have the same
-//! computed style. This addresses constraint 1.
-//!
-//! * We check that the target and candidate have the same inline style and
-//! presentation hint declarations. This addresses constraint 3.
-//!
-//! * We ensure that a target matches a candidate only if they have the same
-//! matching result for all selectors that target either elements or the
-//! originating elements of pseudo-elements. This addresses constraint 4
-//! (because it prevents a target that has pseudo-element styles from matching
-//! a candidate that has different pseudo-element styles) as well as
-//! constraint 2.
-//!
-//! The actual checks that ensure that elements match the same rules are
-//! conceptually split up into two pieces. First, we do various checks on
-//! elements that make sure that the set of possible rules in all selector maps
-//! in the stylist (for normal styling and for pseudo-elements) that might match
-//! the two elements is the same. For example, we enforce that the target and
-//! candidate must have the same localname and namespace. Second, we have a
-//! selector map of "revalidation selectors" that the stylist maintains that we
-//! actually match against the target and candidate and then check whether the
-//! two sets of results were the same. Due to the up-front selector map checks,
-//! we know that the target and candidate will be matched against the same exact
-//! set of revalidation selectors, so the match result arrays can be compared
-//! directly.
-//!
-//! It's very important that a selector be added to the set of revalidation
-//! selectors any time there are two elements that could pass all the up-front
-//! checks but match differently against some ComplexSelector in the selector.
-//! If that happens, then they can have descendants that might themselves pass
-//! the up-front checks but would have different matching results for the
-//! selector in question. In this case, "descendants" includes pseudo-elements,
-//! so there is a single selector map of revalidation selectors that includes
-//! both selectors targeting elements and selectors targeting pseudo-element
-//! originating elements. We ensure that the pseudo-element parts of all these
-//! selectors are effectively stripped off, so that matching them all against
-//! elements makes sense.
-
-use crate::applicable_declarations::ApplicableDeclarationBlock;
-use crate::bloom::StyleBloom;
-use crate::computed_value_flags::ComputedValueFlags;
-use crate::context::{SharedStyleContext, StyleContext};
-use crate::dom::{SendElement, TElement};
-use crate::properties::ComputedValues;
-use crate::rule_tree::StrongRuleNode;
-use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
-use crate::stylist::Stylist;
-use crate::values::AtomIdent;
-use atomic_refcell::{AtomicRefCell, AtomicRefMut};
-use owning_ref::OwningHandle;
-use selectors::matching::{NeedsSelectorFlags, VisitedHandlingMode};
-use selectors::NthIndexCache;
-use servo_arc::Arc;
-use smallbitvec::SmallBitVec;
-use smallvec::SmallVec;
-use std::marker::PhantomData;
-use std::mem::{self, ManuallyDrop};
-use std::ops::Deref;
-use std::ptr::NonNull;
-use uluru::LRUCache;
-
-mod checks;
-
-/// The amount of nodes that the style sharing candidate cache should hold at
-/// most.
-///
-/// The cache size was chosen by measuring style sharing and resulting
-/// performance on a few pages; sizes up to about 32 were giving good sharing
-/// improvements (e.g. 3x fewer styles having to be resolved than at size 8) and
-/// slight performance improvements. Sizes larger than 32 haven't really been
-/// tested.
-pub const SHARING_CACHE_SIZE: usize = 32;
-
-/// Opaque pointer type to compare ComputedValues identities.
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct OpaqueComputedValues(NonNull<()>);
-
-unsafe impl Send for OpaqueComputedValues {}
-unsafe impl Sync for OpaqueComputedValues {}
-
-impl OpaqueComputedValues {
- fn from(cv: &ComputedValues) -> Self {
- let p =
- unsafe { NonNull::new_unchecked(cv as *const ComputedValues as *const () as *mut ()) };
- OpaqueComputedValues(p)
- }
-
- fn eq(&self, cv: &ComputedValues) -> bool {
- Self::from(cv) == *self
- }
-}
-
-/// Some data we want to avoid recomputing all the time while trying to share
-/// style.
-#[derive(Debug, Default)]
-pub struct ValidationData {
- /// The class list of this element.
- ///
- /// TODO(emilio): Maybe check whether rules for these classes apply to the
- /// element?
- class_list: Option<SmallVec<[AtomIdent; 5]>>,
-
- /// The part list of this element.
- ///
- /// TODO(emilio): Maybe check whether rules with these part names apply to
- /// the element?
- part_list: Option<SmallVec<[AtomIdent; 5]>>,
-
- /// The list of presentational attributes of the element.
- pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>,
-
- /// The pointer identity of the parent ComputedValues.
- parent_style_identity: Option<OpaqueComputedValues>,
-
- /// The cached result of matching this entry against the revalidation
- /// selectors.
- revalidation_match_results: Option<SmallBitVec>,
-}
-
-impl ValidationData {
- /// Move the cached data to a new instance, and return it.
- pub fn take(&mut self) -> Self {
- mem::replace(self, Self::default())
- }
-
- /// Get or compute the list of presentational attributes associated with
- /// this element.
- pub fn pres_hints<E>(&mut self, element: E) -> &[ApplicableDeclarationBlock]
- where
- E: TElement,
- {
- self.pres_hints.get_or_insert_with(|| {
- let mut pres_hints = SmallVec::new();
- element.synthesize_presentational_hints_for_legacy_attributes(
- VisitedHandlingMode::AllLinksUnvisited,
- &mut pres_hints,
- );
- pres_hints
- })
- }
-
- /// Get or compute the part-list associated with this element.
- pub fn part_list<E>(&mut self, element: E) -> &[AtomIdent]
- where
- E: TElement,
- {
- if !element.has_part_attr() {
- return &[];
- }
- self.part_list.get_or_insert_with(|| {
- let mut list = SmallVec::<[_; 5]>::new();
- element.each_part(|p| list.push(p.clone()));
- // See below for the reasoning.
- if !list.spilled() {
- list.sort_unstable_by_key(|a| a.get_hash());
- }
- list
- })
- }
-
- /// Get or compute the class-list associated with this element.
- pub fn class_list<E>(&mut self, element: E) -> &[AtomIdent]
- where
- E: TElement,
- {
- self.class_list.get_or_insert_with(|| {
- let mut list = SmallVec::<[_; 5]>::new();
- element.each_class(|c| list.push(c.clone()));
- // Assuming there are a reasonable number of classes (we use the
- // inline capacity as "reasonable number"), sort them to so that
- // we don't mistakenly reject sharing candidates when one element
- // has "foo bar" and the other has "bar foo".
- if !list.spilled() {
- list.sort_unstable_by_key(|a| a.get_hash());
- }
- list
- })
- }
-
- /// Get or compute the parent style identity.
- pub fn parent_style_identity<E>(&mut self, el: E) -> OpaqueComputedValues
- where
- E: TElement,
- {
- self.parent_style_identity
- .get_or_insert_with(|| {
- let parent = el.inheritance_parent().unwrap();
- let values =
- OpaqueComputedValues::from(parent.borrow_data().unwrap().styles.primary());
- values
- })
- .clone()
- }
-
- /// Computes the revalidation results if needed, and returns it.
- /// Inline so we know at compile time what bloom_known_valid is.
- #[inline]
- fn revalidation_match_results<E>(
- &mut self,
- element: E,
- stylist: &Stylist,
- bloom: &StyleBloom<E>,
- nth_index_cache: &mut NthIndexCache,
- bloom_known_valid: bool,
- needs_selector_flags: NeedsSelectorFlags,
- ) -> &SmallBitVec
- where
- E: TElement,
- {
- self.revalidation_match_results.get_or_insert_with(|| {
- // The bloom filter may already be set up for our element.
- // If it is, use it. If not, we must be in a candidate
- // (i.e. something in the cache), and the element is one
- // of our cousins, not a sibling. In that case, we'll
- // just do revalidation selector matching without a bloom
- // filter, to avoid thrashing the filter.
- let bloom_to_use = if bloom_known_valid {
- debug_assert_eq!(bloom.current_parent(), element.traversal_parent());
- Some(bloom.filter())
- } else {
- if bloom.current_parent() == element.traversal_parent() {
- Some(bloom.filter())
- } else {
- None
- }
- };
- stylist.match_revalidation_selectors(
- element,
- bloom_to_use,
- nth_index_cache,
- needs_selector_flags,
- )
- })
- }
-}
-
-/// Information regarding a style sharing candidate, that is, an entry in the
-/// style sharing cache.
-///
-/// Note that this information is stored in TLS and cleared after the traversal,
-/// and once here, the style information of the element is immutable, so it's
-/// safe to access.
-///
-/// Important: If you change the members/layout here, You need to do the same for
-/// FakeCandidate below.
-#[derive(Debug)]
-pub struct StyleSharingCandidate<E: TElement> {
- /// The element.
- element: E,
- validation_data: ValidationData,
- considered_relative_selector: bool,
-}
-
-struct FakeCandidate {
- _element: usize,
- _validation_data: ValidationData,
- _considered_relative_selector: bool,
-}
-
-impl<E: TElement> Deref for StyleSharingCandidate<E> {
- type Target = E;
-
- fn deref(&self) -> &Self::Target {
- &self.element
- }
-}
-
-impl<E: TElement> StyleSharingCandidate<E> {
- /// Get the classlist of this candidate.
- fn class_list(&mut self) -> &[AtomIdent] {
- self.validation_data.class_list(self.element)
- }
-
- /// Get the part list of this candidate.
- fn part_list(&mut self) -> &[AtomIdent] {
- self.validation_data.part_list(self.element)
- }
-
- /// Get the pres hints of this candidate.
- fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
- self.validation_data.pres_hints(self.element)
- }
-
- /// Get the parent style identity.
- fn parent_style_identity(&mut self) -> OpaqueComputedValues {
- self.validation_data.parent_style_identity(self.element)
- }
-
- /// Compute the bit vector of revalidation selector match results
- /// for this candidate.
- fn revalidation_match_results(
- &mut self,
- stylist: &Stylist,
- bloom: &StyleBloom<E>,
- nth_index_cache: &mut NthIndexCache,
- ) -> &SmallBitVec {
- self.validation_data.revalidation_match_results(
- self.element,
- stylist,
- bloom,
- nth_index_cache,
- /* bloom_known_valid = */ false,
- // The candidate must already have the right bits already, if
- // needed.
- NeedsSelectorFlags::No,
- )
- }
-}
-
-impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> {
- fn eq(&self, other: &Self) -> bool {
- self.element == other.element
- }
-}
-
-/// An element we want to test against the style sharing cache.
-pub struct StyleSharingTarget<E: TElement> {
- element: E,
- validation_data: ValidationData,
-}
-
-impl<E: TElement> Deref for StyleSharingTarget<E> {
- type Target = E;
-
- fn deref(&self) -> &Self::Target {
- &self.element
- }
-}
-
-impl<E: TElement> StyleSharingTarget<E> {
- /// Trivially construct a new StyleSharingTarget to test against the cache.
- pub fn new(element: E) -> Self {
- Self {
- element: element,
- validation_data: ValidationData::default(),
- }
- }
-
- fn class_list(&mut self) -> &[AtomIdent] {
- self.validation_data.class_list(self.element)
- }
-
- fn part_list(&mut self) -> &[AtomIdent] {
- self.validation_data.part_list(self.element)
- }
-
- /// Get the pres hints of this candidate.
- fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
- self.validation_data.pres_hints(self.element)
- }
-
- /// Get the parent style identity.
- fn parent_style_identity(&mut self) -> OpaqueComputedValues {
- self.validation_data.parent_style_identity(self.element)
- }
-
- fn revalidation_match_results(
- &mut self,
- stylist: &Stylist,
- bloom: &StyleBloom<E>,
- nth_index_cache: &mut NthIndexCache,
- ) -> &SmallBitVec {
- // It's important to set the selector flags. Otherwise, if we succeed in
- // sharing the style, we may not set the slow selector flags for the
- // right elements (which may not necessarily be |element|), causing
- // missed restyles after future DOM mutations.
- //
- // Gecko's test_bug534804.html exercises this. A minimal testcase is:
- // <style> #e:empty + span { ... } </style>
- // <span id="e">
- // <span></span>
- // </span>
- // <span></span>
- //
- // The style sharing cache will get a hit for the second span. When the
- // child span is subsequently removed from the DOM, missing selector
- // flags would cause us to miss the restyle on the second span.
- self.validation_data.revalidation_match_results(
- self.element,
- stylist,
- bloom,
- nth_index_cache,
- /* bloom_known_valid = */ true,
- NeedsSelectorFlags::Yes,
- )
- }
-
- /// Attempts to share a style with another node.
- pub fn share_style_if_possible(
- &mut self,
- context: &mut StyleContext<E>,
- ) -> Option<ResolvedElementStyles> {
- let cache = &mut context.thread_local.sharing_cache;
- let shared_context = &context.shared;
- let bloom_filter = &context.thread_local.bloom_filter;
- let nth_index_cache = &mut context.thread_local.nth_index_cache;
-
- if cache.dom_depth != bloom_filter.matching_depth() {
- debug!(
- "Can't share style, because DOM depth changed from {:?} to {:?}, element: {:?}",
- cache.dom_depth,
- bloom_filter.matching_depth(),
- self.element
- );
- return None;
- }
- debug_assert_eq!(
- bloom_filter.current_parent(),
- self.element.traversal_parent()
- );
-
- cache.share_style_if_possible(shared_context, bloom_filter, nth_index_cache, self)
- }
-
- /// Gets the validation data used to match against this target, if any.
- pub fn take_validation_data(&mut self) -> ValidationData {
- self.validation_data.take()
- }
-}
-
-struct SharingCacheBase<Candidate> {
- entries: LRUCache<Candidate, SHARING_CACHE_SIZE>,
-}
-
-impl<Candidate> Default for SharingCacheBase<Candidate> {
- fn default() -> Self {
- Self {
- entries: LRUCache::default(),
- }
- }
-}
-
-impl<Candidate> SharingCacheBase<Candidate> {
- fn clear(&mut self) {
- self.entries.clear();
- }
-
- fn is_empty(&self) -> bool {
- self.entries.len() == 0
- }
-}
-
-impl<E: TElement> SharingCache<E> {
- fn insert(
- &mut self,
- element: E,
- considered_relative_selector: bool,
- validation_data_holder: Option<&mut StyleSharingTarget<E>>,
- ) {
- let validation_data = match validation_data_holder {
- Some(v) => v.take_validation_data(),
- None => ValidationData::default(),
- };
- self.entries.insert(StyleSharingCandidate {
- element,
- considered_relative_selector,
- validation_data,
- });
- }
-}
-
-/// Style sharing caches are are large allocations, so we store them in thread-local
-/// storage such that they can be reused across style traversals. Ideally, we'd just
-/// stack-allocate these buffers with uninitialized memory, but right now rustc can't
-/// avoid memmoving the entire cache during setup, which gets very expensive. See
-/// issues like [1] and [2].
-///
-/// Given that the cache stores entries of type TElement, we transmute to usize
-/// before storing in TLS. This is safe as long as we make sure to empty the cache
-/// before we let it go.
-///
-/// [1] https://github.com/rust-lang/rust/issues/42763
-/// [2] https://github.com/rust-lang/rust/issues/13707
-type SharingCache<E> = SharingCacheBase<StyleSharingCandidate<E>>;
-type TypelessSharingCache = SharingCacheBase<FakeCandidate>;
-type StoredSharingCache = Arc<AtomicRefCell<TypelessSharingCache>>;
-
-thread_local! {
- // See the comment on bloom.rs about why do we leak this.
- static SHARING_CACHE_KEY: ManuallyDrop<StoredSharingCache> =
- ManuallyDrop::new(Arc::new_leaked(Default::default()));
-}
-
-/// An LRU cache of the last few nodes seen, so that we can aggressively try to
-/// reuse their styles.
-///
-/// Note that this cache is flushed every time we steal work from the queue, so
-/// storing nodes here temporarily is safe.
-pub struct StyleSharingCache<E: TElement> {
- /// The LRU cache, with the type cast away to allow persisting the allocation.
- cache_typeless: OwningHandle<StoredSharingCache, AtomicRefMut<'static, TypelessSharingCache>>,
- /// Bind this structure to the lifetime of E, since that's what we effectively store.
- marker: PhantomData<SendElement<E>>,
- /// The DOM depth we're currently at. This is used as an optimization to
- /// clear the cache when we change depths, since we know at that point
- /// nothing in the cache will match.
- dom_depth: usize,
-}
-
-impl<E: TElement> Drop for StyleSharingCache<E> {
- fn drop(&mut self) {
- self.clear();
- }
-}
-
-impl<E: TElement> StyleSharingCache<E> {
- #[allow(dead_code)]
- fn cache(&self) -> &SharingCache<E> {
- let base: &TypelessSharingCache = &*self.cache_typeless;
- unsafe { mem::transmute(base) }
- }
-
- fn cache_mut(&mut self) -> &mut SharingCache<E> {
- let base: &mut TypelessSharingCache = &mut *self.cache_typeless;
- unsafe { mem::transmute(base) }
- }
-
- /// Create a new style sharing candidate cache.
-
- // Forced out of line to limit stack frame sizes after extra inlining from
- // https://github.com/rust-lang/rust/pull/43931
- //
- // See https://github.com/servo/servo/pull/18420#issuecomment-328769322
- #[inline(never)]
- pub fn new() -> Self {
- assert_eq!(
- mem::size_of::<SharingCache<E>>(),
- mem::size_of::<TypelessSharingCache>()
- );
- assert_eq!(
- mem::align_of::<SharingCache<E>>(),
- mem::align_of::<TypelessSharingCache>()
- );
- let cache_arc = SHARING_CACHE_KEY.with(|c| Arc::clone(&*c));
- let cache =
- OwningHandle::new_with_fn(cache_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut());
- debug_assert!(cache.is_empty());
-
- StyleSharingCache {
- cache_typeless: cache,
- marker: PhantomData,
- dom_depth: 0,
- }
- }
-
- /// Tries to insert an element in the style sharing cache.
- ///
- /// Fails if we know it should never be in the cache.
- ///
- /// NB: We pass a source for the validation data, rather than the data itself,
- /// to avoid memmoving at each function call. See rust issue #42763.
- pub fn insert_if_possible(
- &mut self,
- element: &E,
- style: &PrimaryStyle,
- validation_data_holder: Option<&mut StyleSharingTarget<E>>,
- dom_depth: usize,
- shared_context: &SharedStyleContext,
- ) {
- let parent = match element.traversal_parent() {
- Some(element) => element,
- None => {
- debug!("Failing to insert to the cache: no parent element");
- return;
- },
- };
-
- if !element.matches_user_and_content_rules() {
- debug!("Failing to insert into the cache: no tree rules:");
- return;
- }
-
- // We can't share style across shadow hosts right now, because they may
- // match different :host rules.
- //
- // TODO(emilio): We could share across the ones that don't have :host
- // rules or have the same.
- if element.shadow_root().is_some() {
- debug!("Failing to insert into the cache: Shadow Host");
- return;
- }
-
- // If the element has running animations, we can't share style.
- //
- // This is distinct from the specifies_{animations,transitions} check below,
- // because:
- // * Animations can be triggered directly via the Web Animations API.
- // * Our computed style can still be affected by animations after we no
- // longer match any animation rules, since removing animations involves
- // a sequential task and an additional traversal.
- if element.has_animations(shared_context) {
- debug!("Failing to insert to the cache: running animations");
- return;
- }
-
- // If this element was considered for matching a relative selector, we can't
- // share style, as that requires evaluating the relative selector in the
- // first place. A couple notes:
- // - This means that a document that contains a standalone `:has()`
- // rule would basically turn style sharing off.
- // - Since the flag is set on the element, we may be overly pessimistic:
- // For example, given `<div class="foo"><div class="bar"></div></div>`,
- // if we run into a `.foo:has(.bar) .baz` selector, we refuse any selector
- // matching `.foo`, even if `:has()` may not even be used. Ideally we'd
- // have something like `RelativeSelectorConsidered::RightMost`, but the
- // element flag is required for invalidation, and this reduces more tracking.
- if style
- .style
- .0
- .flags
- .intersects(ComputedValueFlags::ANCHORS_RELATIVE_SELECTOR) {
- debug!("Failing to insert to the cache: may anchor relative selector");
- return;
- }
-
- // In addition to the above running animations check, we also need to
- // check CSS animation and transition styles since it's possible that
- // we are about to create CSS animations/transitions.
- //
- // These are things we don't check in the candidate match because they
- // are either uncommon or expensive.
- let ui_style = style.style().get_ui();
- if ui_style.specifies_transitions() {
- debug!("Failing to insert to the cache: transitions");
- return;
- }
-
- if ui_style.specifies_animations() {
- debug!("Failing to insert to the cache: animations");
- return;
- }
-
- debug!(
- "Inserting into cache: {:?} with parent {:?}",
- element, parent
- );
-
- if self.dom_depth != dom_depth {
- debug!(
- "Clearing cache because depth changed from {:?} to {:?}, element: {:?}",
- self.dom_depth, dom_depth, element
- );
- self.clear();
- self.dom_depth = dom_depth;
- }
- self.cache_mut().insert(
- *element,
- style
- .style
- .0
- .flags
- .intersects(ComputedValueFlags::CONSIDERED_RELATIVE_SELECTOR),
- validation_data_holder,
- );
- }
-
- /// Clear the style sharing candidate cache.
- pub fn clear(&mut self) {
- self.cache_mut().clear();
- }
-
- /// Attempts to share a style with another node.
- fn share_style_if_possible(
- &mut self,
- shared_context: &SharedStyleContext,
- bloom_filter: &StyleBloom<E>,
- nth_index_cache: &mut NthIndexCache,
- target: &mut StyleSharingTarget<E>,
- ) -> Option<ResolvedElementStyles> {
- if shared_context.options.disable_style_sharing_cache {
- debug!(
- "{:?} Cannot share style: style sharing cache disabled",
- target.element
- );
- return None;
- }
-
- if target.inheritance_parent().is_none() {
- debug!(
- "{:?} Cannot share style: element has no parent",
- target.element
- );
- return None;
- }
-
- if !target.matches_user_and_content_rules() {
- debug!("{:?} Cannot share style: content rules", target.element);
- return None;
- }
-
- self.cache_mut().entries.lookup(|candidate| {
- Self::test_candidate(
- target,
- candidate,
- &shared_context,
- bloom_filter,
- nth_index_cache,
- shared_context,
- )
- })
- }
-
- fn test_candidate(
- target: &mut StyleSharingTarget<E>,
- candidate: &mut StyleSharingCandidate<E>,
- shared: &SharedStyleContext,
- bloom: &StyleBloom<E>,
- nth_index_cache: &mut NthIndexCache,
- shared_context: &SharedStyleContext,
- ) -> Option<ResolvedElementStyles> {
- debug_assert!(target.matches_user_and_content_rules());
-
- // Check that we have the same parent, or at least that the parents
- // share styles and permit sharing across their children. The latter
- // check allows us to share style between cousins if the parents
- // shared style.
- if !checks::parents_allow_sharing(target, candidate) {
- trace!("Miss: Parent");
- return None;
- }
-
- if target.local_name() != candidate.element.local_name() {
- trace!("Miss: Local Name");
- return None;
- }
-
- if target.namespace() != candidate.element.namespace() {
- trace!("Miss: Namespace");
- return None;
- }
-
- // We do not ignore visited state here, because Gecko needs to store
- // extra bits on visited styles, so these contexts cannot be shared.
- if target.element.state() != candidate.state() {
- trace!("Miss: User and Author State");
- return None;
- }
-
- if target.is_link() != candidate.element.is_link() {
- trace!("Miss: Link");
- return None;
- }
-
- // If two elements belong to different shadow trees, different rules may
- // apply to them, from the respective trees.
- if target.element.containing_shadow() != candidate.element.containing_shadow() {
- trace!("Miss: Different containing shadow roots");
- return None;
- }
-
- // If the elements are not assigned to the same slot they could match
- // different ::slotted() rules in the slot scope.
- //
- // If two elements are assigned to different slots, even within the same
- // shadow root, they could match different rules, due to the slot being
- // assigned to yet another slot in another shadow root.
- if target.element.assigned_slot() != candidate.element.assigned_slot() {
- // TODO(emilio): We could have a look at whether the shadow roots
- // actually have slotted rules and such.
- trace!("Miss: Different assigned slots");
- return None;
- }
-
- if target.element.shadow_root().is_some() {
- trace!("Miss: Shadow host");
- return None;
- }
-
- if target.element.has_animations(shared_context) {
- trace!("Miss: Has Animations");
- return None;
- }
-
- if target.matches_user_and_content_rules() !=
- candidate.element.matches_user_and_content_rules()
- {
- trace!("Miss: User and Author Rules");
- return None;
- }
-
- // It's possible that there are no styles for either id.
- if checks::may_match_different_id_rules(shared, target.element, candidate.element) {
- trace!("Miss: ID Attr");
- return None;
- }
-
- if !checks::have_same_style_attribute(target, candidate) {
- trace!("Miss: Style Attr");
- return None;
- }
-
- if !checks::have_same_class(target, candidate) {
- trace!("Miss: Class");
- return None;
- }
-
- if !checks::have_same_presentational_hints(target, candidate) {
- trace!("Miss: Pres Hints");
- return None;
- }
-
- if !checks::have_same_parts(target, candidate) {
- trace!("Miss: Shadow parts");
- return None;
- }
-
- if !checks::revalidate(target, candidate, shared, bloom, nth_index_cache) {
- trace!("Miss: Revalidation");
- return None;
- }
-
- debug!(
- "Sharing allowed between {:?} and {:?}",
- target.element, candidate.element
- );
- Some(candidate.element.borrow_data().unwrap().share_styles())
- }
-
- /// Attempts to find an element in the cache with the given primary rule
- /// node and parent.
- ///
- /// FIXME(emilio): re-measure this optimization, and remove if it's not very
- /// useful... It's probably not worth the complexity / obscure bugs.
- pub fn lookup_by_rules(
- &mut self,
- shared_context: &SharedStyleContext,
- inherited: &ComputedValues,
- rules: &StrongRuleNode,
- visited_rules: Option<&StrongRuleNode>,
- target: E,
- ) -> Option<PrimaryStyle> {
- if shared_context.options.disable_style_sharing_cache {
- return None;
- }
-
- self.cache_mut().entries.lookup(|candidate| {
- debug_assert_ne!(candidate.element, target);
- if !candidate.parent_style_identity().eq(inherited) {
- return None;
- }
- let data = candidate.element.borrow_data().unwrap();
- let style = data.styles.primary();
- if style.rules.as_ref() != Some(&rules) {
- return None;
- }
- if style.visited_rules() != visited_rules {
- return None;
- }
- // NOTE(emilio): We only need to check name / namespace because we
- // do name-dependent style adjustments, like the display: contents
- // to display: none adjustment.
- if target.namespace() != candidate.element.namespace() ||
- target.local_name() != candidate.element.local_name()
- {
- return None;
- }
- // When using container units, inherited style + rules matched aren't enough to
- // determine whether the style is the same. We could actually do a full container
- // lookup but for now we just check that our actual traversal parent matches.
- if data
- .styles
- .primary()
- .flags
- .intersects(ComputedValueFlags::USES_CONTAINER_UNITS) &&
- candidate.element.traversal_parent() != target.traversal_parent()
- {
- return None;
- }
- // Rule nodes and styles are computed independent of the element's actual visitedness,
- // but at the end of the cascade (in `adjust_for_visited`) we do store the
- // RELEVANT_LINK_VISITED flag, so we can't share by rule node between visited and
- // unvisited styles. We don't check for visitedness and just refuse to share for links
- // entirely, so that visitedness doesn't affect timing.
- debug_assert_eq!(target.is_link(), candidate.element.is_link(), "Linkness mismatch");
- if target.is_link() {
- return None;
- }
-
- Some(data.share_primary_style())
- })
- }
-}