Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from May 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 15 additions & 5 deletions components/script/dom/element.rs
Expand Up @@ -85,7 +85,7 @@ use net_traits::request::CorsSettings;
use ref_filter_map::ref_filter_map;
use script_layout_interface::message::ReflowQueryType;
use script_thread::Runnable;
use selectors::matching::{ElementSelectorFlags, MatchingContext, matches_selector_list};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, matches_selector_list};
use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
use selectors::parser::{AttrSelector, NamespaceConstraint};
use servo_atoms::Atom;
Expand All @@ -103,7 +103,7 @@ use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBloc
use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x};
use style::restyle_hints::RESTYLE_SELF;
use style::rule_tree::CascadeLevel;
use style::selector_parser::{NonTSPseudoClass, RestyleDamage, SelectorImpl, SelectorParser};
use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser};
use style::shared_lock::{SharedRwLock, Locked};
use style::sink::Push;
use style::stylearc::Arc;
Expand Down Expand Up @@ -2046,7 +2046,8 @@ impl ElementMethods for Element {
match SelectorParser::parse_author_origin_no_namespace(&selectors) {
Err(()) => Err(Error::Syntax),
Ok(selectors) => {
Ok(matches_selector_list(&selectors.0, &Root::from_ref(self), None))
let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
Ok(matches_selector_list(&selectors.0, &Root::from_ref(self), &mut ctx))
}
}
}
Expand All @@ -2064,8 +2065,8 @@ impl ElementMethods for Element {
let root = self.upcast::<Node>();
for element in root.inclusive_ancestors() {
if let Some(element) = Root::downcast::<Element>(element) {
if matches_selector_list(&selectors.0, &element, None)
{
let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
if matches_selector_list(&selectors.0, &element, &mut ctx) {
return Ok(Some(element));
}
}
Expand Down Expand Up @@ -2374,6 +2375,15 @@ impl<'a> ::selectors::Element for Root<Element> {
self.upcast::<Node>().GetParentElement()
}

fn match_pseudo_element(&self,
_pseudo: &PseudoElement,
_context: &mut MatchingContext)
-> bool
{
false
}


fn first_child_element(&self) -> Option<Root<Element>> {
self.node.child_elements().next()
}
Expand Down
10 changes: 7 additions & 3 deletions components/script/dom/node.rs
Expand Up @@ -68,7 +68,7 @@ use script_layout_interface::{LayoutElementType, LayoutNodeType, TrustedNodeAddr
use script_layout_interface::message::Msg;
use script_traits::DocumentActivity;
use script_traits::UntrustedNodeAddress;
use selectors::matching::matches_selector_list;
use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode};
use selectors::parser::SelectorList;
use servo_url::ServoUrl;
use std::borrow::ToOwned;
Expand Down Expand Up @@ -341,11 +341,14 @@ impl<'a> Iterator for QuerySelectorIterator {

fn next(&mut self) -> Option<Root<Node>> {
let selectors = &self.selectors.0;

// TODO(cgaebel): Is it worth it to build a bloom filter here
// (instead of passing `None`)? Probably.
let mut ctx = MatchingContext::new(MatchingMode::Normal, None);

self.iterator.by_ref().filter_map(|node| {
if let Some(element) = Root::downcast(node) {
if matches_selector_list(selectors, &element, None) {
if matches_selector_list(selectors, &element, &mut ctx) {
return Some(Root::upcast(element));
}
}
Expand Down Expand Up @@ -707,8 +710,9 @@ impl Node {
Err(()) => Err(Error::Syntax),
// Step 3.
Ok(selectors) => {
let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| {
matches_selector_list(&selectors.0, element, None)
matches_selector_list(&selectors.0, element, &mut ctx)
}))
}
}
Expand Down
16 changes: 16 additions & 0 deletions components/script/layout_wrapper.rs
Expand Up @@ -652,6 +652,14 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
self.element.namespace()
}

fn match_pseudo_element(&self,
_pseudo: &PseudoElement,
_context: &mut MatchingContext)
-> bool
{
false
}

fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
_: &mut MatchingContext,
Expand Down Expand Up @@ -1150,6 +1158,14 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
self.element.get_namespace()
}

fn match_pseudo_element(&self,
_pseudo: &PseudoElement,
_context: &mut MatchingContext)
-> bool
{
false
}

fn match_non_ts_pseudo_class<F>(&self,
_: &NonTSPseudoClass,
_: &mut MatchingContext,
Expand Down
2 changes: 0 additions & 2 deletions components/script_layout_interface/wrapper_traits.rs
Expand Up @@ -20,7 +20,6 @@ use style::context::SharedStyleContext;
use style::data::ElementData;
use style::dom::{LayoutIterator, NodeInfo, PresentationalHintsSynthesizer, TNode};
use style::dom::OpaqueNode;
use style::element_state::ElementState;
use style::font_metrics::ServoMetricsProvider;
use style::properties::{CascadeFlags, ServoComputedValues};
use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorImpl};
Expand Down Expand Up @@ -435,7 +434,6 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug +
&context.guards,
unsafe { &self.unsafe_get() },
&style_pseudo,
ElementState::empty(),
data.styles().primary.values(),
&ServoMetricsProvider);
data.styles_mut().cached_pseudos
Expand Down
3 changes: 0 additions & 3 deletions components/selectors/gecko_like_types.rs
Expand Up @@ -19,9 +19,6 @@ pub enum PseudoElement {
B,
}

#[derive(Eq, PartialEq, Clone, Debug)]
pub struct PseudoElementSelector(PseudoElement, u64);

#[derive(Eq, PartialEq, Clone, Debug, Default)]
pub struct Atom(usize);

Expand Down
152 changes: 115 additions & 37 deletions components/selectors/matching.rs
Expand Up @@ -69,28 +69,67 @@ impl ElementSelectorFlags {
}
}

