mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
Bug 1364412: Allow pseudo-element selectors to store state. r=bholley
MozReview-Commit-ID: CzAwg2uxqO2 Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
parent
5bd6b92494
commit
10560ae043
4 changed files with 117 additions and 22 deletions
|
@ -61,7 +61,7 @@ macro_rules! with_all_bounds {
|
|||
type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>;
|
||||
|
||||
/// pseudo-elements
|
||||
type PseudoElement: $($CommonBounds)* + Sized + ToCss;
|
||||
type PseudoElementSelector: $($CommonBounds)* + Sized + ToCss;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,8 +98,8 @@ pub trait Parser {
|
|||
Err(())
|
||||
}
|
||||
|
||||
fn parse_pseudo_element(&self, _name: Cow<str>)
|
||||
-> Result<<Self::Impl as SelectorImpl>::PseudoElement, ()> {
|
||||
fn parse_pseudo_element(&self, _name: Cow<str>, _input: &mut CssParser)
|
||||
-> Result<<Self::Impl as SelectorImpl>::PseudoElementSelector, ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
|
@ -179,7 +179,7 @@ impl<Impl: SelectorImpl> SelectorInner<Impl> {
|
|||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub struct Selector<Impl: SelectorImpl> {
|
||||
pub inner: SelectorInner<Impl>,
|
||||
pub pseudo_element: Option<Impl::PseudoElement>,
|
||||
pub pseudo_element: Option<Impl::PseudoElementSelector>,
|
||||
pub specificity: u32,
|
||||
}
|
||||
|
||||
|
@ -816,7 +816,7 @@ impl From<Specificity> for u32 {
|
|||
}
|
||||
|
||||
fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>,
|
||||
pseudo_element: Option<&Impl::PseudoElement>)
|
||||
pseudo_element: Option<&Impl::PseudoElementSelector>)
|
||||
-> u32
|
||||
where Impl: SelectorImpl {
|
||||
let mut specificity = complex_selector_specificity(complex_selector);
|
||||
|
@ -916,7 +916,7 @@ type ParseVec<Impl> = SmallVec<[Component<Impl>; 8]>;
|
|||
fn parse_complex_selector_and_pseudo_element<P, Impl>(
|
||||
parser: &P,
|
||||
input: &mut CssParser)
|
||||
-> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElement>), ()>
|
||||
-> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElementSelector>), ()>
|
||||
where P: Parser<Impl=Impl>, Impl: SelectorImpl
|
||||
{
|
||||
let mut sequence = ParseVec::new();
|
||||
|
@ -1014,7 +1014,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::PseudoElement),
|
||||
PseudoElement(Impl::PseudoElementSelector),
|
||||
}
|
||||
|
||||
/// * `Err(())`: Invalid selector, abort
|
||||
|
@ -1210,7 +1210,7 @@ fn parse_compound_selector<P, Impl>(
|
|||
input: &mut CssParser,
|
||||
mut sequence: &mut ParseVec<Impl>,
|
||||
inside_negation: bool)
|
||||
-> Result<Option<Impl::PseudoElement>, ()>
|
||||
-> Result<Option<Impl::PseudoElementSelector>, ()>
|
||||
where P: Parser<Impl=Impl>, Impl: SelectorImpl
|
||||
{
|
||||
// Consume any leading whitespace.
|
||||
|
@ -1335,7 +1335,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)?;
|
||||
let pseudo_element = P::parse_pseudo_element(parser, name, input)?;
|
||||
Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element)))
|
||||
} else {
|
||||
let pseudo_class = parse_simple_pseudo_class(parser, name)?;
|
||||
|
@ -1351,7 +1351,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)?;
|
||||
let pseudo = P::parse_pseudo_element(parser, name, input)?;
|
||||
Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo)))
|
||||
}
|
||||
_ => Err(())
|
||||
|
@ -1455,7 +1455,7 @@ pub mod tests {
|
|||
type BorrowedLocalName = DummyAtom;
|
||||
type BorrowedNamespaceUrl = DummyAtom;
|
||||
type NonTSPseudoClass = PseudoClass;
|
||||
type PseudoElement = PseudoElement;
|
||||
type PseudoElementSelector = PseudoElement;
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Hash, Clone, PartialEq, Eq)]
|
||||
|
@ -1505,7 +1505,7 @@ pub mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_pseudo_element(&self, name: Cow<str>)
|
||||
fn parse_pseudo_element(&self, name: Cow<str>, input: &mut CssParser)
|
||||
-> Result<PseudoElement, ()> {
|
||||
match_ignore_ascii_case! { &name,
|
||||
"before" => Ok(PseudoElement::Before),
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//! 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 gecko_bindings::structs::nsIAtom;
|
||||
|
@ -325,6 +326,15 @@ impl NonTSPseudoClass {
|
|||
apply_non_ts_list!(pseudo_class_check_internal)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/selectors-4/#useraction-pseudos
|
||||
///
|
||||
/// We intentionally skip the link-related ones.
|
||||
fn is_safe_user_action_state(&self) -> bool {
|
||||
matches!(*self, NonTSPseudoClass::Hover |
|
||||
NonTSPseudoClass::Active |
|
||||
NonTSPseudoClass::Focus)
|
||||
}
|
||||
|
||||
/// Get the state flag associated with a pseudo-class, if any.
|
||||
pub fn state_flag(&self) -> ElementState {
|
||||
macro_rules! flag {
|
||||
|
@ -375,6 +385,53 @@ impl NonTSPseudoClass {
|
|||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -385,7 +442,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
|
|||
type BorrowedNamespaceUrl = WeakNamespace;
|
||||
type BorrowedLocalName = WeakAtom;
|
||||
|
||||
type PseudoElement = PseudoElement;
|
||||
type PseudoElementSelector = PseudoElementSelector;
|
||||
type NonTSPseudoClass = NonTSPseudoClass;
|
||||
}
|
||||
|
||||
|
@ -447,11 +504,34 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
|
||||
match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) {
|
||||
Some(pseudo) => Ok(pseudo),
|
||||
None => Err(()),
|
||||
}
|
||||
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 = 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(PseudoElementSelector {
|
||||
pseudo: pseudo,
|
||||
state: state.unwrap_or(ElementState::empty()),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_namespace(&self) -> Option<Namespace> {
|
||||
|
|
|
@ -78,6 +78,18 @@ 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 {
|
||||
|
@ -252,7 +264,7 @@ impl NonTSPseudoClass {
|
|||
pub struct SelectorImpl;
|
||||
|
||||
impl ::selectors::SelectorImpl for SelectorImpl {
|
||||
type PseudoElement = PseudoElement;
|
||||
type PseudoElementSelector = PseudoElement;
|
||||
type NonTSPseudoClass = NonTSPseudoClass;
|
||||
|
||||
type AttrValue = String;
|
||||
|
@ -311,7 +323,10 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
|
|||
Ok(pseudo_class)
|
||||
}
|
||||
|
||||
fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
|
||||
fn parse_pseudo_element(&self,
|
||||
name: Cow<str>,
|
||||
_input: &mut CssParser)
|
||||
-> Result<PseudoElement, ()> {
|
||||
use self::PseudoElement::*;
|
||||
let pseudo_element = match_ignore_ascii_case! { &name,
|
||||
"before" => Before,
|
||||
|
|
|
@ -449,9 +449,9 @@ impl Stylist {
|
|||
rule: &Arc<Locked<StyleRule>>,
|
||||
stylesheet: &Stylesheet)
|
||||
{
|
||||
let map = if let Some(ref pseudo) = selector.pseudo_element {
|
||||
let map = if let Some(ref pseudo_selector) = selector.pseudo_element {
|
||||
self.pseudos_map
|
||||
.entry(pseudo.clone())
|
||||
.entry(pseudo_selector.pseudo_element().clone())
|
||||
.or_insert_with(PerPseudoElementSelectorMap::new)
|
||||
.borrow_for_origin(&stylesheet.origin)
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue