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