mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
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:
parent
83c7824fda
commit
9376abdd2c
15 changed files with 377 additions and 357 deletions
|
@ -2,24 +2,54 @@
|
|||
* 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/. */
|
||||
|
||||
use cssparser::ToCss;
|
||||
use parser::SelectorImpl;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::fmt;
|
||||
|
||||
pub enum AttrSelectorOperation<'a, Impl: SelectorImpl> where Impl::AttrValue: 'a {
|
||||
#[derive(Eq, PartialEq, Clone)]
|
||||
pub struct AttrSelectorWithNamespace<Impl: SelectorImpl> {
|
||||
pub namespace: NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>,
|
||||
pub local_name: Impl::LocalName,
|
||||
pub local_name_lower: Impl::LocalName,
|
||||
pub operation: AttrSelectorOperation<Impl::AttrValue>,
|
||||
pub never_matches: bool,
|
||||
}
|
||||
|
||||
impl<Impl: SelectorImpl> AttrSelectorWithNamespace<Impl> {
|
||||
pub fn namespace(&self) -> NamespaceConstraint<&Impl::NamespaceUrl> {
|
||||
match self.namespace {
|
||||
NamespaceConstraint::Any => NamespaceConstraint::Any,
|
||||
NamespaceConstraint::Specific((_, ref url)) => {
|
||||
NamespaceConstraint::Specific(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone)]
|
||||
pub enum NamespaceConstraint<NamespaceUrl> {
|
||||
Any,
|
||||
|
||||
/// Empty string for no namespace
|
||||
Specific(NamespaceUrl),
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone)]
|
||||
pub enum AttrSelectorOperation<AttrValue> {
|
||||
Exists,
|
||||
WithValue {
|
||||
operator: AttrSelectorOperator,
|
||||
case_sensitivity: CaseSensitivity,
|
||||
expected_value: &'a Impl::AttrValue,
|
||||
expected_value: AttrValue,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Impl: SelectorImpl> AttrSelectorOperation<'a, Impl> {
|
||||
pub fn eval_str(&self, element_attr_value: &str) -> bool
|
||||
where Impl::AttrValue: AsRef<str> {
|
||||
impl<AttrValue> AttrSelectorOperation<AttrValue> {
|
||||
pub fn eval_str(&self, element_attr_value: &str) -> bool where AttrValue: AsRef<str> {
|
||||
match *self {
|
||||
AttrSelectorOperation::Exists => true,
|
||||
AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => {
|
||||
AttrSelectorOperation::WithValue { operator, case_sensitivity, ref expected_value } => {
|
||||
operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity)
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +58,25 @@ impl<'a, Impl: SelectorImpl> AttrSelectorOperation<'a, Impl> {
|
|||
|
||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||
pub enum AttrSelectorOperator {
|
||||
Equal, // =
|
||||
Includes, // ~=
|
||||
DashMatch, // |=
|
||||
Prefix, // ^=
|
||||
Substring, // *=
|
||||
Suffix, // $=
|
||||
Equal,
|
||||
Includes,
|
||||
DashMatch,
|
||||
Prefix,
|
||||
Substring,
|
||||
Suffix,
|
||||
}
|
||||
|
||||
impl ToCss for AttrSelectorOperator {
|
||||
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
||||
dest.write_str(match *self {
|
||||
AttrSelectorOperator::Equal => " = ",
|
||||
AttrSelectorOperator::Includes => " ~= ",
|
||||
AttrSelectorOperator::DashMatch => " |= ",
|
||||
AttrSelectorOperator::Prefix => " ^= ",
|
||||
AttrSelectorOperator::Substring => " *= ",
|
||||
AttrSelectorOperator::Suffix => " $= ",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AttrSelectorOperator {
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
* 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/. */
|
||||
|
||||
use attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity};
|
||||
use attr::{AttrSelectorOperation, NamespaceConstraint};
|
||||
use bloom::BloomFilter;
|
||||
use parser::{Combinator, ComplexSelector, Component, LocalName, NamespaceConstraint};
|
||||
use parser::{Selector, SelectorInner, SelectorIter, SelectorImpl, AttrSelector};
|
||||
use parser::{Combinator, ComplexSelector, Component, LocalName};
|
||||
use parser::{Selector, SelectorInner, SelectorIter, SelectorImpl};
|
||||
use std::borrow::Borrow;
|
||||
use tree::Element;
|
||||
|
||||
|
@ -387,8 +387,7 @@ fn matches_simple_selector<E, F>(
|
|||
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()
|
||||
element.get_local_name() == select_name(element, name, lower_name).borrow()
|
||||
}
|
||||
Component::ExplicitUniversalType |
|
||||
Component::ExplicitAnyNamespace => {
|
||||
|
@ -399,9 +398,8 @@ fn matches_simple_selector<E, F>(
|
|||
element.get_namespace() == url.borrow()
|
||||
}
|
||||
Component::ExplicitNoNamespace => {
|
||||
// Rust type’s default, not default namespace
|
||||
let empty_string = <E::Impl as SelectorImpl>::NamespaceUrl::default();
|
||||
element.get_namespace() == empty_string.borrow()
|
||||
let ns = ::parser::namespace_empty_string::<E::Impl>();
|
||||
element.get_namespace() == ns.borrow()
|
||||
}
|
||||
// TODO: case-sensitivity depends on the document type and quirks mode
|
||||
Component::ID(ref id) => {
|
||||
|
@ -411,63 +409,58 @@ fn matches_simple_selector<E, F>(
|
|||
Component::Class(ref class) => {
|
||||
element.has_class(class)
|
||||
}
|
||||
Component::AttrExists(ref attr) => {
|
||||
let (ns, n) = unpack_attr_selector(element, attr);
|
||||
element.attr_matches(ns, n, &AttrSelectorOperation::Exists)
|
||||
Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => {
|
||||
element.attr_matches(
|
||||
&NamespaceConstraint::Specific(&::parser::namespace_empty_string::<E::Impl>()),
|
||||
select_name(element, local_name, local_name_lower),
|
||||
&AttrSelectorOperation::Exists
|
||||
)
|
||||
}
|
||||
Component::AttrEqual(ref attr, ref value, case_sensitivity) => {
|
||||
let (ns, n) = unpack_attr_selector(element, attr);
|
||||
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
|
||||
operator: AttrSelectorOperator::Equal,
|
||||
case_sensitivity: case_sensitivity,
|
||||
expected_value: value
|
||||
})
|
||||
Component::AttributeInNoNamespace {
|
||||
ref local_name,
|
||||
ref local_name_lower,
|
||||
ref value,
|
||||
operator,
|
||||
case_sensitivity,
|
||||
never_matches,
|
||||
} => {
|
||||
if never_matches {
|
||||
false
|
||||
} else {
|
||||
element.attr_matches(
|
||||
&NamespaceConstraint::Specific(&::parser::namespace_empty_string::<E::Impl>()),
|
||||
select_name(element, local_name, local_name_lower),
|
||||
&AttrSelectorOperation::WithValue {
|
||||
operator: operator,
|
||||
case_sensitivity: case_sensitivity,
|
||||
expected_value: value,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Component::AttrIncludes(ref attr, ref value) => {
|
||||
let (ns, n) = unpack_attr_selector(element, attr);
|
||||
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
|
||||
operator: AttrSelectorOperator::Includes,
|
||||
case_sensitivity: CaseSensitivity::CaseSensitive,
|
||||
expected_value: value
|
||||
})
|
||||
}
|
||||
Component::AttrDashMatch(ref attr, ref value) => {
|
||||
let (ns, n) = unpack_attr_selector(element, attr);
|
||||
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
|
||||
operator: AttrSelectorOperator::DashMatch,
|
||||
case_sensitivity: CaseSensitivity::CaseSensitive,
|
||||
expected_value: value
|
||||
})
|
||||
}
|
||||
Component::AttrPrefixMatch(ref attr, ref value) => {
|
||||
let (ns, n) = unpack_attr_selector(element, attr);
|
||||
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
|
||||
operator: AttrSelectorOperator::Prefix,
|
||||
case_sensitivity: CaseSensitivity::CaseSensitive,
|
||||
expected_value: value
|
||||
})
|
||||
}
|
||||
Component::AttrSubstringMatch(ref attr, ref value) => {
|
||||
let (ns, n) = unpack_attr_selector(element, attr);
|
||||
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
|
||||
operator: AttrSelectorOperator::Substring,
|
||||
case_sensitivity: CaseSensitivity::CaseSensitive,
|
||||
expected_value: value
|
||||
})
|
||||
}
|
||||
Component::AttrSuffixMatch(ref attr, ref value) => {
|
||||
let (ns, n) = unpack_attr_selector(element, attr);
|
||||
element.attr_matches(ns, n, &AttrSelectorOperation::WithValue {
|
||||
operator: AttrSelectorOperator::Suffix,
|
||||
case_sensitivity: CaseSensitivity::CaseSensitive,
|
||||
expected_value: value
|
||||
})
|
||||
}
|
||||
Component::AttrIncludesNeverMatch(..) |
|
||||
Component::AttrPrefixNeverMatch(..) |
|
||||
Component::AttrSubstringNeverMatch(..) |
|
||||
Component::AttrSuffixNeverMatch(..) => {
|
||||
false
|
||||
Component::AttributeOther(ref attr_sel) => {
|
||||
if attr_sel.never_matches {
|
||||
return false
|
||||
} else {
|
||||
element.attr_matches(
|
||||
&attr_sel.namespace(),
|
||||
select_name(element, &attr_sel.local_name, &attr_sel.local_name_lower),
|
||||
&match attr_sel.operation {
|
||||
AttrSelectorOperation::Exists => AttrSelectorOperation::Exists,
|
||||
AttrSelectorOperation::WithValue {
|
||||
operator,
|
||||
case_sensitivity,
|
||||
ref expected_value,
|
||||
} => {
|
||||
AttrSelectorOperation::WithValue {
|
||||
operator: operator,
|
||||
case_sensitivity: case_sensitivity,
|
||||
expected_value: expected_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Component::NonTSPseudoClass(ref pc) => {
|
||||
element.match_non_ts_pseudo_class(pc, context, flags_setter)
|
||||
|
@ -519,16 +512,15 @@ fn matches_simple_selector<E, F>(
|
|||
}
|
||||
}
|
||||
|
||||
fn unpack_attr_selector<'a, E>(element: &E, attr: &'a AttrSelector<E::Impl>)
|
||||
-> (&'a NamespaceConstraint<E::Impl>,
|
||||
&'a <E::Impl as SelectorImpl>::LocalName)
|
||||
fn select_name<'a, E>(element: &E, local_name: &'a <E::Impl as SelectorImpl>::LocalName,
|
||||
local_name_lower: &'a <E::Impl as SelectorImpl>::LocalName)
|
||||
-> &'a <E::Impl as SelectorImpl>::LocalName
|
||||
where E: Element {
|
||||
let name = if element.is_html_element_in_html_document() {
|
||||
&attr.lower_name
|
||||
if element.is_html_element_in_html_document() {
|
||||
local_name_lower
|
||||
} else {
|
||||
&attr.name
|
||||
};
|
||||
(&attr.namespace, name)
|
||||
local_name
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -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 type’s 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),
|
||||
}])));
|
||||
|
|
|
@ -16,8 +16,7 @@ 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);
|
||||
|
||||
size_of_test!(size_of_component, Component<Impl>, 64);
|
||||
size_of_test!(size_of_attr_selector, AttrSelector<Impl>, 48);
|
||||
size_of_test!(size_of_component, Component<Impl>, 32);
|
||||
size_of_test!(size_of_pseudo_class, PseudoClass, 24);
|
||||
|
||||
impl parser::PseudoElement for gecko_like_types::PseudoElement {
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and
|
||||
//! style.
|
||||
|
||||
use attr::AttrSelectorOperation;
|
||||
use attr::{AttrSelectorOperation, NamespaceConstraint};
|
||||
use matching::{ElementSelectorFlags, MatchingContext};
|
||||
use parser::{NamespaceConstraint, SelectorImpl};
|
||||
use parser::SelectorImpl;
|
||||
|
||||
pub trait Element: Sized {
|
||||
type Impl: SelectorImpl;
|
||||
|
@ -22,26 +22,29 @@ pub trait Element: Sized {
|
|||
self.parent_element()
|
||||
}
|
||||
|
||||
// Skips non-element nodes
|
||||
/// Skips non-element nodes
|
||||
fn first_child_element(&self) -> Option<Self>;
|
||||
|
||||
// Skips non-element nodes
|
||||
/// Skips non-element nodes
|
||||
fn last_child_element(&self) -> Option<Self>;
|
||||
|
||||
// Skips non-element nodes
|
||||
/// Skips non-element nodes
|
||||
fn prev_sibling_element(&self) -> Option<Self>;
|
||||
|
||||
// Skips non-element nodes
|
||||
/// Skips non-element nodes
|
||||
fn next_sibling_element(&self) -> Option<Self>;
|
||||
|
||||
fn is_html_element_in_html_document(&self) -> bool;
|
||||
|
||||
fn get_local_name(&self) -> &<Self::Impl as SelectorImpl>::BorrowedLocalName;
|
||||
|
||||
/// Empty string for no namespace
|
||||
fn get_namespace(&self) -> &<Self::Impl as SelectorImpl>::BorrowedNamespaceUrl;
|
||||
|
||||
fn attr_matches(&self,
|
||||
ns: &NamespaceConstraint<Self::Impl>,
|
||||
ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
|
||||
local_name: &<Self::Impl as SelectorImpl>::LocalName,
|
||||
operation: &AttrSelectorOperation<Self::Impl>)
|
||||
operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>)
|
||||
-> bool;
|
||||
|
||||
fn match_non_ts_pseudo_class<F>(&self,
|
||||
|
@ -56,6 +59,7 @@ pub trait Element: Sized {
|
|||
-> bool;
|
||||
|
||||
fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>;
|
||||
|
||||
fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool;
|
||||
|
||||
/// Returns whether this element matches `:empty`.
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use parser::{AttrSelector, Combinator, Component};
|
||||
use parser::{SelectorImpl, SelectorIter};
|
||||
use attr::NamespaceConstraint;
|
||||
use parser::{Combinator, Component, SelectorImpl, SelectorIter};
|
||||
|
||||
/// A trait to visit selector properties.
|
||||
///
|
||||
|
@ -20,7 +20,12 @@ pub trait SelectorVisitor {
|
|||
/// Visit an attribute selector that may match (there are other selectors
|
||||
/// that may never match, like those containing whitespace or the empty
|
||||
/// string).
|
||||
fn visit_attribute_selector(&mut self, _: &AttrSelector<Self::Impl>) -> bool {
|
||||
fn visit_attribute_selector(
|
||||
&mut self,
|
||||
_namespace: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
|
||||
_local_name: &<Self::Impl as SelectorImpl>::LocalName,
|
||||
_local_name_lower: &<Self::Impl as SelectorImpl>::LocalName,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue