tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mlayout/style/GeckoBindings.cpp | 9+++++++++
Mlayout/style/GeckoBindings.h | 1+
Mlayout/style/ServoStyleSet.cpp | 18++++++++----------
Mservo/components/style/author_styles.rs | 12++++++++++--
Mservo/components/style/gecko/data.rs | 4++--
Mservo/components/style/invalidation/stylesheets.rs | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mservo/components/style/stylist.rs | 222++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mservo/ports/geckolib/glue.rs | 68+++++++++++++++++++++++++++++++++-----------------------------------
Dtesting/web-platform/meta/css/css-anchor-position/at-position-try-invalidation-shadow-dom.html.ini | 6------
Dtesting/web-platform/meta/css/css-anchor-position/at-position-try-invalidation.html.ini | 6------
Dtesting/web-platform/meta/css/css-anchor-position/remove-position-try-rules-001.html.ini | 3---
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