зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
4b561945c6
Коммит
b9a37058c8
|
@ -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>());
|
||||
|
|
Загрузка…
Ссылка в новой задаче