/* 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/. */
//! Collects a series of applicable rules for a given element.
usecrate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; usecrate::dom::{TElement, TNode, TShadowRoot}; usecrate::properties::{AnimationDeclarations, PropertyDeclarationBlock}; usecrate::rule_tree::{CascadeLevel, ShadowCascadeOrder}; usecrate::selector_map::SelectorMap; usecrate::selector_parser::PseudoElement; usecrate::shared_lock::Locked; usecrate::stylesheets::{layer_rule::LayerOrder, Origin}; usecrate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; use selectors::matching::{MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; use smallvec::SmallVec;
/// This is a bit of a hack so <svg:use> matches the rules of the enclosing /// tree. /// /// This function returns the containing shadow host ignoring <svg:use> shadow /// trees, since those match the enclosing tree's rules. /// /// Only a handful of places need to really care about this. This is not a /// problem for invalidation and that kind of stuff because they still don't /// match rules based on elements outside of the shadow tree, and because the /// <svg:use> subtrees are immutable and recreated each time the source tree /// changes. /// /// We historically allow cross-document <svg:use> to have these rules applied, /// but I think that's not great. Gecko is the only engine supporting that. /// /// See https://github.com/w3c/svgwg/issues/504 for the relevant spec /// discussion. #[inline] pubfn containing_shadow_ignoring_svg_use<E: TElement>(
element: E,
) -> Option<<E::ConcreteNode as TNode>::ConcreteShadowRoot> { letmut shadow = element.containing_shadow()?; loop { let host = shadow.host(); let host_is_svg_use_element =
host.is_svg_element() && host.local_name() == &**local_name!("use"); if !host_is_svg_use_element { return Some(shadow);
}
debug_assert!(
shadow.style_data().is_none(), "We allow no stylesheets in <svg:use> subtrees"
);
shadow = host.containing_shadow()?;
}
}
/// An object that we use with all the intermediate state needed for the /// cascade. /// /// This is done basically to be able to organize the cascade in smaller /// functions, and be able to reason about it easily. pubstruct RuleCollector<'a, 'b: 'a, E> where
E: TElement,
{
element: E,
rule_hash_target: E,
stylist: &'a Stylist,
pseudo_element: Option<&'a PseudoElement>,
style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
animation_declarations: AnimationDeclarations,
rule_inclusion: RuleInclusion,
rules: &'a mut ApplicableDeclarationList,
context: &'a mut MatchingContext<'b, E::Impl>,
matches_user_and_content_rules: bool,
matches_document_author_rules: bool,
in_sort_scope: bool,
}
impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E> where
E: TElement,
{ /// Trivially construct a new collector. pubfn new(
stylist: &'a Stylist,
element: E,
pseudo_element: Option<&'a PseudoElement>,
style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
animation_declarations: AnimationDeclarations,
rule_inclusion: RuleInclusion,
rules: &'a mut ApplicableDeclarationList,
context: &'a mut MatchingContext<'b, E::Impl>,
) -> Self { // When we're matching with matching_mode = // `ForStatelessPseudoeElement`, the "target" for the rule hash is the // element itself, since it's what's generating the pseudo-element. let rule_hash_target = match context.matching_mode() {
MatchingMode::ForStatelessPseudoElement => element,
MatchingMode::Normal => element.rule_hash_target(),
};
let matches_user_and_content_rules = rule_hash_target.matches_user_and_content_rules();
// Gecko definitely has pseudo-elements with style attributes, like // ::-moz-color-swatch.
debug_assert!(
cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements"
);
debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
/// Sets up the state necessary to collect rules from a given DOM tree /// (either the document tree, or a shadow tree). /// /// All rules in the same tree need to be matched together, and this /// function takes care of sorting them by specificity and source order. #[inline] fn in_tree(&mutself, host: Option<E>, f: impl FnOnce(&mutSelf)) {
debug_assert!(!self.in_sort_scope, "Nested sorting makes no sense"); let start = self.rules.len(); self.in_sort_scope = true; let old_host = self.context.current_host.take(); self.context.current_host = host.map(|e| e.opaque());
f(self); if start != self.rules.len() { self.rules[start..].sort_unstable_by_key(|block| block.sort_key());
} self.context.current_host = old_host; self.in_sort_scope = false;
}
let cascade_data = self.stylist.cascade_data().borrow_for_origin(origin); let map = match cascade_data.normal_rules(self.pseudo_element) {
Some(m) => m,
None => return,
};
fn collect_user_rules(&mutself) { if !self.matches_user_and_content_rules { return;
}
self.collect_stylist_rules(Origin::User);
}
/// Presentational hints. /// /// These go before author rules, but after user rules, see: /// https://drafts.csswg.org/css-cascade/#preshint fn collect_presentational_hints(&mutself) { ifself.pseudo_element.is_some() { return;
}
let length_before_preshints = self.rules.len(); self.element
.synthesize_presentational_hints_for_legacy_attributes( self.context.visited_handling(), self.rules,
); if cfg!(debug_assertions) { ifself.rules.len() != length_before_preshints { for declaration in &self.rules[length_before_preshints..] {
assert_eq!(declaration.level(), CascadeLevel::PresHints);
}
}
}
}
/// Collects the rules for the ::slotted pseudo-element and the :host /// pseudo-class. fn collect_host_and_slotted_rules(&mutself) { letmut slots = SmallVec::<[_; 3]>::new(); letmut current = self.rule_hash_target.assigned_slot(); letmut shadow_cascade_order = ShadowCascadeOrder::for_outermost_shadow_tree();
whilelet Some(slot) = current {
debug_assert!( self.matches_user_and_content_rules, "We should not slot NAC anywhere"
);
slots.push(slot);
current = slot.assigned_slot();
shadow_cascade_order.dec();
}
self.collect_host_rules(shadow_cascade_order);
// Match slotted rules in reverse order, so that the outer slotted rules // come before the inner rules (and thus have less priority). for slot in slots.iter().rev() {
shadow_cascade_order.inc();
let shadow = slot.containing_shadow().unwrap(); let data = match shadow.style_data() {
Some(d) => d,
None => continue,
}; let slotted_rules = match data.slotted_rules(self.pseudo_element) {
Some(r) => r,
None => continue,
};
letmut parts = SmallVec::<[_; 3]>::new(); self.rule_hash_target.each_part(|p| parts.push(p.clone()));
loop { if parts.is_empty() { return;
}
let inner_shadow_host = inner_shadow.host(); let outer_shadow = inner_shadow_host.containing_shadow(); let cascade_data = match outer_shadow {
Some(shadow) => shadow.style_data(),
None => Some( self.stylist
.cascade_data()
.borrow_for_origin(Origin::Author),
),
};
iflet Some(cascade_data) = cascade_data { iflet Some(part_rules) = cascade_data.part_rules(self.pseudo_element) { let containing_host = outer_shadow.map(|s| s.host()); let cascade_level = CascadeLevel::AuthorNormal {
shadow_cascade_order,
}; self.in_tree(containing_host, |collector| { for p in &parts { iflet Some(part_rules) = part_rules.get(&p.0) {
collector.collect_rules_in_list(
part_rules,
cascade_level,
cascade_data,
);
}
}
});
shadow_cascade_order.inc();
}
}
inner_shadow = match outer_shadow {
Some(s) => s,
None => break, // Nowhere to export to.
};
letmut new_parts = SmallVec::new(); for part in &parts {
inner_shadow_host.each_exported_part(part, |exported_part| {
new_parts.push(exported_part.clone());
});
}
parts = new_parts;
}
}
// The animations sheet (CSS animations, script-generated // animations, and CSS transitions that are no longer tied to CSS // markup). iflet Some(anim) = self.animation_declarations.animations.take() { self.rules
.push(ApplicableDeclarationBlock::from_declarations(
anim,
CascadeLevel::Animations,
LayerOrder::root(),
));
}
// The transitions sheet (CSS transitions that are tied to CSS // markup). iflet Some(anim) = self.animation_declarations.transitions.take() { self.rules
.push(ApplicableDeclarationBlock::from_declarations(
anim,
CascadeLevel::Transitions,
LayerOrder::root(),
));
}
}
/// Collects all the rules, leaving the result in `self.rules`. /// /// Note that `!important` rules are handled during rule tree insertion. pubfn collect_all(mutself) { self.collect_user_agent_rules(); self.collect_user_rules(); ifself.rule_inclusion == RuleInclusion::DefaultOnly { return;
} self.collect_presentational_hints(); // FIXME(emilio): Should the author styles enabled stuff avoid the // presentational hints from getting pushed? See bug 1505770. ifself.stylist.author_styles_enabled() == AuthorStylesEnabled::No { return;
} self.collect_host_and_slotted_rules(); self.collect_rules_from_containing_shadow_tree(); self.collect_document_author_rules(); self.collect_style_attribute(); self.collect_part_rules_from_outer_trees(); self.collect_animation_rules();
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet am 2026-06-18)
¤
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.