Skip to content

Commit

Permalink
stylo: Support :hover and :active quirk
Browse files Browse the repository at this point in the history
  • Loading branch information
canova committed Jun 10, 2017
1 parent ff17af0 commit 15fe48f
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 73 deletions.
1 change: 1 addition & 0 deletions components/script/dom/document.rs
Expand Up @@ -209,6 +209,7 @@ pub struct Document {
is_html_document: bool,
activity: Cell<DocumentActivity>,
url: DOMRefCell<ServoUrl>,
#[ignore_heap_size_of = "defined in selectors"]
quirks_mode: Cell<QuirksMode>,
/// Caches for the getElement methods
id_map: DOMRefCell<HashMap<Atom, Vec<JS<Element>>>>,
Expand Down
12 changes: 8 additions & 4 deletions components/script/dom/element.rs
Expand Up @@ -86,7 +86,7 @@ use ref_filter_map::ref_filter_map;
use script_layout_interface::message::ReflowQueryType;
use script_thread::Runnable;
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
use selectors::matching::{RelevantLinkStatus, matches_selector_list};
use servo_atoms::Atom;
Expand Down Expand Up @@ -2063,7 +2063,9 @@ impl ElementMethods for Element {
match SelectorParser::parse_author_origin_no_namespace(&selectors) {
Err(_) => Err(Error::Syntax),
Ok(selectors) => {
let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
let quirks_mode = document_from_node(self).quirks_mode();
let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
quirks_mode);
Ok(matches_selector_list(&selectors, &Root::from_ref(self), &mut ctx))
}
}
Expand All @@ -2082,7 +2084,9 @@ impl ElementMethods for Element {
let root = self.upcast::<Node>();
for element in root.inclusive_ancestors() {
if let Some(element) = Root::downcast::<Element>(element) {
let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
let quirks_mode = document_from_node(self).quirks_mode();
let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
quirks_mode);
if matches_selector_list(&selectors, &element, &mut ctx) {
return Ok(Some(element));
}
Expand Down Expand Up @@ -2432,7 +2436,7 @@ impl<'a> ::selectors::Element for Root<Element> {

fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
_: &mut MatchingContext,
_: &mut LocalMatchingContext<Self::Impl>,
_: &RelevantLinkStatus,
_: &mut F)
-> bool
Expand Down
11 changes: 6 additions & 5 deletions components/script/dom/node.rs
Expand Up @@ -347,11 +347,11 @@ impl<'a> Iterator for QuerySelectorIterator {
fn next(&mut self) -> Option<Root<Node>> {
let selectors = &self.selectors;

// 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| {
// 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,
node.owner_doc().quirks_mode());
if let Some(element) = Root::downcast(node) {
if matches_selector_list(selectors, &element, &mut ctx) {
return Some(Root::upcast(element));
Expand Down Expand Up @@ -720,7 +720,8 @@ impl Node {
Err(_) => Err(Error::Syntax),
// Step 3.
Ok(selectors) => {
let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
self.owner_doc().quirks_mode());
Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| {
matches_selector_list(&selectors, element, &mut ctx)
}))
Expand Down
7 changes: 4 additions & 3 deletions components/script/layout_wrapper.rs
Expand Up @@ -50,7 +50,8 @@ use script_layout_interface::{OpaqueStyleAndLayoutData, StyleData};
use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
use selectors::matching::{ElementSelectorFlags, MatchingContext, RelevantLinkStatus, VisitedHandlingMode};
use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
use selectors::matching::VisitedHandlingMode;
use servo_atoms::Atom;
use servo_url::ServoUrl;
use std::fmt;
Expand Down Expand Up @@ -721,7 +722,7 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {

fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
_: &mut MatchingContext,
_: &mut LocalMatchingContext<Self::Impl>,
_: &RelevantLinkStatus,
_: &mut F)
-> bool
Expand Down Expand Up @@ -1232,7 +1233,7 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {

fn match_non_ts_pseudo_class<F>(&self,
_: &NonTSPseudoClass,
_: &mut MatchingContext,
_: &mut LocalMatchingContext<Self::Impl>,
_: &RelevantLinkStatus,
_: &mut F)
-> bool
Expand Down
141 changes: 129 additions & 12 deletions components/selectors/matching.rs
Expand Up @@ -5,7 +5,7 @@
use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
use bloom::BloomFilter;
use parser::{AncestorHashes, Combinator, Component, LocalName};
use parser::{Selector, SelectorIter, SelectorList};
use parser::{Selector, SelectorImpl, SelectorIter, SelectorList};
use std::borrow::Borrow;
use tree::Element;

Expand Down Expand Up @@ -100,6 +100,19 @@ pub enum VisitedHandlingMode {
RelevantLinkVisited,
}

/// Which quirks mode is this document in.
///
/// See: https://quirks.spec.whatwg.org/
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
pub enum QuirksMode {
/// Quirks mode.
Quirks,
/// Limited quirks mode.
LimitedQuirks,
/// No quirks mode.
NoQuirks,
}

/// 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.
Expand All @@ -119,12 +132,15 @@ pub struct MatchingContext<'a> {
/// `RelevantLinkStatus` which tracks the status for the _current_ selector
/// only.)
pub relevant_link_found: bool,
/// The quirks mode of the document.
pub quirks_mode: QuirksMode,
}

impl<'a> MatchingContext<'a> {
/// Constructs a new `MatchingContext`.
pub fn new(matching_mode: MatchingMode,
bloom_filter: Option<&'a BloomFilter>)
bloom_filter: Option<&'a BloomFilter>,
quirks_mode: QuirksMode)
-> Self
{
Self {
Expand All @@ -133,13 +149,15 @@ impl<'a> MatchingContext<'a> {
bloom_filter: bloom_filter,
visited_handling: VisitedHandlingMode::AllLinksUnvisited,
relevant_link_found: false,
quirks_mode: quirks_mode,
}
}

/// Constructs a new `MatchingContext` for use in visited matching.
pub fn new_for_visited(matching_mode: MatchingMode,
bloom_filter: Option<&'a BloomFilter>,
visited_handling: VisitedHandlingMode)
visited_handling: VisitedHandlingMode,
quirks_mode: QuirksMode)
-> Self
{
Self {
Expand All @@ -148,8 +166,95 @@ impl<'a> MatchingContext<'a> {
bloom_filter: bloom_filter,
visited_handling: visited_handling,
relevant_link_found: false,
quirks_mode: quirks_mode,
}
}
}

/// Holds per-element data alongside a pointer to MatchingContext.
pub struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
/// Shared `MatchingContext`.
pub shared: &'a mut MatchingContext<'b>,
/// A reference to the base selector we're matching against.
pub selector: &'a Selector<Impl>,
/// The offset of the current compound selector being matched, kept up to date by
/// the callees when the iterator is advanced. This, in conjunction with the selector
/// reference above, allows callees to synthesize an iterator for the current compound
/// selector on-demand. This is necessary because the primary iterator may already have
/// been advanced partway through the current compound selector, and the callee may need
/// the whole thing.
offset: usize,
/// Holds a bool flag to see if LocalMatchingContext is within a functional
/// pseudo class argument. This is used for pseudo classes like
/// `:-moz-any` or `:not`. If this flag is true, :active and :hover
/// quirk shouldn't match.
pub within_functional_pseudo_class_argument: bool,
}

impl<'a, 'b, Impl> LocalMatchingContext<'a, 'b, Impl>
where Impl: SelectorImpl
{
/// Constructs a new `LocalMatchingContext`.
pub fn new(shared: &'a mut MatchingContext<'b>,
selector: &'a Selector<Impl>) -> Self {
Self {
shared: shared,
selector: selector,
offset: 0,
within_functional_pseudo_class_argument: false,
}
}

/// Updates offset of Selector to show new compound selector.
/// To be able to correctly re-synthesize main SelectorIter.
pub fn note_next_sequence(&mut self, selector_iter: &SelectorIter<Impl>) {
if let QuirksMode::Quirks = self.shared.quirks_mode {
self.offset = self.selector.len() - selector_iter.selector_length();
}
}

/// Returns true if current compound selector matches :active and :hover quirk.
/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
pub fn active_hover_quirk_matches(&mut self) -> bool {
if self.shared.quirks_mode != QuirksMode::Quirks ||
self.within_functional_pseudo_class_argument {
return false;
}

let mut iter = if self.offset == 0 {
self.selector.iter()
} else {
self.selector.iter_from(self.offset)
};

return iter.all(|simple| {
match *simple {
Component::LocalName(_) |
Component::AttributeInNoNamespaceExists { .. } |
Component::AttributeInNoNamespace { .. } |
Component::AttributeOther(_) |
Component::ID(_) |
Component::Class(_) |
Component::PseudoElement(_) |
Component::Negation(_) |
Component::FirstChild |
Component::LastChild |
Component::OnlyChild |
Component::Empty |
Component::NthChild(_, _) |
Component::NthLastChild(_, _) |
Component::NthOfType(_, _) |
Component::NthLastOfType(_, _) |
Component::FirstOfType |
Component::LastOfType |
Component::OnlyOfType => false,
Component::NonTSPseudoClass(ref pseudo_class) => {
Impl::is_active_or_hover(pseudo_class)
},
_ => true,
}
});
}
}

pub fn matches_selector_list<E>(selector_list: &SelectorList<E::Impl>,
Expand Down Expand Up @@ -361,14 +466,15 @@ pub fn matches_selector<E, F>(selector: &Selector<E::Impl>,
}
}

matches_complex_selector(selector, offset, element, context, flags_setter)
let mut local_context = LocalMatchingContext::new(context, selector);
matches_complex_selector(&selector, offset, element, &mut local_context, flags_setter)
}

/// Matches a complex selector.
pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
offset: usize,
element: &E,
context: &mut MatchingContext,
mut context: &mut LocalMatchingContext<E::Impl>,
flags_setter: &mut F)
-> bool
where E: Element,
Expand All @@ -381,14 +487,14 @@ pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
};

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

