mirror of
https://github.com/servo/servo.git
synced 2025-06-17 12:54:28 +00:00
372 lines
13 KiB
Rust
372 lines
13 KiB
Rust
/* 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};
|
|
use element_state::ElementState;
|
|
use gecko_bindings::structs::CSSPseudoClassType;
|
|
use gecko_bindings::structs::nsIAtom;
|
|
use selector_parser::{SelectorParser, PseudoElementCascadeType};
|
|
use selector_parser::{attr_equals_selector_is_shareable, attr_exists_selector_is_shareable};
|
|
use selectors::parser::AttrSelector;
|
|
use std::borrow::Cow;
|
|
use std::fmt;
|
|
use std::ptr;
|
|
use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
|
|
|
|
/// A representation of a CSS pseudo-element.
|
|
///
|
|
/// In Gecko, we represent pseudo-elements as plain `Atom`s.
|
|
///
|
|
/// The boolean field represents whether this element is an anonymous box. This
|
|
/// is just for convenience, instead of recomputing it.
|
|
///
|
|
/// Also, note that the `Atom` member is always a static atom, so if space is a
|
|
/// concern, we can use the raw pointer and use the lower bit to represent it
|
|
/// without space overhead.
|
|
///
|
|
/// FIXME(emilio): we know all these atoms are static. Patches are starting to
|
|
/// pile up, but a further potential optimisation is generating bindings without
|
|
/// `-no-gen-bitfield-methods` (that was removed to compile on stable, but it no
|
|
/// longer depends on it), and using the raw *mut nsIAtom (properly asserting
|
|
/// we're a static atom).
|
|
///
|
|
/// This should allow us to avoid random FFI overhead when cloning/dropping
|
|
/// pseudos.
|
|
///
|
|
/// Also, we can further optimize PartialEq and hash comparing/hashing only the
|
|
/// atoms.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub struct PseudoElement(Atom, bool);
|
|
|
|
impl PseudoElement {
|
|
/// Get the pseudo-element as an atom.
|
|
#[inline]
|
|
pub fn as_atom(&self) -> &Atom {
|
|
&self.0
|
|
}
|
|
|
|
/// Whether this pseudo-element is an anonymous box.
|
|
#[inline]
|
|
fn is_anon_box(&self) -> bool {
|
|
self.1
|
|
}
|
|
|
|
/// Construct a pseudo-element from an `Atom`, receiving whether it is also
|
|
/// an anonymous box, and don't check it on release builds.
|
|
///
|
|
/// On debug builds we assert it's the result we expect.
|
|
#[inline]
|
|
pub fn from_atom_unchecked(atom: Atom, is_anon_box: bool) -> Self {
|
|
if cfg!(debug_assertions) {
|
|
// Do the check on debug regardless.
|
|
match Self::from_atom(&*atom, true) {
|
|
Some(pseudo) => {
|
|
assert_eq!(pseudo.is_anon_box(), is_anon_box);
|
|
return pseudo;
|
|
}
|
|
None => panic!("Unknown pseudo: {:?}", atom),
|
|
}
|
|
}
|
|
|
|
PseudoElement(atom, is_anon_box)
|
|
}
|
|
|
|
#[inline]
|
|
fn from_atom(atom: &WeakAtom, _in_ua: bool) -> Option<Self> {
|
|
macro_rules! pseudo_element {
|
|
($pseudo_str_with_colon:expr, $atom:expr, $is_anon_box:expr) => {{
|
|
if atom == &*$atom {
|
|
return Some(PseudoElement($atom, $is_anon_box));
|
|
}
|
|
}}
|
|
}
|
|
|
|
include!("generated/gecko_pseudo_element_helper.rs");
|
|
|
|
None
|
|
}
|
|
|
|
/// Constructs an atom from a string of text, and whether we're in a
|
|
/// user-agent stylesheet.
|
|
///
|
|
/// 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]
|
|
fn from_slice(s: &str, in_ua_stylesheet: bool) -> Option<Self> {
|
|
use std::ascii::AsciiExt;
|
|
macro_rules! pseudo_element {
|
|
($pseudo_str_with_colon:expr, $atom:expr, $is_anon_box:expr) => {{
|
|
if !$is_anon_box || in_ua_stylesheet {
|
|
if s.eq_ignore_ascii_case(&$pseudo_str_with_colon[1..]) {
|
|
return Some(PseudoElement($atom, $is_anon_box))
|
|
}
|
|
}
|
|
}}
|
|
}
|
|
|
|
include!("generated/gecko_pseudo_element_helper.rs");
|
|
|
|
None
|
|
}
|
|
|
|
/// Returns null or nsIAtom pointer corresponding to a given PseudoElement.
|
|
#[inline]
|
|
pub fn ns_atom_or_null_from_opt(pseudo: Option<&PseudoElement>) -> *mut nsIAtom {
|
|
pseudo.map(|p| p.as_atom().as_ptr()).unwrap_or(ptr::null_mut())
|
|
}
|
|
}
|
|
|
|
impl ToCss for PseudoElement {
|
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
|
// FIXME: why does the atom contain one colon? Pseudo-element has two
|
|
debug_assert!(self.0.as_slice().starts_with(&[b':' as u16]) &&
|
|
!self.0.as_slice().starts_with(&[b':' as u16, b':' as u16]));
|
|
try!(dest.write_char(':'));
|
|
write!(dest, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
bitflags! {
|
|
flags NonTSPseudoClassFlag: u8 {
|
|
// See NonTSPseudoClass::is_internal()
|
|
const PSEUDO_CLASS_INTERNAL = 0x01,
|
|
}
|
|
}
|
|
|
|
macro_rules! pseudo_class_name {
|
|
(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),)*]) => {
|
|
#[doc = "Our representation of a non tree-structural pseudo-class."]
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub enum NonTSPseudoClass {
|
|
$(
|
|
#[doc = $css]
|
|
$name,
|
|
)*
|
|
$(
|
|
#[doc = $s_css]
|
|
$s_name(Box<str>),
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
apply_non_ts_list!(pseudo_class_name);
|
|
|
|
impl ToCss for NonTSPseudoClass {
|
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
|
macro_rules! pseudo_class_serialize {
|
|
(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),)*]) => {
|
|
match *self {
|
|
$(NonTSPseudoClass::$name => concat!(":", $css),)*
|
|
$(NonTSPseudoClass::$s_name(ref s) => {
|
|
return dest.write_str(&format!(":{}({})", $s_css, s))
|
|
}, )*
|
|
}
|
|
}
|
|
}
|
|
let ser = apply_non_ts_list!(pseudo_class_serialize);
|
|
dest.write_str(ser)
|
|
}
|
|
}
|
|
|
|
impl NonTSPseudoClass {
|
|
/// A pseudo-class is internal if it can only be used inside
|
|
/// user agent style sheets.
|
|
pub fn is_internal(&self) -> bool {
|
|
macro_rules! check_flag {
|
|
(_) => (false);
|
|
($flags:expr) => ($flags.contains(PSEUDO_CLASS_INTERNAL));
|
|
}
|
|
macro_rules! pseudo_class_check_internal {
|
|
(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),)*]) => {
|
|
match *self {
|
|
$(NonTSPseudoClass::$name => check_flag!($flags),)*
|
|
$(NonTSPseudoClass::$s_name(..) => check_flag!($s_flags),)*
|
|
}
|
|
}
|
|
}
|
|
apply_non_ts_list!(pseudo_class_check_internal)
|
|
}
|
|
|
|
/// Get the state flag associated with a pseudo-class, if any.
|
|
pub fn state_flag(&self) -> ElementState {
|
|
macro_rules! flag {
|
|
(_) => (ElementState::empty());
|
|
($state:ident) => (::element_state::$state);
|
|
}
|
|
macro_rules! pseudo_class_state {
|
|
(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),)*]) => {
|
|
match *self {
|
|
$(NonTSPseudoClass::$name => flag!($state),)*
|
|
$(NonTSPseudoClass::$s_name(..) => flag!($s_state),)*
|
|
}
|
|
}
|
|
}
|
|
apply_non_ts_list!(pseudo_class_state)
|
|
}
|
|
|
|
/// Convert NonTSPseudoClass to Gecko's CSSPseudoClassType.
|
|
pub fn to_gecko_pseudoclasstype(&self) -> Option<CSSPseudoClassType> {
|
|
macro_rules! gecko_type {
|
|
(_) => (None);
|
|
($gecko_type:ident) =>
|
|
(Some(::gecko_bindings::structs::CSSPseudoClassType::$gecko_type));
|
|
}
|
|
macro_rules! pseudo_class_geckotype {
|
|
(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),)*]) => {
|
|
match *self {
|
|
$(NonTSPseudoClass::$name => gecko_type!($gecko_type),)*
|
|
$(NonTSPseudoClass::$s_name(..) => gecko_type!($s_gecko_type),)*
|
|
}
|
|
}
|
|
}
|
|
apply_non_ts_list!(pseudo_class_geckotype)
|
|
}
|
|
}
|
|
|
|
/// The dummy struct we use to implement our selector parsing.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SelectorImpl;
|
|
|
|
impl ::selectors::SelectorImpl for SelectorImpl {
|
|
type AttrValue = Atom;
|
|
type Identifier = Atom;
|
|
type ClassName = Atom;
|
|
type LocalName = Atom;
|
|
type NamespacePrefix = Atom;
|
|
type NamespaceUrl = Namespace;
|
|
type BorrowedNamespaceUrl = WeakNamespace;
|
|
type BorrowedLocalName = WeakAtom;
|
|
|
|
type PseudoElement = PseudoElement;
|
|
type NonTSPseudoClass = NonTSPseudoClass;
|
|
|
|
fn attr_exists_selector_is_shareable(attr_selector: &AttrSelector<Self>) -> bool {
|
|
attr_exists_selector_is_shareable(attr_selector)
|
|
}
|
|
|
|
fn attr_equals_selector_is_shareable(attr_selector: &AttrSelector<Self>,
|
|
value: &Self::AttrValue) -> bool {
|
|
attr_equals_selector_is_shareable(attr_selector, value)
|
|
}
|
|
}
|
|
|
|
impl<'a> ::selectors::Parser for SelectorParser<'a> {
|
|
type Impl = SelectorImpl;
|
|
|
|
fn parse_non_ts_pseudo_class(&self, name: Cow<str>) -> Result<NonTSPseudoClass, ()> {
|
|
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),)*]) => {
|
|
match_ignore_ascii_case! { &name,
|
|
$($css => NonTSPseudoClass::$name,)*
|
|
_ => return Err(())
|
|
}
|
|
}
|
|
}
|
|
let pseudo_class = apply_non_ts_list!(pseudo_class_parse);
|
|
if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
|
|
Ok(pseudo_class)
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
fn parse_non_ts_functional_pseudo_class(&self,
|
|
name: Cow<str>,
|
|
parser: &mut Parser)
|
|
-> Result<NonTSPseudoClass, ()> {
|
|
macro_rules! pseudo_class_string_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),)*]) => {
|
|
match_ignore_ascii_case! { &name,
|
|
$($s_css => {
|
|
let name = String::from(parser.expect_ident_or_string()?).into_boxed_str();
|
|
NonTSPseudoClass::$s_name(name)
|
|
}, )*
|
|
_ => return Err(())
|
|
}
|
|
}
|
|
}
|
|
let pseudo_class = apply_non_ts_list!(pseudo_class_string_parse);
|
|
if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
|
|
Ok(pseudo_class)
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
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 default_namespace(&self) -> Option<Namespace> {
|
|
self.namespaces.default.clone()
|
|
}
|
|
|
|
fn namespace_for_prefix(&self, prefix: &Atom) -> Option<Namespace> {
|
|
self.namespaces.prefixes.get(prefix).cloned()
|
|
}
|
|
}
|
|
|
|
impl SelectorImpl {
|
|
#[inline]
|
|
/// Returns the kind of cascade type that a given pseudo is going to use.
|
|
///
|
|
/// In Gecko we only compute ::before and ::after eagerly. We save the rules
|
|
/// for anonymous boxes separately, so we resolve them as precomputed
|
|
/// pseudos.
|
|
///
|
|
/// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
|
|
pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
|
|
if Self::pseudo_is_before_or_after(pseudo) {
|
|
return PseudoElementCascadeType::Eager
|
|
}
|
|
|
|
if pseudo.is_anon_box() {
|
|
return PseudoElementCascadeType::Precomputed
|
|
}
|
|
|
|
PseudoElementCascadeType::Lazy
|
|
}
|
|
|
|
#[inline]
|
|
/// Executes a function for each pseudo-element.
|
|
pub fn each_pseudo_element<F>(mut fun: F)
|
|
where F: FnMut(PseudoElement),
|
|
{
|
|
macro_rules! pseudo_element {
|
|
($pseudo_str_with_colon:expr, $atom:expr, $is_anon_box:expr) => {{
|
|
fun(PseudoElement($atom, $is_anon_box));
|
|
}}
|
|
}
|
|
|
|
include!("generated/gecko_pseudo_element_helper.rs")
|
|
}
|
|
|
|
#[inline]
|
|
/// Returns whether the given pseudo-element is `::before` or `::after`.
|
|
pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
|
|
*pseudo.as_atom() == atom!(":before") ||
|
|
*pseudo.as_atom() == atom!(":after")
|
|
}
|
|
|
|
#[inline]
|
|
/// Returns the relevant state flag for a given non-tree-structural
|
|
/// pseudo-class.
|
|
pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
|
|
pc.state_flag()
|
|
}
|
|
}
|