commit d8ba646c79c834a08b2084958c7db3e38bb096bf
parent aeaaf3b6f595b15cc537a5cb9f92cb431019cbee
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Mon, 5 Jan 2026 19:29:51 +0000
Bug 1910616 - Invalidate position-try rules on changes/insertion/mutations. r=firefox-style-system-reviewers,layout-anchor-positioning-reviewers,dshin
This introduces infrastructure to invalidate stuff based on CascadeData
mutations.
For now we only use it for `@position-try`, in the future it might be
worth expanding it to `@keyframes` and even moving our existing selector
invalidation infrastructure there, so that we can more accurately
invalidate.
Differential Revision: https://phabricator.services.mozilla.com/D277424
Diffstat:
11 files changed, 271 insertions(+), 139 deletions(-)
diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp
@@ -303,6 +303,15 @@ bool Gecko_AnimationNameMayBeReferencedFromStyle(
return aPresContext->AnimationManager()->AnimationMayBeReferenced(aName);
}
+void Gecko_InvalidatePositionTry(const Element* aElement) {
+ auto* f = aElement->GetPrimaryFrame();
+ if (!f || !f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ return;
+ }
+ f->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback());
+ f->PresShell()->MarkPositionedFrameForReflow(f);
+}
+
float Gecko_GetScrollbarInlineSize(const nsPresContext* aPc) {
MOZ_ASSERT(aPc);
auto overlay = aPc->UseOverlayScrollbars() ? nsITheme::Overlay::Yes
diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h
@@ -341,6 +341,7 @@ void Gecko_CopyListStyleImageFrom(nsStyleList* dest, const nsStyleList* src);
void Gecko_NoteDirtyElement(const mozilla::dom::Element*);
void Gecko_NoteDirtySubtreeForInvalidation(const mozilla::dom::Element*);
void Gecko_NoteAnimationOnlyDirtyElement(const mozilla::dom::Element*);
+void Gecko_InvalidatePositionTry(const mozilla::dom::Element*);
bool Gecko_AnimationNameMayBeReferencedFromStyle(const nsPresContext*,
nsAtom* name);
diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp
@@ -1359,24 +1359,22 @@ void ServoStyleSet::UpdateStylist() {
AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Update stylesheet information", LAYOUT);
MOZ_ASSERT(StylistNeedsUpdate());
- if (mStylistState & StylistState::StyleSheetsDirty) {
- Element* root = mDocument->GetRootElement();
- const ServoElementSnapshotTable* snapshots = nullptr;
- if (nsPresContext* pc = GetPresContext()) {
- snapshots = &pc->RestyleManager()->Snapshots();
- }
- Servo_StyleSet_FlushStyleSheets(mRawData.get(), root, snapshots);
+ AutoTArray<StyleAuthorStyles*, 20> nonDocumentStyles;
+ Element* root = mDocument->GetRootElement();
+ const ServoElementSnapshotTable* snapshots = nullptr;
+ if (nsPresContext* pc = GetPresContext()) {
+ snapshots = &pc->RestyleManager()->Snapshots();
}
if (MOZ_UNLIKELY(mStylistState & StylistState::ShadowDOMStyleSheetsDirty)) {
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
- Servo_AuthorStyles_Flush(authorStyles, mRawData.get());
+ nonDocumentStyles.AppendElement(authorStyles);
}
});
- Servo_StyleSet_RemoveUniqueEntriesFromAuthorStylesCache(mRawData.get());
}
-
+ Servo_StyleSet_FlushStyleSheets(mRawData.get(), root, snapshots,
+ &nonDocumentStyles);
mStylistState = StylistState::NotDirty;
}
diff --git a/servo/components/style/author_styles.rs b/servo/components/style/author_styles.rs
@@ -11,6 +11,7 @@ use crate::shared_lock::SharedRwLockReadGuard;
use crate::stylesheet_set::AuthorStylesheetSet;
use crate::stylesheets::StylesheetInDocument;
use crate::stylist::CascadeData;
+use crate::stylist::CascadeDataDifference;
use crate::stylist::Stylist;
use servo_arc::Arc;
use std::sync::LazyLock;
@@ -53,7 +54,11 @@ where
/// TODO(emilio): Need a host element and a snapshot map to do invalidation
/// properly.
#[inline]
- pub fn flush<E>(&mut self, stylist: &mut Stylist, guard: &SharedRwLockReadGuard)
+ pub fn flush<E>(
+ &mut self,
+ stylist: &mut Stylist,
+ guard: &SharedRwLockReadGuard,
+ ) -> CascadeDataDifference
where
E: TElement,
{
@@ -61,9 +66,12 @@ where
.stylesheets
.flush::<E>(/* host = */ None, /* snapshot_map = */ None);
- let result = stylist.rebuild_author_data(&self.data, flusher.sheets, guard);
+ let mut difference = CascadeDataDifference::default();
+ let result =
+ stylist.rebuild_author_data(&self.data, flusher.sheets, guard, &mut difference);
if let Ok(Some(new_data)) = result {
self.data = new_data;
}
+ difference
}
}
diff --git a/servo/components/style/gecko/data.rs b/servo/components/style/gecko/data.rs
@@ -16,7 +16,7 @@ use crate::selector_parser::SnapshotMap;
use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards};
use crate::stylesheets::scope_rule::ImplicitScopeRoot;
use crate::stylesheets::{StylesheetContents, StylesheetInDocument};
-use crate::stylist::Stylist;
+use crate::stylist::{DocumentFlushResult, Stylist};
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use malloc_size_of::MallocSizeOfOps;
use selectors::Element;
@@ -203,7 +203,7 @@ impl PerDocumentStyleDataImpl {
guard: &SharedRwLockReadGuard,
document_element: Option<E>,
snapshots: Option<&SnapshotMap>,
- ) -> bool
+ ) -> DocumentFlushResult
where
E: TElement,
{
diff --git a/servo/components/style/invalidation/stylesheets.rs b/servo/components/style/invalidation/stylesheets.rs
@@ -8,17 +8,21 @@
#![deny(unsafe_code)]
use crate::context::QuirksMode;
+use crate::data::ElementData;
use crate::derives::*;
use crate::dom::{TDocument, TElement, TNode};
use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
use crate::invalidation::element::restyle_hints::RestyleHint;
use crate::media_queries::Device;
+use crate::selector_map::PrecomputedHashSet;
use crate::selector_parser::{SelectorImpl, Snapshot, SnapshotMap};
use crate::shared_lock::SharedRwLockReadGuard;
use crate::simple_buckets_map::SimpleBucketsMap;
use crate::stylesheets::{CssRule, CssRuleRef, CustomMediaMap, StylesheetInDocument};
use crate::stylesheets::{EffectiveRules, EffectiveRulesIterator};
+use crate::values::specified::position::PositionTryFallbacksItem;
use crate::values::AtomIdent;
+use crate::Atom;
use crate::LocalName as SelectorLocalName;
use selectors::parser::{Component, LocalName, Selector};
@@ -377,8 +381,7 @@ impl StylesheetInvalidationSet {
data.hint.contains(RestyleHint::RESTYLE_SELF) || any_children_invalid
}
- /// TODO(emilio): Reuse the bucket stuff from selectormap? That handles
- /// :is() / :where() etc.
+ /// TODO(emilio): Reuse the bucket stuff from selectormap? That handles :is() / :where() etc.
fn scan_component(
component: &Component<SelectorImpl>,
invalidation: &mut Option<Invalidation>,
@@ -684,9 +687,8 @@ impl StylesheetInvalidationSet {
self.invalidate_fully();
},
PositionTry(..) => {
- // Potential change in sizes/positions of anchored elements. TODO(dshin, bug 1910616):
- // We should probably make an effort to see if this position-try is referenced.
- self.invalidate_fully();
+ // @position-try changes doesn't change style-time information (only layout
+ // information) and is handled by invalidate_position_try. So do nothing.
},
CustomMedia(..) => {
// @custom-media might be referenced by other rules which we can't get a hand on in
@@ -698,3 +700,52 @@ impl StylesheetInvalidationSet {
}
}
}
+
+/// Invalidates for any absolutely positioned element that references the given @position-try fallback names.
+pub fn invalidate_position_try<E>(
+ element: E,
+ changed_names: &PrecomputedHashSet<Atom>,
+ invalidate_self: &mut impl FnMut(E, &mut ElementData),
+ invalidated_descendants: &mut impl FnMut(E),
+) -> bool
+where
+ E: TElement,
+{
+ debug_assert!(
+ !changed_names.is_empty(),
+ "Don't call me if there's nothing to do"
+ );
+ let mut data = match element.mutate_data() {
+ Some(data) => data,
+ None => return false,
+ };
+
+ let mut self_invalid = false;
+ let style = data.styles.primary();
+ if style.clone_position().is_absolutely_positioned() {
+ let fallbacks = style.clone_position_try_fallbacks();
+ let referenced = fallbacks.0.iter().any(|f| match f {
+ PositionTryFallbacksItem::IdentAndOrTactic(ident_or_tactic) => {
+ changed_names.contains(&ident_or_tactic.ident.0)
+ },
+ PositionTryFallbacksItem::PositionArea(..) => false,
+ });
+
+ if referenced {
+ self_invalid = true;
+ invalidate_self(element, &mut data);
+ }
+ }
+ let mut any_children_invalid = false;
+ for child in element.traversal_children() {
+ let Some(e) = child.as_element() else {
+ continue;
+ };
+ any_children_invalid |=
+ invalidate_position_try(e, changed_names, invalidate_self, invalidated_descendants);
+ }
+ if any_children_invalid {
+ invalidated_descendants(element);
+ }
+ self_invalid || any_children_invalid
+}
diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs
@@ -85,8 +85,8 @@ use servo_arc::{Arc, ArcBorrow, ThinArc};
use smallvec::SmallVec;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
+use std::mem;
use std::sync::{LazyLock, Mutex};
-use std::{mem, ops};
/// The type of the stylesheets that the stylist contains.
#[cfg(feature = "servo")]
@@ -117,6 +117,56 @@ impl Hash for StylesheetContentsPtr {
type StyleSheetContentList = Vec<StylesheetContentsPtr>;
+/// The result of a flush of document stylesheets.
+#[derive(Default)]
+pub struct DocumentFlushResult {
+ /// The CascadeDataDifference for this flush.
+ pub difference: CascadeDataDifference,
+ /// Whether we had any style invalidations as a result of the flush.
+ pub had_invalidations: bool,
+}
+
+/// The @position-try rules that have changed.
+#[derive(Default)]
+pub struct CascadeDataDifference {
+ /// The set of changed @position-try rule names.
+ pub changed_position_try_names: PrecomputedHashSet<Atom>,
+}
+
+impl CascadeDataDifference {
+ /// Merges another difference into `self`.
+ pub fn merge_with(&mut self, other: Self) {
+ self.changed_position_try_names
+ .extend(other.changed_position_try_names.into_iter())
+ }
+
+ fn update(&mut self, old_data: &PositionTryMap, new_data: &PositionTryMap) {
+ let mut any_different_key = false;
+ let different_len = old_data.len() != new_data.len();
+ for (name, rules) in old_data.iter() {
+ let changed = match new_data.get(name) {
+ Some(new_rule) => !Arc::ptr_eq(&rules.last().unwrap().0, new_rule),
+ None => {
+ any_different_key = true;
+ true
+ },
+ };
+ if changed {
+ self.changed_position_try_names.insert(name.clone());
+ }
+ }
+
+ if any_different_key || different_len {
+ for name in new_data.keys() {
+ // If the key exists in both, we've already checked it above.
+ if !old_data.contains_key(name) {
+ self.changed_position_try_names.insert(name.clone());
+ }
+ }
+ }
+ }
+}
+
/// A key in the cascade data cache.
#[derive(Debug, Hash, Default, PartialEq, Eq)]
struct CascadeDataCacheKey {
@@ -136,6 +186,7 @@ trait CascadeDataCacheEntry: Sized {
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
old_entry: &Self,
+ difference: &mut CascadeDataDifference,
) -> Result<Arc<Self>, AllocErr>
where
S: StylesheetInDocument + PartialEq + 'static;
@@ -173,6 +224,7 @@ where
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
old_entry: &Entry,
+ difference: &mut CascadeDataDifference,
) -> Result<Option<Arc<Entry>>, AllocErr>
where
S: StylesheetInDocument + PartialEq + 'static,
@@ -201,7 +253,14 @@ where
match self.entries.entry(key) {
HashMapEntry::Vacant(e) => {
debug!("> Picking the slow path (not in the cache)");
- new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?;
+ new_entry = Entry::rebuild(
+ device,
+ quirks_mode,
+ collection,
+ guard,
+ old_entry,
+ difference,
+ )?;
e.insert(new_entry.clone());
},
HashMapEntry::Occupied(mut e) => {
@@ -222,7 +281,14 @@ where
}
debug!("> Picking the slow path due to same entry as old");
- new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?;
+ new_entry = Entry::rebuild(
+ device,
+ quirks_mode,
+ collection,
+ guard,
+ old_entry,
+ difference,
+ )?;
e.insert(new_entry.clone());
},
}
@@ -287,20 +353,21 @@ impl CascadeDataCacheEntry for UserAgentCascadeData {
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
- _old: &Self,
+ old: &Self,
+ difference: &mut CascadeDataDifference,
) -> Result<Arc<Self>, AllocErr>
where
S: StylesheetInDocument + PartialEq + 'static,
{
- // TODO: Maybe we should support incremental rebuilds, though they seem
- // uncommon and rebuild() doesn't deal with
- // precomputed_pseudo_element_decls for now so...
- let mut new_data = Self {
+ // TODO: Maybe we should support incremental rebuilds, though they seem uncommon and
+ // rebuild() doesn't deal with precomputed_pseudo_element_decls for now so...
+ let mut new_data = servo_arc::UniqueArc::new(Self {
cascade_data: CascadeData::new(),
precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations::default(),
- };
+ });
for (index, sheet) in collection.sheets().enumerate() {
+ let new_data = &mut *new_data;
new_data.cascade_data.add_stylesheet(
device,
quirks_mode,
@@ -309,12 +376,17 @@ impl CascadeDataCacheEntry for UserAgentCascadeData {
guard,
SheetRebuildKind::Full,
Some(&mut new_data.precomputed_pseudo_element_decls),
+ None,
)?;
}
new_data.cascade_data.did_finish_rebuild();
+ difference.update(
+ &old.cascade_data.extra_data.position_try_rules,
+ &new_data.cascade_data.extra_data.position_try_rules,
+ );
- Ok(Arc::new(new_data))
+ Ok(new_data.shareable())
}
#[cfg(feature = "gecko")]
@@ -428,11 +500,12 @@ impl DocumentCascadeData {
quirks_mode: QuirksMode,
mut flusher: DocumentStylesheetFlusher<'a, S>,
guards: &StylesheetGuards,
- ) -> Result<(), AllocErr>
+ ) -> Result<CascadeDataDifference, AllocErr>
where
S: StylesheetInDocument + PartialEq + 'static,
{
// First do UA sheets.
+ let mut difference = CascadeDataDifference::default();
{
let origin_flusher = flusher.flush_origin(Origin::UserAgent);
// Dirty check is just a minor optimization (no need to grab the
@@ -445,13 +518,13 @@ impl DocumentCascadeData {
origin_flusher,
guards.ua_or_user,
&self.user_agent,
+ &mut difference,
)?;
if let Some(new_data) = new_data {
self.user_agent = new_data;
}
let _unused_entries = ua_cache.take_unused();
- // See the comments in take_unused() as for why the following
- // line.
+ // See the comments in take_unused() as for why the following line.
std::mem::drop(ua_cache);
}
}
@@ -462,6 +535,7 @@ impl DocumentCascadeData {
quirks_mode,
flusher.flush_origin(Origin::User),
guards.ua_or_user,
+ &mut difference,
)?;
// And now the author sheets.
@@ -470,9 +544,10 @@ impl DocumentCascadeData {
quirks_mode,
flusher.flush_origin(Origin::Author),
guards.author,
+ &mut difference,
)?;
- Ok(())
+ Ok(difference)
}
/// Measures heap usage.
@@ -496,6 +571,7 @@ pub enum AuthorStylesEnabled {
/// A wrapper over a DocumentStylesheetSet that can be `Sync`, since it's only
/// used and exposed via mutable methods in the `Stylist`.
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+#[derive(Deref, DerefMut)]
struct StylistStylesheetSet(DocumentStylesheetSet<StylistSheet>);
// Read above to see why this is fine.
unsafe impl Sync for StylistStylesheetSet {}
@@ -506,20 +582,6 @@ impl StylistStylesheetSet {
}
}
-impl ops::Deref for StylistStylesheetSet {
- type Target = DocumentStylesheetSet<StylistSheet>;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl ops::DerefMut for StylistStylesheetSet {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.0
- }
-}
-
/// This structure holds all the selectors and device characteristics
/// for a given document. The selectors are converted into `Rule`s
/// and sorted into `SelectorMap`s keyed off stylesheet origin and
@@ -902,12 +964,19 @@ impl Stylist {
old_data: &CascadeData,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
+ difference: &mut CascadeDataDifference,
) -> Result<Option<Arc<CascadeData>>, AllocErr>
where
S: StylesheetInDocument + PartialEq + 'static,
{
- self.author_data_cache
- .lookup(&self.device, self.quirks_mode, collection, guard, old_data)
+ self.author_data_cache.lookup(
+ &self.device,
+ self.quirks_mode,
+ collection,
+ guard,
+ old_data,
+ difference,
+ )
}
/// Iterate over the extra data in origin order.
@@ -976,12 +1045,12 @@ impl Stylist {
guards: &StylesheetGuards,
document_element: Option<E>,
snapshots: Option<&SnapshotMap>,
- ) -> bool
+ ) -> DocumentFlushResult
where
E: TElement,
{
if !self.stylesheets.has_changed() {
- return false;
+ return DocumentFlushResult::default();
}
self.num_rebuilds += 1;
@@ -990,13 +1059,20 @@ impl Stylist {
let had_invalidations = flusher.had_invalidations();
- self.cascade_data
+ let difference = self
+ .cascade_data
.rebuild(&self.device, self.quirks_mode, flusher, guards)
- .unwrap_or_else(|_| warn!("OOM in Stylist::flush"));
+ .unwrap_or_else(|_| {
+ warn!("OOM in Stylist::flush");
+ CascadeDataDifference::default()
+ });
self.rebuild_initial_values_for_custom_properties();
- had_invalidations
+ DocumentFlushResult {
+ difference,
+ had_invalidations,
+ }
}
/// Marks a given stylesheet origin as dirty, due to, for example, changes
@@ -2063,7 +2139,6 @@ pub struct PageRuleMap {
pub rules: PrecomputedHashMap<Atom, SmallVec<[PageRuleData; 1]>>,
}
-#[cfg(feature = "gecko")]
impl PageRuleMap {
#[inline]
fn clear(&mut self) {
@@ -2145,37 +2220,31 @@ impl MallocShallowSizeOf for PageRuleMap {
}
}
-/// This struct holds data which users of Stylist may want to extract
-/// from stylesheets which can be done at the same time as updating.
+type PositionTryMap = LayerOrderedMap<Arc<Locked<PositionTryRule>>>;
+
+/// This struct holds data which users of Stylist may want to extract from stylesheets which can be
+/// done at the same time as updating.
#[derive(Clone, Debug, Default)]
-#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct ExtraStyleData {
/// A list of effective font-face rules and their origin.
- #[cfg(feature = "gecko")]
pub font_faces: LayerOrderedVec<Arc<Locked<FontFaceRule>>>,
/// A list of effective font-feature-values rules.
- #[cfg(feature = "gecko")]
pub font_feature_values: LayerOrderedVec<Arc<FontFeatureValuesRule>>,
/// A list of effective font-palette-values rules.
- #[cfg(feature = "gecko")]
pub font_palette_values: LayerOrderedVec<Arc<FontPaletteValuesRule>>,
/// A map of effective counter-style rules.
- #[cfg(feature = "gecko")]
pub counter_styles: LayerOrderedMap<Arc<Locked<CounterStyleRule>>>,
/// A map of effective @position-try rules.
- #[cfg(feature = "gecko")]
- pub position_try_rules: LayerOrderedMap<Arc<Locked<PositionTryRule>>>,
+ pub position_try_rules: PositionTryMap,
/// A map of effective page rules.
- #[cfg(feature = "gecko")]
pub pages: PageRuleMap,
}
-#[cfg(feature = "gecko")]
impl ExtraStyleData {
/// Add the given @font-face rule.
fn add_font_face(&mut self, rule: &Arc<Locked<FontFaceRule>>, layer: LayerId) {
@@ -2206,13 +2275,11 @@ impl ExtraStyleData {
/// Add the given @position-try rule.
fn add_position_try(
&mut self,
- guard: &SharedRwLockReadGuard,
- rule: &Arc<Locked<PositionTryRule>>,
+ name: Atom,
+ rule: Arc<Locked<PositionTryRule>>,
layer: LayerId,
) -> Result<(), AllocErr> {
- let name = rule.read_with(guard).name.0.clone();
- self.position_try_rules
- .try_insert(name, rule.clone(), layer)
+ self.position_try_rules.try_insert(name, rule, layer)
}
/// Add the given @page rule.
@@ -2281,7 +2348,6 @@ impl<'a> Iterator for ExtraStyleDataIterator<'a> {
}
}
-#[cfg(feature = "gecko")]
impl MallocSizeOf for ExtraStyleData {
/// Measure heap usage.
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
@@ -3241,14 +3307,14 @@ impl CascadeData {
}
}
- /// Rebuild the cascade data from a given SheetCollection, incrementally if
- /// possible.
+ /// Rebuild the cascade data from a given SheetCollection, incrementally if possible.
pub fn rebuild<'a, S>(
&mut self,
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
+ difference: &mut CascadeDataDifference,
) -> Result<(), AllocErr>
where
S: StylesheetInDocument + PartialEq + 'static,
@@ -3259,10 +3325,13 @@ impl CascadeData {
let validity = collection.data_validity();
- match validity {
- DataValidity::Valid => {},
- DataValidity::CascadeInvalid => self.clear_cascade_data(),
- DataValidity::FullyInvalid => self.clear(),
+ let mut old_position_try_data = LayerOrderedMap::default();
+ if validity != DataValidity::Valid {
+ old_position_try_data = std::mem::take(&mut self.extra_data.position_try_rules);
+ self.clear_cascade_data();
+ if validity == DataValidity::FullyInvalid {
+ self.clear_invalidation_data();
+ }
}
let mut result = Ok(());
@@ -3276,12 +3345,23 @@ impl CascadeData {
guard,
rebuild_kind,
/* precomputed_pseudo_element_decls = */ None,
+ if validity == DataValidity::Valid {
+ Some(difference)
+ } else {
+ None
+ },
);
result.is_ok()
});
self.did_finish_rebuild();
+ // For DataValidity::Valid, we pass the difference down to `add_stylesheet` so that we
+ // populate it with new data. Otherwise we need to diff with the old data.
+ if validity != DataValidity::Valid {
+ difference.update(&old_position_try_data, &self.extra_data.position_try_rules);
+ }
+
result
}
@@ -3798,6 +3878,7 @@ impl CascadeData {
rebuild_kind: SheetRebuildKind,
containing_rule_state: &mut ContainingRuleState,
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
+ mut difference: Option<&mut CascadeDataDifference>,
) -> Result<(), AllocErr>
where
S: StylesheetInDocument + 'static,
@@ -3906,7 +3987,6 @@ impl CascadeData {
containing_rule_state.layer_id,
)?;
},
- #[cfg(feature = "gecko")]
CssRule::FontFace(ref rule) => {
// NOTE(emilio): We don't care about container_condition_id
// because:
@@ -3921,17 +4001,14 @@ impl CascadeData {
self.extra_data
.add_font_face(rule, containing_rule_state.layer_id);
},
- #[cfg(feature = "gecko")]
CssRule::FontFeatureValues(ref rule) => {
self.extra_data
.add_font_feature_values(rule, containing_rule_state.layer_id);
},
- #[cfg(feature = "gecko")]
CssRule::FontPaletteValues(ref rule) => {
self.extra_data
.add_font_palette_values(rule, containing_rule_state.layer_id);
},
- #[cfg(feature = "gecko")]
CssRule::CounterStyle(ref rule) => {
self.extra_data.add_counter_style(
guard,
@@ -3939,15 +4016,17 @@ impl CascadeData {
containing_rule_state.layer_id,
)?;
},
- #[cfg(feature = "gecko")]
CssRule::PositionTry(ref rule) => {
+ let name = rule.read_with(guard).name.0.clone();
+ if let Some(ref mut difference) = difference {
+ difference.changed_position_try_names.insert(name.clone());
+ }
self.extra_data.add_position_try(
- guard,
- rule,
+ name,
+ rule.clone(),
containing_rule_state.layer_id,
)?;
},
- #[cfg(feature = "gecko")]
CssRule::Page(ref rule) => {
self.extra_data
.add_page(guard, rule, containing_rule_state.layer_id)?;
@@ -4195,6 +4274,7 @@ impl CascadeData {
rebuild_kind,
containing_rule_state,
precomputed_pseudo_element_decls.as_deref_mut(),
+ difference.as_deref_mut(),
)?;
}
@@ -4252,6 +4332,7 @@ impl CascadeData {
guard: &SharedRwLockReadGuard,
rebuild_kind: SheetRebuildKind,
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
+ mut difference: Option<&mut CascadeDataDifference>,
) -> Result<(), AllocErr>
where
S: StylesheetInDocument + 'static,
@@ -4280,6 +4361,7 @@ impl CascadeData {
rebuild_kind,
&mut state,
precomputed_pseudo_element_decls.as_deref_mut(),
+ difference.as_deref_mut(),
)?;
Ok(())
@@ -4473,8 +4555,7 @@ impl CascadeData {
self.num_declarations = 0;
}
- fn clear(&mut self) {
- self.clear_cascade_data();
+ fn clear_invalidation_data(&mut self) {
self.invalidation_map.clear();
self.relative_selector_invalidation_map.clear();
self.additional_relative_selector_invalidation_map.clear();
@@ -4584,6 +4665,7 @@ impl CascadeDataCacheEntry for CascadeData {
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
old: &Self,
+ difference: &mut CascadeDataDifference,
) -> Result<Arc<Self>, AllocErr>
where
S: StylesheetInDocument + PartialEq + 'static,
@@ -4594,7 +4676,7 @@ impl CascadeDataCacheEntry for CascadeData {
DataValidity::Valid | DataValidity::CascadeInvalid => old.clone(),
DataValidity::FullyInvalid => Self::new(),
};
- updatable_entry.rebuild(device, quirks_mode, collection, guard)?;
+ updatable_entry.rebuild(device, quirks_mode, collection, guard, difference)?;
Ok(Arc::new(updatable_entry))
}
diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs
@@ -1819,36 +1819,6 @@ pub extern "C" fn Servo_AuthorStyles_IsDirty(styles: &AuthorStyles) -> bool {
}
#[no_mangle]
-pub extern "C" fn Servo_AuthorStyles_Flush(
- styles: &mut AuthorStyles,
- document_set: &PerDocumentStyleData,
-) {
- // Try to avoid the atomic borrow below if possible.
- if !styles.stylesheets.dirty() {
- return;
- }
-
- let global_style_data = &*GLOBAL_STYLE_DATA;
- let guard = global_style_data.shared_lock.read();
-
- let mut document_data = document_set.borrow_mut();
-
- // TODO(emilio): This is going to need an element or something to do proper
- // invalidation in Shadow roots.
- styles.flush::<GeckoElement>(&mut document_data.stylist, &guard);
-}
-
-#[no_mangle]
-pub extern "C" fn Servo_StyleSet_RemoveUniqueEntriesFromAuthorStylesCache(
- document_set: &PerDocumentStyleData,
-) {
- let mut document_data = document_set.borrow_mut();
- document_data
- .stylist
- .remove_unique_author_data_cache_entries();
-}
-
-#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_SizeOfIncludingThis(
malloc_size_of: GeckoMallocSizeOf,
malloc_enclosing_size_of: GeckoMallocSizeOf,
@@ -2008,18 +1978,46 @@ pub unsafe extern "C" fn Servo_StyleSet_FlushStyleSheets(
raw_data: &PerDocumentStyleData,
doc_element: Option<&RawGeckoElement>,
snapshots: *const ServoElementSnapshotTable,
+ non_document_styles: &mut nsTArray<&mut AuthorStyles>,
) {
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let mut data = raw_data.borrow_mut();
let doc_element = doc_element.map(GeckoElement);
- let have_invalidations = data.flush_stylesheets(&guard, doc_element, snapshots.as_ref());
+ let mut doc_flush_result = data.flush_stylesheets(&guard, doc_element, snapshots.as_ref());
+ if !non_document_styles.is_empty() {
+ for author_styles in non_document_styles {
+ // TODO(emilio): This is going to need an element or something to do proper
+ // invalidation in ShadowRoots.
+ let difference = author_styles.flush::<GeckoElement>(&mut data.stylist, &guard);
+ // TODO(emilio): Consider doing scoped invalidation, specially once we have tree-scoped
+ // names.
+ doc_flush_result.difference.merge_with(difference);
+ }
+ data.stylist.remove_unique_author_data_cache_entries();
+ }
+
+ // TODO(emilio): consider merging the existing stylesheet invalidation machinery into the
+ // `CascadeDataDifference`.
+ if let Some(doc_element) = doc_element {
+ let changed_position_try_names = &doc_flush_result.difference.changed_position_try_names;
+ if !changed_position_try_names.is_empty() {
+ style::invalidation::stylesheets::invalidate_position_try(
+ doc_element,
+ &changed_position_try_names,
+ &mut |e, _data| unsafe {
+ bindings::Gecko_InvalidatePositionTry(e.0);
+ },
+ &mut |_| {},
+ );
+ }
- if have_invalidations && doc_element.is_some() {
- // The invalidation machinery propagates the bits up, but we still need
- // to tell the Gecko restyle root machinery about it.
- bindings::Gecko_NoteDirtySubtreeForInvalidation(doc_element.unwrap().0);
+ if doc_flush_result.had_invalidations {
+ // The invalidation machinery propagates the bits up, but we still need to tell the Gecko
+ // restyle root machinery about it.
+ bindings::Gecko_NoteDirtySubtreeForInvalidation(doc_element.0);
+ }
}
}
diff --git a/testing/web-platform/meta/css/css-anchor-position/at-position-try-invalidation-shadow-dom.html.ini b/testing/web-platform/meta/css/css-anchor-position/at-position-try-invalidation-shadow-dom.html.ini
@@ -1,6 +0,0 @@
-[at-position-try-invalidation-shadow-dom.html]
- [#host with inserted @position-try applied]
- expected: FAIL
-
- [#slotted with inserted @position-try applied]
- expected: FAIL
diff --git a/testing/web-platform/meta/css/css-anchor-position/at-position-try-invalidation.html.ini b/testing/web-platform/meta/css/css-anchor-position/at-position-try-invalidation.html.ini
@@ -1,6 +0,0 @@
-[at-position-try-invalidation.html]
- [Enable @position-try rule stylesheet]
- expected: FAIL
-
- [Insert overriding @position-try rule]
- expected: FAIL
diff --git a/testing/web-platform/meta/css/css-anchor-position/remove-position-try-rules-001.html.ini b/testing/web-platform/meta/css/css-anchor-position/remove-position-try-rules-001.html.ini
@@ -1,3 +0,0 @@
-[remove-position-try-rules-001.html]
- [Remove fallback rules]
- expected: FAIL