Bug 1336646 - Apply selector flags during traversal. r=emilio

This commit is contained in:
Bobby Holley 2017-02-04 13:11:02 -08:00
parent 37b8d5231d
commit 9e860df9df
17 changed files with 295 additions and 192 deletions

View file

@ -1,7 +1,7 @@
[package]
name = "selectors"
version = "0.17.0"
version = "0.18.0" # Not yet published
authors = ["Simon Sapin <simon.sapin@exyr.org>", "Alan Jeffrey <ajeffrey@mozilla.com>"]
documentation = "https://docs.rs/selectors/"

View file

@ -7,27 +7,6 @@ use parser::{SimpleSelector, Selector, SelectorImpl};
use std::borrow::Borrow;
use tree::Element;
/// The reason why we're doing selector matching.
///
/// If this is for styling, this will include the flags in the parent element.
///
/// This is done because Servo doesn't need those flags at all when it's not
/// styling (e.g., when you're doing document.querySelector). For example, a
/// slow selector in an API like querySelector doesn't imply that the parent
/// could match it.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MatchingReason {
ForStyling,
Other,
}
impl MatchingReason {
#[inline]
fn for_styling(&self) -> bool {
*self == MatchingReason::ForStyling
}
}
// The bloom filter for descendant CSS selectors will have a <1% false
// positive rate until it has this many selectors in it, then it will
// rapidly increase.
@ -84,23 +63,20 @@ bitflags! {
}
bitflags! {
/// Set of flags that are set on the parent depending on whether a child
/// could potentially match a selector.
///
/// These setters, in the case of Servo, must be atomic, due to the parallel
/// traversal.
pub flags ElementFlags: u8 {
/// When a child is added or removed from this element, all the children
/// Set of flags that are set on either the element or its parent (depending
/// on the flag) if the element could potentially match a selector.
pub flags ElementSelectorFlags: u8 {
/// When a child is added or removed from the parent, all the children
/// must be restyled, because they may match :nth-last-child,
/// :last-of-type, :nth-last-of-type, or :only-of-type.
const HAS_SLOW_SELECTOR = 1 << 0,
/// When a child is added or removed from this element, any later
/// When a child is added or removed from the parent, any later
/// children must be restyled, because they may match :nth-child,
/// :first-of-type, or :nth-of-type.
const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1,
/// When a child is added or removed from this element, the first and
/// When a child is added or removed from the parent, the first and
/// last children must be restyled, because they may match :first-child,
/// :last-child, or :only-child.
const HAS_EDGE_CHILD_SELECTOR = 1 << 2,
@ -111,16 +87,28 @@ bitflags! {
}
}
impl ElementSelectorFlags {
/// Returns the subset of flags that apply to the element.
pub fn for_self(self) -> ElementSelectorFlags {
self & (HAS_EMPTY_SELECTOR)
}
/// Returns the subset of flags that apply to the parent.
pub fn for_parent(self) -> ElementSelectorFlags {
self & (HAS_SLOW_SELECTOR | HAS_SLOW_SELECTOR_LATER_SIBLINGS | HAS_EDGE_CHILD_SELECTOR)
}
}
pub fn matches<E>(selector_list: &[Selector<E::Impl>],
element: &E,
parent_bf: Option<&BloomFilter>,
reason: MatchingReason)
parent_bf: Option<&BloomFilter>)
-> bool
where E: Element
{
selector_list.iter().any(|selector| {
selector.pseudo_element.is_none() &&
matches_complex_selector(&*selector.complex_selector, element, parent_bf, &mut StyleRelations::empty(), reason)
matches_complex_selector(&*selector.complex_selector, element, parent_bf,
&mut StyleRelations::empty(), &mut ElementSelectorFlags::empty())
})
}
@ -134,11 +122,11 @@ pub fn matches_complex_selector<E>(selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
flags: &mut ElementSelectorFlags)
-> bool
where E: Element
{
match matches_complex_selector_internal(selector, element, parent_bf, relations, reason) {
match matches_complex_selector_internal(selector, element, parent_bf, relations, flags) {
SelectorMatchingResult::Matched => {
match selector.next {
Some((_, Combinator::NextSibling)) |
@ -209,12 +197,12 @@ fn can_fast_reject<E>(mut selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
flags: &mut ElementSelectorFlags)
-> Option<SelectorMatchingResult>
where E: Element
{
if !selector.compound_selector.iter().all(|simple_selector| {
matches_simple_selector(simple_selector, element, parent_bf, relations, reason) }) {
matches_simple_selector(simple_selector, element, parent_bf, relations, flags) }) {
return Some(SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling);
}
@ -271,11 +259,11 @@ fn matches_complex_selector_internal<E>(selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
flags: &mut ElementSelectorFlags)
-> SelectorMatchingResult
where E: Element
{
if let Some(result) = can_fast_reject(selector, element, parent_bf, relations, reason) {
if let Some(result) = can_fast_reject(selector, element, parent_bf, relations, flags) {
return result;
}
@ -302,7 +290,7 @@ fn matches_complex_selector_internal<E>(selector: &ComplexSelector<E::Impl>,
&element,
parent_bf,
relations,
reason);
flags);
match (result, combinator) {
// Return the status immediately.
(SelectorMatchingResult::Matched, _) => return result,
@ -346,7 +334,7 @@ fn matches_simple_selector<E>(
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
flags: &mut ElementSelectorFlags)
-> bool
where E: Element
{
@ -429,14 +417,14 @@ fn matches_simple_selector<E>(
AFFECTED_BY_STATE)
}
SimpleSelector::FirstChild => {
relation_if!(matches_first_child(element, reason), AFFECTED_BY_CHILD_INDEX)
relation_if!(matches_first_child(element, flags), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::LastChild => {
relation_if!(matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
relation_if!(matches_last_child(element, flags), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::OnlyChild => {
relation_if!(matches_first_child(element, reason) &&
matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
relation_if!(matches_first_child(element, flags) &&
matches_last_child(element, flags), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::Root => {
// We never share styles with an element with no parent, so no point
@ -444,43 +432,41 @@ fn matches_simple_selector<E>(
element.is_root()
}
SimpleSelector::Empty => {
if reason.for_styling() {
element.insert_flags(HAS_EMPTY_SELECTOR);
}
flags.insert(HAS_EMPTY_SELECTOR);
relation_if!(element.is_empty(), AFFECTED_BY_EMPTY)
}
SimpleSelector::NthChild(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, false, false, reason),
relation_if!(matches_generic_nth_child(element, a, b, false, false, flags),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthLastChild(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, false, true, reason),
relation_if!(matches_generic_nth_child(element, a, b, false, true, flags),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthOfType(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, true, false, reason),
relation_if!(matches_generic_nth_child(element, a, b, true, false, flags),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthLastOfType(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, true, true, reason),
relation_if!(matches_generic_nth_child(element, a, b, true, true, flags),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::FirstOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason),
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, flags),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::LastOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, true, reason),
relation_if!(matches_generic_nth_child(element, 0, 1, true, true, flags),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::OnlyOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason) &&
matches_generic_nth_child(element, 0, 1, true, true, reason),
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, flags) &&
matches_generic_nth_child(element, 0, 1, true, true, flags),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::Negation(ref negated) => {
!negated.iter().all(|s| {
matches_complex_selector(s, element, parent_bf, relations, reason)
matches_complex_selector(s, element, parent_bf, relations, flags)
})
}
}
@ -492,22 +478,15 @@ fn matches_generic_nth_child<E>(element: &E,
b: i32,
is_of_type: bool,
is_from_end: bool,
reason: MatchingReason) -> bool
flags: &mut ElementSelectorFlags)
-> bool
where E: Element
{
// Selectors Level 4 changed from Level 3:
// This can match without a parent element:
// https://drafts.csswg.org/selectors-4/#child-index
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(if is_from_end {
HAS_SLOW_SELECTOR
} else {
HAS_SLOW_SELECTOR_LATER_SIBLINGS
});
}
}
flags.insert(if is_from_end {
HAS_SLOW_SELECTOR
} else {
HAS_SLOW_SELECTOR_LATER_SIBLINGS
});
let mut index = 1;
let mut next_sibling = if is_from_end {
@ -546,28 +525,15 @@ fn matches_generic_nth_child<E>(element: &E,
}
#[inline]
fn matches_first_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
// Selectors Level 4 changed from Level 3:
// This can match without a parent element:
// https://drafts.csswg.org/selectors-4/#child-index
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
fn matches_first_child<E>(element: &E, flags: &mut ElementSelectorFlags)
-> bool where E: Element {
flags.insert(HAS_EDGE_CHILD_SELECTOR);
element.prev_sibling_element().is_none()
}
#[inline]
fn matches_last_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
// Selectors Level 4 changed from Level 3:
// This can match without a parent element:
// https://drafts.csswg.org/selectors-4/#child-index
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
fn matches_last_child<E>(element: &E, flags: &mut ElementSelectorFlags)
-> bool where E: Element {
flags.insert(HAS_EDGE_CHILD_SELECTOR);
element.next_sibling_element().is_none()
}

View file

@ -5,7 +5,6 @@
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and
//! style.
use matching::ElementFlags;
use parser::{AttrSelector, SelectorImpl};
use std::ascii::AsciiExt;
@ -162,16 +161,4 @@ pub trait Element: MatchAttr + Sized {
// in the future when we have associated types and/or a more convenient
// JS GC story... --pcwalton
fn each_class<F>(&self, callback: F) where F: FnMut(&<Self::Impl as SelectorImpl>::ClassName);
/// Add flags to the element. See the `ElementFlags` docs for details.
///
/// This may be called while the element *or one of its children* is being
/// matched. Therefore the implementation must be thread-safe if children
/// may be matched in parallel.
fn insert_flags(&self, _flags: ElementFlags) {}
/// Clears the relevant ElementFlags. This is *not* called from
/// rust-selectors, but provided as part of the Element interface since it
/// makes sense.
fn clear_flags(&self) {}
}