mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 00:00:44 +00:00
fix(style): retain media sheets in tree scopes
This commit is contained in:
@@ -3,7 +3,6 @@ use std::rc::Rc;
|
||||
use moli_web_mime::is_stylesheet_type_attribute;
|
||||
|
||||
use crate::{
|
||||
context_bootstrap::evaluate_match_media_query_list_with_viewport,
|
||||
document_runtime::DomHandle,
|
||||
dom::native::{DomHost, Node},
|
||||
style_engine::{
|
||||
@@ -304,7 +303,6 @@ pub(super) fn active_stylesheet_handles(
|
||||
&& is_stylesheet_type_attribute(
|
||||
runtime.dom_host().get_attribute(*handle, "type").as_deref(),
|
||||
)
|
||||
&& stylesheet_media_matches(runtime, *handle)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let preferred_title = handles
|
||||
@@ -337,8 +335,13 @@ pub(super) fn stylesheet_text(runtime: &JsContextHost, handle: DomHandle) -> Str
|
||||
}
|
||||
|
||||
fn stylesheet_source(runtime: &JsContextHost, handle: DomHandle) -> StyloStylesheetSource {
|
||||
let media = runtime
|
||||
.dom_host()
|
||||
.get_attribute(handle, "media")
|
||||
.unwrap_or_default();
|
||||
let Some(element) = runtime.dom_host().node(handle).and_then(Node::as_element) else {
|
||||
return StyloStylesheetSource::new(String::new(), style_base_url(runtime, handle));
|
||||
return StyloStylesheetSource::new(String::new(), style_base_url(runtime, handle))
|
||||
.with_owner_media_text(&media);
|
||||
};
|
||||
if element.is_html_element("link") {
|
||||
return runtime
|
||||
@@ -349,27 +352,18 @@ fn stylesheet_source(runtime: &JsContextHost, handle: DomHandle) -> StyloStylesh
|
||||
.with_source_id(StyleSourceId::linked_style_sheet(
|
||||
runtime.dom_host(),
|
||||
handle,
|
||||
));
|
||||
))
|
||||
.with_owner_media_text(&media);
|
||||
}
|
||||
if element.is_inline_style_element()
|
||||
&& let Some(source) = runtime.owner_style_sheet_source(handle)
|
||||
{
|
||||
return source.with_source_id(StyleSourceId::owner_style_sheet(runtime.dom_host(), handle));
|
||||
return source
|
||||
.with_source_id(StyleSourceId::owner_style_sheet(runtime.dom_host(), handle))
|
||||
.with_owner_media_text(&media);
|
||||
}
|
||||
StyloStylesheetSource::new(String::new(), style_base_url(runtime, handle))
|
||||
}
|
||||
|
||||
fn stylesheet_media_matches(runtime: &JsContextHost, handle: DomHandle) -> bool {
|
||||
let Some(media) = runtime.dom_host().get_attribute(handle, "media") else {
|
||||
return true;
|
||||
};
|
||||
let media = media.trim();
|
||||
media.is_empty()
|
||||
|| evaluate_match_media_query_list_with_viewport(
|
||||
media,
|
||||
Some(runtime.emulated_media()),
|
||||
runtime.style_viewport(),
|
||||
)
|
||||
.with_owner_media_text(&media)
|
||||
}
|
||||
|
||||
fn link_stylesheet_is_enabled(runtime: &JsContextHost, handle: DomHandle) -> bool {
|
||||
|
||||
@@ -440,8 +440,6 @@ pub(super) fn computed_style_snapshot_from_current_observation(
|
||||
host,
|
||||
&world.document_state,
|
||||
owner_document,
|
||||
viewport,
|
||||
environment,
|
||||
quirks_mode,
|
||||
tree_scope_versions,
|
||||
));
|
||||
|
||||
@@ -39,6 +39,7 @@ pub(super) struct RetainedStyleInvalidations {
|
||||
pub(super) shadow_scopes: Vec<(DomHandle, StylesheetInvalidationSet)>,
|
||||
pub(super) shadow_scope_fallbacks: Vec<DomHandle>,
|
||||
pub(super) removed_shadow_scopes: Vec<DomHandle>,
|
||||
pub(super) viewport_size_changed: bool,
|
||||
}
|
||||
|
||||
/// Compares an explicit full-world snapshot with the canonical retained
|
||||
@@ -74,6 +75,7 @@ impl RetainedStyleInvalidations {
|
||||
shadow_scopes: Vec::new(),
|
||||
shadow_scope_fallbacks: Vec::new(),
|
||||
removed_shadow_scopes: Vec::new(),
|
||||
viewport_size_changed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,6 +203,7 @@ pub(super) fn update_retained_style_system(
|
||||
);
|
||||
invalidations.document = document_update.invalidations;
|
||||
invalidations.document_scope_fallback = document_update.scope_fallback;
|
||||
invalidations.viewport_size_changed = document_update.viewport_size_changed;
|
||||
|
||||
let shadow_reconciliation = reconcile_shadow_scopes(
|
||||
retained,
|
||||
@@ -260,6 +263,7 @@ pub(super) fn update_retained_style_system_incrementally(
|
||||
);
|
||||
invalidations.document = document_update.invalidations;
|
||||
invalidations.document_scope_fallback = document_update.scope_fallback;
|
||||
invalidations.viewport_size_changed = document_update.viewport_size_changed;
|
||||
|
||||
let shadow_reconciliation = reconcile_dirty_shadow_scopes(
|
||||
retained,
|
||||
@@ -274,15 +278,19 @@ pub(super) fn update_retained_style_system_incrementally(
|
||||
invalidations.removed_shadow_scopes = shadow_reconciliation.removed_roots;
|
||||
|
||||
let mut dirty_source_scopes = full_source_projection_scopes.clone();
|
||||
if document_update.device_changed {
|
||||
if document_update
|
||||
.device_affected_origins
|
||||
.contains(OriginSet::ORIGIN_AUTHOR)
|
||||
{
|
||||
dirty_source_scopes.insert(StyleScopeId::Document(document));
|
||||
dirty_source_scopes.extend(
|
||||
retained
|
||||
.shadow_scopes
|
||||
.iter()
|
||||
.map(|scope| StyleScopeId::ShadowRoot(scope.root())),
|
||||
);
|
||||
}
|
||||
dirty_source_scopes.extend(
|
||||
shadow_reconciliation
|
||||
.device_affected_roots
|
||||
.iter()
|
||||
.copied()
|
||||
.map(StyleScopeId::ShadowRoot),
|
||||
);
|
||||
dirty_source_scopes.extend(
|
||||
invalidations
|
||||
.removed_shadow_scopes
|
||||
@@ -296,6 +304,7 @@ pub(super) fn update_retained_style_system_incrementally(
|
||||
retained_source_records,
|
||||
dirty_source_ids,
|
||||
&dirty_source_scopes,
|
||||
document_update.device_changed,
|
||||
|source| install_active_stylesheet(host, shared_lock, source, key.quirks_mode),
|
||||
);
|
||||
refresh_retained_derived_state(
|
||||
@@ -311,6 +320,8 @@ struct DocumentScopeUpdate {
|
||||
invalidations: Option<StylesheetInvalidationSet>,
|
||||
scope_fallback: bool,
|
||||
device_changed: bool,
|
||||
viewport_size_changed: bool,
|
||||
device_affected_origins: OriginSet,
|
||||
stylesheets_changed: bool,
|
||||
}
|
||||
|
||||
@@ -353,11 +364,13 @@ fn update_document_scope(
|
||||
.force_stylesheet_origins_dirty(OriginSet::ORIGIN_AUTHOR);
|
||||
}
|
||||
|
||||
let device_changed = retained.key.viewport_width_bits != key.viewport_width_bits
|
||||
|| retained.key.viewport_height_bits != key.viewport_height_bits
|
||||
let viewport_size_changed = retained.key.viewport_width_bits != key.viewport_width_bits
|
||||
|| retained.key.viewport_height_bits != key.viewport_height_bits;
|
||||
let device_changed = viewport_size_changed
|
||||
|| retained.key.screen_width_bits != key.screen_width_bits
|
||||
|| retained.key.screen_height_bits != key.screen_height_bits
|
||||
|| retained.key.environment != key.environment;
|
||||
let mut device_affected_origins = OriginSet::empty();
|
||||
if device_changed {
|
||||
let device = new_style_device_with_viewport_bits(
|
||||
key.viewport_width_bits,
|
||||
@@ -369,10 +382,10 @@ fn update_document_scope(
|
||||
);
|
||||
let guard = shared_lock.read();
|
||||
let guards = StylesheetGuards::same(&guard);
|
||||
let affected_origins = retained.stylist.set_device(device, &guards);
|
||||
device_affected_origins = retained.stylist.set_device(device, &guards);
|
||||
retained
|
||||
.stylist
|
||||
.force_stylesheet_origins_dirty(affected_origins);
|
||||
.force_stylesheet_origins_dirty(device_affected_origins);
|
||||
}
|
||||
|
||||
let stylesheet_reconciliation = stylesheet_sources.and_then(|sources| {
|
||||
@@ -409,7 +422,8 @@ fn update_document_scope(
|
||||
}
|
||||
|
||||
let stylesheets_changed = stylesheet_reconciliation.is_some();
|
||||
let must_flush = device_changed || stylesheets_changed || custom_properties_changed;
|
||||
let must_flush =
|
||||
!device_affected_origins.is_empty() || stylesheets_changed || custom_properties_changed;
|
||||
let invalidations = must_flush.then(|| {
|
||||
let guard = shared_lock.read();
|
||||
retained.stylist.flush(&StylesheetGuards::same(&guard))
|
||||
@@ -418,6 +432,8 @@ fn update_document_scope(
|
||||
invalidations,
|
||||
scope_fallback,
|
||||
device_changed,
|
||||
viewport_size_changed,
|
||||
device_affected_origins,
|
||||
stylesheets_changed,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ pub(super) struct ShadowScopeReconciliation {
|
||||
pub(super) invalidations: Vec<(DomHandle, StylesheetInvalidationSet)>,
|
||||
pub(super) removed_roots: Vec<DomHandle>,
|
||||
pub(super) scope_fallbacks: Vec<DomHandle>,
|
||||
pub(super) device_affected_roots: Vec<DomHandle>,
|
||||
pub(super) collections_changed: bool,
|
||||
}
|
||||
|
||||
@@ -100,6 +101,7 @@ pub(super) fn reconcile_dirty_shadow_scopes(
|
||||
let mut invalidations = Vec::new();
|
||||
let mut removed_roots = Vec::new();
|
||||
let mut scope_fallbacks = Vec::new();
|
||||
let mut device_affected_roots = Vec::new();
|
||||
let mut collections_changed = false;
|
||||
let mut newly_built_roots = HashSet::new();
|
||||
|
||||
@@ -135,6 +137,18 @@ pub(super) fn reconcile_dirty_shadow_scopes(
|
||||
retained.shadow_scopes = next_scopes;
|
||||
}
|
||||
|
||||
if device_changed {
|
||||
let guard = shared_lock.read();
|
||||
device_affected_roots.extend(
|
||||
retained
|
||||
.shadow_scopes
|
||||
.iter()
|
||||
.filter(|scope| !newly_built_roots.contains(&scope.root()))
|
||||
.filter(|scope| shadow_scope_media_changed(scope, &retained.stylist, &guard))
|
||||
.map(ShadowScopeStyles::root),
|
||||
);
|
||||
}
|
||||
|
||||
for (root, sources) in dirty_scopes {
|
||||
if newly_built_roots.contains(root) {
|
||||
continue;
|
||||
@@ -170,11 +184,12 @@ pub(super) fn reconcile_dirty_shadow_scopes(
|
||||
if newly_built_roots.contains(&scope.root()) {
|
||||
continue;
|
||||
}
|
||||
let must_flush = dirty_roots.contains(&scope.root()) || device_changed;
|
||||
let device_affected = device_affected_roots.contains(&scope.root());
|
||||
let must_flush = dirty_roots.contains(&scope.root()) || device_affected;
|
||||
if !must_flush {
|
||||
continue;
|
||||
}
|
||||
if device_changed {
|
||||
if device_affected {
|
||||
scope.author_styles_mut().stylesheets.force_dirty();
|
||||
}
|
||||
let guard = shared_lock.read();
|
||||
@@ -186,6 +201,7 @@ pub(super) fn reconcile_dirty_shadow_scopes(
|
||||
invalidations,
|
||||
removed_roots,
|
||||
scope_fallbacks,
|
||||
device_affected_roots,
|
||||
collections_changed,
|
||||
}
|
||||
}
|
||||
@@ -203,13 +219,14 @@ pub(super) fn reconcile_shadow_scopes(
|
||||
let mut next_scopes = Vec::with_capacity(desired_scopes.len());
|
||||
let mut invalidations = Vec::new();
|
||||
let mut scope_fallbacks = Vec::new();
|
||||
let mut device_affected_roots = Vec::new();
|
||||
let mut collections_changed = false;
|
||||
for (root, sources) in desired_scopes {
|
||||
let mut scope = if let Some(index) = previous_scopes
|
||||
let (mut scope, retained_scope) = if let Some(index) = previous_scopes
|
||||
.iter()
|
||||
.position(|scope| scope.root() == *root)
|
||||
{
|
||||
previous_scopes.remove(index)
|
||||
(previous_scopes.remove(index), true)
|
||||
} else {
|
||||
collections_changed = true;
|
||||
let (scope, scope_invalidations) = build_shadow_scope(
|
||||
@@ -220,9 +237,17 @@ pub(super) fn reconcile_shadow_scopes(
|
||||
&mut install,
|
||||
);
|
||||
invalidations.push((*root, scope_invalidations));
|
||||
scope
|
||||
(scope, false)
|
||||
};
|
||||
|
||||
let device_affected = device_changed && retained_scope && {
|
||||
let guard = shared_lock.read();
|
||||
shadow_scope_media_changed(&scope, &retained.stylist, &guard)
|
||||
};
|
||||
if device_affected {
|
||||
device_affected_roots.push(*root);
|
||||
}
|
||||
|
||||
let reconciliation = scope
|
||||
.active_stylesheets_mut()
|
||||
.reconcile(sources, &mut install);
|
||||
@@ -237,10 +262,10 @@ pub(super) fn reconcile_shadow_scopes(
|
||||
scope_fallbacks.push(*root);
|
||||
}
|
||||
}
|
||||
if device_changed {
|
||||
if device_affected {
|
||||
scope.author_styles_mut().stylesheets.force_dirty();
|
||||
}
|
||||
if reconciliation.is_some() || device_changed {
|
||||
if reconciliation.is_some() || device_affected {
|
||||
let guard = shared_lock.read();
|
||||
invalidations.push((*root, scope.flush(&mut retained.stylist, &guard)));
|
||||
}
|
||||
@@ -257,10 +282,26 @@ pub(super) fn reconcile_shadow_scopes(
|
||||
invalidations,
|
||||
removed_roots,
|
||||
scope_fallbacks,
|
||||
device_affected_roots,
|
||||
collections_changed,
|
||||
}
|
||||
}
|
||||
|
||||
fn shadow_scope_media_changed(
|
||||
scope: &ShadowScopeStyles,
|
||||
stylist: &Stylist,
|
||||
guard: &SharedRwLockReadGuard<'_>,
|
||||
) -> bool {
|
||||
scope.active_stylesheets().entries().iter().any(|entry| {
|
||||
!scope.author_styles().data.media_feature_affected_matches(
|
||||
entry.stylesheet(),
|
||||
guard,
|
||||
stylist.device(),
|
||||
stylist.quirks_mode(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn build_shadow_scope(
|
||||
stylist: &mut Stylist,
|
||||
shared_lock: &SharedRwLock,
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
use moli_crypto::Sha256Context;
|
||||
use moli_selector::StyloSourceDependencySummary;
|
||||
use style::stylesheets::{CssRule, StylesheetInDocument};
|
||||
use style_traits::ToCss;
|
||||
|
||||
use super::super::{
|
||||
source_id::{StyleScopeId, StyleSourceId, StyleSourceKind},
|
||||
@@ -35,6 +36,12 @@ pub(crate) struct StyloStylesheetSource {
|
||||
cache_key: StyleSourceKey,
|
||||
source_id: Option<StyleSourceId>,
|
||||
adopted_client_id: Option<u64>,
|
||||
/// Top-level media list attached by the stylesheet owner.
|
||||
///
|
||||
/// Live stylesheets carry the same list in Stylo's `Stylesheet::media`.
|
||||
/// Keeping the serialized value on the source also preserves it when a
|
||||
/// live sheet is projected into an independently parsed test/query world.
|
||||
media_text: StdArc<str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -93,6 +100,7 @@ impl StyloStylesheetSource {
|
||||
cache_key,
|
||||
source_id: None,
|
||||
adopted_client_id: None,
|
||||
media_text: StdArc::from(""),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +108,14 @@ impl StyloStylesheetSource {
|
||||
stylesheet: &crate::live_stylesheet::LiveStylesheetRef,
|
||||
) -> Self {
|
||||
let base_url = stylesheet.base_url().clone();
|
||||
let parsed_stylesheet = stylesheet.stylesheet();
|
||||
let media_text = {
|
||||
let guard = parsed_stylesheet.shared_lock.read();
|
||||
parsed_stylesheet.media.read_with(&guard).to_css_string()
|
||||
};
|
||||
Self {
|
||||
contents: StyloStylesheetSourceContents::Live {
|
||||
stylesheet: stylesheet.stylesheet(),
|
||||
stylesheet: parsed_stylesheet,
|
||||
id: stylesheet.id(),
|
||||
contents_revision: stylesheet.contents_revision(),
|
||||
cascade_generation: stylesheet.cascade_generation(),
|
||||
@@ -119,6 +132,7 @@ impl StyloStylesheetSource {
|
||||
),
|
||||
source_id: None,
|
||||
adopted_client_id: None,
|
||||
media_text: StdArc::from(media_text),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +156,22 @@ impl StyloStylesheetSource {
|
||||
self
|
||||
}
|
||||
|
||||
/// Installs an owner attribute media list on a text-backed source.
|
||||
///
|
||||
/// A live stylesheet is already authoritative for CSSOM media mutations,
|
||||
/// so owner projection must not overwrite the list captured from it.
|
||||
pub(crate) fn with_owner_media_text(mut self, media_text: &str) -> Self {
|
||||
if matches!(&self.contents, StyloStylesheetSourceContents::Text { .. }) {
|
||||
self.media_text = StdArc::from(media_text);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn with_media_text(mut self, media_text: StdArc<str>) -> Self {
|
||||
self.media_text = media_text;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn input_css_text(&self) -> Option<&str> {
|
||||
match &self.contents {
|
||||
StyloStylesheetSourceContents::Text { shared } => Some(shared.css_text()),
|
||||
@@ -180,7 +210,8 @@ impl StyloStylesheetSource {
|
||||
)
|
||||
.with_source_id(self.source_id.clone())
|
||||
.with_origin_clean(self.origin_clean)
|
||||
.with_sheet_url(self.sheet_url.as_ref().clone());
|
||||
.with_sheet_url(self.sheet_url.as_ref().clone())
|
||||
.with_media_text(StdArc::clone(&self.media_text));
|
||||
projection.adopted_client_id = self.adopted_client_id;
|
||||
projection
|
||||
}
|
||||
@@ -365,6 +396,14 @@ impl StyloStylesheetSource {
|
||||
self.adopted_client_id
|
||||
}
|
||||
|
||||
pub(in crate::style_engine) fn media_text(&self) -> &str {
|
||||
self.media_text.as_ref()
|
||||
}
|
||||
|
||||
pub(in crate::style_engine) fn has_authoritative_runtime_media(&self) -> bool {
|
||||
matches!(&self.contents, StyloStylesheetSourceContents::Live { .. })
|
||||
}
|
||||
|
||||
pub(in crate::style_engine) fn has_same_installation_identity(&self, other: &Self) -> bool {
|
||||
match (self.live_stylesheet_id(), other.live_stylesheet_id()) {
|
||||
(Some(left), Some(right)) => left == right,
|
||||
@@ -383,6 +422,7 @@ impl StyloStylesheetSource {
|
||||
&& self.sheet_url == other.sheet_url
|
||||
&& self.origin_clean == other.origin_clean
|
||||
&& self.cache_key == other.cache_key
|
||||
&& self.media_text == other.media_text
|
||||
&& match (&self.contents, &other.contents) {
|
||||
(
|
||||
StyloStylesheetSourceContents::Text { shared: left },
|
||||
@@ -431,6 +471,7 @@ impl PartialEq for StyloStylesheetSource {
|
||||
&& self.cache_key == other.cache_key
|
||||
&& self.source_id == other.source_id
|
||||
&& self.adopted_client_id == other.adopted_client_id
|
||||
&& self.media_text == other.media_text
|
||||
&& match (&self.contents, &other.contents) {
|
||||
(
|
||||
StyloStylesheetSourceContents::Text { shared: left },
|
||||
@@ -455,6 +496,7 @@ impl Hash for StyloStylesheetSource {
|
||||
self.cache_key.hash(state);
|
||||
self.source_id.hash(state);
|
||||
self.adopted_client_id.hash(state);
|
||||
self.media_text.hash(state);
|
||||
match &self.contents {
|
||||
StyloStylesheetSourceContents::Text { shared } => {
|
||||
0_u8.hash(state);
|
||||
@@ -544,6 +586,9 @@ pub(in crate::style_engine) fn stylesheet_sources_cache_key(
|
||||
hasher.update(sources.len().to_le_bytes());
|
||||
for source in sources {
|
||||
hasher.update(source.cache_key().fingerprint);
|
||||
let media_text = source.media_text().as_bytes();
|
||||
hasher.update(media_text.len().to_le_bytes());
|
||||
hasher.update(media_text);
|
||||
update_style_source_identity_hash(&mut hasher, source.source_id());
|
||||
match source.adopted_client_id() {
|
||||
Some(client_id) => {
|
||||
|
||||
@@ -74,6 +74,9 @@ pub(super) fn build_source_cascade_data(
|
||||
(previous_keys.get(&source_id) == Some(&key))
|
||||
.then(|| previous_data.get(&source_id).cloned())
|
||||
.flatten()
|
||||
.filter(|data| {
|
||||
source_cascade_matches_device(data, &stylesheets, stylist, shared_lock)
|
||||
})
|
||||
});
|
||||
let data = retained_data
|
||||
.unwrap_or_else(|| build_author_cascade_data(stylist, shared_lock, &stylesheets));
|
||||
@@ -91,35 +94,47 @@ pub(super) fn update_source_cascade_data_for_scopes(
|
||||
retained_source_records: &[RetainedStylesheetSourceRecord<'_>],
|
||||
dirty_source_ids: &HashSet<StyleSourceId>,
|
||||
dirty_scopes: &HashSet<StyleScopeId>,
|
||||
device_changed: bool,
|
||||
mut install: impl FnMut(&StyloStylesheetSource) -> ActiveStylesheet,
|
||||
) {
|
||||
if dirty_source_ids.is_empty() && dirty_scopes.is_empty() {
|
||||
if dirty_source_ids.is_empty() && dirty_scopes.is_empty() && !device_changed {
|
||||
return;
|
||||
}
|
||||
|
||||
let previous_data = std::mem::take(&mut retained.source_cascade_data);
|
||||
let previous_keys = std::mem::take(&mut retained.source_cascade_keys);
|
||||
let mut next_data = retain_clean_entries(&previous_data, dirty_source_ids, dirty_scopes);
|
||||
let mut next_keys = retain_clean_entries(&previous_keys, dirty_source_ids, dirty_scopes);
|
||||
let mut next_data = if device_changed {
|
||||
HashMap::new()
|
||||
} else {
|
||||
retain_clean_entries(&previous_data, dirty_source_ids, dirty_scopes)
|
||||
};
|
||||
let mut next_keys = if device_changed {
|
||||
HashMap::new()
|
||||
} else {
|
||||
retain_clean_entries(&previous_keys, dirty_source_ids, dirty_scopes)
|
||||
};
|
||||
let source_filter = (!device_changed).then_some((dirty_source_ids, dirty_scopes));
|
||||
|
||||
let mut sources_by_id = installed_sources_by_id(
|
||||
&retained.document_stylesheets,
|
||||
&retained.shadow_scopes,
|
||||
Some(dirty_source_ids),
|
||||
Some(dirty_scopes),
|
||||
source_filter.map(|(source_ids, _)| source_ids),
|
||||
source_filter.map(|(_, scopes)| scopes),
|
||||
);
|
||||
add_retained_source_records(
|
||||
&mut sources_by_id,
|
||||
retained_source_records,
|
||||
Some(dirty_source_ids),
|
||||
Some(dirty_scopes),
|
||||
source_filter.map(|(source_ids, _)| source_ids),
|
||||
source_filter.map(|(_, scopes)| scopes),
|
||||
&mut install,
|
||||
);
|
||||
|
||||
for (source_id, (sources, stylesheets)) in sources_by_id {
|
||||
let key = stylesheet_sources_cache_key(&sources);
|
||||
let data = if previous_keys.get(&source_id) == Some(&key) {
|
||||
previous_data.get(&source_id).cloned()
|
||||
previous_data.get(&source_id).cloned().filter(|data| {
|
||||
source_cascade_matches_device(data, &stylesheets, &retained.stylist, shared_lock)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -133,6 +148,23 @@ pub(super) fn update_source_cascade_data_for_scopes(
|
||||
retained.source_cascade_keys = next_keys;
|
||||
}
|
||||
|
||||
fn source_cascade_matches_device(
|
||||
data: &CascadeData,
|
||||
stylesheets: &[DocumentStyleSheet],
|
||||
stylist: &Stylist,
|
||||
shared_lock: &SharedRwLock,
|
||||
) -> bool {
|
||||
let guard = shared_lock.read();
|
||||
stylesheets.iter().all(|stylesheet| {
|
||||
data.media_feature_affected_matches(
|
||||
stylesheet,
|
||||
&guard,
|
||||
stylist.device(),
|
||||
stylist.quirks_mode(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
type SourceCascadeData = HashMap<StyleSourceId, ServoArc<CascadeData>>;
|
||||
type SourceCascadeKeys = HashMap<StyleSourceId, StyleSourceSetKey>;
|
||||
|
||||
|
||||
@@ -21,13 +21,20 @@ pub(crate) fn link_rel_qualifies_as_stylesheet(rel: Option<&str>, title: Option<
|
||||
pub(super) fn stylesheet_owner_is_stylesheet_source_enabled(
|
||||
host: &DomHost,
|
||||
handle: DomHandle,
|
||||
media_text: &str,
|
||||
emulated_media: &EmulatedMediaOverrides,
|
||||
viewport: StyleViewport,
|
||||
) -> bool {
|
||||
host.node(handle).is_some_and(|node| {
|
||||
node.as_element()
|
||||
.is_some_and(|element| element.is_inline_style_element())
|
||||
&& style_element_is_stylesheet_source_enabled(host, handle, emulated_media, viewport)
|
||||
&& style_element_is_stylesheet_source_enabled(
|
||||
host,
|
||||
handle,
|
||||
media_text,
|
||||
emulated_media,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -46,6 +53,7 @@ pub(super) fn stylesheet_source_base_url(host: &DomHost, handle: DomHandle) -> u
|
||||
fn style_element_is_stylesheet_source_enabled(
|
||||
host: &DomHost,
|
||||
handle: DomHandle,
|
||||
media_text: &str,
|
||||
emulated_media: &EmulatedMediaOverrides,
|
||||
viewport: StyleViewport,
|
||||
) -> bool {
|
||||
@@ -57,28 +65,23 @@ fn style_element_is_stylesheet_source_enabled(
|
||||
&& moli_web_mime::is_stylesheet_type_attribute(
|
||||
host.get_attribute(handle, "type").as_deref(),
|
||||
)
|
||||
&& stylesheet_media_matches_for_stylesheet_source(host, handle, emulated_media, viewport)
|
||||
&& stylesheet_media_matches_for_stylesheet_source(media_text, emulated_media, viewport)
|
||||
}
|
||||
|
||||
pub(super) fn linked_stylesheet_media_matches_for_stylesheet_source(
|
||||
host: &DomHost,
|
||||
handle: DomHandle,
|
||||
media_text: &str,
|
||||
emulated_media: &EmulatedMediaOverrides,
|
||||
viewport: StyleViewport,
|
||||
) -> bool {
|
||||
stylesheet_media_matches_for_stylesheet_source(host, handle, emulated_media, viewport)
|
||||
stylesheet_media_matches_for_stylesheet_source(media_text, emulated_media, viewport)
|
||||
}
|
||||
|
||||
fn stylesheet_media_matches_for_stylesheet_source(
|
||||
host: &DomHost,
|
||||
handle: DomHandle,
|
||||
media_text: &str,
|
||||
emulated_media: &EmulatedMediaOverrides,
|
||||
viewport: StyleViewport,
|
||||
) -> bool {
|
||||
let Some(media) = host.get_attribute(handle, "media") else {
|
||||
return true;
|
||||
};
|
||||
let media = media.trim();
|
||||
let media = media_text.trim();
|
||||
media.is_empty()
|
||||
|| evaluate_match_media_query_list_with_viewport(media, Some(emulated_media), viewport)
|
||||
}
|
||||
|
||||
@@ -122,16 +122,23 @@ impl<'a> RetainedStylesheetSourceRecord<'a> {
|
||||
}
|
||||
|
||||
fn dependency_summary(&self) -> Arc<StyloSourceDependencySummary> {
|
||||
self.source().source_dependency_summary()
|
||||
}
|
||||
|
||||
fn source(&self) -> &StyloStylesheetSource {
|
||||
match &self.source {
|
||||
RetainedStylesheetSourceRecordSource::OwnerStyleSheet(source) => {
|
||||
source.source_dependency_summary()
|
||||
}
|
||||
RetainedStylesheetSourceRecordSource::Registered(source) => {
|
||||
source.source_dependency_summary()
|
||||
}
|
||||
RetainedStylesheetSourceRecordSource::OwnerStyleSheet(source)
|
||||
| RetainedStylesheetSourceRecordSource::Registered(source) => source,
|
||||
}
|
||||
}
|
||||
|
||||
fn effective_media_text(&self, host: &DomHost, owner: DomHandle) -> String {
|
||||
if self.source().has_authoritative_runtime_media() {
|
||||
return self.source().media_text().to_owned();
|
||||
}
|
||||
host.get_attribute(owner, "media").unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(super) fn is_dependency_source_enabled(
|
||||
&self,
|
||||
host: &DomHost,
|
||||
@@ -140,12 +147,17 @@ impl<'a> RetainedStylesheetSourceRecord<'a> {
|
||||
) -> bool {
|
||||
match self.id.kind {
|
||||
StyleSourceKind::OwnerStyleSheet { owner } => {
|
||||
stylesheet_owner_is_stylesheet_source_enabled(host, owner, emulated_media, viewport)
|
||||
stylesheet_owner_is_stylesheet_source_enabled(
|
||||
host,
|
||||
owner,
|
||||
&self.effective_media_text(host, owner),
|
||||
emulated_media,
|
||||
viewport,
|
||||
)
|
||||
}
|
||||
StyleSourceKind::LinkedStyleSheet { owner } => {
|
||||
linked_stylesheet_media_matches_for_stylesheet_source(
|
||||
host,
|
||||
owner,
|
||||
&self.effective_media_text(host, owner),
|
||||
emulated_media,
|
||||
viewport,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ use style::{
|
||||
device::{Device, servo::FontMetricsProvider},
|
||||
font_face::FontFaceRule,
|
||||
font_metrics::FontMetrics,
|
||||
media_queries::MediaList,
|
||||
properties::{ComputedValues, style_structs::Font},
|
||||
servo::media_features::PointerCapabilities,
|
||||
servo_arc::Arc as ServoArc,
|
||||
@@ -36,6 +35,7 @@ use crate::{document_runtime::DomHandle, dom::native::DomHost};
|
||||
use super::{
|
||||
StyleViewport, StyloStyleEnvironment,
|
||||
active_stylesheets::{ActiveStylesheet, ActiveStylesheetCollection},
|
||||
media_list::parse_media_query_list_with_context,
|
||||
source::store::{StyleSourceMetadata, StyloStylesheetSource},
|
||||
source_id::{StyleSourceId, StyleSourceKind},
|
||||
ua::HTML_STYLESHEET as MOLI_UA_STYLESHEET,
|
||||
@@ -285,6 +285,7 @@ fn style_source_metadata_for_css_text_with_origin(
|
||||
css_text,
|
||||
origin,
|
||||
QuirksMode::NoQuirks,
|
||||
"",
|
||||
)));
|
||||
let guard = shared_lock.read();
|
||||
let stylist = new_stylist_with_viewport_bits(
|
||||
@@ -434,7 +435,7 @@ pub(super) fn append_stylesheet_to_stylist(
|
||||
origin: Origin,
|
||||
quirks_mode: QuirksMode,
|
||||
) {
|
||||
let stylesheet = parse_stylesheet(shared_lock, base_url, css_text, origin, quirks_mode);
|
||||
let stylesheet = parse_stylesheet(shared_lock, base_url, css_text, origin, quirks_mode, "");
|
||||
let guard = shared_lock.read();
|
||||
stylist.append_stylesheet(DocumentStyleSheet::new(ServoArc::new(stylesheet)), &guard);
|
||||
}
|
||||
@@ -458,6 +459,7 @@ fn author_stylesheet_for_source(
|
||||
css_text,
|
||||
Origin::Author,
|
||||
quirks_mode,
|
||||
source.media_text(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -467,8 +469,10 @@ fn parse_stylesheet(
|
||||
css_text: &str,
|
||||
origin: Origin,
|
||||
quirks_mode: QuirksMode,
|
||||
media_text: &str,
|
||||
) -> Stylesheet {
|
||||
let media = ServoArc::new(shared_lock.wrap(MediaList::empty()));
|
||||
let media = parse_media_query_list_with_context(media_text, base_url, quirks_mode);
|
||||
let media = ServoArc::new(shared_lock.wrap(media));
|
||||
Stylesheet::from_str(
|
||||
css_text,
|
||||
UrlExtraData::from(base_url.clone()),
|
||||
|
||||
@@ -5720,6 +5720,259 @@ fn one_shadow_stylesheet_change_preserves_the_other_scope_cascade_data() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn device_changes_keep_media_sheets_installed_and_flush_only_affected_tree_scopes() {
|
||||
reset_source_cascade_rebuild_count_for_test();
|
||||
let mut host = test_host();
|
||||
let document = host.document_handle();
|
||||
let document_target = host.create_element("div");
|
||||
assert!(host.set_attribute(document_target, "class", "document-target"));
|
||||
assert!(host.set_attribute(document_target, "style", "width: 50vw"));
|
||||
assert!(host.append_child(document, document_target));
|
||||
let first_host = host.create_element("section");
|
||||
let second_host = host.create_element("article");
|
||||
assert!(host.append_child(document, first_host));
|
||||
assert!(host.append_child(document, second_host));
|
||||
let first_root = host
|
||||
.attach_shadow_root(first_host, "open")
|
||||
.expect("first host should accept a shadow root");
|
||||
let second_root = host
|
||||
.attach_shadow_root(second_host, "open")
|
||||
.expect("second host should accept a shadow root");
|
||||
let first_target = host.create_element("span");
|
||||
let second_target = host.create_element("span");
|
||||
assert!(host.set_attribute(first_target, "class", "first-target"));
|
||||
assert!(host.set_attribute(first_target, "style", "height: 50vh"));
|
||||
assert!(host.set_attribute(second_target, "class", "second-target"));
|
||||
assert!(host.append_child(first_root, first_target));
|
||||
assert!(host.append_child(second_root, second_target));
|
||||
|
||||
let engine = MoliStyleEngine::new();
|
||||
let document_url = url::Url::parse("https://example.test/shadow-media.html").unwrap();
|
||||
let mut inputs = FullStyleWorldSnapshot::default();
|
||||
inputs.document_stylesheet_sources.push(
|
||||
StyloStylesheetSource::new(
|
||||
".document-target { color: rgb(7, 8, 9); }".into(),
|
||||
document_url.clone(),
|
||||
)
|
||||
.with_owner_media_text("(max-width: 600px)"),
|
||||
);
|
||||
inputs.shadow_stylesheet_sources.push((
|
||||
first_root,
|
||||
vec![
|
||||
StyloStylesheetSource::new(
|
||||
".first-target { color: rgb(1, 2, 3); }".into(),
|
||||
document_url.clone(),
|
||||
)
|
||||
.with_source_id(Some(StyleSourceId::shadow_root_adopted_style_sheet(
|
||||
first_root, 71,
|
||||
)))
|
||||
.with_owner_media_text("(max-width: 600px)"),
|
||||
],
|
||||
));
|
||||
inputs.shadow_stylesheet_sources.push((
|
||||
second_root,
|
||||
vec![
|
||||
StyloStylesheetSource::new(
|
||||
".second-target { color: rgb(4, 5, 6); }".into(),
|
||||
document_url.clone(),
|
||||
)
|
||||
.with_source_id(Some(StyleSourceId::shadow_root_adopted_style_sheet(
|
||||
second_root,
|
||||
72,
|
||||
)))
|
||||
.with_owner_media_text("(min-width: 100px)"),
|
||||
],
|
||||
));
|
||||
|
||||
let viewport_800 = StyleViewport::new(Some(800.0), Some(600.0));
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
document_target,
|
||||
"color",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_800,
|
||||
),
|
||||
Some("rgb(0, 0, 0)".into()),
|
||||
);
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
first_target,
|
||||
"color",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_800,
|
||||
),
|
||||
Some("rgb(0, 0, 0)".into()),
|
||||
);
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
second_target,
|
||||
"color",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_800,
|
||||
),
|
||||
Some("rgb(4, 5, 6)".into()),
|
||||
);
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
document_target,
|
||||
"width",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_800,
|
||||
),
|
||||
Some("400px".into()),
|
||||
);
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
first_target,
|
||||
"height",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_800,
|
||||
),
|
||||
Some("300px".into()),
|
||||
);
|
||||
engine.with_retained_style_system_for_document_for_test(document, |retained| {
|
||||
assert_eq!(
|
||||
retained.document_stylesheets.entries().len(),
|
||||
1,
|
||||
"a non-matching owner MediaList must not remove its sheet from the Document scope",
|
||||
);
|
||||
assert_eq!(retained.shadow_scopes.len(), 2);
|
||||
assert!(
|
||||
retained
|
||||
.shadow_scopes
|
||||
.iter()
|
||||
.all(|scope| scope.active_stylesheets().entries().len() == 1),
|
||||
"a non-matching owner MediaList must not remove its sheet from the TreeScope",
|
||||
);
|
||||
});
|
||||
let document_flushes = engine.retained_stylist_flush_count_for_document_for_test(document);
|
||||
let first_flushes = engine
|
||||
.retained_shadow_scope_flush_count_for_document_for_test(document, first_root)
|
||||
.expect("first scope should be retained");
|
||||
let second_flushes = engine
|
||||
.retained_shadow_scope_flush_count_for_document_for_test(document, second_root)
|
||||
.expect("second scope should be retained");
|
||||
let source_rebuilds = source_cascade_rebuild_count_for_test();
|
||||
|
||||
let viewport_700 = StyleViewport::new(Some(700.0), Some(500.0));
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
first_target,
|
||||
"color",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_700,
|
||||
),
|
||||
Some("rgb(0, 0, 0)".into()),
|
||||
);
|
||||
assert_eq!(
|
||||
engine.retained_shadow_scope_flush_count_for_document_for_test(document, first_root),
|
||||
Some(first_flushes),
|
||||
"a viewport change that crosses no media boundary must not flush the first scope",
|
||||
);
|
||||
assert_eq!(
|
||||
engine.retained_shadow_scope_flush_count_for_document_for_test(document, second_root),
|
||||
Some(second_flushes),
|
||||
"a viewport change that crosses no media boundary must not flush the second scope",
|
||||
);
|
||||
assert_eq!(source_cascade_rebuild_count_for_test(), source_rebuilds);
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
document_target,
|
||||
"width",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_700,
|
||||
),
|
||||
Some("350px".into()),
|
||||
"viewport units must be invalidated without a Document stylesheet flush",
|
||||
);
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
first_target,
|
||||
"height",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_700,
|
||||
),
|
||||
Some("250px".into()),
|
||||
"viewport units inside ShadowRoot must be invalidated without an AuthorStyles flush",
|
||||
);
|
||||
assert_eq!(
|
||||
engine.retained_stylist_flush_count_for_document_for_test(document),
|
||||
document_flushes,
|
||||
"a viewport change that crosses no media boundary must not flush the Document Stylist",
|
||||
);
|
||||
|
||||
let viewport_500 = StyleViewport::new(Some(500.0), Some(600.0));
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
document_target,
|
||||
"color",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_500,
|
||||
),
|
||||
Some("rgb(7, 8, 9)".into()),
|
||||
);
|
||||
assert_eq!(
|
||||
engine.computed_style_property_value(
|
||||
&host,
|
||||
&document_url,
|
||||
first_target,
|
||||
"color",
|
||||
None,
|
||||
&inputs,
|
||||
viewport_500,
|
||||
),
|
||||
Some("rgb(1, 2, 3)".into()),
|
||||
);
|
||||
assert_eq!(
|
||||
engine.retained_shadow_scope_flush_count_for_document_for_test(document, first_root),
|
||||
Some(first_flushes + 1),
|
||||
"only the scope whose top-level MediaList changed must flush",
|
||||
);
|
||||
assert_eq!(
|
||||
engine.retained_shadow_scope_flush_count_for_document_for_test(document, second_root),
|
||||
Some(second_flushes),
|
||||
"an unrelated matching MediaList must preserve its AuthorStyles",
|
||||
);
|
||||
assert_eq!(
|
||||
engine.retained_stylist_flush_count_for_document_for_test(document),
|
||||
document_flushes + 1,
|
||||
"the Document Stylist must flush once when its installed MediaList changes match state",
|
||||
);
|
||||
assert_eq!(
|
||||
source_cascade_rebuild_count_for_test(),
|
||||
source_rebuilds + 1,
|
||||
"only the affected scope's source-local cascade projection must rebuild",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_subtree_invalidation_clears_only_affected_shadow_cascade_data() {
|
||||
let mut host = test_host();
|
||||
|
||||
@@ -598,6 +598,59 @@ fn source_metadata_respects_style_element_media() {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_metadata_uses_the_stylesheet_runtime_media_list() {
|
||||
use style::{context::QuirksMode, stylesheets::AllowImportRules};
|
||||
|
||||
let mut host = test_host();
|
||||
let style = host.create_element("style");
|
||||
assert!(host.set_attribute(style, "media", "print"));
|
||||
assert!(host.append_child(host.document_handle(), style));
|
||||
|
||||
let mut engine = MoliStyleEngine::new();
|
||||
engine.set_owner_style_sheet_text_with_host(
|
||||
&host,
|
||||
style,
|
||||
"body:has(.marker) .target { color: green; }".into(),
|
||||
);
|
||||
let registry = crate::live_stylesheet::LiveStylesheetRegistry::default();
|
||||
let stylesheet = registry.create(
|
||||
"body:has(.marker) .target { color: green; }",
|
||||
url::Url::parse("https://example.test/runtime-media.css").unwrap(),
|
||||
QuirksMode::NoQuirks,
|
||||
AllowImportRules::No,
|
||||
engine.author_shared_lock(),
|
||||
);
|
||||
stylesheet.set_media_text("screen");
|
||||
assert!(engine.install_owner_live_stylesheet_with_host(&host, style, stylesheet.clone()));
|
||||
|
||||
let media = crate::protocol_types::EmulatedMediaOverrides::default();
|
||||
let source_scope = StyleSourceScope::for_document(host.document_handle());
|
||||
assert_eq!(host.get_attribute(style, "media").as_deref(), Some("print"));
|
||||
assert!(
|
||||
engine.test_author_sources_have_relative_selector_dependency_for_document(
|
||||
&host,
|
||||
host.document_handle(),
|
||||
&source_scope,
|
||||
&media,
|
||||
),
|
||||
"CSSOM runtime media must be authoritative even when the owner attribute differs",
|
||||
);
|
||||
|
||||
stylesheet.set_media_text("print");
|
||||
assert!(engine.refresh_owner_live_stylesheet_with_host(&host, style, stylesheet.id(),));
|
||||
assert!(
|
||||
!engine.test_author_sources_have_relative_selector_dependency_for_document(
|
||||
&host,
|
||||
host.document_handle(),
|
||||
&source_scope,
|
||||
&media,
|
||||
),
|
||||
"an inactive runtime MediaList must not participate in dependency invalidation",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stylo_source_dependency_summary_reads_invalidation_metadata() {
|
||||
let base_url = url::Url::parse("https://example.test/app.css").unwrap();
|
||||
|
||||
@@ -6,8 +6,7 @@ use moli_selector::StyloDomStyleAdapter;
|
||||
use style::context::QuirksMode;
|
||||
|
||||
use super::{
|
||||
FullStyleWorldSnapshot, StyleTreeScopeVersions, StyleViewport, StyleWorldEnvironment,
|
||||
StyloStyleEnvironment,
|
||||
FullStyleWorldSnapshot, StyleTreeScopeVersions, StyleWorldEnvironment,
|
||||
cleanup::StyleCacheCleanup,
|
||||
retained::{
|
||||
RetainedStyleInvalidations, build_retained_style_system,
|
||||
@@ -28,21 +27,16 @@ pub(super) fn retained_style_world_update_plan(
|
||||
host: &DomHost,
|
||||
document_state: &StyleDocumentState,
|
||||
document: DomHandle,
|
||||
viewport: StyleViewport,
|
||||
environment: StyloStyleEnvironment,
|
||||
quirks_mode: QuirksMode,
|
||||
tree_scope_versions: StyleTreeScopeVersions,
|
||||
) -> StyleWorldUpdatePlan {
|
||||
let source_dirty_scope = document_state.source_dirty_scope_snapshot();
|
||||
let Some((replace_world, device_changed, tree_scopes_changed, retained_shadow_roots)) =
|
||||
document_state.try_with_retained_style_system(|retained| {
|
||||
let Some((replace_world, tree_scopes_changed, retained_shadow_roots)) = document_state
|
||||
.try_with_retained_style_system(|retained| {
|
||||
(
|
||||
retained
|
||||
.key
|
||||
.requires_replacement_for_observation(quirks_mode),
|
||||
retained
|
||||
.key
|
||||
.device_differs_from_observation(viewport, environment),
|
||||
retained.key.tree_scope_versions != Some(tree_scope_versions),
|
||||
retained
|
||||
.shadow_scopes
|
||||
@@ -77,22 +71,8 @@ pub(super) fn retained_style_world_update_plan(
|
||||
);
|
||||
}
|
||||
|
||||
// The bridge currently models <style media> and <link media> by active-list
|
||||
// membership instead of installing their MediaList on the Stylo sheet.
|
||||
// A device change therefore has to refresh those ordered lists. This is
|
||||
// still scope-local and parse-free for unchanged installations.
|
||||
if device_changed {
|
||||
shadow_stylesheet_roots.extend(
|
||||
connected_shadow_roots
|
||||
.as_deref()
|
||||
.unwrap_or(&retained_shadow_roots)
|
||||
.iter()
|
||||
.copied(),
|
||||
);
|
||||
}
|
||||
|
||||
StyleWorldUpdatePlan::Incremental(IncrementalStyleWorldUpdatePlan::new(
|
||||
source_dirty_scope.refreshes_document_stylesheets(document) || device_changed,
|
||||
source_dirty_scope.refreshes_document_stylesheets(document),
|
||||
shadow_stylesheet_roots.into_iter().collect(),
|
||||
connected_shadow_roots,
|
||||
source_dirty_scope.refreshes_custom_property_registrations(document),
|
||||
@@ -319,6 +299,7 @@ fn apply_retained_stylesheet_invalidations(
|
||||
shadow_scopes,
|
||||
shadow_scope_fallbacks,
|
||||
removed_shadow_scopes,
|
||||
viewport_size_changed,
|
||||
} = invalidations;
|
||||
if let Some(invalidations) = document_invalidations {
|
||||
// HTML documents normally have one document element. The native DOM
|
||||
@@ -352,6 +333,9 @@ fn apply_retained_stylesheet_invalidations(
|
||||
}
|
||||
dom_adapter.process_stylesheet_invalidations(host, &roots, &invalidations);
|
||||
}
|
||||
if viewport_size_changed {
|
||||
invalidate_viewport_unit_styles(host, dom_adapter, document);
|
||||
}
|
||||
cache_cleanup.invalidate_stylesheet_dirty_subtrees(host);
|
||||
if document_scope_fallback {
|
||||
cache_cleanup.invalidate_subtrees(host, [document]);
|
||||
@@ -363,3 +347,34 @@ fn apply_retained_stylesheet_invalidations(
|
||||
}
|
||||
cache_cleanup.invalidate_subtrees(host, removed_shadow_scopes);
|
||||
}
|
||||
|
||||
fn invalidate_viewport_unit_styles(
|
||||
host: &DomHost,
|
||||
dom_adapter: &StyloDomStyleAdapter,
|
||||
document: DomHandle,
|
||||
) {
|
||||
let mut roots = host
|
||||
.child_handles(document)
|
||||
.filter(|handle| {
|
||||
host.node(*handle)
|
||||
.is_some_and(|node| node.as_element().is_some())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for shadow_root in host
|
||||
.snapshot_connected_shadow_roots()
|
||||
.into_iter()
|
||||
.filter(|root| host.owner_document_handle(*root) == Some(document))
|
||||
{
|
||||
roots.extend(host.child_handles(shadow_root).filter(|handle| {
|
||||
host.node(*handle)
|
||||
.is_some_and(|node| node.as_element().is_some())
|
||||
}));
|
||||
}
|
||||
dom_adapter.with_bound_host(host, |binding| {
|
||||
for root in roots {
|
||||
if let Some(root) = binding.element(host, root) {
|
||||
style::invalidation::viewport_units::invalidate(root);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user