/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
impl Hash for StylesheetContentsPtr { fn hash<H: Hasher>(&self, state: &mut H) { let contents: &StylesheetContents = &*self.0;
(contents as *const StylesheetContents).hash(state)
}
}
type StyleSheetContentList = Vec<StylesheetContentsPtr>;
/// The @position-try rules that have changed. #[derive(Default, Debug, MallocSizeOf)] pubstruct CascadeDataDifference { /// The set of changed @position-try rule names. pub changed_position_try_names: PrecomputedHashSet<Atom>,
}
impl CascadeDataDifference { /// Merges another difference into `self`. pubfn merge_with(&mutself, other: Self) { self.changed_position_try_names
.extend(other.changed_position_try_names.into_iter())
}
fn update(&mutself, old_data: &PositionTryMap, new_data: &PositionTryMap) { letmut 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 {
media_query_results: Vec<MediaListKey>,
contents: StyleSheetContentList,
}
unsafeimpl Send for CascadeDataCacheKey {} unsafeimpl Sync for CascadeDataCacheKey {}
trait CascadeDataCacheEntry: Sized { /// Rebuilds the cascade data for the new stylesheet collection. The /// collection is guaranteed to be dirty. fn rebuild<S>(
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
old_entry: &Self,
difference: &mut CascadeDataDifference,
) -> Result<Arc<Self>, AllocErr> where
S: StylesheetInDocument + PartialEq + 'static; /// Measures heap memory usage. #[cfg(feature = "gecko")] fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes);
}
// FIXME(emilio): This may need to be keyed on quirks-mode too, though for // UA sheets there aren't class / id selectors on those sheets, usually, so // it's probably ok... For the other cache the quirks mode shouldn't differ // so also should be fine. fn lookup<'a, S>(
&'a mut self,
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
old_entry: &Entry,
difference: &mut CascadeDataDifference,
) -> Result<Option<Arc<Entry>>, AllocErr> where
S: StylesheetInDocument + PartialEq + 'static,
{ use std::collections::hash_map::Entry as HashMapEntry;
debug!("StyleSheetCache::lookup({})", self.len());
let new_entry; matchself.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,
difference,
)?;
e.insert(new_entry.clone());
},
HashMapEntry::Occupied(mut e) => { // Avoid reusing our old entry (this can happen if we get // invalidated due to CSSOM mutations and our old stylesheet // contents were already unique, for example). if !std::ptr::eq(&**e.get(), old_entry) { if log_enabled!(log::Level::Debug) {
debug!("cache hit for:"); for sheet in collection.sheets() {
debug!(" > {:?}", sheet);
}
} // The line below ensures the "committed" bit is updated // properly.
collection.each(|_, _, _| true); return Ok(Some(e.get().clone()));
}
debug!("> Picking the slow path due to same entry as old");
new_entry = Entry::rebuild(
device,
quirks_mode,
collection,
guard,
old_entry,
difference,
)?;
e.insert(new_entry.clone());
},
}
Ok(Some(new_entry))
}
/// Returns all the cascade datas that are not being used (that is, that are /// held alive just by this cache). /// /// We return them instead of dropping in place because some of them may /// keep alive some other documents (like the SVG documents kept alive by /// URL references), and thus we don't want to drop them while locking the /// cache to not deadlock. fn take_unused(&mutself) -> SmallVec<[Arc<Entry>; 3]> { letmut unused = SmallVec::new(); self.entries.retain(|_key, value| { // is_unique() returns false for static references, but we never // have static references to UserAgentCascadeDatas. If we did, it // may not make sense to put them in the cache in the first place. if !value.is_unique() { returntrue;
}
unused.push(value.clone()); false
});
unused
}
/// A cache of computed user-agent data, to be shared across documents. static UA_CASCADE_DATA_CACHE: LazyLock<Mutex<UserAgentCascadeDataCache>> =
LazyLock::new(|| Mutex::new(UserAgentCascadeDataCache::new()));
impl CascadeDataCacheEntry for UserAgentCascadeData { fn rebuild<S>(
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
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... letmut 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,
sheet,
index,
guard,
SheetRebuildKind::Full,
Some(&mut new_data.precomputed_pseudo_element_decls),
None,
)?;
}
/// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// /// These are eagerly computed once, and then used to resolve the new /// computed values on the fly on layout. /// /// These are only filled from UA stylesheets.
precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations,
}
/// The empty UA cascade data for un-filled stylists. static EMPTY_UA_CASCADE_DATA: LazyLock<Arc<UserAgentCascadeData>> = LazyLock::new(|| { let arc = Arc::new(UserAgentCascadeData::default());
arc.mark_as_intentionally_leaked();
arc
});
/// All the computed information for all the stylesheets that apply to the /// document. #[derive(MallocSizeOf)] pubstruct DocumentCascadeData { #[ignore_malloc_size_of = "Arc, owned by UserAgentCascadeDataCache or empty"]
user_agent: Arc<UserAgentCascadeData>,
user: CascadeData,
author: CascadeData,
per_origin: PerOrigin<()>,
}
/// An iterator over the cascade data of a given document. pubstruct DocumentCascadeDataIter<'a> {
iter: PerOriginIter<'a, ()>,
cascade_data: &'a DocumentCascadeData,
}
impl<'a> Iterator for DocumentCascadeDataIter<'a> { type Item = (&'a CascadeData, Origin);
/// Rebuild the cascade data for the given document stylesheets, and /// optionally with a set of user agent stylesheets. Returns Err(..) /// to signify OOM. fn rebuild<'a, S>(
&mutself,
device: &Device,
quirks_mode: QuirksMode, mut flusher: DocumentStylesheetFlusher<'a, S>,
guards: &StylesheetGuards,
difference: &mut CascadeDataDifference,
) -> Result<(), AllocErr> where
S: StylesheetInDocument + PartialEq + 'static,
{ // First do UA sheets.
{ let origin_flusher = flusher.flush_origin(Origin::UserAgent); // Dirty check is just a minor optimization (no need to grab the // lock if nothing has changed). if origin_flusher.dirty() { letmut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap(); let new_data = ua_cache.lookup(
device,
quirks_mode,
origin_flusher,
guards.ua_or_user,
&self.user_agent,
difference,
)?; iflet 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.
std::mem::drop(ua_cache);
}
}
// Now do the user sheets. self.user.rebuild(
device,
quirks_mode,
flusher.flush_origin(Origin::User),
guards.ua_or_user,
difference,
)?;
// And now the author sheets. self.author.rebuild(
device,
quirks_mode,
flusher.flush_origin(Origin::Author),
guards.author,
difference,
)?;
/// Whether author styles are enabled. /// /// This is used to support Gecko. #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] pubenum AuthorStylesEnabled {
Yes,
No,
}
/// 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. unsafeimpl Sync for StylistStylesheetSet {}
/// 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 /// pseudo-element (see `CascadeData`). /// /// This structure is effectively created once per pipeline, in the /// LayoutThread corresponding to that pipeline. #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pubstruct Stylist { /// Device that the stylist is currently evaluating against. /// /// This field deserves a bigger comment due to the different use that Gecko /// and Servo give to it (that we should eventually unify). /// /// With Gecko, the device is never changed. Gecko manually tracks whether /// the device data should be reconstructed, and "resets" the state of the /// device. /// /// On Servo, on the other hand, the device is a really cheap representation /// that is recreated each time some constraint changes and calling /// `set_device`.
device: Device,
/// The list of stylesheets.
stylesheets: StylistStylesheetSet,
/// A cache of CascadeDatas for AuthorStylesheetSets (i.e., shadow DOM). #[cfg_attr(feature = "servo", ignore_malloc_size_of = "XXX: how to handle this?")]
author_data_cache: CascadeDataCache<CascadeData>,
/// If true, the quirks-mode stylesheet is applied. #[cfg_attr(feature = "servo", ignore_malloc_size_of = "defined in selectors")]
quirks_mode: QuirksMode,
/// Selector maps for all of the style sheets in the stylist, after /// evalutaing media rules against the current device, split out per /// cascade level.
cascade_data: DocumentCascadeData,
/// Whether author styles are enabled.
author_styles_enabled: AuthorStylesEnabled,
/// The rule tree, that stores the results of selector matching.
rule_tree: RuleTree,
/// Flags set from computing registered custom property initial values.
initial_values_for_custom_properties_flags: ComputedValueFlags,
/// The total number of times the stylist has been rebuilt.
num_rebuilds: usize,
}
/// What cascade levels to include when styling elements. #[derive(Clone, Copy, PartialEq)] pubenum RuleInclusion { /// Include rules for style sheets at all cascade levels. This is the /// normal rule inclusion mode.
All, /// Only include rules from UA and user level sheets. Used to implement /// `getDefaultComputedStyle`.
DefaultOnly,
}
#[cfg(feature = "gecko")] impl From<StyleRuleInclusion> for RuleInclusion { fn from(value: StyleRuleInclusion) -> Self { match value {
StyleRuleInclusion::All => RuleInclusion::All,
StyleRuleInclusion::DefaultOnly => RuleInclusion::DefaultOnly,
}
}
}
/// `:scope` selector, depending on the use case, can match a shadow host. /// If used outside of `@scope`, it cannot possibly match the host. /// Even when inside of `@scope`, it's conditional if the selector will /// match the shadow host. #[derive(Clone, Copy, Eq, PartialEq)] enum ScopeMatchesShadowHost {
NotApplicable,
No,
Yes,
}
impl ScopeMatchesShadowHost { fn nest_for_scope(&mutself, matches_shadow_host: bool) { match *self { Self::NotApplicable => { // We're at the outermost `@scope`.
*self = if matches_shadow_host { Self::Yes
} else { Self::No
};
}, Self::Yes if !matches_shadow_host => { // Inner `@scope` will not be able to match the shadow host.
*self = Self::No;
},
_ => (),
}
}
}
/// Nested declarations have effectively two behaviors: /// * Inside style rules (where they behave as the containing selector). /// * Inside @scope (where they behave as :where(:scope)). /// It is a bit unfortunate ideally we wouldn't need this, because scope also pushes to the /// ancestor_selector_lists, but the behavior isn't quite the same as wrapping in `&`, see /// https://github.com/w3c/csswg-drafts/issues/10431 #[derive(Copy, Clone)] enum NestedDeclarationsContext {
Style,
Scope,
}
/// A struct containing state related to scope rules struct ContainingScopeRuleState {
id: ScopeConditionId,
inner_dependencies: Vec<Dependency>,
matches_shadow_host: ScopeMatchesShadowHost,
}
type ReplacedSelectors = SmallVec<[Selector<SelectorImpl>; 4]>;
impl Stylist { /// Construct a new `Stylist`, using given `Device` and `QuirksMode`. /// If more members are added here, think about whether they should /// be reset in clear(). #[inline] pubfn new(device: Device, quirks_mode: QuirksMode) -> Self { Self {
device,
quirks_mode,
stylesheets: StylistStylesheetSet::new(),
author_data_cache: CascadeDataCache::new(),
cascade_data: Default::default(),
author_styles_enabled: AuthorStylesEnabled::Yes,
rule_tree: RuleTree::new(),
script_custom_properties: Default::default(),
initial_values_for_custom_properties: Default::default(),
initial_values_for_custom_properties_flags: Default::default(),
num_rebuilds: 0,
}
}
/// Returns whether author styles are enabled or not. #[inline] pubfn author_styles_enabled(&self) -> AuthorStylesEnabled { self.author_styles_enabled
}
/// Iterate through all the cascade datas from the document. #[inline] pubfn iter_origins(&self) -> DocumentCascadeDataIter<'_> { self.cascade_data.iter_origins()
}
/// Does what the name says, to prevent author_data_cache to grow without /// bound. pubfn remove_unique_author_data_cache_entries(&mutself) { self.author_data_cache.take_unused();
}
/// Iterate over the extra data in origin order. #[inline] pubfn iter_extra_data_origins(&self) -> ExtraStyleDataIterator<'_> {
ExtraStyleDataIterator(self.cascade_data.iter_origins())
}
/// Iterate over the extra data in reverse origin order. #[inline] pubfn iter_extra_data_origins_rev(&self) -> ExtraStyleDataIterator<'_> {
ExtraStyleDataIterator(self.cascade_data.iter_origins_rev())
}
/// Returns the number of selectors. pubfn num_selectors(&self) -> usize { self.cascade_data
.iter_origins()
.map(|(d, _)| d.num_selectors)
.sum()
}
/// Returns the number of declarations. pubfn num_declarations(&self) -> usize { self.cascade_data
.iter_origins()
.map(|(d, _)| d.num_declarations)
.sum()
}
/// Returns the number of times the stylist has been rebuilt. pubfn num_rebuilds(&self) -> usize { self.num_rebuilds
}
/// Returns the number of revalidation_selectors. pubfn num_revalidation_selectors(&self) -> usize { self.cascade_data
.iter_origins()
.map(|(data, _)| data.selectors_for_cache_revalidation.len())
.sum()
}
/// Returns the number of entries in invalidation maps. pubfn num_invalidations(&self) -> usize { self.cascade_data
.iter_origins()
.map(|(data, _)| {
data.invalidation_map.len() + data.relative_selector_invalidation_map.len()
})
.sum()
}
/// Returns whether the given DocumentState bit is relied upon by a selector /// of some rule. pubfn has_document_state_dependency(&self, state: DocumentState) -> bool { self.cascade_data
.iter_origins()
.any(|(d, _)| d.document_state_dependencies.intersects(state))
}
/// Flush the list of stylesheets if they changed, ensuring the stylist is /// up-to-date. pubfn flush(&mutself, guards: &StylesheetGuards) -> StylesheetInvalidationSet { if !self.stylesheets.has_changed() { return Default::default();
}
self.num_rebuilds += 1;
let (flusher, mut invalidations) = self.stylesheets.flush();
/// Marks a given stylesheet origin as dirty, due to, for example, changes /// in the declarations that affect a given rule. /// /// FIXME(emilio): Eventually it'd be nice for this to become more /// fine-grained. pubfn force_stylesheet_origins_dirty(&mutself, origins: OriginSet) { self.stylesheets.force_dirty(origins)
}
/// Sets whether author style is enabled or not. pubfn set_author_styles_enabled(&mutself, enabled: AuthorStylesEnabled) { self.author_styles_enabled = enabled;
}
/// Returns whether we've recorded any stylesheet change so far. pubfn stylesheets_have_changed(&self) -> bool { self.stylesheets.has_changed()
}
/// Insert a given stylesheet before another stylesheet in the document. pubfn insert_stylesheet_before(
&mutself,
sheet: StylistSheet,
before_sheet: StylistSheet,
guard: &SharedRwLockReadGuard,
) { let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); self.stylesheets.insert_stylesheet_before(
Some(&self.device),
custom_media,
sheet,
before_sheet,
guard,
)
}
/// Appends a new stylesheet to the current set. pubfn append_stylesheet(&mutself, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); self.stylesheets
.append_stylesheet(Some(&self.device), custom_media, sheet, guard)
}
/// Remove a given stylesheet to the current set. pubfn remove_stylesheet(&mutself, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); self.stylesheets
.remove_stylesheet(Some(&self.device), custom_media, sheet, guard)
}
/// Notify of a change of a given rule. pubfn rule_changed(
&mutself,
sheet: &StylistSheet,
rule: &CssRule,
guard: &SharedRwLockReadGuard,
change_kind: RuleChangeKind,
ancestors: &[CssRuleRef],
) { let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); self.stylesheets.rule_changed(
Some(&self.device),
custom_media,
sheet,
rule,
guard,
change_kind,
ancestors,
)
}
/// Get the total stylesheet count for a given origin. #[inline] pubfn sheet_count(&self, origin: Origin) -> usize { self.stylesheets.sheet_count(origin)
}
/// Get the index-th stylesheet for a given origin. #[inline] pubfn sheet_at(&self, origin: Origin, index: usize) -> Option<&StylistSheet> { self.stylesheets.get(origin, index)
}
/// Returns whether for any of the applicable style rule data a given /// condition is true. pubfn any_applicable_rule_data<E, F>(&self, element: E, mut f: F) -> bool where
E: TElement,
F: FnMut(&CascadeData) -> bool,
{ if f(&self.cascade_data.user_agent.cascade_data) { returntrue;
}
/// Execute callback for all applicable style rule data. pubfn for_each_cascade_data_with_scope<'a, E, F>(&'a self, element: E, mut f: F) where
E: TElement + 'a,
F: FnMut(&'a CascadeData, Option<E>),
{
f(&self.cascade_data.user_agent.cascade_data, None);
element.each_applicable_non_document_style_rule_data(|data, scope| {
f(data, Some(scope));
});
f(&self.cascade_data.user, None);
f(&self.cascade_data.author, None);
}
/// Computes the style for a given "precomputed" pseudo-element, taking the /// universal rules and applying them. pubfn precomputed_values_for_pseudo<E>(
&self,
guards: &StylesheetGuards,
pseudo: &PseudoElement,
parent: Option<&ComputedValues>,
) -> Arc<ComputedValues> where
E: TElement,
{
debug_assert!(pseudo.is_precomputed());
let rule_node = self.rule_node_for_precomputed_pseudo(guards, pseudo, vec![]);
/// Computes the style for a given "precomputed" pseudo-element with /// given rule node. /// /// TODO(emilio): The type parameter could go away with a void type /// implementing TElement. pubfn precomputed_values_for_pseudo_with_rule_node<E>(
&self,
guards: &StylesheetGuards,
pseudo: &PseudoElement,
parent: Option<&ComputedValues>,
rules: StrongRuleNode,
) -> Arc<ComputedValues> where
E: TElement,
{ self.compute_pseudo_element_style_with_inputs::<E>(
CascadeInputs {
rules: Some(rules),
visited_rules: None,
flags: Default::default(),
included_cascade_flags: RuleCascadeFlags::empty(),
},
pseudo,
guards,
parent, /* element */ None,
)
}
/// Returns the rule node for a given precomputed pseudo-element. /// /// If we want to include extra declarations to this precomputed /// pseudo-element, we can provide a vector of ApplicableDeclarationBlocks /// to extra_declarations. This is useful for @page rules. pubfn rule_node_for_precomputed_pseudo(
&self,
guards: &StylesheetGuards,
pseudo: &PseudoElement, mut extra_declarations: Vec<ApplicableDeclarationBlock>,
) -> StrongRuleNode { letmut declarations_with_extra; let declarations = matchself
.cascade_data
.user_agent
.precomputed_pseudo_element_decls
.get(pseudo)
{
Some(declarations) => { if !extra_declarations.is_empty() {
declarations_with_extra = declarations.clone();
declarations_with_extra.append(&mut extra_declarations);
&*declarations_with_extra
} else {
&**declarations
}
},
None => &[],
};
/// Returns the style for an anonymous box of the given type. /// /// TODO(emilio): The type parameter could go away with a void type /// implementing TElement. #[cfg(feature = "servo")] pubfn style_for_anonymous<E>(
&self,
guards: &StylesheetGuards,
pseudo: &PseudoElement,
parent_style: &ComputedValues,
) -> Arc<ComputedValues> where
E: TElement,
{ self.precomputed_values_for_pseudo::<E>(guards, &pseudo, Some(parent_style))
}
/// Computes a pseudo-element style lazily during layout. /// /// This can only be done for a certain set of pseudo-elements, like /// :selection. /// /// Check the documentation on lazy pseudo-elements in /// docs/components/style.md pubfn lazily_compute_pseudo_element_style<E>(
&self,
guards: &StylesheetGuards,
element: E,
pseudo: &PseudoElement,
rule_inclusion: RuleInclusion,
originating_element_style: &ComputedValues,
is_probe: bool,
matching_fn: Option<&dynFn(&PseudoElement) -> bool>,
) -> Option<Arc<ComputedValues>> where
E: TElement,
{ let cascade_inputs = self.lazy_pseudo_rules(
guards,
element,
originating_element_style,
pseudo,
is_probe,
rule_inclusion,
matching_fn,
)?;
/// Computes a pseudo-element style lazily using the given CascadeInputs. /// This can be used for truly lazy pseudo-elements or to avoid redoing /// selector matching for eager pseudo-elements when we need to recompute /// their style with a new parent style. pubfn compute_pseudo_element_style_with_inputs<E>(
&self,
inputs: CascadeInputs,
pseudo: &PseudoElement,
guards: &StylesheetGuards,
parent_style: Option<&ComputedValues>,
element: Option<E>,
) -> Arc<ComputedValues> where
E: TElement,
{ // FIXME(emilio): The lack of layout_parent_style here could be // worrying, but we're probably dropping the display fixup for // pseudos other than before and after, so it's probably ok. // // (Though the flags don't indicate so!) // // It'd be fine to assert that this isn't called with a parent style // where display contents is in effect, but in practice this is hard to // do for stuff like :-moz-fieldset-content with a // <fieldset style="display: contents">. That is, the computed value of // display for the fieldset is "contents", even though it's not the used // value, so we don't need to adjust in a different way anyway. self.cascade_style_and_visited(
element,
Some(pseudo),
&inputs,
guards,
parent_style,
parent_style,
FirstLineReparenting::No,
&PositionTryFallbacksTryTactic::default(), /* rule_cache = */ None,
&mut RuleCacheConditions::default(),
&mut TreeCountingCaches::default(),
)
}
/// Computes a fallback style lazily given the current and parent styles, and name. #[cfg(feature = "gecko")] pubfn resolve_position_try<E>(
&self,
style: &ComputedValues,
guards: &StylesheetGuards,
scope: CascadeLevel,
element: E,
fallback_item: &PositionTryFallbacksItem,
) -> Option<Arc<ComputedValues>> where
E: TElement,
{ let name_and_try_tactic = match *fallback_item {
PositionTryFallbacksItem::PositionArea(area) => { // We don't bother passing the parent_style argument here since // we probably don't need it. If we do, we could wrap this up in // a style_resolver::with_default_parent_styles call, as below. letmut builder =
StyleBuilder::for_derived_style(&self.device, Some(self), style, None);
builder.rules = style.rules.clone();
builder.mutate_position().set_position_area(area); return Some(builder.build());
},
PositionTryFallbacksItem::IdentAndOrTactic(ref name_and_try_tactic) => {
name_and_try_tactic
},
};
let fallback_rule = if !name_and_try_tactic.ident.is_empty() {
Some(self.lookup_position_try(&name_and_try_tactic.ident.0, scope, element)?)
} else {
None
}; let fallback_block = fallback_rule
.as_ref()
.map(|r| &r.read_with(guards.author).block); let pseudo = style
.pseudo()
.or_else(|| element.implemented_pseudo_element()); let inputs = { letmut inputs = CascadeInputs::new_from_style(style); // @position-try doesn't care about any :visited-dependent property.
inputs.visited_rules = None; let rules = inputs.rules.as_ref().unwrap_or(self.rule_tree.root()); letmut important_rules_changed = false; iflet Some(fallback_block) = fallback_block { let new_rules = self.rule_tree.update_rule_at_level(
CascadeLevel::new(CascadeOrigin::PositionFallback),
LayerOrder::root(),
Some(fallback_block.borrow_arc()),
rules,
guards,
&mut important_rules_changed,
); if new_rules.is_some() {
inputs.rules = new_rules;
} else { // This will return an identical style to `style`. We could consider optimizing // this a bit more but for now just perform the cascade, this can only happen with // the same position-try name repeated multiple times anyways.
}
}
inputs
}; crate::style_resolver::with_default_parent_styles(
element,
|parent_style, layout_parent_style| {
Some(self.cascade_style_and_visited(
Some(element),
pseudo.as_ref(),
&inputs,
guards,
parent_style,
layout_parent_style,
FirstLineReparenting::No,
&name_and_try_tactic.try_tactic, /* rule_cache = */ None,
&mut RuleCacheConditions::default(),
&mut TreeCountingCaches::default(),
))
},
)
}
/// Computes a style using the given CascadeInputs. This can be used to /// compute a style any time we know what rules apply and just need to use /// the given parent styles. /// /// parent_style is the style to inherit from for properties affected by /// first-line ancestors. /// /// parent_style_ignoring_first_line is the style to inherit from for /// properties not affected by first-line ancestors. /// /// layout_parent_style is the style used for some property fixups. It's /// the style of the nearest ancestor with a layout box. pubfn cascade_style_and_visited<E>(
&self,
element: Option<E>,
pseudo: Option<&PseudoElement>,
inputs: &CascadeInputs,
guards: &StylesheetGuards,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
first_line_reparenting: FirstLineReparenting,
try_tactic: &PositionTryFallbacksTryTactic,
rule_cache: Option<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
tree_counting_caches: &mut TreeCountingCaches,
) -> Arc<ComputedValues> where
E: TElement,
{
debug_assert!(pseudo.is_some() || element.is_some(), "Huh?");
// We need to compute visited values if we have visited rules or if our // parent has visited values. let visited_rules = match inputs.visited_rules.as_ref() {
Some(rules) => Some(rules),
None => { if parent_style.and_then(|s| s.visited_style()).is_some() {
Some(inputs.rules.as_ref().unwrap_or(self.rule_tree.root()))
} else {
None
}
},
};
letmut implemented_pseudo = None; // Read the comment on `precomputed_values_for_pseudo` to see why it's // difficult to assert that display: contents nodes never arrive here // (tl;dr: It doesn't apply for replaced elements and such, but the // computed value is still "contents"). // // FIXME(emilio): We should assert that it holds if pseudo.is_none()!
properties::cascade::<E>(
&self,
pseudo.or_else(|| {
implemented_pseudo = element.unwrap().implemented_pseudo_element();
implemented_pseudo.as_ref()
}),
inputs.rules.as_ref().unwrap_or(self.rule_tree.root()),
guards,
parent_style,
layout_parent_style,
first_line_reparenting,
try_tactic,
visited_rules,
inputs.flags,
inputs.included_cascade_flags,
rule_cache,
rule_cache_conditions,
element,
tree_counting_caches,
)
}
/// Computes the cascade inputs for a lazily-cascaded pseudo-element. /// /// See the documentation on lazy pseudo-elements in /// docs/components/style.md fn lazy_pseudo_rules<E>(
&self,
guards: &StylesheetGuards,
element: E,
originating_element_style: &ComputedValues,
pseudo: &PseudoElement,
is_probe: bool,
rule_inclusion: RuleInclusion,
matching_fn: Option<&dynFn(&PseudoElement) -> bool>,
) -> Option<CascadeInputs> where
E: TElement,
{
debug_assert!(pseudo.is_lazy());
letmut selector_caches = SelectorCaches::default(); // No need to bother setting the selector flags when we're computing // default styles. let needs_selector_flags = if rule_inclusion == RuleInclusion::DefaultOnly {
NeedsSelectorFlags::No
} else {
NeedsSelectorFlags::Yes
};
/// Set a given device, which may change the styles that apply to the /// document. /// /// Returns the sheet origins that were actually affected. /// /// This means that we may need to rebuild style data even if the /// stylesheets haven't changed. /// /// Also, the device that arrives here may need to take the viewport rules /// into account. pubfn set_device(&mutself, device: Device, guards: &StylesheetGuards) -> OriginSet { self.device = device; self.media_features_change_changed_style(guards, &self.device)
}
/// Returns whether, given a media feature change, any previously-applicable /// style has become non-applicable, or vice-versa for each origin, using /// `device`. pubfn media_features_change_changed_style(
&self,
guards: &StylesheetGuards,
device: &Device,
) -> OriginSet {
debug!("Stylist::media_features_change_changed_style {:?}", device);
letmut origins = OriginSet::empty(); let stylesheets = self.stylesheets.iter();
for (stylesheet, origin) in stylesheets { if origins.contains(origin.into()) { continue;
}
let guard = guards.for_origin(origin); let origin_cascade_data = self.cascade_data.borrow_for_origin(origin);
let affected_changed = !origin_cascade_data.media_feature_affected_matches(
stylesheet,
guard,
device, self.quirks_mode,
);
if affected_changed {
origins |= origin;
}
}
origins
}
/// Returns the Quirks Mode of the document. pubfn quirks_mode(&self) -> QuirksMode { self.quirks_mode
}
/// Sets the quirks mode of the document. pubfn set_quirks_mode(&mutself, quirks_mode: QuirksMode) { ifself.quirks_mode == quirks_mode { return;
} self.quirks_mode = quirks_mode; self.force_stylesheet_origins_dirty(OriginSet::all());
}
/// Returns the applicable CSS declarations for the given element. pubfn push_applicable_declarations<E>(
&self,
element: E,
pseudo_element: Option<&PseudoElement>,
style_attribute: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
smil_override: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
animation_declarations: AnimationDeclarations,
rule_inclusion: RuleInclusion,
applicable_declarations: &mut ApplicableDeclarationList,
context: &mut MatchingContext<E::Impl>,
) where
E: TElement,
{ letmut cur = element; letmut pseudos = SmallVec::<[_; 2]>::new(); iflet Some(pseudo) = pseudo_element {
pseudos.push(pseudo.clone());
} whilelet Some(p) = cur.implemented_pseudo_element() {
pseudos.push(p); let Some(parent_pseudo) = cur.pseudo_element_originating_element() else { break;
};
cur = parent_pseudo;
}
RuleCollector::new( self,
element,
cur,
&pseudos,
style_attribute,
smil_override,
animation_declarations,
rule_inclusion,
applicable_declarations,
context,
)
.collect_all();
}
/// Given an id, returns whether there might be any rules for that id in any /// of our rule maps. #[inline] pubfn may_have_rules_for_id<E>(&self, id: &WeakAtom, element: E) -> bool where
E: TElement,
{ // If id needs to be compared case-insensitively, the logic below // wouldn't work. Just conservatively assume it may have such rules. matchself.quirks_mode().classes_and_ids_case_sensitivity() {
CaseSensitivity::AsciiCaseInsensitive => returntrue,
CaseSensitivity::CaseSensitive => {},
}
/// Looks up a CascadeData-dependent rule for a given element. /// /// NOTE(emilio): This is a best-effort thing, the right fix is a bit TBD because it involves /// "recording" which tree the name came from, see [1][2]. /// /// [1]: https://github.com/w3c/csswg-drafts/issues/1995 /// [2]: https://bugzil.la/1458189 #[inline] fn lookup_element_dependent_at_rule<'a, T, F, E>(
&'a self,
element: E,
find_in: F,
) -> Option<&'a T> where
E: TElement + 'a,
F: Fn(&'a CascadeData) -> Option<&'a T>,
{
macro_rules! try_find_in {
($data:expr) => { iflet Some(thing) = find_in(&$data) { return Some(thing);
}
};
}
letmut result = None; let doc_rules_apply =
element.each_applicable_non_document_style_rule_data(|data, _host| { if result.is_none() {
result = find_in(data);
}
});
if result.is_some() { return result;
}
if doc_rules_apply {
try_find_in!(self.cascade_data.author);
}
try_find_in!(self.cascade_data.user);
try_find_in!(self.cascade_data.user_agent.cascade_data);
None
}
/// Returns the registered `@keyframes` animation for the specified name. #[inline] pubfn lookup_keyframes<'a, E>(
&'a self,
name: &Atom,
element: E,
) -> Option<&'a KeyframesAnimation> where
E: TElement + 'a,
{ self.lookup_element_dependent_at_rule(element, |data| data.animations.get(name))
}
/// Returns the last @view-transition rule /// <https://drafts.csswg.org/css-view-transitions-2/#resolve-view-transition-rule> #[inline] pubfn last_view_transition_rule(&self) -> Option<&Arc<ViewTransitionRule>> { // Iterate the effective rules sorted by origin and level self.iter_extra_data_origins()
.flat_map(|(d, _)| d.view_transitions.iter())
.last()
.map(|(rule, _)| rule)
}
/// Returns the registered `@position-try-rule` animation for the specified name. #[inline] #[cfg(feature = "gecko")] fn lookup_position_try<'a, E>(
&'a self,
name: &Atom,
scope: CascadeLevel,
element: E,
) -> Option<&'a Arc<Locked<PositionTryRule>>> where
E: TElement + 'a,
{ letmut shadow_root = scope.get_shadow_root_for_scoped(element); // https://drafts.csswg.org/css-shadow/#tree-scoped-name-global // "First search only the tree-scoped names associated with the same root as the tree-scoped reference." whilelet Some(r) = shadow_root { iflet Some(rule) = r
.style_data()
.map(|data| data.extra_data.position_try_rules.get(name))
.flatten()
{ return Some(rule);
} // "If no relevant tree-scoped name is found, and the root is a shadow root, then repeat this search in the root’s host’s node tree (recursively)."
shadow_root = r.host().containing_shadow();
}
/// Computes the match results of a given element against the set of /// revalidation selectors. pubfn match_revalidation_selectors<E>(
&self,
element: E,
bloom: Option<&BloomFilter>,
selector_caches: &mut SelectorCaches,
needs_selector_flags: NeedsSelectorFlags,
) -> RevalidationResult where
E: TElement,
{ letmut matching_context = MatchingContext::new_for_revalidation(
bloom,
selector_caches, self.quirks_mode,
needs_selector_flags,
);
// Note that, by the time we're revalidating, we're guaranteed that the // candidate and the entry have the same id, classes, and local name. // This means we're guaranteed to get the same rulehash buckets for all // the lookups, which means that the bitvecs are comparable. We verify // this in the caller by asserting that the bitvecs are same-length. letmut result = RevalidationResult::default(); letmut relevant_attributes = &mut result.relevant_attributes; let selectors_matched = &mut result.selectors_matched;
/// Computes styles for a given declaration with parent_style. /// /// FIXME(emilio): the lack of pseudo / cascade flags look quite dubious, /// hopefully this is only used for some canvas font stuff. /// /// TODO(emilio): The type parameter can go away when /// https://github.com/rust-lang/rust/issues/35121 is fixed. pubfn compute_for_declarations<E>(
&self,
guards: &StylesheetGuards,
parent_style: &ComputedValues,
declarations: Arc<Locked<PropertyDeclarationBlock>>,
) -> Arc<ComputedValues> where
E: TElement,
{ let block = declarations.read_with(guards.author);
// We don't bother inserting these declarations in the rule tree, since // it'd be quite useless and slow. // // TODO(emilio): Now that we fixed bug 1493420, we should consider // reversing this as it shouldn't be slow anymore, and should avoid // generating two instantiations of apply_declarations.
properties::apply_declarations::<E, _>(
&self, /* pseudo = */ None, self.rule_tree.root(),
guards,
block.declaration_importance_iter().map(|(declaration, _)| {
(
declaration,
CascadePriority::new(
CascadeLevel::same_tree_author_normal(),
LayerOrder::root(),
RuleCascadeFlags::empty(),
),
)
}),
Some(parent_style),
Some(parent_style),
FirstLineReparenting::No,
&PositionTryFallbacksTryTactic::default(),
CascadeMode::Unvisited {
visited_rules: None,
},
Default::default(),
RuleCascadeFlags::empty(), /* rule_cache = */ None,
&mut Default::default(), /* element = */ None,
&mut TreeCountingCaches::default(),
)
}
/// Accessor for a shared reference to the device. #[inline] pubfn device(&self) -> &Device {
&self.device
}
/// Accessor for a mutable reference to the device. #[inline] pubfn device_mut(&mutself) -> &mut Device {
&mutself.device
}
/// Accessor for a shared reference to the rule tree. #[inline] pubfn rule_tree(&self) -> &RuleTree {
&self.rule_tree
}
// If name is not a custom property name string, throw a SyntaxError and exit this algorithm. let Ok(name) = parse_name(name).map(Atom::from) else { return InvalidName;
};
// If property set already contains an entry with name as its property name (compared // codepoint-wise), throw an InvalidModificationError and exit this algorithm. ifself.custom_property_script_registry().get(&name).is_some() { return AlreadyRegistered;
} // Attempt to consume a syntax definition from syntax. If it returns failure, throw a // SyntaxError. Otherwise, let syntax definition be the returned syntax definition. let Ok(syntax) = Descriptor::from_str(syntax, /* preserve_specified = */ false) else { return InvalidSyntax;
};
let initial_value = match initial_value {
Some(value) => { letmut input = ParserInput::new(value); let parsed = Parser::new(&mut input)
.parse_entirely(|input| {
input.skip_whitespace();
SpecifiedValue::parse(input, None, url_data).map(Arc::new)
})
.ok(); if parsed.is_none() { return InvalidInitialValue;
}
parsed
},
None => None,
};
/// Wrapper to allow better tracking of memory usage by page rule lists. /// /// This includes the layer ID for use with the named page table. #[derive(Clone, Debug, MallocSizeOf)] pubstruct PageRuleData { /// Layer ID for sorting page rules after matching. pub layer: LayerId, /// Page rule #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] pub rule: Arc<Locked<PageRule>>,
}
/// Stores page rules indexed by page names. #[derive(Clone, Debug, Default, MallocSizeOf)] pubstruct PageRuleMap { /// Page rules, indexed by page name. An empty atom indicates no page name. pub rules: PrecomputedHashMap<Atom, SmallVec<[PageRuleData; 1]>>,
}
/// Uses page-name and pseudo-classes to match all applicable /// page-rules and append them to the matched_rules vec. /// This will ensure correct rule order for cascading. pubfn match_and_append_rules(
&self,
matched_rules: &mut Vec<ApplicableDeclarationBlock>,
origin: Origin,
guards: &StylesheetGuards,
cascade_data: &DocumentCascadeData,
name: &Option<Atom>,
pseudos: PagePseudoClassFlags,
) { let level = match origin {
Origin::UserAgent => CascadeLevel::new(CascadeOrigin::UA),
Origin::User => CascadeLevel::new(CascadeOrigin::User),
Origin::Author => CascadeLevel::same_tree_author_normal(),
}; let cascade_data = cascade_data.borrow_for_origin(origin); let start = matched_rules.len();
// Because page-rules do not have source location information stored, // use stable sort to ensure source locations are preserved.
matched_rules[start..].sort_by_key(|block| block.sort_key());
}
fn match_and_add_rules(
&self,
extra_declarations: &mut Vec<ApplicableDeclarationBlock>,
level: CascadeLevel,
guards: &StylesheetGuards,
cascade_data: &CascadeData,
name: &Atom,
pseudos: PagePseudoClassFlags,
) { let rules = matchself.rules.get(name) {
Some(rules) => rules,
None => return,
}; for data in rules.iter() { let rule = data.rule.read_with(level.guard(&guards)); let specificity = match rule.match_specificity(pseudos) {
Some(specificity) => specificity,
None => continue,
}; let block = rule.block.clone();
extra_declarations.push(ApplicableDeclarationBlock::new(
StyleSource::from_declarations(block), 0,
level,
specificity,
cascade_data.layer_order_for(data.layer),
ScopeProximity::infinity(), // Page rule can't have nested rules anyway.
RuleCascadeFlags::empty(),
));
}
}
}
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)] pubstruct ExtraStyleData { /// A list of effective font-face rules and their origin. pub font_faces: LayerOrderedVec<Arc<Locked<FontFaceRule>>>,
/// A list of effective font-feature-values rules. pub font_feature_values: LayerOrderedVec<Arc<FontFeatureValuesRule>>,
/// A list of effective font-palette-values rules. pub font_palette_values: LayerOrderedVec<Arc<FontPaletteValuesRule>>,
/// A map of effective counter-style rules. pub counter_styles: LayerOrderedMap<Arc<Locked<CounterStyleRule>>>,
/// A map of effective @position-try rules. pub position_try_rules: PositionTryMap,
/// A map of effective page rules. pub pages: PageRuleMap,
/// A list of effective @view-transition rules. pub view_transitions: LayerOrderedVec<Arc<ViewTransitionRule>>,
}
impl ExtraStyleData { /// Add the given @font-face rule. fn add_font_face(&mutself, rule: &Arc<Locked<FontFaceRule>>, layer: LayerId) { self.font_faces.push(rule.clone(), layer);
}
/// Add the given @font-feature-values rule. fn add_font_feature_values(&mutself, rule: &Arc<FontFeatureValuesRule>, layer: LayerId) { self.font_feature_values.push(rule.clone(), layer);
}
/// Add the given @font-palette-values rule. fn add_font_palette_values(&mutself, rule: &Arc<FontPaletteValuesRule>, layer: LayerId) { self.font_palette_values.push(rule.clone(), layer);
}
/// Add the given @counter-style rule. fn add_counter_style(
&mutself,
guard: &SharedRwLockReadGuard,
rule: &Arc<Locked<CounterStyleRule>>,
layer: LayerId,
) -> Result<(), AllocErr> { let name = rule.read_with(guard).name().0.clone(); self.counter_styles.try_insert(name, rule.clone(), layer)
}
impl MallocSizeOf for ExtraStyleData { /// Measure heap usage. fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { letmut n = 0;
n += self.font_faces.shallow_size_of(ops);
n += self.font_feature_values.shallow_size_of(ops);
n += self.font_palette_values.shallow_size_of(ops);
n += self.counter_styles.shallow_size_of(ops);
n += self.position_try_rules.shallow_size_of(ops);
n += self.pages.shallow_size_of(ops);
n
}
}
/// SelectorMapEntry implementation for use in our revalidation selector map. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug)] struct RevalidationSelectorAndHashes { #[cfg_attr(
feature = "gecko",
ignore_malloc_size_of = "CssRules have primary refs, we measure there"
)]
selector: Selector<SelectorImpl>,
selector_offset: usize,
hashes: AncestorHashes,
}
impl RevalidationSelectorAndHashes { fn new(selector: Selector<SelectorImpl>, hashes: AncestorHashes) -> Self { let selector_offset = { // We basically want to check whether the first combinator is a // pseudo-element combinator. If it is, we want to use the offset // one past it. Otherwise, our offset is 0. letmut index = 0; letmut iter = selector.iter();
// First skip over the first ComplexSelector. // // We can't check what sort of what combinator we have until we do // that. for _ in &mut iter {
index += 1; // Simple selector
}
match iter.next_sequence() {
Some(Combinator::PseudoElement) => index + 1, // +1 for the combinator
_ => 0,
}
};
/// A selector visitor implementation that collects all the state the Stylist /// cares about a selector. struct StylistSelectorVisitor<'a> { /// Whether we've past the rightmost compound selector, not counting /// pseudo-elements.
passed_rightmost_selector: bool,
/// Whether the selector needs revalidation for the style sharing cache.
needs_revalidation: &'a mut bool,
/// Flags for which selector list-containing components the visitor is /// inside of, if any
in_selector_list_of: SelectorListKind,
/// The filter with all the id's getting referenced from rightmost /// selectors.
mapped_ids: &'a mut PrecomputedHashSet<Atom>,
/// The filter with the IDs getting referenced from the selector list of /// :nth-child(... of <selector list>) selectors.
nth_of_mapped_ids: &'a mut PrecomputedHashSet<Atom>,
/// The filter with the local names of attributes there are selectors for.
attribute_dependencies: &'a mut PrecomputedHashSet<LocalName>,
/// The filter with the classes getting referenced from the selector list of /// :nth-child(... of <selector list>) selectors.
nth_of_class_dependencies: &'a mut PrecomputedHashSet<Atom>,
/// The filter with the local names of attributes there are selectors for /// within the selector list of :nth-child(... of <selector list>) /// selectors.
nth_of_attribute_dependencies: &'a mut PrecomputedHashSet<LocalName>,
/// The filter with the local names of custom states in selectors for /// within the selector list of :nth-child(... of <selector list>) /// selectors.
nth_of_custom_state_dependencies: &'a mut PrecomputedHashSet<AtomIdent>,
/// All the states selectors in the page reference.
state_dependencies: &'a mut ElementState,
/// All the state selectors in the page reference within the selector list /// of :nth-child(... of <selector list>) selectors.
nth_of_state_dependencies: &'a mut ElementState,
/// All the document states selectors in the page reference.
document_state_dependencies: &'a mut DocumentState,
}
fn component_needs_revalidation(
c: &Component<SelectorImpl>,
passed_rightmost_selector: bool,
) -> bool { match *c {
Component::ID(_) => { // TODO(emilio): This could also check that the ID is not already in // the rule hash. In that case, we could avoid making this a // revalidation selector too. // // See https://bugzilla.mozilla.org/show_bug.cgi?id=1369611
passed_rightmost_selector
},
Component::AttributeInNoNamespaceExists { .. }
| Component::AttributeInNoNamespace { .. }
| Component::AttributeOther(_)
| Component::Empty
| Component::Nth(_)
| Component::NthOf(_)
| Component::Has(_) => true,
Component::NonTSPseudoClass(ref p) => p.needs_cache_revalidation(),
_ => false,
}
}
impl<'a> StylistSelectorVisitor<'a> { fn visit_nested_selector(
&mutself,
in_selector_list_of: SelectorListKind,
selector: &Selector<SelectorImpl>,
) { let old_passed_rightmost_selector = self.passed_rightmost_selector; let old_in_selector_list_of = self.in_selector_list_of;
self.passed_rightmost_selector = false; self.in_selector_list_of = in_selector_list_of; let _ret = selector.visit(self);
debug_assert!(_ret, "We never return false");
// NOTE(emilio): this call happens before we visit any of the simple // selectors in the next ComplexSelector, so we can use this to skip // looking at them. self.passed_rightmost_selector = self.passed_rightmost_selector
|| !matches!(combinator, None | Some(Combinator::PseudoElement));
true
}
fn visit_selector_list(
&mutself,
list_kind: SelectorListKind,
list: &[Selector<Self::Impl>],
) -> bool { let in_selector_list_of = self.in_selector_list_of | list_kind; for selector in list { self.visit_nested_selector(in_selector_list_of, selector);
} true
}
fn visit_relative_selector_list(
&mutself,
list: &[selectors::parser::RelativeSelector<Self::Impl>],
) -> bool { let in_selector_list_of = self.in_selector_list_of | SelectorListKind::HAS; for selector in list { self.visit_nested_selector(in_selector_list_of, &selector.selector);
} true
}
match *s {
Component::NonTSPseudoClass(NonTSPseudoClass::CustomState(ref name)) => { // CustomStateSet is special cased as it is a functional pseudo // class with unbounded inner values. This is different to // other psuedo class like :emtpy or :dir() which can be packed // into the ElementState bitflags. For CustomState, however, // the state name should be checked for presence in the selector. ifself.in_selector_list_of.relevant_to_nth_of_dependencies() { self.nth_of_custom_state_dependencies.insert(name.0.clone());
}
},
Component::NonTSPseudoClass(ref p) => { self.state_dependencies.insert(p.state_flag()); self.document_state_dependencies
.insert(p.document_state_flag());
ifself.in_selector_list_of.relevant_to_nth_of_dependencies() { self.nth_of_state_dependencies.insert(p.state_flag());
}
},
Component::ID(ref id) => { // We want to stop storing mapped ids as soon as we've moved off // the rightmost ComplexSelector that is not a pseudo-element. // // That can be detected by a visit_complex_selector call with a // combinator other than None and PseudoElement. // // Importantly, this call happens before we visit any of the // simple selectors in that ComplexSelector. // // NOTE(emilio): See the comment regarding on when this may // break in visit_complex_selector. if !self.passed_rightmost_selector { self.mapped_ids.insert(id.0.clone());
}
/// A set of rules for element and pseudo-elements. #[derive(Clone, Debug, Default, MallocSizeOf)] struct GenericElementAndPseudoRules<Map> { /// Rules from stylesheets at this `CascadeData`'s origin.
element_map: Map,
/// Rules from stylesheets at this `CascadeData`'s origin that correspond /// to a given pseudo-element. /// /// FIXME(emilio): There are a bunch of wasted entries here in practice. /// Figure out a good way to do a `PerNonAnonBox` and `PerAnonBox` (for /// `precomputed_values_for_pseudo`) without duplicating a lot of code.
pseudos_map: PerPseudoElementMap<Self>,
}
impl<Map: Default + MallocSizeOf> GenericElementAndPseudoRules<Map> { #[inline(always)] fn for_insertion<'a>(&mut self, pseudo_elements: &[&'a PseudoElement]) -> &mut Map { letmut current = self; for &pseudo_element in pseudo_elements {
debug_assert!(
!pseudo_element.is_precomputed()
&& !pseudo_element.is_unknown_webkit_pseudo_element(), "Precomputed pseudos should end up in precomputed_pseudo_element_decls, \
and unknown webkit pseudos should be discarded before getting here"
);
current = current
.pseudos_map
.get_or_insert_with(pseudo_element, Default::default);
}
&mut current.element_map
}
#[inline] fn rules(&self, pseudo_elements: &[PseudoElement]) -> Option<&Map> { letmut current = self; for pseudo in pseudo_elements {
current = current.pseudos_map.get(&pseudo)?;
}
Some(¤t.element_map)
}
type ElementAndPseudoRules = GenericElementAndPseudoRules<SelectorMap<Rule>>; type PartMap = PrecomputedHashMap<Atom, SmallVec<[Rule; 1]>>; type PartElementAndPseudoRules = GenericElementAndPseudoRules<PartMap>;
impl ElementAndPseudoRules { // TODO(emilio): Should we retain storage of these? fn clear(&mutself) { self.element_map.clear(); self.pseudos_map.clear();
}
impl PartElementAndPseudoRules { // TODO(emilio): Should we retain storage of these? fn clear(&mutself) { self.element_map.clear(); self.pseudos_map.clear();
}
}
/// The id of a given layer, a sequentially-increasing identifier. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] pubstruct LayerId(u16);
impl LayerId { /// The id of the root layer. pubconstfn root() -> Self { Self(0)
}
}
/// The id of a given container condition, a sequentially-increasing identifier /// for a given style set. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] pubstruct ContainerConditionId(u16);
impl ContainerConditionId { /// A special id that represents no container rule. pubconstfn none() -> Self { Self(0)
}
}
#[derive(Clone, Debug, MallocSizeOf)] struct ContainerConditionReference {
parent: ContainerConditionId, /// Contains the container conditions of a particular rule. /// /// This should only ever be empty for the root rule, which acts as a /// sentinel value. #[ignore_malloc_size_of = "Arc"]
conditions: ArcSlice<ContainerCondition>,
}
/// The id of a given scope condition, a sequentially-increasing identifier /// for a given style set. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] pubstruct ScopeConditionId(u16);
impl ScopeConditionId { /// Construct a new scope condition id. pubfn new(id: u16) -> Self { Self(id)
}
/// A special id that represents no scope rule. pubconstfn none() -> Self { Self(0)
}
}
/// Data required to process this scope condition. #[derive(Clone, Debug, MallocSizeOf)] pubstruct ScopeConditionReference { /// The ID of outer scope condition, `none()` otherwise.
parent: ScopeConditionId, /// Start and end bounds of the scope. None implies sentinel data (i.e. Not a scope condition).
condition: Option<ScopeBoundsWithHashes>, /// Implicit scope root of this scope condition, computed unconditionally, /// even if the start bound may be Some. #[ignore_malloc_size_of = "Raw ptr behind the scenes"]
implicit_scope_root: StylistImplicitScopeRoot, /// Is the condition trivial? See `ScopeBoundsWithHashes::is_trivial`.
is_trivial: bool,
}
/// All potential sscope root candidates. pubstruct ScopeRootCandidates { /// List of scope root candidates. pub candidates: Vec<ScopeRootCandidate>, /// Is the scope condition matching these candidates trivial? See `ScopeBoundsWithHashes::is_trivial`. pub is_trivial: bool,
}
/// Start and end bound of a scope, along with their selector hashes. #[derive(Clone, Debug, MallocSizeOf)] pubstruct ScopeBoundWithHashes { // TODO(dshin): With replaced parent selectors, these may be unique... #[ignore_malloc_size_of = "Arc"]
selectors: SelectorList<SelectorImpl>,
hashes: SmallVec<[AncestorHashes; 1]>,
}
/// Bounds for this scope, along with corresponding selector hashes. #[derive(Clone, Debug, MallocSizeOf)] pubstruct ScopeBoundsWithHashes { /// Start of the scope bound. If None, implies implicit scope root.
start: Option<ScopeBoundWithHashes>, /// Optional end of the scope bound.
end: Option<ScopeBoundWithHashes>,
}
impl ScopeBoundsWithHashes { /// Create a new scope bound, hashing selectors for fast rejection. fn new(
quirks_mode: QuirksMode,
start: Option<SelectorList<SelectorImpl>>,
end: Option<SelectorList<SelectorImpl>>,
) -> Self { Self {
start: start.map(|selectors| ScopeBoundWithHashes::new(quirks_mode, selectors)),
end: end.map(|selectors| ScopeBoundWithHashes::new(quirks_mode, selectors)),
}
}
/// Create a new scope bound, but not hashing any selector. pubfn new_no_hash(
start: Option<SelectorList<SelectorImpl>>,
end: Option<SelectorList<SelectorImpl>>,
) -> Self { Self {
start: start.map(|selectors| ScopeBoundWithHashes::new_no_hash(selectors)),
end: end.map(|selectors| ScopeBoundWithHashes::new_no_hash(selectors)),
}
}
// Given an implicit scope, we are unable to tell if the cousins share the same implicit root.
scope_bound_is_trivial(&self.start, false) && scope_bound_is_trivial(&self.end, true)
}
}
/// Find all scope conditions for a given condition ID, indexing into the given list of scope conditions. pubfn scope_root_candidates<E>(
scope_conditions: &[ScopeConditionReference],
id: ScopeConditionId,
element: &E,
override_matches_shadow_host_for_part: bool,
scope_subject_map: &ScopeSubjectMap,
context: &mut MatchingContext<SelectorImpl>,
) -> ScopeRootCandidates where
E: TElement,
{ let condition_ref = &scope_conditions[id.0as usize]; let bounds = match condition_ref.condition {
None => return ScopeRootCandidates::default(),
Some(ref c) => c,
}; // Make sure the parent scopes ara evaluated first. This runs a bit counter to normal // selector matching where rightmost selectors match first. However, this avoids having // to traverse through descendants (i.e. Avoids tree traversal vs linear traversal). let outer_result = scope_root_candidates(
scope_conditions,
condition_ref.parent,
element,
override_matches_shadow_host_for_part,
scope_subject_map,
context,
);
let is_trivial = condition_ref.is_trivial && outer_result.is_trivial; let is_outermost_scope = condition_ref.parent == ScopeConditionId::none(); if !is_outermost_scope && outer_result.candidates.is_empty() { return ScopeRootCandidates::empty(is_trivial);
}
let (root_target, matches_shadow_host) = iflet Some(start) = bounds.start.as_ref() { iflet Some(filter) = context.bloom_filter { // Use the bloom filter here. If our ancestors do not have the right hashes, // there's no point in traversing up. Besides, the filter is built for this depth, // so the filter contains more data than it should, the further we go up the ancestor // chain. It wouldn't generate wrong results, but makes the traversal even more pointless. if !start
.hashes
.iter()
.any(|entry| selector_may_match(entry, filter))
{ return ScopeRootCandidates::empty(is_trivial);
}
}
(
ScopeTarget::Selector(&start.selectors),
scope_start_matches_shadow_host(&start.selectors),
)
} else { let implicit_root = condition_ref.implicit_scope_root; match implicit_root {
StylistImplicitScopeRoot::Normal(r) => (
ScopeTarget::Implicit(r.element(context.current_host.clone())),
r.matches_shadow_host(),
),
StylistImplicitScopeRoot::Cached(index) => { let host = context
.current_host
.expect("Cached implicit scope for light DOM implicit scope"); match E::implicit_scope_for_sheet_in_shadow_root(host, index) {
None => return ScopeRootCandidates::empty(is_trivial),
Some(root) => (
ScopeTarget::Implicit(root.element(context.current_host.clone())),
root.matches_shadow_host(),
),
}
},
}
};
// For `::part`, we need to be able to reach the outer tree. Parts without the corresponding
// `exportparts` attribute will be rejected at the selector matching time.
let matches_shadow_host = override_matches_shadow_host_for_part || matches_shadow_host;
let potential_scope_roots = if is_outermost_scope {
collect_scope_roots(
*element,
None,
context,
&root_target,
matches_shadow_host,
scope_subject_map,
)
} else {
let mut result = vec![];
for activation in outer_result.candidates {
let mut this_result = collect_scope_roots(
*element,
Some(activation.root),
context,
&root_target,
matches_shadow_host,
scope_subject_map,
);
result.append(&mut this_result);
}
result
};
if potential_scope_roots.is_empty() {
return ScopeRootCandidates::empty(is_trivial);
}
let candidates = if let Some(end) = bounds.end.as_ref() {
let mut result = vec![];
// If any scope-end selector matches, we're not in scope.
for scope_root in potential_scope_roots {
if end
.selectors
.slice()
.iter()
.zip(end.hashes.iter())
.all(|(selector, hashes)| {
// Like checking for scope-start, use the bloom filter here.
if let Some(filter) = context.bloom_filter {
if !selector_may_match(hashes, filter) {
// Selector this hash belongs to won't cause us to be out of this scope.
return true;
}
}
/// Implicit scope root, which may or may not be cached (i.e. For shadow DOM author
/// styles that are cached and shared).
#[derive(Copy, Clone, Debug, MallocSizeOf)]
enum StylistImplicitScopeRoot {
Normal(ImplicitScopeRoot),
Cached(usize),
}
// Should be safe, only mutated through mutable methods in `Stylist`.
unsafe impl Sync for StylistImplicitScopeRoot {}
impl StylistImplicitScopeRoot {
const fn default_const() -> Self {
// Use the "safest" fallback.
Self::Normal(ImplicitScopeRoot::DocumentElement)
}
}
/// Data resulting from performing the CSS cascade that is specific to a given
/// origin.
///
/// FIXME(emilio): Consider renaming and splitting in `CascadeData` and
/// `InvalidationData`? That'd make `clear_cascade_data()` clearer.
#[derive(Debug, Clone, MallocSizeOf)]
pub struct CascadeData {
/// The data coming from normal style rules that apply to elements at this
/// cascade level.
normal_rules: ElementAndPseudoRules,
/// The `:host` pseudo rules that are the rightmost selector (without
/// accounting for pseudo-elements), or `:scope` rules that may match
/// the featureless host.
featureless_host_rules: Option<Box<ElementAndPseudoRules>>,
/// The data coming from ::slotted() pseudo-element rules.
///
/// We need to store them separately because an element needs to match
/// ::slotted() pseudo-element rules in different shadow roots.
///
/// In particular, we need to go through all the style data in all the
/// containing style scopes starting from the closest assigned slot.
slotted_rules: Option<Box<ElementAndPseudoRules>>,
/// The data coming from ::part() pseudo-element rules.
///
/// We need to store them separately because an element needs to match
/// ::part() pseudo-element rules in different shadow roots.
part_rules: Option<Box<PartElementAndPseudoRules>>,
/// The invalidation map for these rules.
invalidation_map: InvalidationMap,
/// The relative selector equivalent of the invalidation map.
relative_selector_invalidation_map: InvalidationMap,
/// The attribute local names that appear in attribute selectors. Used
/// to avoid taking element snapshots when an irrelevant attribute changes.
/// (We don't bother storing the namespace, since namespaced attributes are
/// rare.)
attribute_dependencies: PrecomputedHashSet<LocalName>,
/// The classes that appear in the selector list of
/// :nth-child(... of <selector list>). Used to avoid restyling siblings of
/// an element when an irrelevant class changes.
nth_of_class_dependencies: PrecomputedHashSet<Atom>,
/// The attributes that appear in the selector list of
/// :nth-child(... of <selector list>). Used to avoid restyling siblings of
/// an element when an irrelevant attribute changes.
nth_of_attribute_dependencies: PrecomputedHashSet<LocalName>,
/// The custom states that appear in the selector list of
/// :nth-child(... of <selector list>). Used to avoid restyling siblings of
/// an element when an irrelevant custom state changes.
nth_of_custom_state_dependencies: PrecomputedHashSet<AtomIdent>,
/// The element state bits that are relied on by selectors. Like
/// `attribute_dependencies`, this is used to avoid taking element snapshots
/// when an irrelevant element state bit changes.
state_dependencies: ElementState,
/// The element state bits that are relied on by selectors that appear in
/// the selector list of :nth-child(... of <selector list>).
nth_of_state_dependencies: ElementState,
/// The document state bits that are relied on by selectors. This is used
/// to tell whether we need to restyle the entire document when a document
/// state bit changes.
document_state_dependencies: DocumentState,
/// The ids that appear in the rightmost complex selector of selectors (and
/// hence in our selector maps). Used to determine when sharing styles is
/// safe: we disallow style sharing for elements whose id matches this
/// filter, and hence might be in one of our selector maps.
mapped_ids: PrecomputedHashSet<Atom>,
/// The IDs that appear in the selector list of
/// :nth-child(... of <selector list>). Used to avoid restyling siblings
/// of an element when an irrelevant ID changes.
nth_of_mapped_ids: PrecomputedHashSet<Atom>,
/// Selectors that require explicit cache revalidation (i.e. which depend
/// on state that is not otherwise visible to the cache, like attributes or
/// tree-structural state like child index and pseudos).
#[ignore_malloc_size_of = "Arc"]
selectors_for_cache_revalidation: SelectorMap<RevalidationSelectorAndHashes>,
/// A map with all the animations at this `CascadeData`'s origin, indexed
/// by name.
animations: LayerOrderedMap<KeyframesAnimation>,
/// A map with all the layer-ordered registrations from style at this `CascadeData`'s origin,
/// indexed by name.
#[ignore_malloc_size_of = "Arc"]
custom_property_registrations: LayerOrderedMap<Arc<PropertyRegistration>>,
/// Custom media query registrations.
custom_media: CustomMediaMap,
/// A map from cascade layer name to layer order.
layer_id: FxHashMap<LayerName, LayerId>,
/// The list of cascade layers, indexed by their layer id.
layers: SmallVec<[CascadeLayer; 1]>,
/// The list of container conditions, indexed by their id.
container_conditions: SmallVec<[ContainerConditionReference; 1]>,
/// The list of scope conditions, indexed by their id.
scope_conditions: SmallVec<[ScopeConditionReference; 1]>,
/// Map of unique selectors on scope start selectors' subjects.
scope_subject_map: ScopeSubjectMap,
/// Effective media query results cached from the last rebuild.
effective_media_query_results: EffectiveMediaQueryResults,
/// Extra data, like different kinds of rules, etc.
extra_data: ExtraStyleData,
/// A monotonically increasing counter to represent the order on which a
/// style rule appears in a stylesheet, needed to sort them by source order.
rules_source_order: u32,
/// The total number of selectors.
num_selectors: usize,
/// The total number of declarations.
num_declarations: usize,
}
static IMPLICIT_SCOPE: LazyLock<SelectorList<SelectorImpl>> = LazyLock::new(|| {
// Implicit scope, as per https://github.com/w3c/csswg-drafts/issues/10196
// Also, `&` is `:where(:scope)`, as per https://github.com/w3c/csswg-drafts/issues/9740
// ``:where(:scope)` effectively behaves the same as the implicit scope.
let list = SelectorList::implicit_scope();
list.mark_as_intentionally_leaked();
list
});
fn scope_start_matches_shadow_host(start: &SelectorList<SelectorImpl>) -> bool {
// TODO(emilio): Should we carry a MatchesFeaturelessHost rather than a bool around?
// Pre-existing behavior with multiple selectors matches this tho.
start
.slice()
.iter()
.any(|s| s.matches_featureless_host(true).may_match())
}
/// Replace any occurrence of parent selector in the given selector with a implicit scope selector.
pub fn replace_parent_selector_with_implicit_scope(
selectors: &SelectorList<SelectorImpl>,
) -> SelectorList<SelectorImpl> {
selectors.replace_parent_selector(&IMPLICIT_SCOPE)
}
// 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
}
/// Returns the custom media query map.
pub fn custom_media_map(&self) -> &CustomMediaMap {
&self.custom_media
}
/// Returns whether the given ElementState bit is relied upon by a selector
/// of some rule.
#[inline]
pub fn has_state_dependency(&self, state: ElementState) -> bool {
self.state_dependencies.intersects(state)
}
/// Returns whether the given Custom State is relied upon by a selector
/// of some rule in the selector list of :nth-child(... of <selector list>).
#[inline]
pub fn has_nth_of_custom_state_dependency(&self, state: &AtomIdent) -> bool {
self.nth_of_custom_state_dependencies.contains(state)
}
/// Returns whether the given ElementState bit is relied upon by a selector
/// of some rule in the selector list of :nth-child(... of <selector list>).
#[inline]
pub fn has_nth_of_state_dependency(&self, state: ElementState) -> bool {
self.nth_of_state_dependencies.intersects(state)
}
/// Returns whether the given attribute might appear in an attribute
/// selector of some rule.
#[inline]
pub fn might_have_attribute_dependency(&self, local_name: &LocalName) -> bool {
self.attribute_dependencies.contains(local_name)
}
/// Returns whether the given ID might appear in an ID selector in the
/// selector list of :nth-child(... of <selector list>).
#[inline]
pub fn might_have_nth_of_id_dependency(&self, id: &Atom) -> bool {
self.nth_of_mapped_ids.contains(id)
}
/// Returns whether the given class might appear in a class selector in the
/// selector list of :nth-child(... of <selector list>).
#[inline]
pub fn might_have_nth_of_class_dependency(&self, class: &Atom) -> bool {
self.nth_of_class_dependencies.contains(class)
}
/// Returns whether the given attribute might appear in an attribute
/// selector in the selector list of :nth-child(... of <selector list>).
#[inline]
pub fn might_have_nth_of_attribute_dependency(&self, local_name: &LocalName) -> bool {
self.nth_of_attribute_dependencies.contains(local_name)
}
/// Returns the normal rule map for a given pseudo-element.
#[inline]
pub fn normal_rules(&self, pseudo_elements: &[PseudoElement]) -> Option<&SelectorMap<Rule>> {
self.normal_rules.rules(pseudo_elements)
}
/// Returns the featureless pseudo rule map for a given pseudo-element.
#[inline]
pub fn featureless_host_rules(
&self,
pseudo_elements: &[PseudoElement],
) -> Option<&SelectorMap<Rule>> {
self.featureless_host_rules
.as_ref()
.and_then(|d| d.rules(pseudo_elements))
}
/// Whether there's any featureless rule that could match in this scope.
pub fn any_featureless_host_rules(&self) -> bool {
self.featureless_host_rules.is_some()
}
/// Returns the slotted rule map for a given pseudo-element.
#[inline]
pub fn slotted_rules(&self, pseudo_elements: &[PseudoElement]) -> Option<&SelectorMap<Rule>> {
self.slotted_rules
.as_ref()
.and_then(|d| d.rules(pseudo_elements))
}
/// Whether there's any ::slotted rule that could match in this scope.
pub fn any_slotted_rule(&self) -> bool {
self.slotted_rules.is_some()
}
/// Returns the parts rule map for a given pseudo-element.
#[inline]
pub fn part_rules(&self, pseudo_elements: &[PseudoElement]) -> Option<&PartMap> {
self.part_rules
.as_ref()
.and_then(|d| d.rules(pseudo_elements))
}
/// Whether there's any ::part rule that could match in this scope.
pub fn any_part_rule(&self) -> bool {
self.part_rules.is_some()
}
// Whether the scope root matches a shadow host mostly olny depends on scope-intrinsic
// parameters (i.e. bounds/implicit scope) - except for the use of `::parts`, where
// matching crosses the shadow boundary.
let result = scope_root_candidates(
&self.scope_conditions,
rule.scope_condition_id,
&element,
rule.selector.is_part(),
&self.scope_subject_map,
context,
);
for candidate in result.candidates {
if context.nest_for_scope(Some(candidate.root), |context| {
matches_selector(&rule.selector, 0, Some(&rule.hashes), &element, context)
}) {
return candidate.proximity;
}
}
ScopeProximity::infinity()
}
fn compute_layer_order(&mut self) {
debug_assert_ne!(
self.layers.len(), 0,
"There should be at least the root layer!"
);
if self.layers.len() == 1 {
return; // Nothing to do
}
let (first, remaining) = self.layers.split_at_mut(1);
let root = &mut first[0];
let mut order = LayerOrder::first();
compute_layer_order_for_subtree(root, remaining, &mut order);
// NOTE(emilio): This is a bit trickier than it should to avoid having
// to clone() around layer indices.
fn compute_layer_order_for_subtree(
parent: &mut CascadeLayer,
remaining_layers: &mut [CascadeLayer],
order: &mut LayerOrder,
) {
for child in parent.children.iter() {
debug_assert!(
parent.id < *child,
"Children are always registered after parents"
);
let child_index = (child.0 - parent.id.0 - 1) as usize;
let (first, remaining) = remaining_layers.split_at_mut(child_index + 1);
let child = &mut first[child_index];
compute_layer_order_for_subtree(child, remaining, order);
}
/// Collects all the applicable media query results into `results`.
///
/// This duplicates part of the logic in `add_stylesheet`, which is
/// a bit unfortunate.
///
/// FIXME(emilio): With a bit of smartness in
/// `media_feature_affected_matches`, we could convert
/// `EffectiveMediaQueryResults` into a vector without too much effort.
fn collect_applicable_media_query_results_into<S>(
device: &Device,
stylesheet: &S,
guard: &SharedRwLockReadGuard,
results: &mut Vec<MediaListKey>,
contents_list: &mut StyleSheetContentList,
custom_media_map: &mut CustomMediaMap,
) where
S: StylesheetInDocument + 'static,
{
if !stylesheet.enabled() {
return;
}
if !stylesheet.is_effective_for_device(device, &custom_media_map, guard) {
return;
}
debug!(" + {:?}", stylesheet);
let contents = stylesheet.contents(guard);
results.push(contents.to_media_list_key());
// Safety: StyleSheetContents are reference-counted with Arc.
contents_list.push(StylesheetContentsPtr(unsafe {
Arc::from_raw_addrefed(&*contents)
}));
let mut iter = stylesheet
.contents(guard)
.effective_rules(device, custom_media_map, guard);
while let Some(rule) = iter.next() {
match *rule {
CssRule::CustomMedia(ref custom_media) => {
iter.custom_media()
.insert(custom_media.name.0.clone(), custom_media.condition.clone());
},
CssRule::Import(ref lock) => {
let import_rule = lock.read_with(guard);
debug!(" + {:?}", import_rule.stylesheet.media(guard));
results.push(import_rule.to_media_list_key());
},
CssRule::Media(ref media_rule) => {
debug!(" + {:?}", media_rule.media_queries.read_with(guard));
results.push(media_rule.to_media_list_key());
},
_ => {},
}
}
}
// Part is special, since given it doesn't have any
// selectors inside, it's not worth using a whole
// SelectorMap for it.
if let Some(parts) = rule.selector.parts() {
// ::part() has all semantics, so we just need to
// put any of them in the selector map.
//
// We choose the last one quite arbitrarily,
// expecting it's slightly more likely to be more
// specific.
let map = self
.part_rules
.get_or_insert_with(|| Box::new(Default::default()))
.for_insertion(&pseudo_elements);
map.try_reserve(1)?;
let vec = map.entry(parts.last().unwrap().clone().0).or_default();
vec.try_reserve(1)?;
vec.push(rule);
} else {
let scope_matches_shadow_host = containing_rule_state
.containing_scope_rule_state
.matches_shadow_host
== ScopeMatchesShadowHost::Yes;
let matches_featureless_host_only = match rule
.selector
.matches_featureless_host(scope_matches_shadow_host)
{
MatchesFeaturelessHost::Only => true,
MatchesFeaturelessHost::Yes => {
// We need to insert this in featureless_host_rules but also normal_rules.
self.featureless_host_rules
.get_or_insert_with(|| Box::new(Default::default()))
.for_insertion(&pseudo_elements)
.insert(rule.clone(), quirks_mode)?;
false
},
MatchesFeaturelessHost::Never => false,
};
// NOTE(emilio): It's fine to look at :host and then at
// ::slotted(..), since :host::slotted(..) could never
// possibly match, as <slot> is not a valid shadow host.
// :scope may match featureless shadow host if the scope
// root is the shadow root.
// See https://github.com/w3c/csswg-drafts/issues/9025
let rules = if matches_featureless_host_only {
self.featureless_host_rules
.get_or_insert_with(|| Box::new(Default::default()))
} else if rule.selector.is_slotted() {
self.slotted_rules
.get_or_insert_with(|| Box::new(Default::default()))
} else {
&mut self.normal_rules
}
.for_insertion(&pseudo_elements);
rules.insert(rule, quirks_mode)?;
}
}
self.rules_source_order += 1;
Ok(())
}
fn add_rule_list<S>(
&mut self,
rules: std::slice::Iter<CssRule>,
device: &Device,
quirks_mode: QuirksMode,
stylesheet: &S,
sheet_index: usize,
guard: &SharedRwLockReadGuard,
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,
{
for rule in rules {
// Handle leaf rules first, as those are by far the most common
// ones, and are always effective, so we can skip some checks.
let mut handled = true;
let mut list_for_nested_rules = None;
match *rule {
CssRule::Style(ref locked) => {
let style_rule = locked.read_with(guard);
let has_nested_rules = style_rule.rules.is_some();
let mut replaced_selectors = ReplacedSelectors::new();
let ancestor_selectors = containing_rule_state.ancestor_selector_lists.last();
let collect_replaced_selectors =
has_nested_rules && ancestor_selectors.is_some();
let mut inner_dependencies: Option<Vec<Dependency>> = containing_rule_state
.scope_is_effective()
.then(|| Vec::new());
self.add_styles(
&style_rule.selectors,
&style_rule.block,
ancestor_selectors,
containing_rule_state,
if collect_replaced_selectors {
Some(&mut replaced_selectors)
} else {
None
},
guard,
rebuild_kind,
precomputed_pseudo_element_decls.as_deref_mut(),
quirks_mode,
inner_dependencies.as_mut(),
)?;
if let Some(mut scope_dependencies) = inner_dependencies {
containing_rule_state
.containing_scope_rule_state
.inner_dependencies
.append(&mut scope_dependencies);
}
if has_nested_rules {
handled = false;
list_for_nested_rules = Some(if collect_replaced_selectors {
SelectorList::from_iter(replaced_selectors.drain(..))
} else {
style_rule.selectors.clone()
});
}
},
CssRule::NestedDeclarations(ref rule) => {
if let Some(ref ancestor_selectors) =
containing_rule_state.ancestor_selector_lists.last()
{
let decls = &rule.read_with(guard).block;
let selectors = match containing_rule_state.nested_declarations_context {
NestedDeclarationsContext::Style => ancestor_selectors,
NestedDeclarationsContext::Scope => &*IMPLICIT_SCOPE,
};
let mut inner_dependencies: Option<Vec<Dependency>> = containing_rule_state
.scope_is_effective()
.then(|| Vec::new());
self.add_styles(
selectors,
decls,
/* ancestor_selectors = */ None,
containing_rule_state,
/* replaced_selectors = */ None,
guard,
// We don't need to rebuild invalidation data, since our ancestor style
// rule would've done this.
SheetRebuildKind::CascadeOnly,
precomputed_pseudo_element_decls.as_deref_mut(),
quirks_mode,
inner_dependencies.as_mut(),
)?;
if let Some(mut scope_dependencies) = inner_dependencies {
containing_rule_state
.containing_scope_rule_state
.inner_dependencies
.append(&mut scope_dependencies);
}
}
},
CssRule::Keyframes(ref keyframes_rule) => {
debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
let keyframes_rule = keyframes_rule.read_with(guard);
let name = keyframes_rule.name.as_atom().clone();
let animation = KeyframesAnimation::from_keyframes(
&keyframes_rule.keyframes,
keyframes_rule.vendor_prefix.clone(),
guard,
);
self.animations.try_insert_with(
name,
animation,
containing_rule_state.layer_id,
compare_keyframes_in_same_layer,
)?;
},
CssRule::Property(ref registration) => {
self.custom_property_registrations.try_insert(
registration.name.0.clone(),
Arc::clone(registration),
containing_rule_state.layer_id,
)?;
},
CssRule::FontFace(ref rule) => {
// NOTE(emilio): We don't care about container_condition_id
// because:
//
// Global, name-defining at-rules such as @keyframes or
// @font-face or @layer that are defined inside container
// queries are not constrained by the container query
// conditions.
//
// https://drafts.csswg.org/css-contain-3/#container-rule
// (Same elsewhere)
self.extra_data
.add_font_face(rule, containing_rule_state.layer_id);
},
CssRule::FontFeatureValues(ref rule) => {
self.extra_data
.add_font_feature_values(rule, containing_rule_state.layer_id);
},
CssRule::FontPaletteValues(ref rule) => {
self.extra_data
.add_font_palette_values(rule, containing_rule_state.layer_id);
},
CssRule::CounterStyle(ref rule) => {
self.extra_data.add_counter_style(
guard,
rule,
containing_rule_state.layer_id,
)?;
},
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(
name,
rule.clone(),
containing_rule_state.layer_id,
)?;
},
CssRule::Page(ref rule) => {
self.extra_data
.add_page(guard, rule, containing_rule_state.layer_id)?;
handled = false;
},
CssRule::ViewTransition(ref rule) => {
self.extra_data
.add_view_transition(rule, containing_rule_state.layer_id);
},
_ => {
handled = false;
},
}
if handled {
// Assert that there are no children, and that the rule is
// effective.
if cfg!(debug_assertions) {
let mut effective = false;
let children = EffectiveRulesIterator::<&CustomMediaMap>::children(
rule,
device,
quirks_mode,
&self.custom_media,
guard,
&mut effective,
);
debug_assert!(children.is_empty());
debug_assert!(effective);
}
continue;
}
let mut effective = false;
let children = EffectiveRulesIterator::<&CustomMediaMap>::children(
rule,
device,
quirks_mode,
&self.custom_media,
guard,
&mut effective,
);
if !effective {
continue;
}
fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> LayerId {
// TODO: Measure what's more common / expensive, if
// layer.clone() or the double hash lookup in the insert
// case.
if let Some(id) = data.layer_id.get(layer) {
return *id;
}
let id = LayerId(data.layers.len() as u16);
let parent_layer_id = if layer.layer_names().len() > 1 {
let mut parent = layer.clone();
parent.0.pop();
*data
.layer_id
.get_mut(&parent)
.expect("Parent layers should be registered before child layers")
} else {
LayerId::root()
};
data.layers[parent_layer_id.0 as usize].children.push(id);
data.layers.push(CascadeLayer {
id,
// NOTE(emilio): Order is evaluated after rebuild in
// compute_layer_order.
order: LayerOrder::first(),
children: vec![],
});
data.layer_id.insert(layer.clone(), id);
id
}
fn maybe_register_layers(
data: &mut CascadeData,
name: Option<&LayerName>,
containing_rule_state: &mut ContainingRuleState,
) {
let anon_name;
let name = match name {
Some(name) => name,
None => {
anon_name = LayerName::new_anonymous();
&anon_name
},
};
for name in name.layer_names() {
containing_rule_state.layer_name.0.push(name.clone());
containing_rule_state.layer_id =
maybe_register_layer(data, &containing_rule_state.layer_name);
}
debug_assert_ne!(containing_rule_state.layer_id, LayerId::root());
}
let saved_containing_rule_state = containing_rule_state.save();
match *rule {
CssRule::Import(ref lock) => {
let import_rule = lock.read_with(guard);
if rebuild_kind.should_rebuild_invalidation() {
self.effective_media_query_results
.saw_effective(import_rule);
}
match import_rule.layer {
ImportLayer::Named(ref name) => {
maybe_register_layers(self, Some(name), containing_rule_state)
},
ImportLayer::Anonymous => {
maybe_register_layers(self, None, containing_rule_state)
},
ImportLayer::None => {},
}
},
CssRule::Media(ref media_rule) => {
if rebuild_kind.should_rebuild_invalidation() {
self.effective_media_query_results
.saw_effective(&**media_rule);
}
},
CssRule::LayerBlock(ref rule) => {
maybe_register_layers(self, rule.name.as_ref(), containing_rule_state);
},
CssRule::CustomMedia(ref custom_media) => {
self.custom_media
.insert(custom_media.name.0.clone(), custom_media.condition.clone());
},
CssRule::LayerStatement(ref rule) => {
for name in &*rule.names {
maybe_register_layers(self, Some(name), containing_rule_state);
// Register each layer individually.
containing_rule_state.restore(&saved_containing_rule_state);
}
},
CssRule::Style(..) => {
containing_rule_state.nested_declarations_context =
NestedDeclarationsContext::Style;
if let Some(s) = list_for_nested_rules {
containing_rule_state.ancestor_selector_lists.push(s);
}
},
CssRule::Container(ref rule) => {
let id = ContainerConditionId(self.container_conditions.len() as u16);
let condition = ContainerConditionReference {
parent: containing_rule_state.container_condition_id,
conditions: rule.conditions.0.clone(),
};
self.container_conditions.push(condition);
containing_rule_state.container_condition_id = id;
},
CssRule::StartingStyle(..) => {
containing_rule_state
.cascade_flags
.insert(RuleCascadeFlags::STARTING_STYLE);
},
CssRule::AppearanceBase(..) => {
containing_rule_state
.cascade_flags
.insert(RuleCascadeFlags::APPEARANCE_BASE);
},
CssRule::Scope(ref rule) => {
containing_rule_state.nested_declarations_context =
NestedDeclarationsContext::Scope;
let id = ScopeConditionId(self.scope_conditions.len() as u16);
let mut matches_shadow_host = false;
let implicit_scope_root = if let Some(start) = rule.bounds.start.as_ref() {
matches_shadow_host = scope_start_matches_shadow_host(start);
// Would be unused, but use the default as fallback.
StylistImplicitScopeRoot::default()
} else {
// (Re)Moving stylesheets trigger a complete flush, so saving the implicit
// root here should be safe.
if let Some(root) = stylesheet.implicit_scope_root() {
matches_shadow_host = root.matches_shadow_host();
match root {
ImplicitScopeRoot::InLightTree(_)
| ImplicitScopeRoot::Constructed
| ImplicitScopeRoot::DocumentElement => {
StylistImplicitScopeRoot::Normal(root)
},
ImplicitScopeRoot::ShadowHost(_)
| ImplicitScopeRoot::InShadowTree(_) => {
// Style data can be shared between shadow trees, so we must
// query the implicit root for that specific tree.
// Shared stylesheet means shared sheet indices, so we can
// use that to locate the implicit root.
// Technically, this can also be applied to the light tree,
// but that requires also knowing about what cascade level we're at.
StylistImplicitScopeRoot::Cached(sheet_index)
},
}
} else {
// Could not find implicit scope root, but use the default as fallback.
StylistImplicitScopeRoot::default()
}
};
let replaced =
{
let start = rule.bounds.start.as_ref().map(|selector| {
match containing_rule_state.ancestor_selector_lists.last() {
Some(s) => selector.replace_parent_selector(s),
None => selector.clone(),
}
});
let implicit_scope_selector = &*IMPLICIT_SCOPE;
let end = rule.bounds.end.as_ref().map(|selector| {
selector.replace_parent_selector(implicit_scope_selector)
});
containing_rule_state
.ancestor_selector_lists
.push(implicit_scope_selector.clone());
ScopeBoundsWithHashes::new(quirks_mode, start, end)
};
if let Some(selectors) = replaced.start.as_ref() {
self.scope_subject_map
.add_bound_start(&selectors.selectors, quirks_mode);
}
containing_rule_state
.containing_scope_rule_state
.matches_shadow_host
.nest_for_scope(matches_shadow_host);
containing_rule_state.containing_scope_rule_state.id = id;
containing_rule_state
.containing_scope_rule_state
.inner_dependencies
.reserve(children.iter().len());
},
// We don't care about any other rule.
_ => {},
}
if !stylesheet.is_effective_for_device(device, &self.custom_media, guard) {
return Ok(());
}
let contents = stylesheet.contents(guard);
if rebuild_kind.should_rebuild_invalidation() {
self.effective_media_query_results.saw_effective(&*contents);
}
let mut state = ContainingRuleState::default();
self.add_rule_list(
contents.rules(guard).iter(),
device,
quirks_mode,
stylesheet,
sheet_index,
guard,
rebuild_kind,
&mut state,
precomputed_pseudo_element_decls.as_deref_mut(),
difference.as_deref_mut(),
)?;
Ok(())
}
/// Returns whether all the media-feature affected values matched before and
/// match now in the given stylesheet.
pub fn media_feature_affected_matches<S>(
&self,
stylesheet: &S,
guard: &SharedRwLockReadGuard,
device: &Device,
quirks_mode: QuirksMode,
) -> bool
where
S: StylesheetInDocument + 'static,
{
use crate::invalidation::media_queries::PotentiallyEffectiveMediaRules;
let effective_now = stylesheet.is_effective_for_device(device, &self.custom_media, guard);
let contents = stylesheet.contents(guard);
let effective_then = self.effective_media_query_results.was_effective(contents);
fn revalidate_scopes<E: TElement>(
&self,
element: &E,
matching_context: &mut MatchingContext<E::Impl>,
result: &mut ScopeRevalidationResult,
) {
// TODO(dshin): A scope block may not contain style rule for this element, but we don't keep
// track of that, so we check _all_ scope conditions. It's possible for two comparable elements
// to share scope & relevant styles rules, but also differ in scopes that do not contain style
// rules relevant to them. So while we can be certain that an identical result share scoped styles
// (Given that other sharing conditions are met), it is uncertain if elements with non-matching
// results do not.
for condition_id in 1..self.scope_conditions.len() {
let condition = &self.scope_conditions[condition_id];
let matches = if condition.is_trivial {
// Just ignore this condition - for style sharing candidates, guaranteed
// the same match result.
continue;
} else {
let result = scope_root_candidates(
&self.scope_conditions,
ScopeConditionId(condition_id as u16),
element,
// This should be ok since we aren't sharing styles across shadow boundaries.
false,
&self.scope_subject_map,
matching_context,
);
!result.candidates.is_empty()
};
result.scopes_matched.push(matches);
}
}
/// Clears the cascade data, but not the invalidation data.
fn clear_cascade_data(&mut self) {
self.normal_rules.clear();
if let Some(ref mut slotted_rules) = self.slotted_rules {
slotted_rules.clear();
}
if let Some(ref mut part_rules) = self.part_rules {
part_rules.clear();
}
if let Some(ref mut host_rules) = self.featureless_host_rules {
host_rules.clear();
}
self.animations.clear();
self.custom_property_registrations.clear();
self.layer_id.clear();
self.layers.clear();
self.layers.push(CascadeLayer::root());
self.custom_media.clear();
self.container_conditions.clear();
self.container_conditions
.push(ContainerConditionReference::none());
self.scope_conditions.clear();
self.scope_conditions.push(ScopeConditionReference::none());
self.extra_data.clear();
self.rules_source_order = 0;
self.num_selectors = 0;
self.num_declarations = 0;
}
/// A rule, that wraps a style rule, but represents a single selector of the
/// rule.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct Rule {
/// The selector this struct represents. We store this and the
/// any_{important,normal} booleans inline in the Rule to avoid
/// pointer-chasing when gathering applicable declarations, which
/// can ruin performance when there are a lot of rules.
#[ignore_malloc_size_of = "CssRules have primary refs, we measure there"]
pub selector: Selector<SelectorImpl>,
/// The ancestor hashes associated with the selector.
pub hashes: AncestorHashes,
/// The source order this style rule appears in. Note that we only use
/// three bytes to store this value in ApplicableDeclarationsBlock, so
/// we could repurpose that storage here if we needed to.
pub source_order: u32,
/// The current layer id of this style rule.
pub layer_id: LayerId,
/// The current @container rule id.
pub container_condition_id: ContainerConditionId,
/// Flags for special cascade behaviors.
pub cascade_flags: RuleCascadeFlags,
/// The current @scope rule id.
pub scope_condition_id: ScopeConditionId,
/// The actual style rule.
#[ignore_malloc_size_of = "Secondary ref. Primary ref is in StyleRule under Stylesheet."]
pub style_source: StyleSource,
}
// The size of this is critical to performance on the bloom-basic
// microbenchmark.
// When iterating over a large Rule array, we want to be able to fast-reject
// selectors (with the inline hashes) with as few cache misses as possible.
size_of_test!(Rule, 40);
/// A function to be able to test the revalidation stuff.
pub fn needs_revalidation_for_testing(s: &Selector<SelectorImpl>) -> bool {
let mut needs_revalidation = false;
let mut mapped_ids = Default::default();
let mut nth_of_mapped_ids = Default::default();
let mut attribute_dependencies = Default::default();
let mut nth_of_class_dependencies = Default::default();
let mut nth_of_attribute_dependencies = Default::default();
let mut nth_of_custom_state_dependencies = Default::default();
let mut state_dependencies = ElementState::empty();
let mut nth_of_state_dependencies = ElementState::empty();
let mut document_state_dependencies = DocumentState::empty();
let mut visitor = StylistSelectorVisitor {
passed_rightmost_selector: false,
needs_revalidation: &mut needs_revalidation,
in_selector_list_of: SelectorListKind::default(),
mapped_ids: &mut mapped_ids,
nth_of_mapped_ids: &mut nth_of_mapped_ids,
attribute_dependencies: &mut attribute_dependencies,
nth_of_class_dependencies: &mut nth_of_class_dependencies,
nth_of_attribute_dependencies: &mut nth_of_attribute_dependencies,
nth_of_custom_state_dependencies: &mut nth_of_custom_state_dependencies,
state_dependencies: &mut state_dependencies,
nth_of_state_dependencies: &mut nth_of_state_dependencies,
document_state_dependencies: &mut document_state_dependencies,
};
s.visit(&mut visitor);
needs_revalidation
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.167Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.