script/dom/css/
stylesheetcontentscache.rs1use 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::{AllowImportRules, CssRule, Origin, StylesheetContents, UrlExtraData};
15use stylo_atoms::Atom;
16
17use crate::dom::node::NodeTraits;
18use crate::dom::types::HTMLElement;
19use crate::stylesheet_loader::ElementStylesheetLoader;
20
21const MAX_LENGTH_OF_TEXT_INSERTED_INTO_TABLE: usize = 1024;
22const UNIQUE_OWNED: usize = 2;
23
24#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq)]
32pub(crate) struct StylesheetContentsCacheKey {
33 #[conditional_malloc_size_of]
34 stylesheet_text: Rc<Atom>,
35 base_url: Atom,
36 #[ignore_malloc_size_of = "defined in style crate"]
37 quirks_mode: QuirksMode,
38}
39
40impl StylesheetContentsCacheKey {
41 fn new(stylesheet_text: &str, base_url: &str, quirks_mode: QuirksMode) -> Self {
42 let contents_atom = if stylesheet_text.len() > MAX_LENGTH_OF_TEXT_INSERTED_INTO_TABLE {
47 let mut hasher = DefaultHasher::new();
48 stylesheet_text.hash(&mut hasher);
49 Atom::from(hasher.finish().to_string().as_str())
50 } else {
51 Atom::from(stylesheet_text)
52 };
53
54 Self {
55 stylesheet_text: Rc::new(contents_atom),
56 base_url: Atom::from(base_url),
57 quirks_mode,
58 }
59 }
60
61 pub(crate) fn is_uniquely_owned(&self) -> bool {
62 Rc::strong_count(&self.stylesheet_text) <= UNIQUE_OWNED
64 }
65}
66
67thread_local! {
68 static STYLESHEETCONTENTS_CACHE: RefCell<HashMap<StylesheetContentsCacheKey, ServoArc<StylesheetContents>>> =
69 RefCell::default();
70}
71
72pub(crate) struct StylesheetContentsCache;
73
74impl StylesheetContentsCache {
75 fn contents_can_be_cached(contents: &StylesheetContents, shared_lock: &SharedRwLock) -> bool {
76 let guard = shared_lock.read();
77 let rules = contents.rules(&guard);
78 !(rules.is_empty() || rules.iter().any(|rule| matches!(rule, CssRule::Import(_))))
81 }
82
83 pub(crate) fn get_or_insert_with(
84 stylesheet_text: &str,
85 shared_lock: &SharedRwLock,
86 url_data: UrlExtraData,
87 quirks_mode: QuirksMode,
88 element: &HTMLElement,
89 ) -> (
90 Option<StylesheetContentsCacheKey>,
91 ServoArc<StylesheetContents>,
92 ) {
93 let cache_key =
94 StylesheetContentsCacheKey::new(stylesheet_text, url_data.as_str(), quirks_mode);
95 STYLESHEETCONTENTS_CACHE.with_borrow_mut(|stylesheetcontents_cache| {
96 let entry = stylesheetcontents_cache.entry(cache_key);
97 match entry {
98 Entry::Occupied(occupied_entry) => {
99 (
102 Some(occupied_entry.key().clone()),
103 occupied_entry.get().clone(),
104 )
105 },
106 Entry::Vacant(vacant_entry) => {
107 let contents = {
108 #[cfg(feature = "tracing")]
109 let _span = tracing::trace_span!("ParseStylesheet", servo_profiling = true)
110 .entered();
111 StylesheetContents::from_str(
112 stylesheet_text,
113 url_data,
114 Origin::Author,
115 shared_lock,
116 Some(&ElementStylesheetLoader::new(element)),
117 element.owner_window().css_error_reporter(),
118 quirks_mode,
119 AllowImportRules::Yes,
120 None,
121 )
122 };
123 if Self::contents_can_be_cached(&contents, shared_lock) {
124 let occupied_entry = vacant_entry.insert_entry(contents.clone());
125 (Some(occupied_entry.key().clone()), contents)
128 } else {
129 (None, contents)
130 }
131 },
132 }
133 })
134 }
135
136 pub(crate) fn remove(cache_key: StylesheetContentsCacheKey) {
137 STYLESHEETCONTENTS_CACHE.with_borrow_mut(|stylesheetcontents_cache| {
138 stylesheetcontents_cache.remove(&cache_key)
139 });
140 }
141}