if context.matching_mode == MatchingMode::ForStatelessPseudoElement {
if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
match *iter.next().unwrap() {
// Stateful pseudo, just don't match.
Component::NonTSPseudoClass(..) => return false,
Expand All @@ -401,6 +507,8 @@ pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
if iter.next_sequence().is_none() {
return true;
}
// Inform the context that the we've advanced to the next compound selector.
context.note_next_sequence(&mut iter);
}
_ => panic!("Used MatchingMode::ForStatelessPseudoElement in a non-pseudo selector"),
}
Expand All @@ -418,14 +526,14 @@ pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,

fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Impl>,
element: &E,
context: &mut MatchingContext,
context: &mut LocalMatchingContext<E::Impl>,
relevant_link: &mut RelevantLinkStatus,
flags_setter: &mut F)
-> SelectorMatchingResult
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
*relevant_link = relevant_link.examine_potential_link(element, context);
*relevant_link = relevant_link.examine_potential_link(element, &mut context.shared);

let matches_all_simple_selectors = selector_iter.all(|simple| {
matches_simple_selector(simple, element, context, &relevant_link, flags_setter)
Expand All @@ -435,6 +543,8 @@ fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Im
element, selector_iter, relevant_link);

let combinator = selector_iter.next_sequence();
// Inform the context that the we've advanced to the next compound selector.
context.note_next_sequence(&mut selector_iter);
let siblings = combinator.map_or(false, |c| c.is_sibling());
if siblings {
flags_setter(element, HAS_SLOW_SELECTOR_LATER_SIBLINGS);
Expand Down Expand Up @@ -517,7 +627,7 @@ fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Im
fn matches_simple_selector<E, F>(
selector: &Component<E::Impl>,
element: &E,
context: &mut MatchingContext,
context: &mut LocalMatchingContext<E::Impl>,
relevant_link: &RelevantLinkStatus,
flags_setter: &mut F)
-> bool
Expand All @@ -527,7 +637,7 @@ fn matches_simple_selector<E, F>(
match *selector {
Component::Combinator(_) => unreachable!(),
Component::PseudoElement(ref pseudo) => {
element.match_pseudo_element(pseudo, context)
element.match_pseudo_element(pseudo, context.shared)
}
Component::LocalName(LocalName { ref name, ref lower_name }) => {
let is_html = element.is_html_element_in_html_document();
Expand Down Expand Up @@ -651,7 +761,14 @@ fn matches_simple_selector<E, F>(
matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
}
Component::Negation(ref negated) => {
!negated.iter().all(|ss| matches_simple_selector(ss, element, context, relevant_link, flags_setter))
let old_value = context.within_functional_pseudo_class_argument;
context.within_functional_pseudo_class_argument = true;
let result = !negated.iter().all(|ss| {
matches_simple_selector(ss, element, context,
relevant_link, flags_setter)
});
context.within_functional_pseudo_class_argument = old_value;
result
}
}
}
Expand Down

0 comments on commit 15fe48f

Please sign in to comment.