style: Split up push_applicable_declarations.

Introduce RuleCollector, which contains all the state we need during the
cascade, and allows to reuse a bit of code.

Differential Revision: https://phabricator.services.mozilla.com/D11233
This commit is contained in:
Emilio Cobos Álvarez 2018-11-08 23:07:40 +00:00
parent b7cefa5814
commit 667457a16c
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
3 changed files with 426 additions and 293 deletions

View file

@ -17,6 +17,7 @@ use crate::media_queries::Device;
use crate::properties::{self, CascadeMode, ComputedValues};
use crate::properties::{AnimationRules, PropertyDeclarationBlock};
use crate::rule_cache::{RuleCache, RuleCacheConditions};
use crate::rule_collector::RuleCollector;
use crate::rule_tree::{CascadeLevel, RuleTree, ShadowCascadeOrder, StrongRuleNode, StyleSource};
use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, SelectorMap, SelectorMapEntry};
use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, SnapshotMap};
@ -47,7 +48,6 @@ use selectors::visitor::SelectorVisitor;
use selectors::NthIndexCache;
use servo_arc::{Arc, ArcBorrow};
use smallbitvec::SmallBitVec;
use smallvec::SmallVec;
use std::ops;
use std::sync::Mutex;
use style_traits::viewport::ViewportConstraints;
@ -180,10 +180,11 @@ impl UserAgentCascadeData {
}
}
/// All the computed information for a stylesheet.
/// All the computed information for all the stylesheets that apply to the
/// document.
#[derive(Default)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
struct DocumentCascadeData {
pub struct DocumentCascadeData {
#[cfg_attr(
feature = "servo",
ignore_malloc_size_of = "Arc, owned by UserAgentCascadeDataCache"
@ -210,7 +211,9 @@ impl<'a> Iterator for DocumentCascadeDataIter<'a> {
}
impl DocumentCascadeData {
fn borrow_for_origin(&self, origin: Origin) -> &CascadeData {
/// Borrows the cascade data for a given origin.
#[inline]
pub fn borrow_for_origin(&self, origin: Origin) -> &CascadeData {
match origin {
Origin::UserAgent => &self.user_agent.cascade_data,
Origin::Author => &self.author,
@ -412,10 +415,16 @@ impl Stylist {
}
}
/// Returns the cascade data for the author level.
/// Returns the document cascade data.
#[inline]
pub fn author_cascade_data(&self) -> &CascadeData {
&self.cascade_data.author
pub fn cascade_data(&self) -> &DocumentCascadeData {
&self.cascade_data
}
/// Returns whether author styles are enabled or not.
#[inline]
pub fn author_styles_enabled(&self) -> AuthorStylesEnabled {
self.author_styles_enabled
}
/// Iterate through all the cascade datas from the document.
@ -1098,11 +1107,6 @@ impl Stylist {
}
/// Returns the applicable CSS declarations for the given element.
///
/// This corresponds to `ElementRuleCollector` in WebKit, and should push to
/// elements in the list in the order defined by:
///
/// https://drafts.csswg.org/css-cascade/#cascade-origin
pub fn push_applicable_declarations<E, F>(
&self,
element: E,
@ -1118,284 +1122,18 @@ impl Stylist {
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
// 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()));
let rule_hash_target = element.rule_hash_target();
let matches_user_and_author_rules = rule_hash_target.matches_user_and_author_rules();
// Normal user-agent rules.
if let Some(map) = self
.cascade_data
.user_agent
.cascade_data
.normal_rules(pseudo_element)
{
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::UANormal,
0,
);
}
// NB: the following condition, although it may look somewhat
// inaccurate, would be equivalent to something like:
//
// element.matches_user_and_author_rules() ||
// (is_implemented_pseudo &&
// rule_hash_target.matches_user_and_author_rules())
//
// Which may be more what you would probably expect.
if matches_user_and_author_rules {
// User normal rules.
if let Some(map) = self.cascade_data.user.normal_rules(pseudo_element) {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::UserNormal,
0,
);
}
}
if rule_inclusion == RuleInclusion::DefaultOnly {
return;
}
if pseudo_element.is_none() {
// Presentational hints.
//
// These go before author rules, but after user rules, see:
// https://drafts.csswg.org/css-cascade/#preshint
let length_before_preshints = applicable_declarations.len();
element.synthesize_presentational_hints_for_legacy_attributes(
context.visited_handling(),
applicable_declarations,
);
if cfg!(debug_assertions) {
if applicable_declarations.len() != length_before_preshints {
for declaration in &applicable_declarations[length_before_preshints..] {
assert_eq!(declaration.level(), CascadeLevel::PresHints);
}
}
}
}
if self.author_styles_enabled == AuthorStylesEnabled::No {
return;
}
let mut match_document_author_rules = matches_user_and_author_rules;
let mut shadow_cascade_order = 0;
// XBL / Shadow DOM rules, which are author rules too.
if let Some(shadow) = rule_hash_target.shadow_root() {
debug_assert!(
matches_user_and_author_rules,
"NAC should not be a shadow host"
);
if let Some(map) = shadow
.style_data()
.and_then(|data| data.host_rules(pseudo_element))
{
context.with_shadow_host(Some(rule_hash_target), |context| {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::InnerShadowNormal,
shadow_cascade_order,
);
});
shadow_cascade_order += 1;
}
}
// Match slotted rules in reverse order, so that the outer slotted
// rules come before the inner rules (and thus have less priority).
let mut slots = SmallVec::<[_; 3]>::new();
let mut current = rule_hash_target.assigned_slot();
while let Some(slot) = current {
debug_assert!(
matches_user_and_author_rules,
"We should not slot NAC anywhere"
);
slots.push(slot);
current = slot.assigned_slot();
}
for slot in slots.iter().rev() {
let shadow = slot.containing_shadow().unwrap();
if let Some(map) = shadow
.style_data()
.and_then(|data| data.slotted_rules(pseudo_element))
{
context.with_shadow_host(Some(shadow.host()), |context| {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::InnerShadowNormal,
shadow_cascade_order,
);
});
shadow_cascade_order += 1;
}
}
if matches_user_and_author_rules {
let mut current_containing_shadow = rule_hash_target.containing_shadow();
while let Some(containing_shadow) = current_containing_shadow {
let cascade_data = containing_shadow.style_data();
let host = containing_shadow.host();
if let Some(map) = cascade_data.and_then(|data| data.normal_rules(pseudo_element)) {
context.with_shadow_host(Some(host), |context| {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::SameTreeAuthorNormal,
shadow_cascade_order,
);
});
shadow_cascade_order += 1;
}
let host_is_svg_use_element =
host.is_svg_element() && host.local_name() == &*local_name!("use");
if !host_is_svg_use_element {
match_document_author_rules = false;
break;
}
debug_assert!(
cascade_data.is_none(),
"We allow no stylesheets in <svg:use> subtrees"
);
// NOTE(emilio): Hack so <svg:use> matches the rules of the
// enclosing tree.
//
// 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.
current_containing_shadow = host.containing_shadow();
match_document_author_rules = current_containing_shadow.is_none();
}
}
let cut_xbl_binding_inheritance =
element.each_xbl_cascade_data(|cascade_data, quirks_mode| {
if let Some(map) = cascade_data.normal_rules(pseudo_element) {
// NOTE(emilio): This is needed because the XBL stylist may
// think it has a different quirks mode than the document.
let mut matching_context = MatchingContext::new(
context.matching_mode(),
context.bloom_filter,
context.nth_index_cache.as_mut().map(|s| &mut **s),
quirks_mode,
);
matching_context.pseudo_element_matching_fn =
context.pseudo_element_matching_fn;
// SameTreeAuthorNormal instead of InnerShadowNormal to
// preserve behavior, though that's kinda fishy...
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
&mut matching_context,
flags_setter,
CascadeLevel::SameTreeAuthorNormal,
shadow_cascade_order,
);
}
});
match_document_author_rules &= !cut_xbl_binding_inheritance;
if match_document_author_rules {
// Author normal rules.
if let Some(map) = self.cascade_data.author.normal_rules(pseudo_element) {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::SameTreeAuthorNormal,
shadow_cascade_order,
);
}
}
// Style attribute ("Normal override declarations").
if let Some(sa) = style_attribute {
applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
sa.clone_arc(),
CascadeLevel::StyleAttributeNormal,
));
}
// Declarations from SVG SMIL animation elements.
if let Some(so) = smil_override {
applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
so.clone_arc(),
CascadeLevel::SMILOverride,
));
}
// The animations sheet (CSS animations, script-generated
// animations, and CSS transitions that are no longer tied to CSS
// markup).
if let Some(anim) = animation_rules.0 {
applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
anim.clone(),
CascadeLevel::Animations,
));
}
//
// !important rules are handled during rule tree insertion.
//
// The transitions sheet (CSS transitions that are tied to CSS
// markup).
if let Some(anim) = animation_rules.1 {
applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
anim.clone(),
CascadeLevel::Transitions,
));
}
RuleCollector::new(
self,
element,
pseudo_element,
style_attribute,
smil_override,
animation_rules,
rule_inclusion,
applicable_declarations,
context,
flags_setter,
).collect_all();
}
/// Given an id, returns whether there might be any rules for that id in any
@ -2100,18 +1838,21 @@ impl CascadeData {
self.attribute_dependencies.contains(local_name)
}
/// Returns the normal rule map for a given pseudo-element.
#[inline]
fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
pub fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
self.normal_rules.rules(pseudo)
}
/// Returns the host pseudo rule map for a given pseudo-element.
#[inline]
fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
pub fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
self.host_rules.as_ref().and_then(|d| d.rules(pseudo))
}
/// Returns the slotted rule map for a given pseudo-element.
#[inline]
fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
pub fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo))
}