/// What kind of selector matching mode we should use.
///
/// There are two modes of selector matching. The difference is only noticeable
/// in presence of pseudo-elements.
#[derive(Debug, PartialEq)]
pub enum MatchingMode {
/// Don't ignore any pseudo-element selectors.
Normal,

/// Ignores any stateless pseudo-element selectors in the rightmost sequence
/// of simple selectors.
///
/// This is useful, for example, to match against ::before when you aren't a
/// pseudo-element yourself.
///
/// For example, in presence of `::before:hover`, it would never match, but
/// `::before` would be ignored as in "matching".
///
/// It's required for all the selectors you match using this mode to have a
/// pseudo-element.
ForStatelessPseudoElement,
}


/// Data associated with the matching process for a element. This context is
/// used across many selectors for an element, so it's not appropriate for
/// transient data that applies to only a single selector.
#[derive(Default)]
pub struct MatchingContext {
pub struct MatchingContext<'a> {
/// Output that records certains relations between elements noticed during
/// matching (and also extended after matching).
pub relations: StyleRelations,
/// The matching mode we should use when matching selectors.
pub matching_mode: MatchingMode,
/// The bloom filter used to fast-reject selectors.
pub bloom_filter: Option<&'a BloomFilter>,
}

impl<'a> MatchingContext<'a> {
/// Constructs a new `MatchingContext`.
pub fn new(matching_mode: MatchingMode,
bloom_filter: Option<&'a BloomFilter>)
-> Self
{
Self {
relations: StyleRelations::empty(),
matching_mode: matching_mode,
bloom_filter: bloom_filter,
}
}
}

pub fn matches_selector_list<E>(selector_list: &[Selector<E::Impl>],
element: &E,
parent_bf: Option<&BloomFilter>)
context: &mut MatchingContext)
-> bool
where E: Element
{
selector_list.iter().any(|selector| {
selector.pseudo_element.is_none() &&
matches_selector(&selector.inner,
element,
parent_bf,
&mut MatchingContext::default(),
context,
&mut |_, _| {})
})
}
Expand All @@ -115,27 +154,6 @@ fn may_match<E>(sel: &SelectorInner<E::Impl>,
true
}

