/* 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::{CssRule, StylesheetContents, UrlExtraData}; use stylo_atoms::Atom; 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, 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>> = 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, stylesheetcontents_create_callback: impl FnOnce() -> ServoArc, ) -> ( Option, ServoArc, ) { 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 = stylesheetcontents_create_callback(); 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) }); } }