Implement parsing of tree pseudo-elements. r?heycam
draft
Implement parsing of tree pseudo-elements. r?heycam
MozReview-Commit-ID: Ik3wD5XLQW5
--- a/servo/components/style/gecko/pseudo_element.rs
+++ b/servo/components/style/gecko/pseudo_element.rs
@@ -3,17 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Gecko's definition of a pseudo-element.
//!
//! Note that a few autogenerated bits of this live in
//! `pseudo_element_definition.mako.rs`. If you touch that file, you probably
//! need to update the checked-in files for Servo.
-use cssparser::ToCss;
+use cssparser::{ToCss, serialize_identifier};
use gecko_bindings::structs::{self, CSSPseudoElementType};
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 {
@@ -109,15 +109,8 @@ impl PseudoElement {
/// canonical one as it is.
pub fn canonical(&self) -> PseudoElement {
match *self {
PseudoElement::MozPlaceholder => PseudoElement::Placeholder,
_ => self.clone(),
}
}
}
-
-impl ToCss for PseudoElement {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
- dest.write_char(':')?;
- dest.write_str(self.as_str())
- }
-}
--- a/servo/components/style/gecko/pseudo_element_definition.mako.rs
+++ b/servo/components/style/gecko/pseudo_element_definition.mako.rs
@@ -2,43 +2,51 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/// Gecko's pseudo-element definition.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PseudoElement {
% for pseudo in PSEUDOS:
/// ${pseudo.value}
- ${pseudo.capitalized()},
+ % if pseudo.is_tree_pseudo_element():
+ ${pseudo.capitalized()}(Box<[String]>),
+ % else:
+ ${pseudo.capitalized()},
+ % endif
% endfor
}
<% EAGER_PSEUDOS = ["Before", "After", "FirstLine", "FirstLetter"] %>
/// The number of eager pseudo-elements.
pub const EAGER_PSEUDO_COUNT: usize = ${len(EAGER_PSEUDOS)};
/// The list of eager pseudos.
pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [
% for eager_pseudo_name in EAGER_PSEUDOS:
PseudoElement::${eager_pseudo_name},
% endfor
];
-<%def name="pseudo_element_variant(pseudo)">
+<% TREE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_tree_pseudo_element()] %>
+<% SIMPLE_PSEUDOS = [pseudo for pseudo in PSEUDOS if not pseudo.is_tree_pseudo_element()] %>
+
+<%def name="pseudo_element_variant(pseudo, tree_arg='..')">
PseudoElement::${pseudo.capitalized()}
+ ${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}
</%def>
impl PseudoElement {
/// Executes a closure with each simple (not functional)
/// pseudo-element as an argument.
pub fn each_simple<F>(mut fun: F)
where F: FnMut(Self),
{
- % for pseudo in PSEUDOS:
+ % for pseudo in SIMPLE_PSEUDOS:
fun(${pseudo_element_variant(pseudo)});
% endfor
}
/// Get the pseudo-element as an atom.
#[inline]
pub fn atom(&self) -> Atom {
match *self {
@@ -69,17 +77,19 @@ impl PseudoElement {
}
/// Gets the flags associated to this pseudo-element, or 0 if it's an
/// anonymous box.
pub fn flags(&self) -> u32 {
match *self {
% for pseudo in PSEUDOS:
${pseudo_element_variant(pseudo)} =>
- % if pseudo.is_anon_box():
+ % if pseudo.is_tree_pseudo_element():
+ 0,
+ % elif pseudo.is_anon_box():
structs::CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY,
% else:
structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.original_ident},
% endif
% endfor
}
}
@@ -97,17 +107,19 @@ impl PseudoElement {
_ => None,
}
}
/// Construct a pseudo-element from an anonymous box `Atom`.
#[inline]
pub fn from_anon_box_atom(atom: &Atom) -> Option<Self> {
% for pseudo in PSEUDOS:
- % if pseudo.is_anon_box():
+ % if pseudo.is_tree_pseudo_element():
+ // We cannot generate tree pseudo-element from just an atom.
+ % elif pseudo.is_anon_box():
if atom == &atom!("${pseudo.value}") {
return Some(${pseudo_element_variant(pseudo)});
}
% endif
% endfor
None
}
@@ -117,29 +129,63 @@ impl PseudoElement {
/// If we're not in a user-agent stylesheet, we will never parse anonymous
/// box pseudo-elements.
///
/// Returns `None` if the pseudo-element is not recognised.
#[inline]
pub fn from_slice(s: &str, in_ua_stylesheet: bool) -> Option<Self> {
use std::ascii::AsciiExt;
- % for pseudo in PSEUDOS:
+ % for pseudo in SIMPLE_PSEUDOS:
if in_ua_stylesheet || ${pseudo_element_variant(pseudo)}.exposed_in_non_ua_sheets() {
if s.eq_ignore_ascii_case("${pseudo.value[1:]}") {
return Some(${pseudo_element_variant(pseudo)});
}
}
% endfor
None
}
- /// Returns the pseudo-element's definition as a string, with only one colon
- /// before it.
- pub fn as_str(&self) -> &'static str {
+ /// Constructs a tree pseudo-element from the given name and arguments.
+ /// "name" must start with "-moz-tree-".
+ ///
+ /// Returns `None` if the pseudo-element is not recognized.
+ #[inline]
+ pub fn tree_pseudo_element(name: &str, args: Box<[String]>) -> Option<Self> {
+ use std::ascii::AsciiExt;
+ debug_assert!(name.starts_with("-moz-tree-"));
+ let tree_part = &name[10..];
+ % for pseudo in TREE_PSEUDOS:
+ if tree_part.eq_ignore_ascii_case("${pseudo.value[11:]}") {
+ return Some(${pseudo_element_variant(pseudo, "args")});
+ }
+ % endfor
+ None
+ }
+}
+
+impl ToCss for PseudoElement {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+ dest.write_char(':')?;
match *self {
- % for pseudo in PSEUDOS:
- PseudoElement::${pseudo.capitalized()} => "${pseudo.value}",
- % endfor
+ % for pseudo in PSEUDOS:
+ ${pseudo_element_variant(pseudo)} => dest.write_str("${pseudo.value}")?,
+ % endfor
+ }
+ match *self {
+ ${" | ".join("PseudoElement::{}(ref args)".format(pseudo.capitalized())
+ for pseudo in TREE_PSEUDOS)} => {
+ dest.write_char('(')?;
+ let mut iter = args.iter();
+ if let Some(first) = iter.next() {
+ serialize_identifier(first, dest)?;
+ for item in iter {
+ dest.write_str(", ")?;
+ serialize_identifier(item, dest)?;
+ }
+ }
+ dest.write_char(')')
+ }
+ _ => Ok(()),
}
}
}
--- a/servo/components/style/gecko/regen_atoms.py
+++ b/servo/components/style/gecko/regen_atoms.py
@@ -98,16 +98,19 @@ class Atom:
return self.source.TYPE
def capitalized(self):
return self.original_ident[0].upper() + self.original_ident[1:]
def is_anon_box(self):
return self.type() == "nsICSSAnonBoxPseudo"
+ def is_tree_pseudo_element(self):
+ return self.value.startswith(":-moz-tree-")
+
def collect_atoms(objdir):
atoms = []
for source in SOURCES:
path = os.path.abspath(os.path.join(objdir, source.FILE))
print("cargo:rerun-if-changed={}".format(path))
with open(path) as f:
for line in f.readlines():
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -1,15 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Gecko-specific bits for selector-parsing.
-use cssparser::{Parser, ToCss, CompactCowStr};
+use cssparser::{BasicParseError, Parser, ToCss, Token, CompactCowStr};
use element_state::ElementState;
use gecko_bindings::structs::CSSPseudoClassType;
use selector_parser::{SelectorParser, PseudoElementCascadeType};
use selectors::parser::{Selector, SelectorMethods, SelectorParseError};
use selectors::visitor::SelectorVisitor;
use std::fmt;
use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
use style_traits::{ParseError, StyleParseError};
@@ -262,16 +262,21 @@ impl ::selectors::SelectorImpl for Selec
NonTSPseudoClass::Hover)
}
}
impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
type Impl = SelectorImpl;
type Error = StyleParseError<'i>;
+ fn is_pseudo_element_allows_single_colon(name: &CompactCowStr<'i>) -> bool {
+ ::selectors::parser::is_css2_pseudo_element(name) ||
+ name.starts_with("-moz-tree-") // tree pseudo-elements
+ }
+
fn parse_non_ts_pseudo_class(&self, name: CompactCowStr<'i>)
-> Result<NonTSPseudoClass, ParseError<'i>> {
macro_rules! pseudo_class_parse {
(bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*],
keyword: [$(($k_css:expr, $k_name:ident, $k_gecko_type:tt, $k_state:tt, $k_flags:tt),)*]) => {
match_ignore_ascii_case! { &name,
$($css => NonTSPseudoClass::$name,)*
@@ -333,16 +338,40 @@ impl<'a, 'i> ::selectors::Parser<'i> for
}
}
fn parse_pseudo_element(&self, name: CompactCowStr<'i>) -> Result<PseudoElement, ParseError<'i>> {
PseudoElement::from_slice(&name, self.in_user_agent_stylesheet())
.ok_or(SelectorParseError::UnexpectedIdent(name.clone()).into())
}
+ fn parse_functional_pseudo_element<'t>(&self, name: CompactCowStr<'i>,
+ parser: &mut Parser<'i, 't>)
+ -> Result<PseudoElement, ParseError<'i>> {
+ if name.starts_with("-moz-tree-") {
+ // Tree pseudo-elements can have zero or more arguments,
+ // separated by either comma or space.
+ let mut args = Vec::new();
+ loop {
+ match parser.next() {
+ Ok(Token::Ident(ident)) => args.push(ident.into_owned()),
+ Ok(Token::Comma) => {},
+ Ok(t) => return Err(BasicParseError::UnexpectedToken(t).into()),
+ Err(BasicParseError::EndOfInput) => break,
+ _ => unreachable!("Parser::next() shouldn't return any other error"),
+ }
+ }
+ let args = args.into_boxed_slice();
+ if let Some(pseudo) = PseudoElement::tree_pseudo_element(&name, args) {
+ return Ok(pseudo);
+ }
+ }
+ Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
+ }
+
fn default_namespace(&self) -> Option<Namespace> {
self.namespaces.default.clone().as_ref().map(|&(ref ns, _)| ns.clone())
}
fn namespace_for_prefix(&self, prefix: &Atom) -> Option<Namespace> {
self.namespaces.prefixes.get(prefix).map(|&(ref ns, _)| ns.clone())
}
}