script/dom/
stylesheetcontentscache.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
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::collections::hash_map::Entry;
8use std::hash::{DefaultHasher, Hash, Hasher};
9use std::rc::Rc;
10
11use servo_arc::Arc as ServoArc;
12use style::context::QuirksMode;
13use style::shared_lock::SharedRwLock;
14use style::stylesheets::{CssRule, StylesheetContents, UrlExtraData};
15use stylo_atoms::Atom;
16
17const MAX_LENGTH_OF_TEXT_INSERTED_INTO_TABLE: usize = 1024;
18const UNIQUE_OWNED: usize = 2;
19
20/// Using [`Atom`] as a cache key to avoid inefficient string content comparison. Although
21/// the [`Atom`] is already based on reference counting, an extra [`Rc`] is introduced
22/// to trace how many [`style::stylesheets::Stylesheet`]s of style elements are sharing
23/// same [`StylesheetContents`], based on the following considerations:
24/// * The reference count within [`Atom`] is dedicated to lifecycle management and is not
25///   suitable for tracking the number of [`StylesheetContents`]s owners.
26/// * The reference count within [`Atom`] is not publicly acessible.
27#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq)]
28pub(crate) struct StylesheetContentsCacheKey {
29    #[conditional_malloc_size_of]
30    stylesheet_text: Rc<Atom>,
31    base_url: Atom,
32    #[ignore_malloc_size_of = "defined in style crate"]
33    quirks_mode: QuirksMode,
34}
35
36impl StylesheetContentsCacheKey {
37    fn new(stylesheet_text: &str, base_url: &str, quirks_mode: QuirksMode) -> Self {
38        // The stylesheet text may be quite lengthy, exceeding hundreds of kilobytes.
39        // Instead of directly inserting such a huge string into AtomicString table,
40        // take its hash value and use that. (This is not a cryptographic hash, so a
41        // page could cause collisions if it wanted to.)
42        let contents_atom = if stylesheet_text.len() > MAX_LENGTH_OF_TEXT_INSERTED_INTO_TABLE {
43            let mut hasher = DefaultHasher::new();
44            stylesheet_text.hash(&mut hasher);
45            Atom::from(hasher.finish().to_string().as_str())
46        } else {
47            Atom::from(stylesheet_text)
48        };
49
50        Self {
51            stylesheet_text: Rc::new(contents_atom),
52            base_url: Atom::from(base_url),
53            quirks_mode,
54        }
55    }
56
57    pub(crate) fn is_uniquely_owned(&self) -> bool {
58        // The cache itself already holds one reference.
59        Rc::strong_count(&self.stylesheet_text) <= UNIQUE_OWNED
60    }
61}
62
63thread_local! {
64    static STYLESHEETCONTENTS_CACHE: RefCell<HashMap<StylesheetContentsCacheKey, ServoArc<StylesheetContents>>> =
65       RefCell::default();
66}
67
68pub(crate) struct StylesheetContentsCache;
69
70impl StylesheetContentsCache {
71    fn contents_can_be_cached(contents: &StylesheetContents, shared_lock: &SharedRwLock) -> bool {
72        let guard = shared_lock.read();
73        let rules = contents.rules(&guard);
74        // The copy-on-write can not be performed when the modification happens on the
75        // imported stylesheet, because it containing cssom has no owner dom node.
76        !(rules.is_empty() || rules.iter().any(|rule| matches!(rule, CssRule::Import(_))))
77    }
78
79    pub(crate) fn get_or_insert_with(
80        stylesheet_text: &str,
81        shared_lock: &SharedRwLock,
82        url_data: UrlExtraData,
83        quirks_mode: QuirksMode,
84        stylesheetcontents_create_callback: impl FnOnce() -> ServoArc<StylesheetContents>,
85    ) -> (
86        Option<StylesheetContentsCacheKey>,
87        ServoArc<StylesheetContents>,
88    ) {
89        let cache_key =
90            StylesheetContentsCacheKey::new(stylesheet_text, url_data.as_str(), quirks_mode);
91        STYLESHEETCONTENTS_CACHE.with_borrow_mut(|stylesheetcontents_cache| {
92            let entry = stylesheetcontents_cache.entry(cache_key);
93            match entry {
94                Entry::Occupied(occupied_entry) => {
95                    // Use a copy of the cache key from `Entry` instead of the newly created one above
96                    // to correctly update and track to owner count of `StylesheetContents`.
97                    (
98                        Some(occupied_entry.key().clone()),
99                        occupied_entry.get().clone(),
100                    )
101                },
102                Entry::Vacant(vacant_entry) => {
103                    let contents = stylesheetcontents_create_callback();
104                    if Self::contents_can_be_cached(&contents, shared_lock) {
105                        let occupied_entry = vacant_entry.insert_entry(contents.clone());
106                        // Use a copy of the cache key from `Entry` instead of the newly created one above
107                        // to correctly update and track to owner count of `StylesheetContents`.
108                        (Some(occupied_entry.key().clone()), contents)
109                    } else {
110                        (None, contents)
111                    }
112                },
113            }
114        })
115    }
116
117    pub(crate) fn remove(cache_key: StylesheetContentsCacheKey) {
118        STYLESHEETCONTENTS_CACHE.with_borrow_mut(|stylesheetcontents_cache| {
119            stylesheetcontents_cache.remove(&cache_key)
120        });
121    }
122}