--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -204,16 +204,17 @@ pub struct Document {
location: MutNullableJS<Location>,
content_type: DOMString,
last_modified: Option<String>,
encoding: Cell<EncodingRef>,
has_browsing_context: bool,
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>>>>,
tag_map: DOMRefCell<HashMap<LocalName, JS<HTMLCollection>>>,
tagns_map: DOMRefCell<HashMap<QualName, JS<HTMLCollection>>>,
classes_map: DOMRefCell<HashMap<Vec<Atom>, JS<HTMLCollection>>>,
images: MutNullableJS<HTMLCollection>,
embeds: MutNullableJS<HTMLCollection>,
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -81,17 +81,17 @@ use html5ever::serialize::SerializeOpts;
use html5ever::serialize::TraversalScope;
use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
use js::jsapi::{HandleValue, JSAutoCompartment};
use net_traits::request::CorsSettings;
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;
use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::cell::{Cell, Ref};
use std::convert::TryFrom;
use std::default::Default;
@@ -2058,17 +2058,19 @@ impl ElementMethods for Element {
self.upcast::<Node>().remove_self();
}
// https://dom.spec.whatwg.org/#dom-element-matches
fn Matches(&self, selectors: DOMString) -> Fallible<bool> {
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))
}
}
}
// https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector
fn WebkitMatchesSelector(&self, selectors: DOMString) -> Fallible<bool> {
self.Matches(selectors)
@@ -2077,17 +2079,19 @@ impl ElementMethods for Element {
// https://dom.spec.whatwg.org/#dom-element-closest
fn Closest(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
match SelectorParser::parse_author_origin_no_namespace(&selectors) {
Err(_) => Err(Error::Syntax),
Ok(selectors) => {
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));
}
}
}
Ok(None)
}
}
@@ -2427,17 +2431,17 @@ impl<'a> ::selectors::Element for Root<E
}
fn get_namespace(&self) -> &Namespace {
self.namespace()
}
fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
- _: &mut MatchingContext,
+ _: &mut LocalMatchingContext<Self::Impl>,
_: &RelevantLinkStatus,
_: &mut F)
-> bool
where F: FnMut(&Self, ElementSelectorFlags),
{
match *pseudo_class {
// https://github.com/servo/servo/issues/8718
NonTSPseudoClass::Link |
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -342,21 +342,21 @@ impl<'a> QuerySelectorIterator {
}
impl<'a> Iterator for QuerySelectorIterator {
type Item = Root<Node>;
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));
}
}
None
}).next()
}
@@ -715,17 +715,18 @@ impl Node {
// https://dom.spec.whatwg.org/#dom-parentnode-queryselector
pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
// Step 1.
match SelectorParser::parse_author_origin_no_namespace(&selectors) {
// Step 2.
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)
}))
}
}
}
/// https://dom.spec.whatwg.org/#scope-match-a-selectors-string
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -45,17 +45,18 @@ use gfx_traits::ByteIndex;
use html5ever::{LocalName, Namespace};
use msg::constellation_msg::{BrowsingContextId, PipelineId};
use range::Range;
use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, TrustedNodeAddress};
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;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::mem::transmute;
use std::sync::atomic::Ordering;
@@ -716,17 +717,17 @@ impl<'le> ::selectors::Element for Servo
_context: &mut MatchingContext)
-> bool
{
false
}
fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
- _: &mut MatchingContext,
+ _: &mut LocalMatchingContext<Self::Impl>,
_: &RelevantLinkStatus,
_: &mut F)
-> bool
where F: FnMut(&Self, ElementSelectorFlags),
{
match *pseudo_class {
// https://github.com/servo/servo/issues/8718
NonTSPseudoClass::Link |
@@ -1227,17 +1228,17 @@ impl<'le> ::selectors::Element for Servo
};
values.iter().any(|v| v.eval_selector(operation))
}
}
}
fn match_non_ts_pseudo_class<F>(&self,
_: &NonTSPseudoClass,
- _: &mut MatchingContext,
+ _: &mut LocalMatchingContext<Self::Impl>,
_: &RelevantLinkStatus,
_: &mut F)
-> bool
where F: FnMut(&Self, ElementSelectorFlags),
{
// NB: This could maybe be implemented
warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
false
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -1,16 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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;
// 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.
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
@@ -95,16 +95,29 @@ pub enum VisitedHandlingMode {
/// All links are matched as if they are unvisted.
AllLinksUnvisited,
/// A element's "relevant link" is the element being matched if it is a link
/// or the nearest ancestor link. The relevant link is matched as though it
/// is visited, and all other links are matched as if they are unvisited.
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.
#[derive(Clone)]
pub struct MatchingContext<'a> {
/// Output that records certains relations between elements noticed during
/// matching (and also extended after matching).
pub relations: StyleRelations,
@@ -114,49 +127,141 @@ pub struct MatchingContext<'a> {
pub bloom_filter: Option<&'a BloomFilter>,
/// Input that controls how matching for links is handled.
pub visited_handling: VisitedHandlingMode,
/// Output that records whether we encountered a "relevant link" while
/// matching _any_ selector for this element. (This differs from
/// `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 {
relations: StyleRelations::empty(),
matching_mode: matching_mode,
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 {
relations: StyleRelations::empty(),
matching_mode: matching_mode,
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>,
element: &E,
context: &mut MatchingContext)
-> bool
where E: Element
{
selector_list.0.iter().any(|selector_and_hashes| {
matches_selector(&selector_and_hashes.selector,
@@ -356,56 +461,59 @@ pub fn matches_selector<E, F>(selector:
{
// Use the bloom filter to fast-reject.
if let Some(filter) = context.bloom_filter {
if !may_match::<E>(hashes, filter) {
return false;
}
}
- 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,
F: FnMut(&E, ElementSelectorFlags),
{
let mut iter = if offset == 0 {
complex_selector.iter()
} else {
complex_selector.iter_from(offset)
};
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,
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;
}
+ // 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"),
}
}
match matches_complex_selector_internal(iter,
element,
context,
@@ -413,33 +521,35 @@ pub fn matches_complex_selector<E, F>(co
flags_setter) {
SelectorMatchingResult::Matched => true,
_ => false
}
}
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)
});
debug!("Matching for {:?}, simple selector {:?}, relevant link {:?}",
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);
}
if !matches_all_simple_selectors {
return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;
}
@@ -512,27 +622,27 @@ fn matches_complex_selector_internal<E,
}
}
/// Determines whether the given element matches the given single selector.
#[inline]
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
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
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();
element.get_local_name() == select_name(is_html, name, lower_name).borrow()
}
Component::ExplicitUniversalType |
Component::ExplicitAnyNamespace => {
true
@@ -646,17 +756,22 @@ fn matches_simple_selector<E, F>(
Component::LastOfType => {
matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
}
Component::OnlyOfType => {
matches_generic_nth_child(element, 0, 1, true, false, flags_setter) &&
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
}
}
}
fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T {
if is_html {
local_name_lower
} else {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -98,16 +98,21 @@ macro_rules! with_all_bounds {
type BorrowedLocalName: ?Sized + Eq;
/// non tree-structural pseudo-classes
/// (see: https://drafts.csswg.org/selectors/#structural-pseudos)
type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>;
/// pseudo-elements
type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>;
+
+ /// Returns whether the selector matches conditions for the :active and
+ /// :hover quirk.
+ #[inline]
+ fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool;
}
}
}
macro_rules! with_bounds {
( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => {
with_all_bounds! {
[$($CommonBounds)* + $($FromStr)* + Display]
@@ -422,17 +427,17 @@ impl<Impl: SelectorImpl> Selector<Impl>
SelectorIter {
iter: self.iter_raw(),
next_combinator: None,
}
}
pub fn iter_from(&self, offset: usize) -> SelectorIter<Impl> {
// Note: selectors are stored left-to-right but logical order is right-to-left.
- let iter = self.0.slice[..(self.0.slice.len() - offset)].iter().rev();
+ let iter = self.0.slice[..(self.len() - offset)].iter().rev();
SelectorIter {
iter: iter,
next_combinator: None,
}
}
/// Returns an iterator over the entire sequence of simple selectors and combinators,
/// from right to left.
@@ -446,30 +451,40 @@ impl<Impl: SelectorImpl> Selector<Impl>
self.0.slice.iter()
}
/// Creates a Selector from a vec of Components. Used in tests.
pub fn from_vec(vec: Vec<Component<Impl>>, specificity_and_flags: u32) -> Self {
let header = HeaderWithLength::new(SpecificityAndFlags(specificity_and_flags), vec.len());
Selector(Arc::into_thin(Arc::from_header_and_iter(header, vec.into_iter())))
}
+
+ /// Returns count of simple selectors and combinators in the Selector.
+ pub fn len(&self) -> usize {
+ self.0.slice.len()
+ }
}
#[derive(Clone)]
pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> {
iter: Rev<slice::Iter<'a, Component<Impl>>>,
next_combinator: Option<Combinator>,
}
impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
/// Prepares this iterator to point to the next sequence to the left,
/// returning the combinator if the sequence was found.
pub fn next_sequence(&mut self) -> Option<Combinator> {
self.next_combinator.take()
}
+
+ /// Returns remaining count of the simple selectors and combinators in the Selector.
+ pub fn selector_length(&self) -> usize {
+ self.iter.len()
+ }
}
impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> {
type Item = &'a Component<Impl>;
fn next(&mut self) -> Option<Self::Item> {
debug_assert!(self.next_combinator.is_none(),
"You should call next_sequence!");
match self.iter.next() {
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,17 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency
//! between layout and style.
use attr::{AttrSelectorOperation, NamespaceConstraint};
-use matching::{ElementSelectorFlags, MatchingContext, RelevantLinkStatus};
+use matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
use parser::SelectorImpl;
use std::fmt::Debug;
pub trait Element: Sized + Debug {
type Impl: SelectorImpl;
fn parent_element(&self) -> Option<Self>;
@@ -45,17 +45,17 @@ pub trait Element: Sized + Debug {
fn attr_matches(&self,
ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
local_name: &<Self::Impl as SelectorImpl>::LocalName,
operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>)
-> bool;
fn match_non_ts_pseudo_class<F>(&self,
pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
- context: &mut MatchingContext,
+ context: &mut LocalMatchingContext<Self::Impl>,
relevant_link: &RelevantLinkStatus,
flags_setter: &mut F) -> bool
where F: FnMut(&Self, ElementSelectorFlags);
fn match_pseudo_element(&self,
pe: &<Self::Impl as SelectorImpl>::PseudoElement,
context: &mut MatchingContext)
-> bool;
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -28,46 +28,34 @@ use std::ops::Add;
#[cfg(feature = "servo")] use std::sync::mpsc::Sender;
use stylearc::Arc;
use stylist::Stylist;
use thread_state;
use time;
use timer::Timer;
use traversal::{DomTraversal, TraversalFlags};
+pub use selectors::matching::QuirksMode;
+
/// This structure is used to create a local style context from a shared one.
#[cfg(feature = "servo")]
pub struct ThreadLocalStyleContextCreationInfo {
new_animations_sender: Sender<Animation>,
}
#[cfg(feature = "servo")]
impl ThreadLocalStyleContextCreationInfo {
/// Trivially constructs a `ThreadLocalStyleContextCreationInfo`.
pub fn new(animations_sender: Sender<Animation>) -> Self {
ThreadLocalStyleContextCreationInfo {
new_animations_sender: animations_sender,
}
}
}
-/// Which quirks mode is this document in.
-///
-/// See: https://quirks.spec.whatwg.org/
-#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum QuirksMode {
- /// Quirks mode.
- Quirks,
- /// Limited quirks mode.
- LimitedQuirks,
- /// No quirks mode.
- NoQuirks,
-}
-
/// A global options structure for the style system. We use this instead of
/// opts to abstract across Gecko and Servo.
#[derive(Clone)]
pub struct StyleSystemOptions {
/// Whether the style sharing cache is disabled.
pub disable_style_sharing_cache: bool,
/// Whether we should dump statistics about the style system.
pub dump_style_statistics: bool,
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -232,16 +232,22 @@ impl ::selectors::SelectorImpl for Selec
type LocalName = Atom;
type NamespacePrefix = Atom;
type NamespaceUrl = Namespace;
type BorrowedNamespaceUrl = WeakNamespace;
type BorrowedLocalName = WeakAtom;
type PseudoElement = PseudoElement;
type NonTSPseudoClass = NonTSPseudoClass;
+
+ #[inline]
+ fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
+ matches!(*pseudo_class, NonTSPseudoClass::Active |
+ NonTSPseudoClass::Hover)
+ }
}
impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
type Impl = SelectorImpl;
type Error = StyleParseError<'i>;
fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
-> Result<NonTSPseudoClass, ParseError<'i>> {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -67,17 +67,17 @@ use media_queries::Device;
use properties::{ComputedValues, parse_style_attribute};
use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
use properties::style_structs::Font;
use rule_tree::CascadeLevel as ServoCascadeLevel;
use selector_parser::{AttrValue, ElementExt, PseudoClassStringArg};
use selectors::Element;
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext};
use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode};
use shared_lock::Locked;
use sink::Push;
use smallvec::VecLike;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
@@ -1419,28 +1419,25 @@ impl<'le> ::selectors::Element for Gecko
fn get_namespace(&self) -> &WeakNamespace {
unsafe {
WeakNamespace::new(Gecko_Namespace(self.0))
}
}
fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
- context: &mut MatchingContext,
+ context: &mut LocalMatchingContext<Self::Impl>,
relevant_link: &RelevantLinkStatus,
flags_setter: &mut F)
-> bool
where F: FnMut(&Self, ElementSelectorFlags),
{
use selectors::matching::*;
match *pseudo_class {
- NonTSPseudoClass::AnyLink |
- NonTSPseudoClass::Active |
NonTSPseudoClass::Focus |
- NonTSPseudoClass::Hover |
NonTSPseudoClass::Enabled |
NonTSPseudoClass::Disabled |
NonTSPseudoClass::Checked |
NonTSPseudoClass::Fullscreen |
NonTSPseudoClass::Indeterminate |
NonTSPseudoClass::PlaceholderShown |
NonTSPseudoClass::Target |
NonTSPseudoClass::Valid |
@@ -1472,22 +1469,31 @@ impl<'le> ::selectors::Element for Gecko
NonTSPseudoClass::Default |
NonTSPseudoClass::MozSubmitInvalid |
NonTSPseudoClass::MozUIInvalid |
NonTSPseudoClass::MozMeterOptimum |
NonTSPseudoClass::MozMeterSubOptimum |
NonTSPseudoClass::MozMeterSubSubOptimum |
NonTSPseudoClass::MozAutofill |
NonTSPseudoClass::MozAutofillPreview => {
- // NB: It's important to use `intersect` instead of `contains`
- // here, to handle `:any-link` correctly.
+ /// FIXME: This can/should probably be contains() now that any-link
+ // (which depends in multiple bits) is handled in its own case below.
self.get_state().intersects(pseudo_class.state_flag())
},
- NonTSPseudoClass::Link => relevant_link.is_unvisited(self, context),
- NonTSPseudoClass::Visited => relevant_link.is_visited(self, context),
+ NonTSPseudoClass::AnyLink => self.is_link(),
+ NonTSPseudoClass::Link => relevant_link.is_unvisited(self, context.shared),
+ NonTSPseudoClass::Visited => relevant_link.is_visited(self, context.shared),
+ NonTSPseudoClass::Active |
+ NonTSPseudoClass::Hover => {
+ if context.active_hover_quirk_matches() && !self.is_link() {
+ false
+ } else {
+ self.get_state().contains(pseudo_class.state_flag())
+ }
+ },
NonTSPseudoClass::MozFirstNode => {
flags_setter(self, HAS_EDGE_CHILD_SELECTOR);
let mut elem = self.as_node();
while let Some(prev) = elem.prev_sibling() {
if prev.contains_non_whitespace_content() {
return false
}
elem = prev;
@@ -1517,19 +1523,23 @@ impl<'le> ::selectors::Element for Gecko
NonTSPseudoClass::MozNativeAnonymous => unsafe {
Gecko_MatchesElement(pseudo_class.to_gecko_pseudoclasstype().unwrap(), self.0)
},
NonTSPseudoClass::MozIsHTML => {
self.is_html_element_in_html_document()
}
NonTSPseudoClass::MozPlaceholder => false,
NonTSPseudoClass::MozAny(ref sels) => {
- sels.iter().any(|s| {
+ let old_value = context.within_functional_pseudo_class_argument;
+ context.within_functional_pseudo_class_argument = true;
+ let result = sels.iter().any(|s| {
matches_complex_selector(s, 0, self, context, flags_setter)
- })
+ });
+ context.within_functional_pseudo_class_argument = old_value;
+ result
}
NonTSPseudoClass::Lang(ref lang_arg) => {
self.match_element_lang(None, lang_arg)
}
NonTSPseudoClass::MozSystemMetric(ref s) |
NonTSPseudoClass::MozLocaleDir(ref s) |
NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) |
NonTSPseudoClass::Dir(ref s) => {
@@ -1558,21 +1568,17 @@ impl<'le> ::selectors::Element for Gecko
match self.implemented_pseudo_element() {
Some(ref pseudo) => *pseudo == pseudo_element.canonical(),
None => false,
}
}
#[inline]
fn is_link(&self) -> bool {
- let mut context = MatchingContext::new(MatchingMode::Normal, None);
- self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
- &mut context,
- &RelevantLinkStatus::default(),
- &mut |_, _| {})
+ self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag())
}
fn get_id(&self) -> Option<Atom> {
if !self.has_id() {
return None;
}
let ptr = unsafe {
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -1016,17 +1016,18 @@ pub trait MatchMethods : TElement {
} else {
RuleInclusion::All
};
let bloom_filter = context.thread_local.bloom_filter.filter();
let mut matching_context =
MatchingContext::new_for_visited(MatchingMode::Normal,
Some(bloom_filter),
- visited_handling);
+ visited_handling,
+ context.shared.quirks_mode);
{
let smil_override = data.get_smil_override();
let animation_rules = if self.may_have_animations() {
data.get_animation_rules()
} else {
AnimationRules(None, None)
};
@@ -1112,17 +1113,18 @@ pub trait MatchMethods : TElement {
RuleInclusion::All
};
let bloom_filter = context.thread_local.bloom_filter.filter();
let mut matching_context =
MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement,
Some(bloom_filter),
- visited_handling);
+ visited_handling,
+ context.shared.quirks_mode);
// Compute rule nodes for eagerly-cascaded pseudo-elements.
let mut matches_different_pseudos = false;
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
// For pseudo-elements, we only try to match visited rules if there
// are also unvisited rules. (This matches Gecko's behavior.)
if visited_handling == VisitedHandlingMode::RelevantLinkVisited &&
!data.styles().pseudos.has(&pseudo) {
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -15,17 +15,17 @@ use element_state::*;
#[cfg(feature = "gecko")]
use gecko_bindings::structs::nsRestyleHint;
#[cfg(feature = "servo")]
use heapsize::HeapSizeOf;
use selector_map::{SelectorMap, SelectorMapEntry};
use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue};
use selectors::Element;
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode, matches_selector};
use selectors::parser::{AncestorHashes, Combinator, Component};
use selectors::parser::{Selector, SelectorAndHashes, SelectorIter, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use smallvec::SmallVec;
use std::cell::Cell;
use std::clone::Clone;
use std::cmp;
@@ -659,17 +659,17 @@ fn dir_selector_to_state(s: &[u16]) -> E
impl<'a, E> Element for ElementWrapper<'a, E>
where E: TElement,
{
type Impl = SelectorImpl;
fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
- context: &mut MatchingContext,
+ context: &mut LocalMatchingContext<Self::Impl>,
relevant_link: &RelevantLinkStatus,
_setter: &mut F)
-> bool
where F: FnMut(&Self, ElementSelectorFlags),
{
// Some pseudo-classes need special handling to evaluate them against
// the snapshot.
match *pseudo_class {
@@ -702,20 +702,20 @@ impl<'a, E> Element for ElementWrapper<'
};
return state.contains(selector_flag);
}
// For :link and :visited, we don't actually want to test the element
// state directly. Instead, we use the `relevant_link` to determine if
// they match.
NonTSPseudoClass::Link => {
- return relevant_link.is_unvisited(self, context);
+ return relevant_link.is_unvisited(self, context.shared);
}
NonTSPseudoClass::Visited => {
- return relevant_link.is_visited(self, context);
+ return relevant_link.is_visited(self, context.shared);
}
#[cfg(feature = "gecko")]
NonTSPseudoClass::MozTableBorderNonzero => {
if let Some(snapshot) = self.snapshot() {
if snapshot.has_other_pseudo_class_state() {
return snapshot.mIsTableBorderNonzero();
}
@@ -762,21 +762,17 @@ impl<'a, E> Element for ElementWrapper<'
pseudo_element: &PseudoElement,
context: &mut MatchingContext)
-> bool
{
self.element.match_pseudo_element(pseudo_element, context)
}
fn is_link(&self) -> bool {
- let mut context = MatchingContext::new(MatchingMode::Normal, None);
- self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
- &mut context,
- &RelevantLinkStatus::default(),
- &mut |_, _| {})
+ self.element.is_link()
}
fn parent_element(&self) -> Option<Self> {
self.element.parent_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn first_child_element(&self) -> Option<Self> {
@@ -1197,27 +1193,29 @@ impl DependencySet {
// filter, and as such we may fast-reject selectors incorrectly.
//
// We may be able to improve this if we record as we go down the
// tree whether any parent had a snapshot, and whether those
// snapshots were taken due to an element class/id change, but it's
// not clear we _need_ it right now.
let mut then_context =
MatchingContext::new_for_visited(MatchingMode::Normal, None,
- VisitedHandlingMode::AllLinksUnvisited);
+ VisitedHandlingMode::AllLinksUnvisited,
+ shared_context.quirks_mode);
let matched_then =
matches_selector(&dep.selector,
dep.selector_offset,
&dep.hashes,
&snapshot_el,
&mut then_context,
&mut |_, _| {});
let mut now_context =
MatchingContext::new_for_visited(MatchingMode::Normal, bloom_filter,
- VisitedHandlingMode::AllLinksUnvisited);
+ VisitedHandlingMode::AllLinksUnvisited,
+ shared_context.quirks_mode);
let matches_now =
matches_selector(&dep.selector,
dep.selector_offset,
&dep.hashes,
el,
&mut now_context,
&mut |_, _| {});
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -294,16 +294,22 @@ impl ::selectors::SelectorImpl for Selec
type AttrValue = String;
type Identifier = Atom;
type ClassName = Atom;
type LocalName = LocalName;
type NamespacePrefix = Prefix;
type NamespaceUrl = Namespace;
type BorrowedLocalName = LocalName;
type BorrowedNamespaceUrl = Namespace;
+
+ #[inline]
+ fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
+ matches!(*pseudo_class, NonTSPseudoClass::Active |
+ NonTSPseudoClass::Hover)
+ }
}
impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
type Impl = SelectorImpl;
type Error = StyleParseError<'i>;
fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
-> Result<NonTSPseudoClass, ParseError<'i>> {
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -83,16 +83,17 @@ pub struct Stylist {
/// Viewport constraints based on the current device.
viewport_constraints: Option<ViewportConstraints>,
/// Effective media query results cached from the last rebuild.
effective_media_query_results: EffectiveMediaQueryResults,
/// If true, the quirks-mode stylesheet is applied.
+ #[cfg_attr(feature = "servo", ignore_heap_size_of = "defined in selectors")]
quirks_mode: QuirksMode,
/// If true, the device has changed, and the stylist needs to be updated.
is_device_dirty: bool,
/// If true, the stylist is in a cleared state (e.g. just-constructed, or
/// had clear() called on it with no following rebuild()).
is_cleared: bool,
@@ -730,17 +731,19 @@ impl Stylist {
unsafe { p.set_selector_flags(parent_flags); }
}
}
};
// Bug 1364242: We need to add visited support for lazy pseudos
let mut declarations = ApplicableDeclarationList::new();
let mut matching_context =
- MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None);
+ MatchingContext::new(MatchingMode::ForStatelessPseudoElement,
+ None,
+ self.quirks_mode);
self.push_applicable_declarations(element,
Some(&pseudo),
None,
None,
AnimationRules(None, None),
rule_inclusion,
&mut declarations,
&mut matching_context,
@@ -922,17 +925,17 @@ impl Stylist {
/// treating us as an XBL stylesheet-only stylist.
pub fn push_applicable_declarations_as_xbl_only_stylist<E, V>(&self,
element: &E,
applicable_declarations: &mut V)
where E: TElement,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
{
let mut matching_context =
- MatchingContext::new(MatchingMode::Normal, None);
+ MatchingContext::new(MatchingMode::Normal, None, self.quirks_mode);
let mut dummy_flag_setter = |_: &E, _: ElementSelectorFlags| {};
self.element_map.author.get_all_matching_rules(element,
element,
applicable_declarations,
&mut matching_context,
&mut dummy_flag_setter,
CascadeLevel::XBL);
@@ -1151,17 +1154,17 @@ impl Stylist {
flags_setter: &mut F)
-> BitVec
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, bloom);
+ MatchingContext::new(MatchingMode::Normal, bloom, self.quirks_mode);
// 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
// the lookups, which means that the bitvecs are comparable. We verify
// this in the caller by asserting that the bitvecs are same-length.
let mut results = BitVec::new();
self.selectors_for_cache_revalidation.lookup(*element, &mut |selector_and_hashes| {