/// Determines whether the given element matches the given complex selector.
pub fn matches_selector<E, F>(selector: &SelectorInner<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
context: &mut MatchingContext,
flags_setter: &mut F)
-> bool
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
// Use the bloom filter to fast-reject.
if let Some(filter) = parent_bf {
if !may_match::<E>(selector, filter) {
return false;
}
}

// Match the selector.
matches_complex_selector(&selector.complex, element, context, flags_setter)
}

/// A result of selector matching, includes 3 failure types,
///
/// NotMatchedAndRestartFromClosestLaterSibling
Expand Down Expand Up @@ -186,16 +204,65 @@ enum SelectorMatchingResult {
NotMatchedGlobally,
}

/// Matches an inner selector.
pub fn matches_selector<E, F>(selector: &SelectorInner<E::Impl>,
element: &E,
context: &mut MatchingContext,
flags_setter: &mut F)
-> bool
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
// Use the bloom filter to fast-reject.
if let Some(filter) = context.bloom_filter {
if !may_match::<E>(&selector, filter) {
return false;
}
}

matches_complex_selector(&selector.complex, element, context, flags_setter)
}

/// Matches a complex selector.
pub fn matches_complex_selector<E, F>(selector: &ComplexSelector<E::Impl>,
///
/// Use `matches_selector` if you need to skip pseudos.
pub fn matches_complex_selector<E, F>(complex_selector: &ComplexSelector<E::Impl>,
element: &E,
context: &mut MatchingContext,
flags_setter: &mut F)
-> bool
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
match matches_complex_selector_internal(selector.iter(),
let mut iter = complex_selector.iter();

if cfg!(debug_assertions) {
if context.matching_mode == MatchingMode::ForStatelessPseudoElement {
assert!(complex_selector.iter().any(|c| {
matches!(*c, Component::PseudoElement(..))
}));
}
}

if context.matching_mode == MatchingMode::ForStatelessPseudoElement {
match *iter.next().unwrap() {
// Stateful pseudo, just don't match.
Component::NonTSPseudoClass(..) => return false,
Component::PseudoElement(..) => {
// Pseudo, just eat the whole sequence.
let next = iter.next();
debug_assert!(next.is_none(),
"Someone messed up pseudo-element parsing?");

if iter.next_sequence().is_none() {
return true;
}
}
_ => panic!("Used MatchingMode::ForStatelessPseudoElement in a non-pseudo selector"),
}
}

match matches_complex_selector_internal(iter,
element,
context,
flags_setter) {
Expand Down Expand Up @@ -229,12 +296,19 @@ fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Im
match combinator {
None => SelectorMatchingResult::Matched,
Some(c) => {
let (mut next_element, candidate_not_found) = if siblings {
(element.prev_sibling_element(),
SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant)
} else {
(element.parent_element(),
SelectorMatchingResult::NotMatchedGlobally)
let (mut next_element, candidate_not_found) = match c {
Combinator::NextSibling | Combinator::LaterSibling => {
(element.prev_sibling_element(),
SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant)
}
Combinator::Child | Combinator::Descendant => {
(element.parent_element(),
SelectorMatchingResult::NotMatchedGlobally)
}
Combinator::PseudoElement => {
(element.pseudo_element_originating_element(),
SelectorMatchingResult::NotMatchedGlobally)
}
};

loop {
Expand All @@ -253,6 +327,7 @@ fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Im

// Upgrade the failure status to
// NotMatchedAndRestartFromClosestDescendant.
(_, Combinator::PseudoElement) |
(_, Combinator::Child) => return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant,

// Return the status directly.
Expand Down Expand Up @@ -306,6 +381,9 @@ fn matches_simple_selector<E, F>(

match *selector {
Component::Combinator(_) => unreachable!(),
Component::PseudoElement(ref pseudo) => {
element.match_pseudo_element(pseudo, context)
}
Component::LocalName(LocalName { ref name, ref lower_name }) => {
let name = if element.is_html_element_in_html_document() { lower_name } else { name };
element.get_local_name() == name.borrow()
Expand Down