mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Bug 1336646 - Apply selector flags during traversal. r=emilio
This commit is contained in:
parent
37b8d5231d
commit
9e860df9df
17 changed files with 295 additions and 192 deletions
|
@ -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/"
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue