servo: Merge #16900 - Bug 1364850: Move PseudoElement to be just another combinator in selectors. r=bholley (from emilio:pseudos); r=bholley

On top of https://github.com/servo/servo/pull/16890.

Source-Repo: https://github.com/servo/servo
Source-Revision: d8b7013fcddff79a9c879077e1a564d83201359c

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 12c4ec9e73c7d9b747303a5629b20719db05f03a
This commit is contained in:
Emilio Cobos Álvarez 2017-05-17 05:40:14 -05:00
Родитель 4b561945c6
Коммит b9a37058c8
19 изменённых файлов: 675 добавлений и 541 удалений

Просмотреть файл

@ -86,7 +86,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;
@ -104,7 +104,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;
@ -2058,7 +2058,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))
}
}
}
@ -2076,8 +2077,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));
}
}
@ -2386,6 +2387,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()
}

Просмотреть файл

@ -70,7 +70,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;
@ -346,11 +346,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));
}
}
@ -717,8 +720,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)
}))
}
}

Просмотреть файл

@ -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,
@ -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,

Просмотреть файл

@ -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};
@ -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

Просмотреть файл

@ -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);

Просмотреть файл

@ -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 |_, _| {})
})
}
@ -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
@ -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) {
@ -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 {
@ -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.
@ -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()

Просмотреть файл

@ -16,6 +16,22 @@ use std::slice;
use tree::SELECTOR_WHITESPACE;
use visitor::SelectorVisitor;
/// A trait that represents a pseudo-element.
pub trait PseudoElement : Sized + ToCss {
/// The `SelectorImpl` this pseudo-element is used for.
type Impl: SelectorImpl;
/// Whether the pseudo-element supports a given state selector to the right
/// of it.
fn supports_pseudo_class(
&self,
_pseudo_class: &<Self::Impl as SelectorImpl>::NonTSPseudoClass)
-> bool
{
false
}
}
macro_rules! with_all_bounds {
(
[ $( $InSelector: tt )* ]
@ -60,7 +76,7 @@ macro_rules! with_all_bounds {
type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>;
/// pseudo-elements
type PseudoElementSelector: $($CommonBounds)* + Sized + ToCss;
type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>;
}
}
}
@ -97,8 +113,8 @@ pub trait Parser {
Err(())
}
fn parse_pseudo_element(&self, _name: Cow<str>, _input: &mut CssParser)
-> Result<<Self::Impl as SelectorImpl>::PseudoElementSelector, ()> {
fn parse_pseudo_element(&self, _name: Cow<str>)
-> Result<<Self::Impl as SelectorImpl>::PseudoElement, ()> {
Err(())
}
@ -178,18 +194,57 @@ impl<Impl: SelectorImpl> SelectorInner<Impl> {
#[derive(PartialEq, Eq, Clone)]
pub struct Selector<Impl: SelectorImpl> {
pub inner: SelectorInner<Impl>,
pub pseudo_element: Option<Impl::PseudoElementSelector>,
pub specificity: u32,
specificity_and_flags: u32,
}
impl<Impl: SelectorImpl> ::std::borrow::Borrow<SelectorInner<Impl>> for Selector<Impl> {
fn borrow(&self) -> &SelectorInner<Impl> {
&self.inner
}
}
const HAS_PSEUDO_BIT: u32 = 1 << 30;
impl<Impl: SelectorImpl> Selector<Impl> {
pub fn specificity(&self) -> u32 {
self.specificity_and_flags & !HAS_PSEUDO_BIT
}
pub fn new_for_unit_testing(inner: SelectorInner<Impl>, specificity: u32) -> Self {
Self {
inner: inner,
specificity_and_flags: specificity,
}
}
pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> {
if !self.has_pseudo_element() {
return None
}
for component in self.inner.complex.iter() {
if let Component::PseudoElement(ref pseudo) = *component {
return Some(pseudo)
}
}
debug_assert!(false, "has_pseudo_element lied!");
None
}
pub fn has_pseudo_element(&self) -> bool {
(self.specificity_and_flags & HAS_PSEUDO_BIT) != 0
}
/// Whether this selector (pseudo-element part excluded) matches every element.
///
/// Used for "pre-computed" pseudo-elements in components/style/stylist.rs
pub fn is_universal(&self) -> bool {
self.inner.complex.iter_raw().all(|c| matches!(*c,
Component::ExplicitUniversalType |
Component::ExplicitAnyNamespace
Component::ExplicitAnyNamespace |
Component::Combinator(Combinator::PseudoElement) |
Component::PseudoElement(..)
))
}
}
@ -361,7 +416,8 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
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(), "Should call take_combinator!");
debug_assert!(self.next_combinator.is_none(),
"You should call next_sequence!");
match self.iter.next() {
None => None,
Some(&Component::Combinator(c)) => {
@ -384,12 +440,12 @@ impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, Impl> {
result
}
/// Skips a sequence of simple selectors and all subsequent sequences until an
/// ancestor combinator is reached.
/// Skips a sequence of simple selectors and all subsequent sequences until
/// a non-pseudo-element ancestor combinator is reached.
fn skip_until_ancestor(&mut self) {
loop {
while let Some(_) = self.0.next() {}
if self.0.next_sequence().map_or(true, |x| x.is_ancestor()) {
while self.0.next().is_some() {}
if self.0.next_sequence().map_or(true, |x| matches!(x, Combinator::Child | Combinator::Descendant)) {
break;
}
}
@ -407,7 +463,7 @@ impl<'a, Impl: SelectorImpl> Iterator for AncestorIter<'a, Impl> {
// See if there are more sequences. If so, skip any non-ancestor sequences.
if let Some(combinator) = self.0.next_sequence() {
if !combinator.is_ancestor() {
if !matches!(combinator, Combinator::Child | Combinator::Descendant) {
self.skip_until_ancestor();
}
}
@ -422,12 +478,24 @@ pub enum Combinator {
Descendant, // space
NextSibling, // +
LaterSibling, // ~
/// A dummy combinator we use to the left of pseudo-elements.
///
/// It serializes as the empty string, and acts effectively as a child
/// combinator.
PseudoElement,
}
impl Combinator {
/// Returns true if this combinator is a child or descendant combinator.
pub fn is_ancestor(&self) -> bool {
matches!(*self, Combinator::Child | Combinator::Descendant)
matches!(*self, Combinator::Child |
Combinator::Descendant |
Combinator::PseudoElement)
}
/// Returns true if this combinator is a pseudo-element combinator.
pub fn is_pseudo_element(&self) -> bool {
matches!(*self, Combinator::PseudoElement)
}
/// Returns true if this combinator is a next- or later-sibling combinator.
@ -491,6 +559,7 @@ pub enum Component<Impl: SelectorImpl> {
LastOfType,
OnlyOfType,
NonTSPseudoClass(Impl::NonTSPseudoClass),
PseudoElement(Impl::PseudoElement),
// ...
}
@ -582,7 +651,7 @@ impl<Impl: SelectorImpl> Debug for Selector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Selector(")?;
self.to_css(f)?;
write!(f, ", specificity = 0x{:x})", self.specificity)
write!(f, ", specificity = 0x{:x})", self.specificity())
}
}
@ -621,11 +690,7 @@ impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> {
impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.inner.complex.to_css(dest)?;
if let Some(ref pseudo) = self.pseudo_element {
pseudo.to_css(dest)?;
}
Ok(())
self.inner.complex.to_css(dest)
}
}
@ -646,6 +711,7 @@ impl ToCss for Combinator {
Combinator::Descendant => dest.write_str(" "),
Combinator::NextSibling => dest.write_str(" + "),
Combinator::LaterSibling => dest.write_str(" ~ "),
Combinator::PseudoElement => Ok(()),
}
}
}
@ -657,6 +723,9 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
Combinator(ref c) => {
c.to_css(dest)
}
PseudoElement(ref p) => {
p.to_css(dest)
}
ID(ref s) => {
dest.write_char('#')?;
display_to_css_identifier(s, dest)
@ -841,25 +910,23 @@ impl From<Specificity> for u32 {
}
}
fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>,
pseudo_element: Option<&Impl::PseudoElementSelector>)
-> u32
where Impl: SelectorImpl {
let mut specificity = complex_selector_specificity(complex_selector);
if pseudo_element.is_some() {
specificity.element_selectors += 1;
}
specificity.into()
fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>) -> u32
where Impl: SelectorImpl
{
complex_selector_specificity(complex_selector).into()
}
fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>)
-> Specificity
where Impl: SelectorImpl {
where Impl: SelectorImpl
{
fn simple_selector_specificity<Impl>(simple_selector: &Component<Impl>,
specificity: &mut Specificity)
where Impl: SelectorImpl {
where Impl: SelectorImpl
{
match *simple_selector {
Component::Combinator(..) => unreachable!(),
Component::PseudoElement(..) |
Component::LocalName(..) => {
specificity.element_selectors += 1
}
@ -928,12 +995,14 @@ fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>)
fn parse_selector<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let (complex, pseudo_element) =
parse_complex_selector_and_pseudo_element(parser, input)?;
let (complex, has_pseudo_element) = parse_complex_selector(parser, input)?;
let mut specificity = specificity(&complex);
if has_pseudo_element {
specificity |= HAS_PSEUDO_BIT;
}
Ok(Selector {
specificity: specificity(&complex, pseudo_element.as_ref()),
specificity_and_flags: specificity,
inner: SelectorInner::new(complex),
pseudo_element: pseudo_element,
})
}
@ -947,19 +1016,25 @@ fn parse_selector<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector
/// If we parse N > 8 entries, we save two reallocations.
type ParseVec<Impl> = SmallVec<[Component<Impl>; 8]>;
fn parse_complex_selector_and_pseudo_element<P, Impl>(
/// Parses a complex selector, including any pseudo-element.
///
/// For now, it always forces the pseudo-element to be at the end of the
/// selector, and the boolean represents whether the last thing parsed was a
/// pseudo-element.
fn parse_complex_selector<P, Impl>(
parser: &P,
input: &mut CssParser)
-> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElementSelector>), ()>
-> Result<(ComplexSelector<Impl>, bool), ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let mut sequence = ParseVec::new();
let mut pseudo_element;
let mut parsed_pseudo_element;
'outer_loop: loop {
// Parse a sequence of simple selectors.
pseudo_element = parse_compound_selector(parser, input, &mut sequence,
/* inside_negation = */ false)?;
if pseudo_element.is_some() {
parsed_pseudo_element =
parse_compound_selector(parser, input, &mut sequence,
/* inside_negation = */ false)?;
if parsed_pseudo_element {
break;
}
@ -998,17 +1073,17 @@ fn parse_complex_selector_and_pseudo_element<P, Impl>(
}
let complex = ComplexSelector(ArcSlice::new(sequence.into_vec().into_boxed_slice()));
Ok((complex, pseudo_element))
Ok((complex, parsed_pseudo_element))
}
impl<Impl: SelectorImpl> ComplexSelector<Impl> {
/// Parse a complex selector.
/// Parse a complex selector, without any pseudo-element.
pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()>
where P: Parser<Impl=Impl>
{
let (complex, pseudo_element) =
parse_complex_selector_and_pseudo_element(parser, input)?;
if pseudo_element.is_some() {
let (complex, has_pseudo_element) =
parse_complex_selector(parser, input)?;
if has_pseudo_element {
return Err(())
}
Ok(complex)
@ -1062,7 +1137,7 @@ fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser, sequence: &mu
#[derive(Debug)]
enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(Component<Impl>),
PseudoElement(Impl::PseudoElementSelector),
PseudoElement(Impl::PseudoElement),
}
#[derive(Debug)]
@ -1295,13 +1370,15 @@ fn single_simple_selector<Impl: SelectorImpl>(v: &[Component<Impl>]) -> bool {
/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
/// | [ HASH | class | attrib | pseudo | negation ]+
///
/// `Err(())` means invalid selector
/// `Err(())` means invalid selector.
///
/// The boolean represent whether a pseudo-element has been parsed.
fn parse_compound_selector<P, Impl>(
parser: &P,
input: &mut CssParser,
mut sequence: &mut ParseVec<Impl>,
inside_negation: bool)
-> Result<Option<Impl::PseudoElementSelector>, ()>
-> Result<bool, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
// Consume any leading whitespace.
@ -1328,7 +1405,7 @@ fn parse_compound_selector<P, Impl>(
empty = false;
}
let mut pseudo_element = None;
let mut pseudo = false;
loop {
match parse_one_simple_selector(parser, input, inside_negation)? {
None => break,
@ -1337,7 +1414,41 @@ fn parse_compound_selector<P, Impl>(
empty = false
}
Some(SimpleSelectorParseResult::PseudoElement(p)) => {
pseudo_element = Some(p);
// Try to parse state to its right.
let mut state_selectors = ParseVec::new();
loop {
match input.next_including_whitespace() {
Ok(Token::Colon) => {},
Ok(Token::WhiteSpace(_)) | Err(()) => break,
_ => return Err(()),
}
// TODO(emilio): Functional pseudo-classes too?
// We don't need it for now.
let name = match input.next_including_whitespace() {
Ok(Token::Ident(name)) => name,
_ => return Err(()),
};
let pseudo_class =
P::parse_non_ts_pseudo_class(parser, name)?;
if !p.supports_pseudo_class(&pseudo_class) {
return Err(());
}
state_selectors.push(Component::NonTSPseudoClass(pseudo_class));
}
if !sequence.is_empty() {
sequence.push(Component::Combinator(Combinator::PseudoElement));
}
sequence.push(Component::PseudoElement(p));
for state_selector in state_selectors {
sequence.push(state_selector);
}
pseudo = true;
empty = false;
break
}
@ -1347,7 +1458,7 @@ fn parse_compound_selector<P, Impl>(
// An empty selector is invalid.
Err(())
} else {
Ok(pseudo_element)
Ok(pseudo)
}
}
@ -1423,7 +1534,7 @@ fn parse_one_simple_selector<P, Impl>(parser: &P,
name.eq_ignore_ascii_case("after") ||
name.eq_ignore_ascii_case("first-line") ||
name.eq_ignore_ascii_case("first-letter") {
let pseudo_element = P::parse_pseudo_element(parser, name, input)?;
let pseudo_element = P::parse_pseudo_element(parser, name)?;
Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element)))
} else {
let pseudo_class = parse_simple_pseudo_class(parser, name)?;
@ -1439,7 +1550,7 @@ fn parse_one_simple_selector<P, Impl>(parser: &P,
Ok(Token::Colon) => {
match input.next_including_whitespace() {
Ok(Token::Ident(name)) => {
let pseudo = P::parse_pseudo_element(parser, name, input)?;
let pseudo = P::parse_pseudo_element(parser, name)?;
Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo)))
}
_ => Err(())
@ -1478,6 +1589,7 @@ fn parse_simple_pseudo_class<P, Impl>(parser: &P, name: Cow<str>) -> Result<Comp
#[cfg(test)]
pub mod tests {
use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
use parser;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
@ -1486,6 +1598,7 @@ pub mod tests {
#[derive(PartialEq, Clone, Debug, Eq)]
pub enum PseudoClass {
Hover,
Active,
Lang(String),
}
@ -1495,10 +1608,23 @@ pub mod tests {
After,
}
impl parser::PseudoElement for PseudoElement {
type Impl = DummySelectorImpl;
fn supports_pseudo_class(&self, pc: &PseudoClass) -> bool {
match *pc {
PseudoClass::Hover => true,
PseudoClass::Active |
PseudoClass::Lang(..) => false,
}
}
}
impl ToCss for PseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
PseudoClass::Hover => dest.write_str(":hover"),
PseudoClass::Active => dest.write_str(":active"),
PseudoClass::Lang(ref lang) => {
dest.write_str(":lang(")?;
serialize_identifier(lang, dest)?;
@ -1543,7 +1669,7 @@ pub mod tests {
type BorrowedLocalName = DummyAtom;
type BorrowedNamespaceUrl = DummyAtom;
type NonTSPseudoClass = PseudoClass;
type PseudoElementSelector = PseudoElement;
type PseudoElement = PseudoElement;
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
@ -1580,6 +1706,7 @@ pub mod tests {
-> Result<PseudoClass, ()> {
match_ignore_ascii_case! { &name,
"hover" => Ok(PseudoClass::Hover),
"active" => Ok(PseudoClass::Active),
_ => Err(())
}
}
@ -1593,8 +1720,7 @@ pub mod tests {
}
}
fn parse_pseudo_element(&self, name: Cow<str>, _input: &mut CssParser)
-> Result<PseudoElement, ()> {
fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
match_ignore_ascii_case! { &name,
"before" => Ok(PseudoElement::Before),
"after" => Ok(PseudoElement::After),
@ -1647,11 +1773,9 @@ pub mod tests {
inner: SelectorInner::from_vec(vec!(
Component::LocalName(LocalName {
name: DummyAtom::from("EeÉ"),
lower_name: DummyAtom::from("eeÉ")
}),
)),
pseudo_element: None,
specificity: specificity(0, 0, 1),
lower_name: DummyAtom::from("eeÉ") })),
),
specificity_and_flags: specificity(0, 0, 1),
}))));
assert_eq!(parse("|e"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
@ -1661,8 +1785,7 @@ pub mod tests {
lower_name: DummyAtom::from("e")
}),
)),
pseudo_element: None,
specificity: specificity(0, 0, 1),
specificity_and_flags: specificity(0, 0, 1),
}))));
// https://github.com/servo/servo/issues/16020
assert_eq!(parse("*|e"), Ok(SelectorList(vec!(Selector {
@ -1673,44 +1796,38 @@ pub mod tests {
lower_name: DummyAtom::from("e")
}),
)),
pseudo_element: None,
specificity: specificity(0, 0, 1),
specificity_and_flags: specificity(0, 0, 1),
}))));
assert_eq!(parse("*"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
Component::ExplicitUniversalType,
)),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse("|*"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
Component::ExplicitNoNamespace,
Component::ExplicitUniversalType,
)),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse("*|*"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
Component::ExplicitAnyNamespace,
Component::ExplicitUniversalType,
)),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec![
Component::Class(DummyAtom::from("foo")),
Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned()))
]),
pseudo_element: None,
specificity: specificity(0, 2, 0),
specificity_and_flags: specificity(0, 2, 0),
}))));
assert_eq!(parse("#bar"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(Component::ID(DummyAtom::from("bar")))),
pseudo_element: None,
specificity: specificity(1, 0, 0),
specificity_and_flags: specificity(1, 0, 0),
}))));
assert_eq!(parse("e.foo#bar"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(Component::LocalName(LocalName {
@ -1718,8 +1835,7 @@ pub mod tests {
lower_name: DummyAtom::from("e") }),
Component::Class(DummyAtom::from("foo")),
Component::ID(DummyAtom::from("bar")))),
pseudo_element: None,
specificity: specificity(1, 1, 1),
specificity_and_flags: specificity(1, 1, 1),
}))));
assert_eq!(parse("e.foo #bar"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
@ -1731,8 +1847,7 @@ pub mod tests {
Component::Combinator(Combinator::Descendant),
Component::ID(DummyAtom::from("bar")),
)),
pseudo_element: None,
specificity: specificity(1, 1, 1),
specificity_and_flags: specificity(1, 1, 1),
}))));
// Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652
@ -1746,8 +1861,7 @@ pub mod tests {
prefix: None,
url: "".into(),
}) }))),
pseudo_element: None,
specificity: specificity(0, 1, 0),
specificity_and_flags: specificity(0, 1, 0),
}))));
assert_eq!(parse_ns("svg|circle", &parser), Err(()));
parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into()));
@ -1760,8 +1874,7 @@ pub mod tests {
lower_name: DummyAtom::from("circle"),
})
]),
pseudo_element: None,
specificity: specificity(0, 0, 1),
specificity_and_flags: specificity(0, 0, 1),
}])));
assert_eq!(parse_ns("svg|*", &parser), Ok(SelectorList(vec![Selector {
inner: SelectorInner::from_vec(
@ -1769,8 +1882,7 @@ pub mod tests {
Component::Namespace(DummyAtom("svg".into()), SVG.into()),
Component::ExplicitUniversalType,
]),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}])));
// Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652
@ -1790,8 +1902,7 @@ pub mod tests {
}),
}),
]),
pseudo_element: None,
specificity: specificity(0, 1, 0),
specificity_and_flags: specificity(0, 1, 0),
}))));
// Default namespace does apply to type selectors
assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector {
@ -1802,8 +1913,7 @@ pub mod tests {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") }),
)),
pseudo_element: None,
specificity: specificity(0, 0, 1),
specificity_and_flags: specificity(0, 0, 1),
}))));
assert_eq!(parse_ns("*", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(
@ -1811,8 +1921,7 @@ pub mod tests {
Component::DefaultNamespace(MATHML.into()),
Component::ExplicitUniversalType,
)),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse_ns("*|*", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(
@ -1820,8 +1929,7 @@ pub mod tests {
Component::ExplicitAnyNamespace,
Component::ExplicitUniversalType,
)),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
// Default namespace applies to universal and type selectors inside :not and :matches,
// but not otherwise.
@ -1832,8 +1940,7 @@ pub mod tests {
Component::Class(DummyAtom::from("cl"))
].into_boxed_slice()),
)),
pseudo_element: None,
specificity: specificity(0, 1, 0),
specificity_and_flags: specificity(0, 1, 0),
}))));
assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
@ -1843,8 +1950,7 @@ pub mod tests {
Component::ExplicitUniversalType,
].into_boxed_slice()),
)),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse_ns(":not(e)", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
@ -1857,8 +1963,7 @@ pub mod tests {
}),
].into_boxed_slice())
)),
pseudo_element: None,
specificity: specificity(0, 0, 1),
specificity_and_flags: specificity(0, 0, 1),
}))));
assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector {
inner: SelectorInner::from_vec(
@ -1872,15 +1977,42 @@ pub mod tests {
}),
}, DummyAtom::from("foo"))
]),
pseudo_element: None,
specificity: specificity(0, 1, 0),
specificity_and_flags: specificity(0, 1, 0),
}])));
// https://github.com/mozilla/servo/issues/1723
assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec![]),
pseudo_element: Some(PseudoElement::Before),
specificity: specificity(0, 0, 1),
inner: SelectorInner::from_vec(
vec![
Component::PseudoElement(PseudoElement::Before),
]
),
specificity_and_flags: specificity(0, 0, 1) | HAS_PSEUDO_BIT,
}))));
assert_eq!(parse("::before:hover"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(
vec![
Component::PseudoElement(PseudoElement::Before),
Component::NonTSPseudoClass(PseudoClass::Hover),
]
),
specificity_and_flags: specificity(0, 1, 1) | HAS_PSEUDO_BIT,
}))));
assert_eq!(parse("::before:hover:hover"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(
vec![
Component::PseudoElement(PseudoElement::Before),
Component::NonTSPseudoClass(PseudoClass::Hover),
Component::NonTSPseudoClass(PseudoClass::Hover),
]
),
specificity_and_flags: specificity(0, 2, 1) | HAS_PSEUDO_BIT,
}))));
assert_eq!(parse("::before:hover:active"), Err(()));
assert_eq!(parse("::before:hover .foo"), Err(()));
assert_eq!(parse("::before .foo"), Err(()));
assert_eq!(parse("::before ~ bar"), Err(()));
assert_eq!(parse("::before:active"), Err(()));
// https://github.com/servo/servo/issues/15335
assert_eq!(parse(":: before"), Err(()));
assert_eq!(parse("div ::after"), Ok(SelectorList(vec!(Selector {
@ -1890,9 +2022,10 @@ pub mod tests {
name: DummyAtom::from("div"),
lower_name: DummyAtom::from("div") }),
Component::Combinator(Combinator::Descendant),
Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::After),
]),
pseudo_element: Some(PseudoElement::After),
specificity: specificity(0, 0, 2),
specificity_and_flags: specificity(0, 0, 2) | HAS_PSEUDO_BIT,
}))));
assert_eq!(parse("#d1 > .ok"), Ok(SelectorList(vec![Selector {
inner: SelectorInner::from_vec(
@ -1901,8 +2034,7 @@ pub mod tests {
Component::Combinator(Combinator::Child),
Component::Class(DummyAtom::from("ok")),
]),
pseudo_element: None,
specificity: (1 << 20) + (1 << 10) + (0 << 0),
specificity_and_flags: (1 << 20) + (1 << 10) + (0 << 0),
}])));
parser.default_ns = None;
assert_eq!(parse(":not(#provel.old)"), Err(()));
@ -1914,8 +2046,7 @@ pub mod tests {
Component::ID(DummyAtom::from("provel")),
].into_boxed_slice()
))),
pseudo_element: None,
specificity: specificity(1, 0, 0),
specificity_and_flags: specificity(1, 0, 0),
}))));
assert_eq!(parse_ns(":not(svg|circle)", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(Component::Negation(
@ -1927,8 +2058,7 @@ pub mod tests {
}),
].into_boxed_slice()
))),
pseudo_element: None,
specificity: specificity(0, 0, 1),
specificity_and_flags: specificity(0, 0, 1),
}))));
// https://github.com/servo/servo/issues/16017
assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector {
@ -1937,8 +2067,7 @@ pub mod tests {
Component::ExplicitUniversalType,
].into_boxed_slice()
))),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse_ns(":not(|*)", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(Component::Negation(
@ -1947,8 +2076,7 @@ pub mod tests {
Component::ExplicitUniversalType,
].into_boxed_slice()
))),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse_ns(":not(*|*)", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(Component::Negation(
@ -1957,8 +2085,7 @@ pub mod tests {
Component::ExplicitUniversalType,
].into_boxed_slice()
))),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
assert_eq!(parse_ns(":not(svg|*)", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(Component::Negation(
@ -1967,11 +2094,40 @@ pub mod tests {
Component::ExplicitUniversalType,
].into_boxed_slice()
))),
pseudo_element: None,
specificity: specificity(0, 0, 0),
specificity_and_flags: specificity(0, 0, 0),
}))));
}
#[test]
fn test_pseudo_iter() {
let selector = &parse("q::before").unwrap().0[0];
assert!(!selector.is_universal());
let mut iter = selector.inner.complex.iter();
assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before)));
assert_eq!(iter.next(), None);
let combinator = iter.next_sequence();
assert_eq!(combinator, Some(Combinator::PseudoElement));
assert!(matches!(iter.next(), Some(&Component::LocalName(..))));
assert_eq!(iter.next(), None);
assert_eq!(iter.next_sequence(), None);
}
#[test]
fn test_universal() {
let selector = &parse("*|*::before").unwrap().0[0];
assert!(selector.is_universal());
}
#[test]
fn test_empty_pseudo_iter() {
let selector = &parse("::before").unwrap().0[0];
assert!(selector.is_universal());
let mut iter = selector.inner.complex.iter();
assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before)));
assert_eq!(iter.next(), None);
assert_eq!(iter.next_sequence(), None);
}
struct TestVisitor {
seen: Vec<String>,
}
@ -1992,5 +2148,9 @@ pub mod tests {
let mut test_visitor = TestVisitor { seen: vec![], };
parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor);
assert!(test_visitor.seen.contains(&":hover".into()));
let mut test_visitor = TestVisitor { seen: vec![], };
parse("::before:hover").unwrap().0[0].visit(&mut test_visitor);
assert!(test_visitor.seen.contains(&":hover".into()));
}
}

Просмотреть файл

@ -3,14 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::ToCss;
use gecko_like_types;
use gecko_like_types::*;
use parser;
use parser::*;
use precomputed_hash::PrecomputedHash;
use std::fmt;
use visitor::SelectorVisitor;
size_of_test!(size_of_selector, Selector<Impl>, 72);
size_of_test!(size_of_pseudo_element, PseudoElementSelector, 16);
size_of_test!(size_of_selector, Selector<Impl>, 48);
size_of_test!(size_of_pseudo_element, gecko_like_types::PseudoElement, 1);
size_of_test!(size_of_selector_inner, SelectorInner<Impl>, 40);
size_of_test!(size_of_complex_selector, ComplexSelector<Impl>, 24);
@ -18,6 +20,9 @@ size_of_test!(size_of_component, Component<Impl>, 64);
size_of_test!(size_of_attr_selector, AttrSelector<Impl>, 48);
size_of_test!(size_of_pseudo_class, PseudoClass, 24);
impl parser::PseudoElement for gecko_like_types::PseudoElement {
type Impl = Impl;
}
// Boilerplate
@ -31,7 +36,7 @@ impl SelectorImpl for Impl {
type BorrowedLocalName = Atom;
type BorrowedNamespaceUrl = Atom;
type NonTSPseudoClass = PseudoClass;
type PseudoElementSelector = PseudoElementSelector;
type PseudoElement = gecko_like_types::PseudoElement;
}
impl SelectorMethods for PseudoClass {
@ -45,7 +50,7 @@ impl ToCss for PseudoClass {
fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write { unimplemented!() }
}
impl ToCss for PseudoElementSelector {
impl ToCss for gecko_like_types::PseudoElement {
fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write { unimplemented!() }
}

Просмотреть файл

@ -123,6 +123,14 @@ impl<T> MatchAttr for T where T: MatchAttrGeneric, T::Impl: SelectorImpl<AttrVal
pub trait Element: MatchAttr + Sized {
fn parent_element(&self) -> Option<Self>;
/// The parent of a given pseudo-element, after matching a pseudo-element
/// selector.
///
/// This is guaranteed to be called in a pseudo-element.
fn pseudo_element_originating_element(&self) -> Option<Self> {
self.parent_element()
}
// Skips non-element nodes
fn first_child_element(&self) -> Option<Self>;
@ -145,6 +153,11 @@ pub trait Element: MatchAttr + Sized {
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;
fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>;
fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool;

Просмотреть файл

@ -10,12 +10,24 @@
use cssparser::ToCss;
use gecko_bindings::structs::{self, CSSPseudoElementType};
use selector_parser::PseudoElementCascadeType;
use selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
use std::fmt;
use string_cache::Atom;
include!(concat!(env!("OUT_DIR"), "/gecko/pseudo_element_definition.rs"));
impl ::selectors::parser::PseudoElement for PseudoElement {
type Impl = SelectorImpl;
fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
if !self.supports_user_action_state() {
return false;
}
return pseudo_class.is_safe_user_action_state();
}
}
impl PseudoElement {
/// Returns the kind of cascade type that a given pseudo is going to use.
///

Просмотреть файл

@ -5,7 +5,6 @@
//! Gecko-specific bits for selector-parsing.
use cssparser::{Parser, ToCss};
use element_state::{IN_ACTIVE_STATE, IN_FOCUS_STATE, IN_HOVER_STATE};
use element_state::ElementState;
use gecko_bindings::structs::CSSPseudoClassType;
use selector_parser::{SelectorParser, PseudoElementCascadeType};
@ -132,7 +131,7 @@ impl NonTSPseudoClass {
/// https://drafts.csswg.org/selectors-4/#useraction-pseudos
///
/// We intentionally skip the link-related ones.
fn is_safe_user_action_state(&self) -> bool {
pub fn is_safe_user_action_state(&self) -> bool {
matches!(*self, NonTSPseudoClass::Hover |
NonTSPseudoClass::Active |
NonTSPseudoClass::Focus)
@ -195,58 +194,6 @@ impl NonTSPseudoClass {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SelectorImpl;
/// Some subset of pseudo-elements in Gecko are sensitive to some state
/// selectors.
///
/// We store the sensitive states in this struct in order to properly handle
/// these.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PseudoElementSelector {
pseudo: PseudoElement,
state: ElementState,
}
impl PseudoElementSelector {
/// Returns the pseudo-element this selector represents.
pub fn pseudo_element(&self) -> &PseudoElement {
&self.pseudo
}
/// Returns the pseudo-element selector state.
pub fn state(&self) -> ElementState {
self.state
}
}
impl ToCss for PseudoElementSelector {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write,
{
if cfg!(debug_assertions) {
let mut state = self.state;
state.remove(IN_HOVER_STATE | IN_ACTIVE_STATE | IN_FOCUS_STATE);
assert_eq!(state, ElementState::empty(),
"Unhandled pseudo-element state selector?");
}
self.pseudo.to_css(dest)?;
if self.state.contains(IN_HOVER_STATE) {
dest.write_str(":hover")?
}
if self.state.contains(IN_ACTIVE_STATE) {
dest.write_str(":active")?
}
if self.state.contains(IN_FOCUS_STATE) {
dest.write_str(":focus")?
}
Ok(())
}
}
impl ::selectors::SelectorImpl for SelectorImpl {
type AttrValue = Atom;
type Identifier = Atom;
@ -257,7 +204,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type BorrowedNamespaceUrl = WeakNamespace;
type BorrowedLocalName = WeakAtom;
type PseudoElementSelector = PseudoElementSelector;
type PseudoElement = PseudoElement;
type NonTSPseudoClass = NonTSPseudoClass;
}
@ -319,38 +266,9 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
}
}
fn parse_pseudo_element(&self, name: Cow<str>, input: &mut Parser) -> Result<PseudoElementSelector, ()> {
let pseudo =
match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) {
Some(pseudo) => pseudo,
None => return Err(()),
};
let state = if pseudo.supports_user_action_state() {
input.try(|input| {
let mut state = ElementState::empty();
while !input.is_exhausted() {
input.expect_colon()?;
let ident = input.expect_ident()?;
let pseudo_class = self.parse_non_ts_pseudo_class(ident)?;
if !pseudo_class.is_safe_user_action_state() {
return Err(())
}
state.insert(pseudo_class.state_flag());
}
Ok(state)
}).ok()
} else {
None
};
Ok(PseudoElementSelector {
pseudo: pseudo,
state: state.unwrap_or(ElementState::empty()),
})
fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
PseudoElement::from_slice(&name, self.in_user_agent_stylesheet())
.ok_or(())
}
fn default_namespace(&self) -> Option<Namespace> {

Просмотреть файл

@ -64,7 +64,7 @@ use properties::style_structs::Font;
use rule_tree::CascadeLevel as ServoCascadeLevel;
use selector_parser::ElementExt;
use selectors::Element;
use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, NamespaceConstraint};
use shared_lock::Locked;
use sink::Push;
@ -1055,6 +1055,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
parent_node.and_then(|n| n.as_element())
}
fn pseudo_element_originating_element(&self) -> Option<Self> {
debug_assert!(self.implemented_pseudo_element().is_some());
self.closest_non_native_anonymous_ancestor()
}
fn first_child_element(&self) -> Option<Self> {
let mut child = self.as_node().first_child();
while let Some(child_node) = child {
@ -1244,6 +1249,20 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
}
}
fn match_pseudo_element(&self,
pseudo_element: &PseudoElement,
_context: &mut MatchingContext)
-> bool
{
// TODO(emilio): I believe we could assert we are a pseudo-element and
// match the proper pseudo-element, given how we rulehash the stuff
// based on the pseudo.
match self.implemented_pseudo_element() {
Some(ref pseudo) => pseudo == pseudo_element,
None => false,
}
}
fn get_id(&self) -> Option<Atom> {
let ptr = unsafe {
bindings::Gecko_AtomAttrValue(self.0,
@ -1378,8 +1397,9 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> {
impl<'le> ElementExt for GeckoElement<'le> {
#[inline]
fn is_link(&self) -> bool {
let mut context = MatchingContext::new(MatchingMode::Normal, None);
self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
&mut MatchingContext::default(),
&mut context,
&mut |_, _| {})
}

Просмотреть файл

@ -15,7 +15,6 @@ use cascade_info::CascadeInfo;
use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, ElementStyles, RestyleData};
use dom::{AnimationRules, SendElement, TElement, TNode};
use element_state::ElementState;
use font_metrics::FontMetricsProvider;
use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
use properties::longhands::display::computed_value as display;
@ -24,7 +23,7 @@ use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode};
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::bloom::BloomFilter;
use selectors::matching::{ElementSelectorFlags, MatchingContext, StyleRelations};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS;
use shared_lock::StylesheetGuards;
use sink::ForgetfulSink;
@ -895,10 +894,10 @@ pub trait MatchMethods : TElement {
sharing: StyleSharingBehavior)
{
// Perform selector matching for the primary style.
let mut primary_matching_context = MatchingContext::default();
let mut relations = StyleRelations::empty();
let _rule_node_changed = self.match_primary(context,
data,
&mut primary_matching_context);
&mut relations);
// Cascade properties and compute primary values.
self.cascade_primary(context, data);
@ -912,7 +911,7 @@ pub trait MatchMethods : TElement {
// If we have any pseudo elements, indicate so in the primary StyleRelations.
if !data.styles().pseudos.is_empty() {
primary_matching_context.relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
}
// If the style is shareable, add it to the LRU cache.
@ -932,7 +931,7 @@ pub trait MatchMethods : TElement {
.style_sharing_candidate_cache
.insert_if_possible(self,
data.styles().primary.values(),
primary_matching_context.relations,
relations,
revalidation_match_results);
}
}
@ -952,7 +951,7 @@ pub trait MatchMethods : TElement {
fn match_primary(&self,
context: &mut StyleContext<Self>,
data: &mut ElementData,
matching_context: &mut MatchingContext)
relations: &mut StyleRelations)
-> bool
{
let implemented_pseudo = self.implemented_pseudo_element();
@ -1004,35 +1003,27 @@ pub trait MatchMethods : TElement {
let animation_rules = self.get_animation_rules();
let bloom = context.thread_local.bloom_filter.filter();
let map = &mut context.thread_local.selector_flags;
let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
self.apply_selector_flags(map, element, flags);
};
let selector_matching_target = match implemented_pseudo {
Some(..) => {
self.closest_non_native_anonymous_ancestor()
.expect("Pseudo-element without non-NAC parent?")
},
None => *self,
};
let pseudo_and_state = match implemented_pseudo {
Some(ref pseudo) => Some((pseudo, self.get_state())),
None => None,
};
let mut matching_context =
MatchingContext::new(MatchingMode::Normal, Some(bloom));
// Compute the primary rule node.
stylist.push_applicable_declarations(&selector_matching_target,
Some(bloom),
stylist.push_applicable_declarations(self,
implemented_pseudo.as_ref(),
style_attribute,
smil_override,
animation_rules,
pseudo_and_state,
&mut applicable_declarations,
matching_context,
&mut matching_context,
&mut set_selector_flags);
*relations = matching_context.relations;
let primary_rule_node =
compute_rule_node::<Self>(&stylist.rule_tree,
&mut applicable_declarations,
@ -1041,8 +1032,8 @@ pub trait MatchMethods : TElement {
return data.set_primary_rules(primary_rule_node);
}
/// Runs selector matching to (re)compute eager pseudo-element rule nodes for this
/// element.
/// Runs selector matching to (re)compute eager pseudo-element rule nodes
/// for this element.
///
/// Returns whether any of the pseudo rule nodes changed (including, but not
/// limited to, cases where we match different pseudos altogether).
@ -1070,6 +1061,10 @@ pub trait MatchMethods : TElement {
let rule_tree = &stylist.rule_tree;
let bloom_filter = context.thread_local.bloom_filter.filter();
let mut matching_context =
MatchingContext::new(MatchingMode::ForStatelessPseudoElement,
Some(bloom_filter));
// Compute rule nodes for eagerly-cascaded pseudo-elements.
let mut matches_different_pseudos = false;
let mut rule_nodes_changed = false;
@ -1079,13 +1074,12 @@ pub trait MatchMethods : TElement {
// NB: We handle animation rules for ::before and ::after when
// traversing them.
stylist.push_applicable_declarations(self,
Some(bloom_filter),
Some(&pseudo),
None,
None,
AnimationRules(None, None),
Some((&pseudo, ElementState::empty())),
&mut applicable_declarations,
&mut MatchingContext::default(),
&mut matching_context,
&mut set_selector_flags);
if !applicable_declarations.is_empty() {

Просмотреть файл

@ -9,14 +9,13 @@
use Atom;
use dom::TElement;
use element_state::*;
use fnv::FnvHashMap;
#[cfg(feature = "gecko")]
use gecko_bindings::structs::nsRestyleHint;
#[cfg(feature = "servo")]
use heapsize::HeapSizeOf;
use selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap};
use selectors::{Element, MatchAttr};
use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::matching::matches_selector;
use selectors::parser::{AttrSelector, Combinator, Component, Selector};
use selectors::parser::{SelectorInner, SelectorMethods};
@ -406,6 +405,14 @@ impl<'a, E> Element for ElementWrapper<'a, E>
}
}
fn match_pseudo_element(&self,
pseudo_element: &PseudoElement,
context: &mut MatchingContext)
-> bool
{
self.element.match_pseudo_element(pseudo_element, context)
}
fn parent_element(&self) -> Option<Self> {
self.element.parent_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
@ -475,6 +482,11 @@ impl<'a, E> Element for ElementWrapper<'a, E>
_ => self.element.each_class(callback)
}
}
fn pseudo_element_originating_element(&self) -> Option<Self> {
self.element.closest_non_native_anonymous_ancestor()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
}
fn selector_to_state(sel: &Component<SelectorImpl>) -> ElementState {
@ -507,6 +519,9 @@ fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
match combinator {
None => RESTYLE_SELF,
Some(c) => match c {
// NB: RESTYLE_SELF is needed to handle properly eager pseudos,
// otherwise we may leave a stale style on the parent.
Combinator::PseudoElement => RESTYLE_SELF | RESTYLE_DESCENDANTS,
Combinator::Child => RESTYLE_DESCENDANTS,
Combinator::Descendant => RESTYLE_DESCENDANTS,
Combinator::NextSibling => RESTYLE_LATER_SIBLINGS,
@ -634,13 +649,6 @@ impl SelectorVisitor for SensitivitiesVisitor {
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct DependencySet {
/// A map used for pseudo-element's dependencies.
///
/// Note that pseudo-elements are somewhat special, because some of them in
/// Gecko track state, and also because they don't do selector-matching as
/// normal, but against their parent element.
pseudo_dependencies: FnvHashMap<PseudoElement, SelectorMap<PseudoElementDependency>>,
/// This is for all other normal element's selectors/selector parts.
dependencies: SelectorMap<Dependency>,
}
@ -668,34 +676,9 @@ impl DependencySet {
index += 1; // Account for the simple selector.
}
let pseudo_selector_is_state_dependent =
sequence_start == 0 &&
selector.pseudo_element.as_ref().map_or(false, |pseudo_selector| {
!pseudo_selector.state().is_empty()
});
if pseudo_selector_is_state_dependent {
let pseudo_selector = selector.pseudo_element.as_ref().unwrap();
self.pseudo_dependencies
.entry(pseudo_selector.pseudo_element().clone())
.or_insert_with(SelectorMap::new)
.insert(PseudoElementDependency {
selector: selector.clone(),
});
}
// If we found a sensitivity, add an entry in the dependency set.
if !visitor.sensitivities.is_empty() {
let mut hint = combinator_to_restyle_hint(combinator);
if sequence_start == 0 && selector.pseudo_element.is_some() {
// FIXME(emilio): Be more granular about this. See the
// comment in `PseudoElementDependency` about how could this
// be modified in order to be more efficient and restyle
// less.
hint |= RESTYLE_DESCENDANTS;
}
let hint = combinator_to_restyle_hint(combinator);
let dep_selector = if sequence_start == 0 {
// Reuse the bloom hashes if this is the base selector.
@ -724,82 +707,22 @@ impl DependencySet {
pub fn new() -> Self {
DependencySet {
dependencies: SelectorMap::new(),
pseudo_dependencies: FnvHashMap::default(),
}
}
/// Return the total number of dependencies that this set contains.
pub fn len(&self) -> usize {
self.dependencies.len() +
self.pseudo_dependencies.values().fold(0, |acc, val| acc + val.len())
self.dependencies.len()
}
/// Clear this dependency set.
pub fn clear(&mut self) {
self.dependencies = SelectorMap::new();
self.pseudo_dependencies.clear()
}
fn compute_pseudo_hint<E>(
&self,
pseudo: &E,
pseudo_element: PseudoElement,
snapshots: &SnapshotMap)
-> RestyleHint
where E: TElement,
{
debug!("compute_pseudo_hint: {:?}, {:?}", pseudo, pseudo_element);
debug_assert!(pseudo.has_snapshot());
let map = match self.pseudo_dependencies.get(&pseudo_element) {
Some(map) => map,
None => return RestyleHint::empty(),
};
// Only pseudo-element's state is relevant.
let pseudo_state_changes =
ElementWrapper::new(*pseudo, snapshots).state_changes();
debug!("pseudo_state_changes: {:?}", pseudo_state_changes);
if pseudo_state_changes.is_empty() {
return RestyleHint::empty();
}
let selector_matching_target =
pseudo.closest_non_native_anonymous_ancestor().unwrap();
// Note that we rely on that, if the originating element changes, it'll
// post a restyle hint that would make us redo selector matching, so we
// don't need to care about that.
//
// If that ever changes, we'd need to share more code with
// `compute_element_hint`.
let mut hint = RestyleHint::empty();
map.lookup(selector_matching_target, &mut |dep| {
// If the selector didn't match before, it either doesn't match now
// either (or it doesn't matter because our parent posted a restyle
// for us above).
if !matches_selector(&dep.selector.inner, &selector_matching_target,
None, &mut MatchingContext::default(),
&mut |_, _| {}) {
return true;
}
let pseudo_selector = dep.selector.pseudo_element.as_ref().unwrap();
debug_assert!(!pseudo_selector.state().is_empty());
if pseudo_selector.state().intersects(pseudo_state_changes) {
hint = RESTYLE_SELF;
return false;
}
true
});
hint
}
fn compute_element_hint<E>(
/// Compute a restyle hint given an element and a snapshot, per the rules
/// explained in the rest of the documentation.
pub fn compute_hint<E>(
&self,
el: &E,
snapshots: &SnapshotMap)
@ -838,8 +761,18 @@ impl DependencySet {
});
}
// FIXME(emilio): A bloom filter here would be neat.
let mut matching_context =
MatchingContext::new(MatchingMode::Normal, None);
let lookup_element = if el.implemented_pseudo_element().is_some() {
el.closest_non_native_anonymous_ancestor().unwrap()
} else {
*el
};
self.dependencies
.lookup_with_additional(*el, additional_id, &additional_classes, &mut |dep| {
.lookup_with_additional(lookup_element, additional_id, &additional_classes, &mut |dep| {
trace!("scanning dependency: {:?}", dep);
if !dep.sensitivities.sensitive_to(attrs_changed,
state_changes) {
@ -856,12 +789,12 @@ impl DependencySet {
// been set during original matching for any element that might
// change its matching behavior here.
let matched_then =
matches_selector(&dep.selector, &snapshot_el, None,
&mut MatchingContext::default(),
matches_selector(&dep.selector, &snapshot_el,
&mut matching_context,
&mut |_, _| {});
let matches_now =
matches_selector(&dep.selector, el, None,
&mut MatchingContext::default(),
matches_selector(&dep.selector, el,
&mut matching_context,
&mut |_, _| {});
if matched_then != matches_now {
hint.insert(dep.hint);
@ -875,21 +808,4 @@ impl DependencySet {
hint
}
/// Compute a restyle hint given an element and a snapshot, per the rules
/// explained in the rest of the documentation.
pub fn compute_hint<E>(&self,
el: &E,
snapshots: &SnapshotMap)
-> RestyleHint
where E: TElement + Clone,
{
debug!("DependencySet::compute_hint({:?})", el);
if let Some(pseudo) = el.implemented_pseudo_element() {
return self.compute_pseudo_hint(el, pseudo, snapshots);
}
self.compute_element_hint(el, snapshots)
}
}

Просмотреть файл

@ -15,7 +15,7 @@ use fnv::FnvHashMap;
use restyle_hints::ElementSnapshot;
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
use selectors::{Element, MatchAttrGeneric};
use selectors::matching::MatchingContext;
use selectors::matching::{MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use std::borrow::Cow;
@ -51,6 +51,14 @@ pub enum PseudoElement {
ServoInlineAbsolute,
}
impl ::selectors::parser::PseudoElement for PseudoElement {
type Impl = SelectorImpl;
fn supports_pseudo_class(&self, _: &NonTSPseudoClass) -> bool {
false
}
}
impl ToCss for PseudoElement {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
use self::PseudoElement::*;
@ -78,18 +86,6 @@ impl ToCss for PseudoElement {
pub const EAGER_PSEUDO_COUNT: usize = 3;
impl PseudoElement {
/// The pseudo-element, used for compatibility with Gecko's
/// `PseudoElementSelector`.
pub fn pseudo_element(&self) -> &Self {
self
}
/// The pseudo-element selector's state, used for compatibility with Gecko's
/// `PseudoElementSelector`.
pub fn state(&self) -> ElementState {
ElementState::empty()
}
/// Gets the canonical index of this eagerly-cascaded pseudo-element.
#[inline]
pub fn eager_index(&self) -> usize {
@ -264,7 +260,7 @@ impl NonTSPseudoClass {
pub struct SelectorImpl;
impl ::selectors::SelectorImpl for SelectorImpl {
type PseudoElementSelector = PseudoElement;
type PseudoElement = PseudoElement;
type NonTSPseudoClass = NonTSPseudoClass;
type AttrValue = String;
@ -323,9 +319,7 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
Ok(pseudo_class)
}
fn parse_pseudo_element(&self,
name: Cow<str>,
_input: &mut CssParser)
fn parse_pseudo_element(&self, name: Cow<str>)
-> Result<PseudoElement, ()> {
use self::PseudoElement::*;
let pseudo_element = match_ignore_ascii_case! { &name,
@ -579,8 +573,9 @@ impl MatchAttrGeneric for ServoElementSnapshot {
impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E {
fn is_link(&self) -> bool {
let mut context = MatchingContext::new(MatchingMode::Normal, None);
self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
&mut MatchingContext::default(),
&mut context,
&mut |_, _| {})
}

Просмотреть файл

@ -26,10 +26,9 @@ use properties::PropertyDeclarationBlock;
use restyle_hints::{RestyleHint, DependencySet};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
use selector_parser::{SelectorImpl, PseudoElement, SnapshotMap};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext};
use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode};
use selectors::parser::{AttrSelector, Combinator, Component, Selector, SelectorInner, SelectorIter};
use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector};
use selectors::visitor::SelectorVisitor;
@ -479,9 +478,9 @@ impl Stylist {
rule: &Arc<Locked<StyleRule>>,
stylesheet: &Stylesheet)
{
let map = if let Some(ref pseudo_selector) = selector.pseudo_element {
let map = if let Some(pseudo) = selector.pseudo_element() {
self.pseudos_map
.entry(pseudo_selector.pseudo_element().clone())
.entry(pseudo.clone())
.or_insert_with(PerPseudoElementSelectorMap::new)
.borrow_for_origin(&stylesheet.origin)
} else {
@ -525,9 +524,6 @@ impl Stylist {
#[inline]
fn note_attribute_and_state_dependencies(&mut self, selector: &Selector<SelectorImpl>) {
if let Some(ref pseudo_selector) = selector.pseudo_element {
self.state_dependencies.insert(pseudo_selector.state());
}
selector.visit(&mut AttributeAndStateDependencyVisitor(self));
}
@ -635,14 +631,13 @@ impl Stylist {
guards: &StylesheetGuards,
element: &E,
pseudo: &PseudoElement,
pseudo_state: ElementState,
parent: &Arc<ComputedValues>,
font_metrics: &FontMetricsProvider)
-> Option<ComputedStyle>
where E: TElement,
{
let rule_node =
match self.lazy_pseudo_rules(guards, element, pseudo, pseudo_state) {
match self.lazy_pseudo_rules(guards, element, pseudo) {
Some(rule_node) => rule_node,
None => return None
};
@ -673,8 +668,7 @@ impl Stylist {
pub fn lazy_pseudo_rules<E>(&self,
guards: &StylesheetGuards,
element: &E,
pseudo: &PseudoElement,
pseudo_state: ElementState)
pseudo: &PseudoElement)
-> Option<StrongRuleNode>
where E: TElement
{
@ -708,14 +702,15 @@ impl Stylist {
};
let mut declarations = ApplicableDeclarationList::new();
let mut matching_context =
MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None);
self.push_applicable_declarations(element,
None,
Some(pseudo),
None,
None,
AnimationRules(None, None),
Some((pseudo, pseudo_state)),
&mut declarations,
&mut MatchingContext::default(),
&mut matching_context,
&mut set_selector_flags);
if declarations.is_empty() {
return None
@ -839,16 +834,15 @@ impl Stylist {
pub fn push_applicable_declarations<E, V, F>(
&self,
element: &E,
parent_bf: Option<&BloomFilter>,
pseudo_element: Option<&PseudoElement>,
style_attribute: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
smil_override: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
animation_rules: AnimationRules,
pseudo_element: Option<(&PseudoElement, ElementState)>,
applicable_declarations: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F)
where E: TElement,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> + ::std::fmt::Debug,
F: FnMut(&E, ElementSelectorFlags),
{
debug_assert!(!self.is_device_dirty);
@ -857,20 +851,31 @@ impl Stylist {
debug_assert!(cfg!(feature = "gecko") ||
style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.0.is_precomputed()));
debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
let map = match pseudo_element {
Some((ref pseudo, _)) => self.pseudos_map.get(pseudo).unwrap(),
Some(pseudo) => self.pseudos_map.get(pseudo).unwrap(),
None => &self.element_map,
};
let is_implemented_pseudo =
element.implemented_pseudo_element().is_some();
// NB: This causes use to rule has pseudo selectors based on the
// properties of the originating element (which is fine, given the
// find_first_from_right usage).
let rule_hash_target = if is_implemented_pseudo {
element.closest_non_native_anonymous_ancestor().unwrap()
} else {
*element
};
debug!("Determining if style is shareable: pseudo: {}",
pseudo_element.is_some());
// Step 1: Normal user-agent rules.
map.user_agent.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
@ -893,19 +898,25 @@ impl Stylist {
debug!("preshints: {:?}", context.relations);
}
if element.matches_user_and_author_rules() {
// NB: the following condition, although it may look somewhat
// inaccurate, would be equivalent to something like:
//
// element.matches_user_and_author_rules() ||
// (is_implemented_pseudo &&
// rule_hash_target.matches_user_and_author_rules())
//
// Which may be more what you would probably expect.
if rule_hash_target.matches_user_and_author_rules() {
// Step 3: User and author normal rules.
map.user.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::UserNormal);
debug!("user normal: {:?}", context.relations);
map.author.get_all_matching_rules(element,
pseudo_element,
parent_bf,
&rule_hash_target,
applicable_declarations,
context,
flags_setter,
@ -960,7 +971,6 @@ impl Stylist {
ApplicableDeclarationBlock::from_declarations(anim, CascadeLevel::Transitions));
}
debug!("transition: {:?}", context.relations);
debug!("push_applicable_declarations: shareable: {:?}", context.relations);
}
@ -993,6 +1003,11 @@ impl Stylist {
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, Some(bloom));
// 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
@ -1002,8 +1017,7 @@ impl Stylist {
self.selectors_for_cache_revalidation.lookup(*element, &mut |selector| {
results.push(matches_selector(selector,
element,
Some(bloom),
&mut MatchingContext::default(),
&mut matching_context,
flags_setter));
true
});
@ -1182,6 +1196,7 @@ pub fn needs_revalidation(selector: &Selector<SelectorImpl>) -> bool {
/// Map that contains the CSS rules for a specific PseudoElement
/// (or lack of PseudoElement).
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Debug)]
struct PerPseudoElementSelectorMap {
/// Rules from user agent stylesheets
user_agent: SelectorMap<Rule>,
@ -1283,13 +1298,12 @@ impl SelectorMap<Rule> {
/// Sort the Rules at the end to maintain cascading order.
pub fn get_all_matching_rules<E, V, F>(&self,
element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
rule_hash_target: &E,
matching_rules_list: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
V: VecLike<ApplicableDeclarationBlock>,
F: FnMut(&E, ElementSelectorFlags),
{
@ -1299,10 +1313,8 @@ impl SelectorMap<Rule> {
// At the end, we're going to sort the rules that we added, so remember where we began.
let init_len = matching_rules_list.len();
if let Some(id) = element.get_id() {
if let Some(id) = rule_hash_target.get_id() {
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.id_hash,
&id,
matching_rules_list,
@ -1311,10 +1323,8 @@ impl SelectorMap<Rule> {
cascade_level)
}
element.each_class(|class| {
rule_hash_target.each_class(|class| {
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.class_hash,
class,
matching_rules_list,
@ -1324,18 +1334,14 @@ impl SelectorMap<Rule> {
});
SelectorMap::get_matching_rules_from_hash(element,
pseudo_element,
parent_bf,
&self.local_name_hash,
element.get_local_name(),
rule_hash_target.get_local_name(),
matching_rules_list,
context,
flags_setter,
cascade_level);
SelectorMap::get_matching_rules(element,
pseudo_element,
parent_bf,
&self.other,
matching_rules_list,
context,
@ -1372,15 +1378,13 @@ impl SelectorMap<Rule> {
fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>(
element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
hash: &FnvHashMap<Str, Vec<Rule>>,
key: &BorrowedStr,
matching_rules: &mut Vector,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
Str: Borrow<BorrowedStr> + Eq + Hash,
BorrowedStr: Eq + Hash,
Vector: VecLike<ApplicableDeclarationBlock>,
@ -1388,8 +1392,6 @@ impl SelectorMap<Rule> {
{
if let Some(rules) = hash.get(key) {
SelectorMap::get_matching_rules(element,
pseudo_element,
parent_bf,
rules,
matching_rules,
context,
@ -1400,42 +1402,18 @@ impl SelectorMap<Rule> {
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
fn get_matching_rules<E, V, F>(element: &E,
pseudo_element: Option<(&PseudoElement, ElementState)>,
parent_bf: Option<&BloomFilter>,
rules: &[Rule],
matching_rules: &mut V,
context: &mut MatchingContext,
flags_setter: &mut F,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
where E: TElement,
V: VecLike<ApplicableDeclarationBlock>,
F: FnMut(&E, ElementSelectorFlags),
{
for rule in rules.iter() {
debug_assert_eq!(rule.selector.pseudo_element.is_some(),
pseudo_element.is_some(),
"Testing pseudo-elements against the wrong map");
if let Some((pseudo, pseudo_state)) = pseudo_element {
let pseudo_selector =
rule.selector.pseudo_element.as_ref().unwrap();
debug_assert_eq!(pseudo_selector.pseudo_element(), pseudo,
"Testing pseudo-element against the wrong entry");
let state = pseudo_selector.state();
// NB: We only allow a subset of the flags here, so using
// contains for them is fine, (and it's necessary, to handle
// multiple state flags properly).
if !state.is_empty() && !pseudo_state.contains(state) {
continue;
}
}
for rule in rules {
if matches_selector(&rule.selector.inner,
element,
parent_bf,
context,
flags_setter) {
matching_rules.push(
@ -1593,45 +1571,70 @@ impl<T> SelectorMap<T> where T: Clone + Borrow<SelectorInner<SelectorImpl>> {
}
}
/// Searches the selector from right to left, beginning to the left of the
/// ::pseudo-element (if any), and ending at the first combinator.
///
/// The first non-None value returned from |f| is returned.
///
/// Effectively, pseudo-elements are ignored, given only state pseudo-classes
/// may appear before them.
fn find_from_right<F, R>(selector: &SelectorInner<SelectorImpl>, mut f: F) -> Option<R>
where F: FnMut(&Component<SelectorImpl>) -> Option<R>,
{
let mut iter = selector.complex.iter();
for ss in &mut iter {
if let Some(r) = f(ss) {
return Some(r)
}
}
if iter.next_sequence() == Some(Combinator::PseudoElement) {
for ss in &mut iter {
if let Some(r) = f(ss) {
return Some(r)
}
}
}
None
}
/// Retrieve the first ID name in the selector, or None otherwise.
pub fn get_id_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let Component::ID(ref id) = *ss {
return Some(id.clone());
}
}
None
None
})
}
/// Retrieve the FIRST class name in the selector, or None otherwise.
pub fn get_class_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let Component::Class(ref class) = *ss {
return Some(class.clone());
}
}
None
None
})
}
/// Retrieve the name if it is a type selector, or None otherwise.
pub fn get_local_name(selector: &SelectorInner<SelectorImpl>)
-> Option<LocalNameSelector<SelectorImpl>> {
for ss in selector.complex.iter() {
find_from_right(selector, |ss| {
if let Component::LocalName(ref n) = *ss {
return Some(LocalNameSelector {
name: n.name.clone(),
lower_name: n.lower_name.clone(),
})
}
}
None
None
})
}
/// A rule, that wraps a style rule, but represents a single selector of the
@ -1661,7 +1664,7 @@ impl Borrow<SelectorInner<SelectorImpl>> for Rule {
impl Rule {
/// Returns the specificity of the rule.
pub fn specificity(&self) -> u32 {
self.selector.specificity
self.selector.specificity()
}
fn to_applicable_declaration_block(&self, level: CascadeLevel) -> ApplicableDeclarationBlock {

Просмотреть файл

@ -1068,7 +1068,6 @@ fn get_pseudo_style(guard: &SharedRwLockReadGuard,
d.stylist.lazily_compute_pseudo_element_style(&guards,
&element,
&pseudo,
ElementState::empty(),
base,
&metrics)
.map(|s| s.values().clone())

Просмотреть файл

@ -90,8 +90,8 @@ fn test_parse_stylesheet() {
}))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList(vec![
Selector {
inner: SelectorInner::from_vec(vec![
Selector::new_for_unit_testing(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::LocalName(LocalName {
name: local_name!("input"),
@ -106,9 +106,8 @@ fn test_parse_stylesheet() {
}),
}, "hidden".to_owned(), CaseSensitivity::CaseInsensitive)
]),
pseudo_element: None,
specificity: (0 << 20) + (1 << 10) + (1 << 0),
},
(0 << 20) + (1 << 10) + (1 << 0)
),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(PropertyDeclaration::Display(longhands::display::SpecifiedValue::none),
@ -124,28 +123,26 @@ fn test_parse_stylesheet() {
}))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList(vec![
Selector {
inner: SelectorInner::from_vec(vec![
Selector::new_for_unit_testing(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::LocalName(LocalName {
name: local_name!("html"),
lower_name: local_name!("html"),
}),
]),
pseudo_element: None,
specificity: (0 << 20) + (0 << 10) + (1 << 0),
},
Selector {
inner: SelectorInner::from_vec(vec![
(0 << 20) + (0 << 10) + (1 << 0)
),
Selector::new_for_unit_testing(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::LocalName(LocalName {
name: local_name!("body"),
lower_name: local_name!("body"),
}),
]),
pseudo_element: None,
specificity: (0 << 20) + (0 << 10) + (1 << 0),
},
(0 << 20) + (0 << 10) + (1 << 0)
),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(PropertyDeclaration::Display(longhands::display::SpecifiedValue::block),
@ -158,17 +155,16 @@ fn test_parse_stylesheet() {
}))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList(vec![
Selector {
inner: SelectorInner::from_vec(vec![
Selector::new_for_unit_testing(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::ID(Atom::from("d1")),
Component::Combinator(Combinator::Child),
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::Class(Atom::from("ok")),
]),
pseudo_element: None,
specificity: (1 << 20) + (1 << 10) + (0 << 0),
},
(1 << 20) + (1 << 10) + (0 << 0)
),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(PropertyDeclaration::BackgroundColor(

Просмотреть файл

@ -12,8 +12,8 @@ fn size_of_selectors_dummy_types() {
assert_eq!(size_of::<dummies::PseudoClass>(), size_of::<real::NonTSPseudoClass>());
assert_eq!(align_of::<dummies::PseudoClass>(), align_of::<real::NonTSPseudoClass>());
assert_eq!(size_of::<dummies::PseudoElementSelector>(), size_of::<real::PseudoElementSelector>());
assert_eq!(align_of::<dummies::PseudoElementSelector>(), align_of::<real::PseudoElementSelector>());
assert_eq!(size_of::<dummies::PseudoElement>(), size_of::<real::PseudoElement>());
assert_eq!(align_of::<dummies::PseudoElement>(), align_of::<real::PseudoElement>());
assert_eq!(size_of::<dummies::Atom>(), size_of::<style::Atom>());
assert_eq!(align_of::<dummies::Atom>(), align_of::<style::Atom>());