Reuse StylesheetContent for inline style sheets with identical content (#38540)

For duplicate style sheets with identical content, `StylesheetContents`
can be reused to avoid redundant parsing of the inline style sheets.
Since duplicate stylesheets is a common case with web components, this
change will significantly improve performance. Additionally, the cache
hit rate of stylo's `CascadeDataCache` can now be significantly
improved.

When shared `StylesheetContents` is modified, copy-on-write will occur
to avoid affecting other sharers. And then updates the references to
`CssRule` or `PropertyDeclarationBlock` stored in the CSSOMs to ensure
that modifications are made only on the new copy.

Signed-off-by: sharpshooter_pt <ibluegalaxy_taoj@163.com>
This commit is contained in:
JoeDow 2025-08-20 20:31:49 +08:00 committed by GitHub
parent f6b77f94e2
commit 6e6ef513a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 711 additions and 125 deletions

View file

@ -2,16 +2,17 @@
* 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::Cell;
use std::cell::{Cell, Ref};
use std::rc::Rc;
use dom_struct::dom_struct;
use js::rust::HandleObject;
use script_bindings::inheritance::Castable;
use script_bindings::realms::InRealm;
use script_bindings::root::Dom;
use servo_arc::Arc;
use style::media_queries::MediaList as StyleMediaList;
use style::shared_lock::SharedRwLock;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
use style::stylesheets::{
AllowImportRules, CssRuleTypes, Origin, Stylesheet as StyleStyleSheet, UrlExtraData,
};
@ -33,6 +34,7 @@ use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::cssrulelist::{CSSRuleList, RulesSource};
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::htmlstyleelement::HTMLStyleElement;
use crate::dom::medialist::MediaList;
use crate::dom::node::NodeTraits;
use crate::dom::stylesheet::StyleSheet;
@ -55,7 +57,12 @@ pub(crate) struct CSSStyleSheet {
/// The inner Stylo's [Stylesheet].
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
style_stylesheet: Arc<StyleStyleSheet>,
style_stylesheet: DomRefCell<Arc<StyleStyleSheet>>,
/// The inner Stylo's [SharedRwLock], stored at here to avoid referencing
/// temporary variables.
#[no_trace]
style_shared_lock: SharedRwLock,
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-origin-clean-flag>
origin_clean: Cell<bool>,
@ -86,7 +93,8 @@ impl CSSStyleSheet {
stylesheet: StyleSheet::new_inherited(type_, href, title),
owner_node: MutNullableDom::new(owner),
rulelist: MutNullableDom::new(None),
style_stylesheet: stylesheet,
style_shared_lock: stylesheet.shared_lock.clone(),
style_stylesheet: DomRefCell::new(stylesheet),
origin_clean: Cell::new(true),
constructor_document: constructor_document.map(Dom::from_ref),
adopters: Default::default(),
@ -150,7 +158,7 @@ impl CSSStyleSheet {
fn rulelist(&self, can_gc: CanGc) -> DomRoot<CSSRuleList> {
self.rulelist.or_init(|| {
let rules = self.style_stylesheet.contents.rules.clone();
let rules = self.style_stylesheet.borrow().contents.rules.clone();
CSSRuleList::new(
self.global().as_window(),
self,
@ -161,7 +169,7 @@ impl CSSStyleSheet {
}
pub(crate) fn disabled(&self) -> bool {
self.style_stylesheet.disabled()
self.style_stylesheet.borrow().disabled()
}
pub(crate) fn owner_node(&self) -> Option<DomRoot<Element>> {
@ -169,7 +177,7 @@ impl CSSStyleSheet {
}
pub(crate) fn set_disabled(&self, disabled: bool) {
if self.style_stylesheet.set_disabled(disabled) {
if self.style_stylesheet.borrow().set_disabled(disabled) {
self.notify_invalidations();
}
}
@ -179,15 +187,11 @@ impl CSSStyleSheet {
}
pub(crate) fn shared_lock(&self) -> &SharedRwLock {
&self.style_stylesheet.shared_lock
&self.style_shared_lock
}
pub(crate) fn style_stylesheet(&self) -> &StyleStyleSheet {
&self.style_stylesheet
}
pub(crate) fn style_stylesheet_arc(&self) -> &Arc<StyleStyleSheet> {
&self.style_stylesheet
pub(crate) fn style_stylesheet(&self) -> Ref<'_, Arc<StyleStyleSheet>> {
self.style_stylesheet.borrow()
}
pub(crate) fn set_origin_clean(&self, origin_clean: bool) {
@ -231,6 +235,36 @@ impl CSSStyleSheet {
}
}
pub(crate) fn will_modify(&self) {
let Some(node) = self.owner_node.get() else {
return;
};
let Some(node) = node.downcast::<HTMLStyleElement>() else {
return;
};
node.will_modify_stylesheet();
}
pub(crate) fn update_style_stylesheet(
&self,
style_stylesheet: &Arc<StyleStyleSheet>,
guard: &SharedRwLockReadGuard,
) {
// When the shared `StylesheetContents` is about to be modified,
// `CSSStyleSheet::owner_node` performs a copy-on-write to avoid
// affecting other sharers, see `CSSStyleSheet::will_modify`. And
// then updates the references to `CssRule` or `PropertyDeclarationBlock`
// stored in the CSSOMs to ensure that modifications are made only
// on the new copy.
*self.style_stylesheet.borrow_mut() = style_stylesheet.clone();
if let Some(rulelist) = self.rulelist.get() {
let rules = style_stylesheet.contents.rules.clone();
rulelist.update_rules(RulesSource::Rules(rules), guard);
}
}
/// Invalidate all stylesheet set this stylesheet is a part on.
pub(crate) fn notify_invalidations(&self) {
if let Some(owner) = self.owner_node() {
@ -402,8 +436,12 @@ impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
let global = sheet.global();
let window = global.as_window();
sheet.will_modify();
#[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ParseStylesheet", servo_profiling = true).entered();
StyleStyleSheet::update_from_str(
&sheet.style_stylesheet,
&sheet.style_stylesheet(),
&text,
UrlExtraData(window.get_url().get_arc()),
None,
@ -441,8 +479,12 @@ impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
let global = self.global();
let window = global.as_window();
self.will_modify();
#[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ParseStylesheet", servo_profiling = true).entered();
StyleStyleSheet::update_from_str(
&self.style_stylesheet,
&self.style_stylesheet(),
&text,
UrlExtraData(window.get_url().get_arc()),
None,