mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
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:
parent
b7cefa5814
commit
667457a16c
3 changed files with 426 additions and 293 deletions
|
@ -143,6 +143,7 @@ pub mod media_queries;
|
||||||
pub mod parallel;
|
pub mod parallel;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod rule_cache;
|
pub mod rule_cache;
|
||||||
|
pub mod rule_collector;
|
||||||
pub mod rule_tree;
|
pub mod rule_tree;
|
||||||
pub mod scoped_tls;
|
pub mod scoped_tls;
|
||||||
pub mod selector_map;
|
pub mod selector_map;
|
||||||
|
|
391
components/style/rule_collector.rs
Normal file
391
components/style/rule_collector.rs
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Collects a series of applicable rules for a given element.
|
||||||
|
|
||||||
|
use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
|
||||||
|
use dom::{TElement, TShadowRoot};
|
||||||
|
use properties::{AnimationRules, PropertyDeclarationBlock};
|
||||||
|
use rule_tree::{CascadeLevel, ShadowCascadeOrder};
|
||||||
|
use selector_map::SelectorMap;
|
||||||
|
use selector_parser::PseudoElement;
|
||||||
|
use selectors::matching::{ElementSelectorFlags, MatchingContext};
|
||||||
|
use servo_arc::ArcBorrow;
|
||||||
|
use shared_lock::Locked;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use stylesheets::Origin;
|
||||||
|
use stylist::{AuthorStylesEnabled, Rule, RuleInclusion, Stylist};
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub struct RuleCollector<'a, 'b: 'a, E, F: 'a>
|
||||||
|
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_rules: AnimationRules,
|
||||||
|
rule_inclusion: RuleInclusion,
|
||||||
|
rules: &'a mut ApplicableDeclarationList,
|
||||||
|
context: &'a mut MatchingContext<'b, E::Impl>,
|
||||||
|
flags_setter: &'a mut F,
|
||||||
|
shadow_cascade_order: ShadowCascadeOrder,
|
||||||
|
matches_user_and_author_rules: bool,
|
||||||
|
matches_document_author_rules: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F>
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
F: FnMut(&E, ElementSelectorFlags),
|
||||||
|
{
|
||||||
|
/// Trivially construct a new collector.
|
||||||
|
pub fn 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_rules: AnimationRules,
|
||||||
|
rule_inclusion: RuleInclusion,
|
||||||
|
rules: &'a mut ApplicableDeclarationList,
|
||||||
|
context: &'a mut MatchingContext<'b, E::Impl>,
|
||||||
|
flags_setter: &'a mut F,
|
||||||
|
) -> Self {
|
||||||
|
let rule_hash_target = element.rule_hash_target();
|
||||||
|
let matches_user_and_author_rules = rule_hash_target.matches_user_and_author_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()));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
element,
|
||||||
|
rule_hash_target,
|
||||||
|
stylist,
|
||||||
|
pseudo_element,
|
||||||
|
style_attribute,
|
||||||
|
smil_override,
|
||||||
|
animation_rules,
|
||||||
|
rule_inclusion,
|
||||||
|
context,
|
||||||
|
flags_setter,
|
||||||
|
rules,
|
||||||
|
matches_user_and_author_rules,
|
||||||
|
shadow_cascade_order: 0,
|
||||||
|
matches_document_author_rules: matches_user_and_author_rules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_stylist_rules(&mut self, origin: Origin) {
|
||||||
|
let cascade_level = match origin {
|
||||||
|
Origin::UserAgent => CascadeLevel::UANormal,
|
||||||
|
Origin::User => CascadeLevel::UserNormal,
|
||||||
|
Origin::Author => CascadeLevel::SameTreeAuthorNormal,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
map.get_all_matching_rules(
|
||||||
|
self.element,
|
||||||
|
self.rule_hash_target,
|
||||||
|
self.rules,
|
||||||
|
self.context,
|
||||||
|
self.flags_setter,
|
||||||
|
cascade_level,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_user_agent_rules(&mut self) {
|
||||||
|
self.collect_stylist_rules(Origin::UserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_user_rules(&mut self) {
|
||||||
|
if !self.matches_user_and_author_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(&mut self) {
|
||||||
|
if self.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) {
|
||||||
|
if self.rules.len() != length_before_preshints {
|
||||||
|
for declaration in &self.rules[length_before_preshints..] {
|
||||||
|
assert_eq!(declaration.level(), CascadeLevel::PresHints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_rules_in_shadow_tree(
|
||||||
|
&mut self,
|
||||||
|
shadow_host: E,
|
||||||
|
map: &SelectorMap<Rule>,
|
||||||
|
cascade_level: CascadeLevel,
|
||||||
|
) {
|
||||||
|
debug_assert!(shadow_host.shadow_root().is_some());
|
||||||
|
let element = self.element;
|
||||||
|
let rule_hash_target = self.rule_hash_target;
|
||||||
|
let rules = &mut self.rules;
|
||||||
|
let flags_setter = &mut self.flags_setter;
|
||||||
|
let shadow_cascade_order = self.shadow_cascade_order;
|
||||||
|
self.context.with_shadow_host(Some(shadow_host), |context| {
|
||||||
|
map.get_all_matching_rules(
|
||||||
|
element,
|
||||||
|
rule_hash_target,
|
||||||
|
rules,
|
||||||
|
context,
|
||||||
|
flags_setter,
|
||||||
|
cascade_level,
|
||||||
|
shadow_cascade_order,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
self.shadow_cascade_order += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects the rules for the ::slotted pseudo-element.
|
||||||
|
fn collect_slotted_rules(&mut self) {
|
||||||
|
let mut slots = SmallVec::<[_; 3]>::new();
|
||||||
|
let mut current = self.rule_hash_target.assigned_slot();
|
||||||
|
while let Some(slot) = current {
|
||||||
|
debug_assert!(
|
||||||
|
self.matches_user_and_author_rules,
|
||||||
|
"We should not slot NAC anywhere"
|
||||||
|
);
|
||||||
|
slots.push(slot);
|
||||||
|
current = slot.assigned_slot();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
self.collect_rules_in_shadow_tree(
|
||||||
|
shadow.host(),
|
||||||
|
slotted_rules,
|
||||||
|
CascadeLevel::InnerShadowNormal,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_normal_rules_from_containing_shadow_tree(&mut self) {
|
||||||
|
if !self.matches_user_and_author_rules {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_containing_shadow = self.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(self.pseudo_element))
|
||||||
|
{
|
||||||
|
self.collect_rules_in_shadow_tree(host, map, CascadeLevel::SameTreeAuthorNormal);
|
||||||
|
}
|
||||||
|
let host_is_svg_use_element =
|
||||||
|
host.is_svg_element() && host.local_name() == &*local_name!("use");
|
||||||
|
if !host_is_svg_use_element {
|
||||||
|
self.matches_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();
|
||||||
|
self.matches_document_author_rules = current_containing_shadow.is_none();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects the rules for the :host pseudo-class.
|
||||||
|
fn collect_host_rules(&mut self) {
|
||||||
|
let shadow = match self.rule_hash_target.shadow_root() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
self.matches_user_and_author_rules,
|
||||||
|
"NAC should not be a shadow host"
|
||||||
|
);
|
||||||
|
|
||||||
|
let style_data = match shadow.style_data() {
|
||||||
|
Some(d) => d,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let host_rules = match style_data.host_rules(self.pseudo_element) {
|
||||||
|
Some(rules) => rules,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let rule_hash_target = self.rule_hash_target;
|
||||||
|
self.collect_rules_in_shadow_tree(
|
||||||
|
rule_hash_target,
|
||||||
|
host_rules,
|
||||||
|
CascadeLevel::InnerShadowNormal,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_document_author_rules(&mut self) {
|
||||||
|
if !self.matches_document_author_rules {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.collect_stylist_rules(Origin::Author);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_xbl_rules(&mut self) {
|
||||||
|
let element = self.element;
|
||||||
|
let cut_xbl_binding_inheritance =
|
||||||
|
element.each_xbl_cascade_data(|cascade_data, quirks_mode| {
|
||||||
|
let map = match cascade_data.normal_rules(self.pseudo_element) {
|
||||||
|
Some(m) => m,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
self.context.matching_mode(),
|
||||||
|
self.context.bloom_filter,
|
||||||
|
self.context.nth_index_cache.as_mut().map(|s| &mut **s),
|
||||||
|
quirks_mode,
|
||||||
|
);
|
||||||
|
matching_context.pseudo_element_matching_fn =
|
||||||
|
self.context.pseudo_element_matching_fn;
|
||||||
|
|
||||||
|
// SameTreeAuthorNormal instead of InnerShadowNormal to
|
||||||
|
// preserve behavior, though that's kinda fishy...
|
||||||
|
map.get_all_matching_rules(
|
||||||
|
self.element,
|
||||||
|
self.rule_hash_target,
|
||||||
|
self.rules,
|
||||||
|
&mut matching_context,
|
||||||
|
self.flags_setter,
|
||||||
|
CascadeLevel::SameTreeAuthorNormal,
|
||||||
|
self.shadow_cascade_order,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.matches_document_author_rules &= !cut_xbl_binding_inheritance;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_style_attribute_and_animation_rules(&mut self) {
|
||||||
|
if let Some(sa) = self.style_attribute {
|
||||||
|
self.rules
|
||||||
|
.push(ApplicableDeclarationBlock::from_declarations(
|
||||||
|
sa.clone_arc(),
|
||||||
|
CascadeLevel::StyleAttributeNormal,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(so) = self.smil_override {
|
||||||
|
self.rules
|
||||||
|
.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) = self.animation_rules.0.take() {
|
||||||
|
self.rules
|
||||||
|
.push(ApplicableDeclarationBlock::from_declarations(
|
||||||
|
anim,
|
||||||
|
CascadeLevel::Animations,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The transitions sheet (CSS transitions that are tied to CSS
|
||||||
|
// markup).
|
||||||
|
if let Some(anim) = self.animation_rules.1.take() {
|
||||||
|
self.rules
|
||||||
|
.push(ApplicableDeclarationBlock::from_declarations(
|
||||||
|
anim,
|
||||||
|
CascadeLevel::Transitions,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects all the rules, leaving the result in `self.rules`.
|
||||||
|
///
|
||||||
|
/// Note that `!important` rules are handled during rule tree insertion.
|
||||||
|
pub fn collect_all(mut self) {
|
||||||
|
self.collect_user_agent_rules();
|
||||||
|
self.collect_user_rules();
|
||||||
|
if self.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.
|
||||||
|
if self.stylist.author_styles_enabled() == AuthorStylesEnabled::No {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.collect_host_rules();
|
||||||
|
self.collect_slotted_rules();
|
||||||
|
self.collect_normal_rules_from_containing_shadow_tree();
|
||||||
|
self.collect_xbl_rules();
|
||||||
|
self.collect_document_author_rules();
|
||||||
|
self.collect_style_attribute_and_animation_rules();
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ use crate::media_queries::Device;
|
||||||
use crate::properties::{self, CascadeMode, ComputedValues};
|
use crate::properties::{self, CascadeMode, ComputedValues};
|
||||||
use crate::properties::{AnimationRules, PropertyDeclarationBlock};
|
use crate::properties::{AnimationRules, PropertyDeclarationBlock};
|
||||||
use crate::rule_cache::{RuleCache, RuleCacheConditions};
|
use crate::rule_cache::{RuleCache, RuleCacheConditions};
|
||||||
|
use crate::rule_collector::RuleCollector;
|
||||||
use crate::rule_tree::{CascadeLevel, RuleTree, ShadowCascadeOrder, StrongRuleNode, StyleSource};
|
use crate::rule_tree::{CascadeLevel, RuleTree, ShadowCascadeOrder, StrongRuleNode, StyleSource};
|
||||||
use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, SelectorMap, SelectorMapEntry};
|
use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, SelectorMap, SelectorMapEntry};
|
||||||
use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, SnapshotMap};
|
use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, SnapshotMap};
|
||||||
|
@ -47,7 +48,6 @@ use selectors::visitor::SelectorVisitor;
|
||||||
use selectors::NthIndexCache;
|
use selectors::NthIndexCache;
|
||||||
use servo_arc::{Arc, ArcBorrow};
|
use servo_arc::{Arc, ArcBorrow};
|
||||||
use smallbitvec::SmallBitVec;
|
use smallbitvec::SmallBitVec;
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::ops;
|
use std::ops;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use style_traits::viewport::ViewportConstraints;
|
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)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||||
struct DocumentCascadeData {
|
pub struct DocumentCascadeData {
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "servo",
|
feature = "servo",
|
||||||
ignore_malloc_size_of = "Arc, owned by UserAgentCascadeDataCache"
|
ignore_malloc_size_of = "Arc, owned by UserAgentCascadeDataCache"
|
||||||
|
@ -210,7 +211,9 @@ impl<'a> Iterator for DocumentCascadeDataIter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentCascadeData {
|
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 {
|
match origin {
|
||||||
Origin::UserAgent => &self.user_agent.cascade_data,
|
Origin::UserAgent => &self.user_agent.cascade_data,
|
||||||
Origin::Author => &self.author,
|
Origin::Author => &self.author,
|
||||||
|
@ -412,10 +415,16 @@ impl Stylist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the cascade data for the author level.
|
/// Returns the document cascade data.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn author_cascade_data(&self) -> &CascadeData {
|
pub fn cascade_data(&self) -> &DocumentCascadeData {
|
||||||
&self.cascade_data.author
|
&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.
|
/// Iterate through all the cascade datas from the document.
|
||||||
|
@ -1098,11 +1107,6 @@ impl Stylist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the applicable CSS declarations for the given element.
|
/// 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>(
|
pub fn push_applicable_declarations<E, F>(
|
||||||
&self,
|
&self,
|
||||||
element: E,
|
element: E,
|
||||||
|
@ -1118,284 +1122,18 @@ impl Stylist {
|
||||||
E: TElement,
|
E: TElement,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
F: FnMut(&E, ElementSelectorFlags),
|
||||||
{
|
{
|
||||||
// Gecko definitely has pseudo-elements with style attributes, like
|
RuleCollector::new(
|
||||||
// ::-moz-color-swatch.
|
self,
|
||||||
debug_assert!(
|
element,
|
||||||
cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_element.is_none(),
|
pseudo_element,
|
||||||
"Style attributes do not apply to pseudo-elements"
|
style_attribute,
|
||||||
);
|
smil_override,
|
||||||
debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
|
animation_rules,
|
||||||
|
rule_inclusion,
|
||||||
let rule_hash_target = element.rule_hash_target();
|
applicable_declarations,
|
||||||
|
context,
|
||||||
let matches_user_and_author_rules = rule_hash_target.matches_user_and_author_rules();
|
flags_setter,
|
||||||
|
).collect_all();
|
||||||
// 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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given an id, returns whether there might be any rules for that id in any
|
/// 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)
|
self.attribute_dependencies.contains(local_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the normal rule map for a given pseudo-element.
|
||||||
#[inline]
|
#[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)
|
self.normal_rules.rules(pseudo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the host pseudo rule map for a given pseudo-element.
|
||||||
#[inline]
|
#[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))
|
self.host_rules.as_ref().and_then(|d| d.rules(pseudo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the slotted rule map for a given pseudo-element.
|
||||||
#[inline]
|
#[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))
|
self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue