Shrink selectors::Component, add case-insensitive for other attr selectors

* https://bugzilla.mozilla.org/show_bug.cgi?id=1364148
* https://bugzilla.mozilla.org/show_bug.cgi?id=1364162
This commit is contained in:
Simon Sapin 2017-05-17 22:21:00 +02:00
parent 83c7824fda
commit 9376abdd2c
15 changed files with 377 additions and 357 deletions

View file

@ -3,7 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use arcslice::ArcSlice;
use attr::{CaseSensitivity, SELECTOR_WHITESPACE};
use attr::{AttrSelectorWithNamespace, AttrSelectorOperation, AttrSelectorOperator};
use attr::{CaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint};
use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
use precomputed_hash::PrecomputedHash;
use smallvec::SmallVec;
@ -313,18 +314,28 @@ impl<Impl: SelectorImpl> SelectorMethods for Component<Impl> {
return false;
}
}
},
AttrExists(ref selector) |
AttrEqual(ref selector, _, _) |
AttrIncludes(ref selector, _) |
AttrDashMatch(ref selector, _) |
AttrPrefixMatch(ref selector, _) |
AttrSubstringMatch(ref selector, _) |
AttrSuffixMatch(ref selector, _) => {
if !visitor.visit_attribute_selector(selector) {
}
AttributeInNoNamespace { ref local_name, ref local_name_lower, .. } |
AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => {
if !visitor.visit_attribute_selector(
&NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
local_name,
local_name_lower,
) {
return false;
}
}
AttributeOther(ref attr_selector) => {
if !visitor.visit_attribute_selector(
&attr_selector.namespace(),
&attr_selector.local_name,
&attr_selector.local_name_lower,
) {
return false;
}
}
NonTSPseudoClass(ref pseudo_class) => {
if !pseudo_class.visit(visitor) {
return false;
@ -337,6 +348,11 @@ impl<Impl: SelectorImpl> SelectorMethods for Component<Impl> {
}
}
pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl {
// Rust types default, not default namespace
Impl::NamespaceUrl::default()
}
/// A ComplexSelectors stores a sequence of simple selectors and combinators. The
/// iterator classes allow callers to iterate at either the raw sequence level or
/// at the level of sequences of simple selectors separated by combinators. Most
@ -523,19 +539,20 @@ pub enum Component<Impl: SelectorImpl> {
ID(Impl::Identifier),
Class(Impl::ClassName),
// Attribute selectors
AttrExists(AttrSelector<Impl>), // [foo]
AttrEqual(AttrSelector<Impl>, Impl::AttrValue, CaseSensitivity), // [foo=bar]
AttrIncludes(AttrSelector<Impl>, Impl::AttrValue), // [foo~=bar]
AttrDashMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo|=bar]
AttrPrefixMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo^=bar]
AttrSubstringMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo*=bar]
AttrSuffixMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo$=bar]
AttrIncludesNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value or with whitespace
AttrPrefixNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
AttrSubstringNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
AttrSuffixNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
AttributeInNoNamespaceExists {
local_name: Impl::LocalName,
local_name_lower: Impl::LocalName,
},
AttributeInNoNamespace {
local_name: Impl::LocalName,
local_name_lower: Impl::LocalName,
operator: AttrSelectorOperator,
value: Impl::AttrValue,
case_sensitivity: CaseSensitivity,
never_matches: bool,
},
// Use a Box in the less common cases with more data to keep size_of::<Component>() small.
AttributeOther(Box<AttrSelectorWithNamespace<Impl>>),
// Pseudo-classes
//
@ -610,35 +627,6 @@ pub struct LocalName<Impl: SelectorImpl> {
pub lower_name: Impl::LocalName,
}
#[derive(Eq, PartialEq, Clone)]
pub struct AttrSelector<Impl: SelectorImpl> {
pub name: Impl::LocalName,
pub lower_name: Impl::LocalName,
pub namespace: NamespaceConstraint<Impl>,
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum NamespaceConstraint<Impl: SelectorImpl> {
Any,
Specific(Namespace<Impl>),
}
#[derive(Eq, PartialEq, Clone)]
pub struct Namespace<Impl: SelectorImpl> {
pub prefix: Option<Impl::NamespacePrefix>,
pub url: Impl::NamespaceUrl,
}
impl<Impl: SelectorImpl> Default for Namespace<Impl> {
fn default() -> Self {
Namespace {
prefix: None,
url: Impl::NamespaceUrl::default(), // empty string
}
}
}
impl<Impl: SelectorImpl> Debug for Selector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Selector(")?;
@ -656,10 +644,7 @@ impl<Impl: SelectorImpl> Debug for ComplexSelector<Impl> {
impl<Impl: SelectorImpl> Debug for Component<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for AttrSelector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for Namespace<Impl> {
impl<Impl: SelectorImpl> Debug for AttrSelectorWithNamespace<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for LocalName<Impl> {
@ -737,27 +722,25 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
dest.write_char('|')
}
// Attribute selectors
AttrExists(ref a) => {
AttributeInNoNamespaceExists { ref local_name, .. } => {
dest.write_char('[')?;
a.to_css(dest)?;
display_to_css_identifier(local_name, dest)?;
dest.write_char(']')
}
AttrEqual(ref a, ref v, case) => {
attr_selector_to_css(a, " = ", v, match case {
CaseSensitivity::CaseSensitive => None,
CaseSensitivity::AsciiCaseInsensitive => Some(" i"),
}, dest)
AttributeInNoNamespace { ref local_name, operator, ref value, case_sensitivity, .. } => {
dest.write_char('[')?;
display_to_css_identifier(local_name, dest)?;
operator.to_css(dest)?;
dest.write_char('"')?;
write!(CssStringWriter::new(dest), "{}", value)?;
dest.write_char('"')?;
match case_sensitivity {
CaseSensitivity::CaseSensitive => {},
CaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
}
dest.write_char(']')
}
AttrDashMatch(ref a, ref v) => attr_selector_to_css(a, " |= ", v, None, dest),
AttrIncludesNeverMatch(ref a, ref v) |
AttrIncludes(ref a, ref v) => attr_selector_to_css(a, " ~= ", v, None, dest),
AttrPrefixNeverMatch(ref a, ref v) |
AttrPrefixMatch(ref a, ref v) => attr_selector_to_css(a, " ^= ", v, None, dest),
AttrSubstringNeverMatch(ref a, ref v) |
AttrSubstringMatch(ref a, ref v) => attr_selector_to_css(a, " *= ", v, None, dest),
AttrSuffixNeverMatch(ref a, ref v) |
AttrSuffixMatch(ref a, ref v) => attr_selector_to_css(a, " $= ", v, None, dest),
AttributeOther(ref attr_selector) => attr_selector.to_css(dest),
// Pseudo-classes
Negation(ref arg) => {
@ -786,22 +769,33 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
}
}
impl<Impl: SelectorImpl> ToCss for AttrSelector<Impl> {
impl<Impl: SelectorImpl> ToCss for AttrSelectorWithNamespace<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let NamespaceConstraint::Specific(ref ns) = self.namespace {
ns.to_css(dest)?;
dest.write_char('[')?;
match self.namespace {
NamespaceConstraint::Specific((ref prefix, _)) => {
display_to_css_identifier(prefix, dest)?;
dest.write_char('|')?
}
NamespaceConstraint::Any => {
dest.write_str("*|")?
}
}
display_to_css_identifier(&self.name, dest)
}
}
impl<Impl: SelectorImpl> ToCss for Namespace<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let Some(ref prefix) = self.prefix {
display_to_css_identifier(prefix, dest)?;
dest.write_char('|')?;
display_to_css_identifier(&self.local_name, dest)?;
match self.operation {
AttrSelectorOperation::Exists => {},
AttrSelectorOperation::WithValue { operator, case_sensitivity, ref expected_value } => {
operator.to_css(dest)?;
dest.write_char('"')?;
write!(CssStringWriter::new(dest), "{}", expected_value)?;
dest.write_char('"')?;
match case_sensitivity {
CaseSensitivity::CaseSensitive => {},
CaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
}
},
}
Ok(())
dest.write_char(']')
}
}
@ -811,26 +805,6 @@ impl<Impl: SelectorImpl> ToCss for LocalName<Impl> {
}
}
fn attr_selector_to_css<Impl, W>(attr: &AttrSelector<Impl>,
operator: &str,
value: &Impl::AttrValue,
modifier: Option<&str>,
dest: &mut W)
-> fmt::Result
where Impl: SelectorImpl, W: fmt::Write
{
dest.write_char('[')?;
attr.to_css(dest)?;
dest.write_str(operator)?;
dest.write_char('"')?;
write!(CssStringWriter::new(dest), "{}", value)?;
dest.write_char('"')?;
if let Some(m) = modifier {
dest.write_str(m)?;
}
dest.write_char(']')
}
/// Serialize the output of Display as a CSS identifier
fn display_to_css_identifier<T: Display, W: fmt::Write>(x: &T, dest: &mut W) -> fmt::Result {
// FIXME(SimonSapin): it is possible to avoid this heap allocation
@ -926,18 +900,9 @@ fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>)
specificity.id_selectors += 1
}
Component::Class(..) |
Component::AttrExists(..) |
Component::AttrEqual(..) |
Component::AttrIncludes(..) |
Component::AttrDashMatch(..) |
Component::AttrPrefixMatch(..) |
Component::AttrSubstringMatch(..) |
Component::AttrSuffixMatch(..) |
Component::AttrIncludesNeverMatch(..) |
Component::AttrPrefixNeverMatch(..) |
Component::AttrSubstringNeverMatch(..) |
Component::AttrSuffixNeverMatch(..) |
Component::AttributeInNoNamespace { .. } |
Component::AttributeInNoNamespaceExists { .. } |
Component::AttributeOther(..) |
Component::FirstChild | Component::LastChild |
Component::OnlyChild | Component::Root |
@ -1223,89 +1188,118 @@ fn parse_attribute_selector<P, Impl>(parser: &P, input: &mut CssParser)
-> Result<Component<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let attr = match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {
let namespace;
let local_name;
let local_name_lower;
match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {
None => return Err(()),
Some((_, None)) => unreachable!(),
Some((namespace, Some(local_name))) => AttrSelector {
namespace: match namespace {
Some((ns, Some(ln))) => {
local_name_lower = from_ascii_lowercase(&ln);
local_name = from_cow_str(ln);
namespace = match ns {
QNamePrefix::ImplicitNoNamespace |
QNamePrefix::ExplicitNoNamespace => {
NamespaceConstraint::Specific(Namespace {
prefix: None,
url: Impl::NamespaceUrl::default(),
})
None
}
QNamePrefix::ExplicitNamespace(prefix, url) => {
NamespaceConstraint::Specific(Namespace {
prefix: Some(prefix),
url: url,
})
Some(NamespaceConstraint::Specific((prefix, url)))
}
QNamePrefix::ExplicitAnyNamespace => {
NamespaceConstraint::Any
Some(NamespaceConstraint::Any)
}
QNamePrefix::ImplicitAnyNamespace |
QNamePrefix::ImplicitDefaultNamespace(_) => {
unreachable!() // Not returned with in_attr_selector = true
}
},
lower_name: from_ascii_lowercase(&local_name),
name: from_cow_str(local_name),
},
};
}
}
}
let operator;
let value;
let never_matches;
match input.next() {
// [foo]
Err(()) => Ok(Component::AttrExists(attr)),
Err(()) => {
if let Some(namespace) = namespace {
return Ok(Component::AttributeOther(Box::new(AttrSelectorWithNamespace {
namespace: namespace,
local_name: local_name,
local_name_lower: local_name_lower,
operation: AttrSelectorOperation::Exists,
never_matches: false,
})))
} else {
return Ok(Component::AttributeInNoNamespaceExists {
local_name: local_name,
local_name_lower: local_name_lower,
})
}
}
// [foo=bar]
Ok(Token::Delim('=')) => {
let value = input.expect_ident_or_string()?;
let flags = parse_attribute_flags(input)?;
Ok(Component::AttrEqual(attr, from_cow_str(value), flags))
value = input.expect_ident_or_string()?;
never_matches = false;
operator = AttrSelectorOperator::Equal;
}
// [foo~=bar]
Ok(Token::IncludeMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() || value.contains(SELECTOR_WHITESPACE) {
Ok(Component::AttrIncludesNeverMatch(attr, from_cow_str(value)))
} else {
Ok(Component::AttrIncludes(attr, from_cow_str(value)))
}
value = input.expect_ident_or_string()?;
never_matches = value.is_empty() || value.contains(SELECTOR_WHITESPACE);
operator = AttrSelectorOperator::Includes;
}
// [foo|=bar]
Ok(Token::DashMatch) => {
let value = input.expect_ident_or_string()?;
Ok(Component::AttrDashMatch(attr, from_cow_str(value)))
value = input.expect_ident_or_string()?;
never_matches = false;
operator = AttrSelectorOperator::DashMatch;
}
// [foo^=bar]
Ok(Token::PrefixMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() {
Ok(Component::AttrPrefixNeverMatch(attr, from_cow_str(value)))
} else {
Ok(Component::AttrPrefixMatch(attr, from_cow_str(value)))
}
value = input.expect_ident_or_string()?;
never_matches = value.is_empty();
operator = AttrSelectorOperator::Prefix;
}
// [foo*=bar]
Ok(Token::SubstringMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() {
Ok(Component::AttrSubstringNeverMatch(attr, from_cow_str(value)))
} else {
Ok(Component::AttrSubstringMatch(attr, from_cow_str(value)))
}
value = input.expect_ident_or_string()?;
never_matches = value.is_empty();
operator = AttrSelectorOperator::Substring;
}
// [foo$=bar]
Ok(Token::SuffixMatch) => {
let value = input.expect_ident_or_string()?;
if value.is_empty() {
Ok(Component::AttrSuffixNeverMatch(attr, from_cow_str(value)))
} else {
Ok(Component::AttrSuffixMatch(attr, from_cow_str(value)))
}
value = input.expect_ident_or_string()?;
never_matches = value.is_empty();
operator = AttrSelectorOperator::Suffix;
}
_ => Err(())
_ => return Err(())
}
let case_sensitivity = parse_attribute_flags(input)?;
let value = from_cow_str(value);
if let Some(namespace) = namespace {
Ok(Component::AttributeOther(Box::new(AttrSelectorWithNamespace {
namespace: namespace,
local_name: local_name,
local_name_lower: local_name_lower,
never_matches: never_matches,
operation: AttrSelectorOperation::WithValue {
operator: operator,
case_sensitivity: case_sensitivity,
expected_value: value,
}
})))
} else {
Ok(Component::AttributeInNoNamespace {
local_name: local_name,
local_name_lower: local_name_lower,
operator: operator,
value: value,
case_sensitivity: case_sensitivity,
never_matches: never_matches,
})
}
}
@ -1845,14 +1839,12 @@ pub mod tests {
// https://github.com/mozilla/servo/pull/1652
let mut parser = DummyParser::default();
assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(vec!(
Component::AttrExists(AttrSelector {
name: DummyAtom::from("Foo"),
lower_name: DummyAtom::from("foo"),
namespace: NamespaceConstraint::Specific(Namespace {
prefix: None,
url: "".into(),
}) }))),
inner: SelectorInner::from_vec(vec![
Component::AttributeInNoNamespaceExists {
local_name: DummyAtom::from("Foo"),
local_name_lower: DummyAtom::from("foo"),
}
]),
specificity_and_flags: specificity(0, 1, 0),
}))));
assert_eq!(parse_ns("svg|circle", &parser), Err(()));
@ -1885,14 +1877,10 @@ pub mod tests {
inner: SelectorInner::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::AttrExists(AttrSelector {
name: DummyAtom::from("Foo"),
lower_name: DummyAtom::from("foo"),
namespace: NamespaceConstraint::Specific(Namespace {
prefix: None,
url: "".into(),
}),
}),
Component::AttributeInNoNamespaceExists {
local_name: DummyAtom::from("Foo"),
local_name_lower: DummyAtom::from("foo"),
},
]),
specificity_and_flags: specificity(0, 1, 0),
}))));
@ -1960,14 +1948,14 @@ pub mod tests {
assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector {
inner: SelectorInner::from_vec(
vec![
Component::AttrDashMatch(AttrSelector {
name: DummyAtom::from("attr"),
lower_name: DummyAtom::from("attr"),
namespace: NamespaceConstraint::Specific(Namespace {
prefix: None,
url: "".into(),
}),
}, DummyAtom::from("foo"))
Component::AttributeInNoNamespace {
local_name: DummyAtom::from("attr"),
local_name_lower: DummyAtom::from("attr"),
operator: AttrSelectorOperator::DashMatch,
value: DummyAtom::from("foo"),
never_matches: false,
case_sensitivity: CaseSensitivity::CaseSensitive,
}
]),
specificity_and_flags: specificity(0, 1, 0),
}])));