Bug 1364850: Move PseudoElement to be just another combinator in selectors. r=bholley

MozReview-Commit-ID: 8OoOIodkKJ5
Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
Emilio Cobos Álvarez 2017-05-15 23:52:09 +02:00
parent 8375319928
commit 522f8489d6
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
19 changed files with 675 additions and 541 deletions

View file

@ -26,10 +26,9 @@ use properties::PropertyDeclarationBlock;
use restyle_hints::{RestyleHint, DependencySet};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
use selector_parser::{SelectorImpl, PseudoElement, SnapshotMap};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext};
use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, Combinator, Component, Selector, SelectorInner, SelectorIter};
use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector};
use selectors::visitor::SelectorVisitor;
@ -479,9 +478,9 @@ impl Stylist {
rule: &Arc<Locked<StyleRule>>,
stylesheet: &Stylesheet)
{
let map = if let Some(ref pseudo_selector) = selector.pseudo_element {
let map = if let Some(pseudo) = selector.pseudo_element() {
self.pseudos_map
.entry(pseudo_selector.pseudo_element().clone())
.entry(pseudo.clone())
.or_insert_with(PerPseudoElementSelectorMap::new)
.borrow_for_origin(&stylesheet.origin)
} else {
@ -525,9 +524,6 @@ impl Stylist {
#[inline]
fn note_attribute_and_state_dependencies(&mut self, selector: &Selector<SelectorImpl>) {
if let Some(ref pseudo_selector) = selector.pseudo_element {
self.state_dependencies.insert(pseudo_selector.state());
}
selector.visit(&mut AttributeAndStateDependencyVisitor(self));
}
@ -635,14 +631,13 @@ impl Stylist {
guards: &StylesheetGuards,
element: &E,
pseudo: &PseudoElement,
pseudo_state: ElementState,
parent: &Arc<ComputedValues>,
font_metrics: &FontMetricsProvider)
-> Option<ComputedStyle>
where E: TElement,
{
let rule_node =
match self.lazy_pseudo_rules(guards, element, pseudo, pseudo_state) {
match self.lazy_pseudo_rules(guards, element, pseudo) {
Some(rule_node) => rule_node,
None => return None
};
@ -673,8 +668,7 @@ impl Stylist {
pub fn lazy_pseudo_rules<E>(&self,
guards: &StylesheetGuards,
element: &E,
pseudo: &PseudoElement,
pseudo_state: ElementState)
pseudo: &PseudoElement)
-> Option<StrongRuleNode>
where E: TElement
{
@ -708,14 +702,15 @@ impl Stylist {
};
let mut declarations = ApplicableDeclarationList::new();
let mut matching_context =
MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None);
self.push_applicable_declarations(element,
None,
Some(pseudo),
None,
None,
AnimationRules(None, None),
Some((pseudo, pseudo_state)),
&mut declarations,
&mut MatchingContext::default(),
&mut matching_context,
&mut set_selector_flags);
if declarations.is_empty() {
return None
@ -839,16 +834,15 @@ impl Stylist {
pub fn push_applicable_declarations<E, V, F>(
&self,
element: &E,
parent_bf: Option<&BloomFilter>,
pseudo_element: Option<&PseudoElement>,
style_attribute: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
smil_override: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
animation_rules: AnimationRules,
pseudo_element: Option<(&PseudoElement, ElementState)>,
applicable_declarations: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F)
where E: TElement,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> + ::std::fmt::Debug,
F: FnMut(&E, ElementSelectorFlags),
{
debug_assert!(!self.is_device_dirty);
@ -857,20 +851,31 @@ impl Stylist {
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.as_ref().map_or(true, |p| !p.0.is_precomputed()));
debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
let map = match pseudo_element {
Some((ref pseudo, _)) => self.pseudos_map.get(pseudo).unwrap(),
Some(pseudo) => self.pseudos_map.get(pseudo).unwrap(),
None => &self.element_map,
};
let is_implemented_pseudo =
element.implemented_pseudo_element().is_some();
// NB: This causes use to rule has pseudo selectors based on the
// properties of the originating element (which is fine, given the
// find_first_from_right usage).
let rule_hash_target = if is_implemented_pseudo {
element.closest_non_native_anonymous_ancestor().unwrap()
} else {
*element
};
debug!("Determining if style is shareable: pseudo: {}",
pseudo_element.is_some());
// Step 1: Normal user-agent rules.
map.user_agent.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
@ -893,19 +898,25 @@ impl Stylist {
debug!("preshints: {:?}", context.relations);
}
if element.matches_user_and_author_rules() {
// 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 rule_hash_target.matches_user_and_author_rules() {
// Step 3: User and author normal rules.
map.user.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::UserNormal);
debug!("user normal: {:?}", context.relations);
map.author.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
@ -960,7 +971,6 @@ impl Stylist {
ApplicableDeclarationBlock::from_declarations(anim, CascadeLevel::Transitions));
}
debug!("transition: {:?}", context.relations);
debug!("push_applicable_declarations: shareable: {:?}", context.relations);
}
@ -993,6 +1003,11 @@ impl Stylist {
where E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
// NB: `MatchingMode` doesn't really matter, given we don't share style
// between pseudos.
let mut matching_context =
MatchingContext::new(MatchingMode::Normal, Some(bloom));
// 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
@ -1002,8 +1017,7 @@ impl Stylist {
self.selectors_for_cache_revalidation.lookup(*element, &mut |selector| {
results.push(matches_selector(selector,
element,
Some(bloom),
&mut MatchingContext::default(),
&mut matching_context,
flags_setter));
true
});
@ -1182,6 +1196,7 @@ pub fn needs_revalidation(selector: &Selector<SelectorImpl>) -> bool {
/// Map that contains the CSS rules for a specific PseudoElement
/// (or lack of PseudoElement).
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Debug)]
struct PerPseudoElementSelectorMap {
/// Rules from user agent stylesheets
user_agent: SelectorMap<Rule>,
@ -1283,13 +1298,12 @@ impl SelectorMap<Rule> {
/// Sort the Rules at the end to maintain cascading order.
pub fn get_all_matching_rules<E, V, F>(&self,
element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
rule_hash_target: &E,
matching_rules_list: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
V: VecLike<ApplicableDeclarationBlock>,
F: FnMut(&E, ElementSelectorFlags),
{
@ -1299,10 +1313,8 @@ impl SelectorMap<Rule> {
// At the end, we're going to sort the rules that we added, so remember where we began.
let init_len = matching_rules_list.len();
if let Some(id) = element.get_id() {
if let Some(id) = rule_hash_target.get_id() {
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.id_hash,
&id,
matching_rules_list,
@ -1311,10 +1323,8 @@ impl SelectorMap<Rule> {
cascade_level)
}
element.each_class(|class| {
rule_hash_target.each_class(|class| {
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.class_hash,
class,
matching_rules_list,
@ -1324,18 +1334,14 @@ impl SelectorMap<Rule> {
});
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.local_name_hash,
element.get_local_name(),
rule_hash_target.get_local_name(),
matching_rules_list,
context,
flags_setter,
cascade_level);
SelectorMap::get_matching_rules(element,
pseudo_element,
parent_bf,
&self.other,
matching_rules_list,
context,
@ -1372,15 +1378,13 @@ impl SelectorMap<Rule> {
fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>(
element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
hash: &FnvHashMap<Str, Vec<Rule>>,
key: &BorrowedStr,
matching_rules: &mut Vector,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
Str: Borrow<BorrowedStr> + Eq + Hash,
BorrowedStr: Eq + Hash,
Vector: VecLike<ApplicableDeclarationBlock>,
@ -1388,8 +1392,6 @@ impl SelectorMap<Rule> {
{
if let Some(rules) = hash.get(key) {
SelectorMap::get_matching_rules(element,
pseudo_element,
parent_bf,
rules,
matching_rules,
context,
@ -1400,42 +1402,18 @@ impl SelectorMap<Rule> {
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
fn get_matching_rules<E, V, F>(element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
rules: &[Rule],
matching_rules: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
V: VecLike<ApplicableDeclarationBlock>,
F: FnMut(&E, ElementSelectorFlags),
{
for rule in rules.iter() {
debug_assert_eq!(rule.selector.pseudo_element.is_some(),
pseudo_element.is_some(),
"Testing pseudo-elements against the wrong map");
if let Some((pseudo, pseudo_state)) = pseudo_element {
let pseudo_selector =
rule.selector.pseudo_element.as_ref().unwrap();
debug_assert_eq!(pseudo_selector.pseudo_element(), pseudo,
"Testing pseudo-element against the wrong entry");
let state = pseudo_selector.state();
// NB: We only allow a subset of the flags here, so using
// contains for them is fine, (and it's necessary, to handle
// multiple state flags properly).
if !state.is_empty() && !pseudo_state.contains(state) {
continue;
}
}
for rule in rules {
if matches_selector(&rule.selector.inner,
element,
parent_bf,
context,
flags_setter) {
matching_rules.push(
@ -1593,45 +1571,70 @@ impl<T> SelectorMap<T> where T: Clone + Borrow<SelectorInner<SelectorImpl>> {
}
}
/// Searches the selector from right to left, beginning to the left of the
/// ::pseudo-element (if any), and ending at the first combinator.
///
/// The first non-None value returned from |f| is returned.
///
/// Effectively, pseudo-elements are ignored, given only state pseudo-classes
/// may appear before them.
fn find_from_right<F, R>(selector: &SelectorInner<SelectorImpl>, mut f: F) -> Option<R>
where F: FnMut(&Component<SelectorImpl>) -> Option<R>,
{
let mut iter = selector.complex.iter();
for ss in &mut iter {
if let Some(r) = f(ss) {
return Some(r)
}
}
if iter.next_sequence() == Some(Combinator::PseudoElement) {
for ss in &mut iter {
if let Some(r) = f(ss) {
return Some(r)
}
}
}
None
}
/// Retrieve the first ID name in the selector, or None otherwise.
pub fn get_id_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let Component::ID(ref id) = *ss {
return Some(id.clone());
}
}
None
None
})
}
/// Retrieve the FIRST class name in the selector, or None otherwise.
pub fn get_class_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let Component::Class(ref class) = *ss {
return Some(class.clone());
}
}
None
None
})
}
/// Retrieve the name if it is a type selector, or None otherwise.
pub fn get_local_name(selector: &SelectorInner<SelectorImpl>)
-> Option<LocalNameSelector<SelectorImpl>> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
if let Component::LocalName(ref n) = *ss {
return Some(LocalNameSelector {
name: n.name.clone(),
lower_name: n.lower_name.clone(),
})
}
}
None
None
})
}
/// A rule, that wraps a style rule, but represents a single selector of the
@ -1661,7 +1664,7 @@ impl Borrow<SelectorInner<SelectorImpl>> for Rule {
impl Rule {
/// Returns the specificity of the rule.
pub fn specificity(&self) -> u32 {
self.selector.specificity
self.selector.specificity()
}
fn to_applicable_declaration_block(&self, level: CascadeLevel) -> ApplicableDeclarationBlock {