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;
#[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 {
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 {
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);
!(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) => {
(
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,
None,
)
};
if Self::contents_can_be_cached(&contents, shared_lock) {
let occupied_entry = vacant_entry.insert_entry(contents.clone());
(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)
});
}
}