diff options
30 files changed, 964 insertions, 842 deletions
diff --git a/Cargo.lock b/Cargo.lock index b21ff19f356..f9acb8824b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2457,6 +2457,8 @@ dependencies = [ "cssparser 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "size_of_test 0.0.1", "smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 6b844dd83a2..55175fd45c6 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -86,9 +86,9 @@ use net_traits::request::CorsSettings; use ref_filter_map::ref_filter_map; use script_layout_interface::message::ReflowQueryType; use script_thread::Runnable; +use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, matches_selector_list}; use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS}; -use selectors::parser::{AttrSelector, NamespaceConstraint}; use servo_atoms::Atom; use std::ascii::AsciiExt; use std::borrow::Cow; @@ -288,7 +288,7 @@ pub trait RawLayoutElementHelpers { -> Option<&'a AttrValue>; unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str>; - unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a str>; + unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a AttrValue>; } #[inline] @@ -314,6 +314,7 @@ impl RawLayoutElementHelpers for Element { }) } + #[inline] unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> { get_attr_for_layout(self, namespace, name).map(|attr| { @@ -322,12 +323,12 @@ impl RawLayoutElementHelpers for Element { } #[inline] - unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a str> { + unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a AttrValue> { let attrs = self.attrs.borrow_for_layout(); attrs.iter().filter_map(|attr| { let attr = attr.to_layout(); if *name == attr.local_name_atom_forever() { - Some(attr.value_ref_forever()) + Some(attr.value_forever()) } else { None } @@ -2352,37 +2353,9 @@ impl VirtualMethods for Element { } } -impl<'a> ::selectors::MatchAttrGeneric for Root<Element> { +impl<'a> ::selectors::Element for Root<Element> { type Impl = SelectorImpl; - fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool - where F: Fn(&str) -> bool - { - use ::selectors::Element; - let local_name = { - if self.is_html_element_in_html_document() { - &attr.lower_name - } else { - &attr.name - } - }; - match attr.namespace { - NamespaceConstraint::Specific(ref ns) => { - self.get_attribute(&ns.url, local_name) - .map_or(false, |attr| { - test(&attr.value()) - }) - }, - NamespaceConstraint::Any => { - self.attrs.borrow().iter().any(|attr| { - attr.local_name() == local_name && test(&attr.value()) - }) - } - } - } -} - -impl<'a> ::selectors::Element for Root<Element> { fn parent_element(&self) -> Option<Root<Element>> { self.upcast::<Node>().GetParentElement() } @@ -2412,6 +2385,25 @@ impl<'a> ::selectors::Element for Root<Element> { self.node.following_siblings().filter_map(Root::downcast).next() } + fn attr_matches(&self, + ns: &NamespaceConstraint<&Namespace>, + local_name: &LocalName, + operation: &AttrSelectorOperation<&String>) + -> bool { + match *ns { + NamespaceConstraint::Specific(ref ns) => { + self.get_attribute(ns, local_name) + .map_or(false, |attr| attr.value().eval_selector(operation)) + } + NamespaceConstraint::Any => { + self.attrs.borrow().iter().any(|attr| { + attr.local_name() == local_name && + attr.value().eval_selector(operation) + }) + } + } + } + fn is_root(&self) -> bool { match self.node.GetParentNode() { None => false, @@ -2459,6 +2451,11 @@ impl<'a> ::selectors::Element for Root<Element> { } }, + NonTSPseudoClass::ServoCaseSensitiveTypeAttr(ref expected_value) => { + self.get_attribute(&ns!(), &local_name!("type")) + .map_or(false, |attr| attr.value().eq(expected_value)) + } + // FIXME(#15746): This is wrong, we need to instead use extended filtering as per RFC4647 // https://tools.ietf.org/html/rfc4647#section-3.3.2 NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.get_lang(), &*lang), @@ -2489,18 +2486,6 @@ impl<'a> ::selectors::Element for Root<Element> { Element::has_class(&**self, name) } - fn each_class<F>(&self, mut callback: F) - where F: FnMut(&Atom) - { - if let Some(ref attr) = self.get_attribute(&ns!(), &local_name!("class")) { - let tokens = attr.value(); - let tokens = tokens.as_tokens(); - for token in tokens { - callback(token); - } - } - } - fn is_html_element_in_html_document(&self) -> bool { self.html_element_in_html_document() } diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 4d1810978d8..62e5158f321 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -50,8 +50,8 @@ use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, Truste use script_layout_interface::{OpaqueStyleAndLayoutData, PartialPersistentLayoutData}; use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode}; use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode}; +use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::matching::{ElementSelectorFlags, MatchingContext}; -use selectors::parser::{AttrSelector, NamespaceConstraint}; use servo_atoms::Atom; use servo_url::ServoUrl; use std::fmt; @@ -402,6 +402,17 @@ impl<'le> TElement for ServoLayoutElement<'le> { self.get_attr(namespace, attr).map_or(false, |x| x == val) } + #[inline(always)] + fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) { + unsafe { + if let Some(ref classes) = self.element.get_classes_for_layout() { + for class in *classes { + callback(class) + } + } + } + } + #[inline] fn existing_style_for_restyle_damage<'a>(&'a self, current_cv: &'a ComputedValues, @@ -510,6 +521,13 @@ impl<'le> ServoLayoutElement<'le> { } #[inline] + fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { + unsafe { + (*self.element.unsafe_get()).get_attr_for_layout(namespace, name) + } + } + + #[inline] fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> { unsafe { (*self.element.unsafe_get()).get_attr_val_for_layout(namespace, name) @@ -558,32 +576,9 @@ fn as_element<'le>(node: LayoutJS<Node>) -> Option<ServoLayoutElement<'le>> { node.downcast().map(ServoLayoutElement::from_layout_js) } -impl<'le> ::selectors::MatchAttrGeneric for ServoLayoutElement<'le> { +impl<'le> ::selectors::Element for ServoLayoutElement<'le> { type Impl = SelectorImpl; - fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool - where F: Fn(&str) -> bool { - use ::selectors::Element; - let name = if self.is_html_element_in_html_document() { - &attr.lower_name - } else { - &attr.name - }; - match attr.namespace { - NamespaceConstraint::Specific(ref ns) => { - self.get_attr(&ns.url, name).map_or(false, |attr| test(attr)) - }, - NamespaceConstraint::Any => { - let attrs = unsafe { - (*self.element.unsafe_get()).get_attr_vals_for_layout(name) - }; - attrs.iter().any(|attr| test(*attr)) - } - } - } -} - -impl<'le> ::selectors::Element for ServoLayoutElement<'le> { fn parent_element(&self) -> Option<ServoLayoutElement<'le>> { unsafe { self.element.upcast().parent_node_ref().and_then(as_element) @@ -620,6 +615,25 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { None } + fn attr_matches(&self, + ns: &NamespaceConstraint<&Namespace>, + local_name: &LocalName, + operation: &AttrSelectorOperation<&String>) + -> bool { + match *ns { + NamespaceConstraint::Specific(ref ns) => { + self.get_attr_enum(ns, local_name) + .map_or(false, |value| value.eval_selector(operation)) + } + NamespaceConstraint::Any => { + let values = unsafe { + (*self.element.unsafe_get()).get_attr_vals_for_layout(local_name) + }; + values.iter().any(|value| value.eval_selector(operation)) + } + } + } + fn is_root(&self) -> bool { match self.as_node().parent_node() { None => false, @@ -692,7 +706,10 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { _ => true, } }, - + NonTSPseudoClass::ServoCaseSensitiveTypeAttr(ref expected_value) => { + self.get_attr_enum(&ns!(), &local_name!("type")) + .map_or(false, |attr| attr == expected_value) + } NonTSPseudoClass::ReadOnly => !self.element.get_state_for_layout().contains(pseudo_class.state_flag()), @@ -725,17 +742,6 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { } } - #[inline(always)] - fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) { - unsafe { - if let Some(ref classes) = self.element.get_classes_for_layout() { - for class in *classes { - callback(class) - } - } - } - } - fn is_html_element_in_html_document(&self) -> bool { unsafe { self.element.html_element_in_html_document_for_layout() @@ -1075,6 +1081,10 @@ impl<'le> ThreadSafeLayoutElement for ServoThreadSafeLayoutElement<'le> { self.element } + fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { + self.element.get_attr_enum(namespace, name) + } + fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> { self.element.get_attr(namespace, name) } @@ -1091,30 +1101,14 @@ impl<'le> ThreadSafeLayoutElement for ServoThreadSafeLayoutElement<'le> { /// i.e., local_name, attributes, so they can only be used for **private** /// pseudo-elements (like `::-servo-details-content`). /// -/// Probably a few more of this functions can be implemented (like `has_class`, -/// `each_class`, etc), but they have no use right now. +/// Probably a few more of this functions can be implemented (like `has_class`, etc.), +/// but they have no use right now. /// /// Note that the element implementation is needed only for selector matching, /// not for inheritance (styles are inherited appropiately). -impl<'le> ::selectors::MatchAttrGeneric for ServoThreadSafeLayoutElement<'le> { +impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { type Impl = SelectorImpl; - fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool - where F: Fn(&str) -> bool { - match attr.namespace { - NamespaceConstraint::Specific(ref ns) => { - self.get_attr(&ns.url, &attr.name).map_or(false, |attr| test(attr)) - }, - NamespaceConstraint::Any => { - unsafe { - (*self.element.element.unsafe_get()).get_attr_vals_for_layout(&attr.name).iter() - .any(|attr| test(*attr)) - } - } - } - } -} -impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { fn parent_element(&self) -> Option<Self> { warn!("ServoThreadSafeLayoutElement::parent_element called"); None @@ -1166,6 +1160,25 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { false } + fn attr_matches(&self, + ns: &NamespaceConstraint<&Namespace>, + local_name: &LocalName, + operation: &AttrSelectorOperation<&String>) + -> bool { + match *ns { + NamespaceConstraint::Specific(ref ns) => { + self.get_attr_enum(ns, local_name) + .map_or(false, |value| value.eval_selector(operation)) + } + NamespaceConstraint::Any => { + let values = unsafe { + (*self.element.element.unsafe_get()).get_attr_vals_for_layout(local_name) + }; + values.iter().any(|v| v.eval_selector(operation)) + } + } + } + fn match_non_ts_pseudo_class<F>(&self, _: &NonTSPseudoClass, _: &mut MatchingContext, @@ -1197,11 +1210,6 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { warn!("ServoThreadSafeLayoutElement::is_root called"); false } - - fn each_class<F>(&self, _callback: F) - where F: FnMut(&Atom) { - warn!("ServoThreadSafeLayoutElement::each_class called"); - } } impl<'le> PresentationalHintsSynthesizer for ServoThreadSafeLayoutElement<'le> { diff --git a/components/script_layout_interface/wrapper_traits.rs b/components/script_layout_interface/wrapper_traits.rs index 2ba72231269..40b719f6dbd 100644 --- a/components/script_layout_interface/wrapper_traits.rs +++ b/components/script_layout_interface/wrapper_traits.rs @@ -15,6 +15,7 @@ use msg::constellation_msg::{BrowsingContextId, PipelineId}; use range::Range; use servo_url::ServoUrl; use std::fmt::Debug; +use style::attr::AttrValue; use style::computed_values::display; use style::context::SharedStyleContext; use style::data::ElementData; @@ -335,6 +336,8 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + #[inline] fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>; + fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>; + fn get_style_data(&self) -> Option<&AtomicRefCell<ElementData>>; #[inline] diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml index ceef176eae1..4ca9a240c67 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/servo/servo" readme = "README.md" keywords = ["css", "selectors"] license = "MPL-2.0" +build = "build.rs" [lib] name = "selectors" @@ -25,8 +26,12 @@ bitflags = "0.7" matches = "0.1" cssparser = "0.13.3" fnv = "1.0" +phf = "0.7.18" precomputed-hash = "0.1" smallvec = "0.3" [dev-dependencies] size_of_test = {path = "../size_of_test"} + +[build-dependencies] +phf_codegen = "0.7.18" diff --git a/components/selectors/attr.rs b/components/selectors/attr.rs new file mode 100644 index 00000000000..274081ae9f7 --- /dev/null +++ b/components/selectors/attr.rs @@ -0,0 +1,190 @@ +/* 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/. */ + +use cssparser::ToCss; +use parser::SelectorImpl; +use std::ascii::AsciiExt; +use std::fmt; + +#[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: ParsedAttrSelectorOperation<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 ParsedAttrSelectorOperation<AttrValue> { + Exists, + WithValue { + operator: AttrSelectorOperator, + case_sensitivity: ParsedCaseSensitivity, + expected_value: AttrValue, + } +} + +#[derive(Eq, PartialEq, Clone)] +pub enum AttrSelectorOperation<AttrValue> { + Exists, + WithValue { + operator: AttrSelectorOperator, + case_sensitivity: CaseSensitivity, + expected_value: AttrValue, + } +} + +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, ref expected_value } => { + operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity) + } + } + } +} + +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum AttrSelectorOperator { + 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 { + pub fn eval_str(self, element_attr_value: &str, attr_selector_value: &str, + case_sensitivity: CaseSensitivity) -> bool { + let e = element_attr_value.as_bytes(); + let s = attr_selector_value.as_bytes(); + let case = case_sensitivity; + match self { + AttrSelectorOperator::Equal => { + case.eq(e, s) + } + AttrSelectorOperator::Prefix => { + e.len() >= s.len() && case.eq(&e[..s.len()], s) + } + AttrSelectorOperator::Suffix => { + e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) + } + AttrSelectorOperator::Substring => { + case.contains(element_attr_value, attr_selector_value) + } + AttrSelectorOperator::Includes => { + element_attr_value.split(SELECTOR_WHITESPACE) + .any(|part| case.eq(part.as_bytes(), s)) + } + AttrSelectorOperator::DashMatch => { + case.eq(e, s) || ( + e.get(s.len()) == Some(&b'-') && + case.eq(&e[..s.len()], s) + ) + } + } + } +} + +/// The definition of whitespace per CSS Selectors Level 3 § 4. +pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C']; + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub enum ParsedCaseSensitivity { + CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive. + AsciiCaseInsensitive, + AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, +} + +impl ParsedCaseSensitivity { + pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity { + match self { + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument + if is_html_element_in_html_document => { + CaseSensitivity::AsciiCaseInsensitive + } + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => { + CaseSensitivity::CaseSensitive + } + ParsedCaseSensitivity::CaseSensitive => CaseSensitivity::CaseSensitive, + ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive, + } + } +} + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub enum CaseSensitivity { + CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive. + AsciiCaseInsensitive, +} + +impl CaseSensitivity { + pub fn eq(self, a: &[u8], b: &[u8]) -> bool { + match self { + CaseSensitivity::CaseSensitive => a == b, + CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), + } + } + + pub fn contains(self, haystack: &str, needle: &str) -> bool { + match self { + CaseSensitivity::CaseSensitive => haystack.contains(needle), + CaseSensitivity::AsciiCaseInsensitive => { + if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() { + haystack.bytes().enumerate().any(|(i, byte)| { + if !byte.eq_ignore_ascii_case(&n_first_byte) { + return false + } + let after_this_byte = &haystack.as_bytes()[i + 1..]; + match after_this_byte.get(..n_rest.len()) { + None => false, + Some(haystack_slice) => { + haystack_slice.eq_ignore_ascii_case(n_rest) + } + } + }) + } else { + // any_str.contains("") == true, + // though these cases should be handled with *NeverMatches and never go here. + true + } + } + } + } +} diff --git a/components/selectors/build.rs b/components/selectors/build.rs new file mode 100644 index 00000000000..0d0a40256f3 --- /dev/null +++ b/components/selectors/build.rs @@ -0,0 +1,75 @@ +/* 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/. */ + +extern crate phf_codegen; + +use std::env; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::Path; + +fn main() { + let path = Path::new(&env::var("OUT_DIR").unwrap()) + .join("ascii_case_insensitive_html_attributes.rs"); + let mut file = BufWriter::new(File::create(&path).unwrap()); + + write!(&mut file, "{{ static SET: ::phf::Set<&'static str> = ", + ).unwrap(); + let mut set = phf_codegen::Set::new(); + for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() { + set.entry(name); + } + set.build(&mut file).unwrap(); + write!(&mut file, "; &SET }}").unwrap(); +} + +/// https://html.spec.whatwg.org/multipage/#selectors +static ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES: &'static str = r#" + accept + accept-charset + align + alink + axis + bgcolor + charset + checked + clear + codetype + color + compact + declare + defer + dir + direction + disabled + enctype + face + frame + hreflang + http-equiv + lang + language + link + media + method + multiple + nohref + noresize + noshade + nowrap + readonly + rel + rev + rules + scope + scrolling + selected + shape + target + text + type + valign + valuetype + vlink +"#; diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index d352d2f1d3c..3e413c0c08b 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -6,11 +6,13 @@ #[macro_use] extern crate cssparser; #[macro_use] extern crate matches; extern crate fnv; +extern crate phf; extern crate precomputed_hash; #[cfg(test)] #[macro_use] extern crate size_of_test; extern crate smallvec; pub mod arcslice; +pub mod attr; pub mod bloom; pub mod matching; pub mod parser; @@ -21,4 +23,3 @@ pub mod visitor; pub use parser::{SelectorImpl, Parser, SelectorList}; pub use tree::Element; -pub use tree::{MatchAttr, MatchAttrGeneric}; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 9c60e6b78d7..50b686f2558 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -1,9 +1,11 @@ /* 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/. */ + +use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint}; use bloom::BloomFilter; -use parser::{CaseSensitivity, Combinator, ComplexSelector, Component, LocalName}; -use parser::{Selector, SelectorInner, SelectorIter, SelectorImpl}; +use parser::{Combinator, ComplexSelector, Component, LocalName}; +use parser::{Selector, SelectorInner, SelectorIter}; use std::borrow::Borrow; use tree::Element; @@ -385,8 +387,8 @@ 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() + let is_html = element.is_html_element_in_html_document(); + element.get_local_name() == select_name(is_html, name, lower_name).borrow() } Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => { @@ -397,9 +399,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) => { @@ -409,35 +410,59 @@ fn matches_simple_selector<E, F>( Component::Class(ref class) => { element.has_class(class) } - Component::AttrExists(ref attr) => { - element.match_attr_has(attr) - } - Component::AttrEqual(ref attr, ref value, case_sensitivity) => { - match case_sensitivity { - CaseSensitivity::CaseSensitive => element.match_attr_equals(attr, value), - CaseSensitivity::CaseInsensitive => element.match_attr_equals_ignore_ascii_case(attr, value), + Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => { + let is_html = element.is_html_element_in_html_document(); + element.attr_matches( + &NamespaceConstraint::Specific(&::parser::namespace_empty_string::<E::Impl>()), + select_name(is_html, local_name, local_name_lower), + &AttrSelectorOperation::Exists + ) + } + Component::AttributeInNoNamespace { + ref local_name, + ref local_name_lower, + ref value, + operator, + case_sensitivity, + never_matches, + } => { + if never_matches { + return false } + let is_html = element.is_html_element_in_html_document(); + element.attr_matches( + &NamespaceConstraint::Specific(&::parser::namespace_empty_string::<E::Impl>()), + select_name(is_html, local_name, local_name_lower), + &AttrSelectorOperation::WithValue { + operator: operator, + case_sensitivity: case_sensitivity.to_unconditional(is_html), + expected_value: value, + } + ) } - Component::AttrIncludes(ref attr, ref value) => { - element.match_attr_includes(attr, value) - } - Component::AttrDashMatch(ref attr, ref value) => { - element.match_attr_dash(attr, value) - } - Component::AttrPrefixMatch(ref attr, ref value) => { - element.match_attr_prefix(attr, value) - } - Component::AttrSubstringMatch(ref attr, ref value) => { - element.match_attr_substring(attr, value) - } - Component::AttrSuffixMatch(ref attr, ref value) => { - element.match_attr_suffix(attr, value) - } - Component::AttrIncludesNeverMatch(..) | - Component::AttrPrefixNeverMatch(..) | - Component::AttrSubstringNeverMatch(..) | - Component::AttrSuffixNeverMatch(..) => { - false + Component::AttributeOther(ref attr_sel) => { + if attr_sel.never_matches { + return false + } + let is_html = element.is_html_element_in_html_document(); + element.attr_matches( + &attr_sel.namespace(), + select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower), + &match attr_sel.operation { + ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists, + ParsedAttrSelectorOperation::WithValue { + operator, + case_sensitivity, + ref expected_value, + } => { + AttrSelectorOperation::WithValue { + operator: operator, + case_sensitivity: case_sensitivity.to_unconditional(is_html), + expected_value: expected_value, + } + } + } + ) } Component::NonTSPseudoClass(ref pc) => { element.match_non_ts_pseudo_class(pc, context, flags_setter) @@ -489,6 +514,14 @@ fn matches_simple_selector<E, F>( } } +fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T { + if is_html { + local_name_lower + } else { + local_name + } +} + #[inline] fn matches_generic_nth_child<E, F>(element: &E, a: i32, diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 01f1eafc07c..197ce4f2629 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use arcslice::ArcSlice; +use attr::{AttrSelectorWithNamespace, ParsedAttrSelectorOperation, AttrSelectorOperator}; +use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint}; use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter}; use precomputed_hash::PrecomputedHash; use smallvec::SmallVec; @@ -13,7 +15,6 @@ use std::fmt::{self, Display, Debug, Write}; use std::iter::Rev; use std::ops::Add; use std::slice; -use tree::SELECTOR_WHITESPACE; use visitor::SelectorVisitor; /// A trait that represents a pseudo-element. @@ -32,6 +33,16 @@ pub trait PseudoElement : Sized + ToCss { } } +fn to_ascii_lowercase(s: &str) -> Cow<str> { + if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { + let mut string = s.to_owned(); + string[first_uppercase..].make_ascii_lowercase(); + string.into() + } else { + s.into() + } +} + macro_rules! with_all_bounds { ( [ $( $InSelector: tt )* ] @@ -45,16 +56,6 @@ macro_rules! with_all_bounds { } } - fn from_ascii_lowercase<T>(s: &str) -> T where T: $($FromStr)* { - if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { - let mut string = s.to_owned(); - string[first_uppercase..].make_ascii_lowercase(); - T::from(string) - } else { - T::from(s) - } - } - /// This trait allows to define the parser implementation in regards /// of pseudo-classes/elements /// @@ -313,18 +314,37 @@ 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) { + } + + 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; + } + } + AttributeInNoNamespace { ref local_name, ref local_name_lower, never_matches, .. } + if !never_matches => { + if !visitor.visit_attribute_selector( + &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()), + local_name, + local_name_lower, + ) { + return false; + } + } + AttributeOther(ref attr_selector) if !attr_selector.never_matches => { + 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 +357,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 +548,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: ParsedCaseSensitivity, + 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 // @@ -560,7 +586,6 @@ pub enum Component<Impl: SelectorImpl> { OnlyOfType, NonTSPseudoClass(Impl::NonTSPseudoClass), PseudoElement(Impl::PseudoElement), - // ... } impl<Impl: SelectorImpl> Component<Impl> { @@ -605,48 +630,12 @@ impl<Impl: SelectorImpl> Component<Impl> { } } -#[derive(Eq, PartialEq, Clone, Copy, Debug)] -pub enum CaseSensitivity { - CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive. - CaseInsensitive, -} - - #[derive(Eq, PartialEq, Clone)] pub struct LocalName<Impl: SelectorImpl> { pub name: Impl::LocalName, 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(")?; @@ -664,10 +653,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> { @@ -745,27 +731,26 @@ 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::CaseInsensitive => 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 { + ParsedCaseSensitivity::CaseSensitive | + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, + ParsedCaseSensitivity::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) => { @@ -794,22 +779,36 @@ 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 { + ParsedAttrSelectorOperation::Exists => {}, + ParsedAttrSelectorOperation::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 { + ParsedCaseSensitivity::CaseSensitive | + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, + ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, + } + }, } - Ok(()) + dest.write_char(']') } } @@ -819,26 +818,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 @@ -934,18 +913,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 | @@ -1121,7 +1091,7 @@ fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser, sequence: &mu match local_name { Some(name) => { sequence.push(Component::LocalName(LocalName { - lower_name: from_ascii_lowercase(&name), + lower_name: from_cow_str(to_ascii_lowercase(&name)), name: from_cow_str(name), })) } @@ -1231,98 +1201,143 @@ 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; + 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 = 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(()) => { + let local_name_lower = from_cow_str(to_ascii_lowercase(&local_name)); + let local_name = from_cow_str(local_name); + 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: ParsedAttrSelectorOperation::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; + } + _ => return Err(()) + } + + let mut case_sensitivity = parse_attribute_flags(input)?; + + let value = from_cow_str(value); + let local_name_lower; + { + let local_name_lower_cow = to_ascii_lowercase(&local_name); + if let ParsedCaseSensitivity::CaseSensitive = case_sensitivity { + if namespace.is_none() && + include!(concat!(env!("OUT_DIR"), "/ascii_case_insensitive_html_attributes.rs")) + .contains(&*local_name_lower_cow) + { + case_sensitivity = + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument } } - _ => Err(()) + local_name_lower = from_cow_str(local_name_lower_cow); + } + let local_name = from_cow_str(local_name); + 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: ParsedAttrSelectorOperation::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, + }) } } -fn parse_attribute_flags(input: &mut CssParser) -> Result<CaseSensitivity, ()> { +fn parse_attribute_flags(input: &mut CssParser) -> Result<ParsedCaseSensitivity, ()> { match input.next() { - Err(()) => Ok(CaseSensitivity::CaseSensitive), + Err(()) => Ok(ParsedCaseSensitivity::CaseSensitive), Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => { - Ok(CaseSensitivity::CaseInsensitive) + Ok(ParsedCaseSensitivity::AsciiCaseInsensitive) } _ => Err(()) } @@ -1853,14 +1868,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(())); @@ -1893,14 +1906,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), })))); @@ -1968,14 +1977,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: ParsedCaseSensitivity::CaseSensitive, + } ]), specificity_and_flags: specificity(0, 1, 0), }]))); diff --git a/components/selectors/size_of_tests.rs b/components/selectors/size_of_tests.rs index c03a0c80db5..5fec3085fcb 100644 --- a/components/selectors/size_of_tests.rs +++ b/components/selectors/size_of_tests.rs @@ -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 { diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index e5a4539e3cc..276c788d05b 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -5,122 +5,13 @@ //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and //! style. +use attr::{AttrSelectorOperation, NamespaceConstraint}; use matching::{ElementSelectorFlags, MatchingContext}; -use parser::{AttrSelector, SelectorImpl}; -use std::ascii::AsciiExt; +use parser::SelectorImpl; -/// The definition of whitespace per CSS Selectors Level 3 § 4. -pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C']; - -// Attribute matching routines. Consumers with simple implementations can implement -// MatchAttrGeneric instead. -pub trait MatchAttr { - type Impl: SelectorImpl; - - fn match_attr_has( - &self, - attr: &AttrSelector<Self::Impl>) -> bool; - - fn match_attr_equals( - &self, - attr: &AttrSelector<Self::Impl>, - value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool; - - fn match_attr_equals_ignore_ascii_case( - &self, - attr: &AttrSelector<Self::Impl>, - value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool; - - fn match_attr_includes( - &self, - attr: &AttrSelector<Self::Impl>, - value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool; - - fn match_attr_dash( - &self, - attr: &AttrSelector<Self::Impl>, - value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool; - - fn match_attr_prefix( - &self, - attr: &AttrSelector<Self::Impl>, - value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool; - - fn match_attr_substring( - &self, - attr: &AttrSelector<Self::Impl>, - value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool; - - fn match_attr_suffix( - &self, - attr: &AttrSelector<Self::Impl>, - value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool; -} - -pub trait MatchAttrGeneric { +pub trait Element: Sized { type Impl: SelectorImpl; - fn match_attr<F>(&self, attr: &AttrSelector<Self::Impl>, test: F) -> bool where F: Fn(&str) -> bool; -} - -impl<T> MatchAttr for T where T: MatchAttrGeneric, T::Impl: SelectorImpl<AttrValue = String> { - type Impl = T::Impl; - - fn match_attr_has(&self, attr: &AttrSelector<Self::Impl>) -> bool { - self.match_attr(attr, |_| true) - } - - fn match_attr_equals(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool { - self.match_attr(attr, |v| v == value) - } - - fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<Self::Impl>, - value: &String) -> bool { - self.match_attr(attr, |v| v.eq_ignore_ascii_case(value)) - } - - fn match_attr_includes(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool { - self.match_attr(attr, |attr_value| { - attr_value.split(SELECTOR_WHITESPACE).any(|v| v == value) - }) - } - fn match_attr_dash(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool { - self.match_attr(attr, |attr_value| { - // The attribute must start with the pattern. - if !attr_value.starts_with(value) { - return false - } - - // If the strings are the same, we're done. - if attr_value.len() == value.len() { - return true - } - - // The attribute is long than the pattern, so the next character must be '-'. - attr_value.as_bytes()[value.len()] == '-' as u8 - }) - } - - fn match_attr_prefix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool { - self.match_attr(attr, |attr_value| { - attr_value.starts_with(value) - }) - } - - fn match_attr_substring(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool { - self.match_attr(attr, |attr_value| { - attr_value.contains(value) - }) - } - - fn match_attr_suffix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool { - self.match_attr(attr, |attr_value| { - attr_value.ends_with(value) - }) - } -} - -pub trait Element: MatchAttr + Sized { fn parent_element(&self) -> Option<Self>; /// The parent of a given pseudo-element, after matching a pseudo-element @@ -131,22 +22,31 @@ pub trait Element: MatchAttr + 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 as SelectorImpl>::NamespaceUrl>, + local_name: &<Self::Impl as SelectorImpl>::LocalName, + operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>) + -> bool; + fn match_non_ts_pseudo_class<F>(&self, pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass, context: &mut MatchingContext, @@ -159,6 +59,7 @@ pub trait Element: MatchAttr + 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`. @@ -173,10 +74,4 @@ pub trait Element: MatchAttr + Sized { /// Note: this can be false even if `.parent_element()` is `None` /// if the parent node is a `DocumentFragment`. fn is_root(&self) -> bool; - - // Ordinarily I wouldn't use callbacks like this, but the alternative is - // really messy, since there is a `JSRef` and a `RefCell` involved. Maybe - // in the future when we have associated types and/or a more convenient - // JS GC story... --pcwalton - fn each_class<F>(&self, callback: F) where F: FnMut(&<Self::Impl as SelectorImpl>::ClassName); } diff --git a/components/selectors/visitor.rs b/components/selectors/visitor.rs index be335aed87b..07f121fe82f 100644 --- a/components/selectors/visitor.rs +++ b/components/selectors/visitor.rs @@ -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 } diff --git a/components/style/attr.rs b/components/style/attr.rs index bbf8d53a430..535d9109151 100644 --- a/components/style/attr.rs +++ b/components/style/attr.rs @@ -12,6 +12,7 @@ use cssparser::{self, Color, RGBA}; use euclid::num::Zero; use num_traits::ToPrimitive; use properties::PropertyDeclarationBlock; +use selectors::attr::AttrSelectorOperation; use servo_url::ServoUrl; use shared_lock::Locked; use std::ascii::AsciiExt; @@ -349,6 +350,13 @@ impl AttrValue { panic!("Uint not found"); } } + + pub fn eval_selector(&self, selector: &AttrSelectorOperation<&String>) -> bool { + // FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants + // and doing Atom comparisons instead of string comparisons where possible, + // with SelectorImpl::AttrValue changed to Atom. + selector.eval_str(self) + } } impl ::std::ops::Deref for AttrValue { @@ -371,6 +379,15 @@ impl ::std::ops::Deref for AttrValue { } } +impl PartialEq<Atom> for AttrValue { + fn eq(&self, other: &Atom) -> bool { + match *self { + AttrValue::Atom(ref value) => value == other, + _ => other == &**self, + } + } +} + /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-zero-dimension-values pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto { match parse_length(value) { diff --git a/components/style/dom.rs b/components/style/dom.rs index 1cff5d1c519..36997de5c32 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -381,6 +381,9 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// Whether an attribute value equals `value`. fn attr_equals(&self, namespace: &Namespace, attr: &LocalName, value: &Atom) -> bool; + /// Internal iterator for the classes of this element. + fn each_class<F>(&self, callback: F) where F: FnMut(&Atom); + /// Get the pre-existing style to calculate restyle damage (change hints). /// /// This needs to be generic since it varies between Servo and Gecko. diff --git a/components/style/gecko/snapshot.rs b/components/style/gecko/snapshot.rs index 261af44f6d6..4a79b492996 100644 --- a/components/style/gecko/snapshot.rs +++ b/components/style/gecko/snapshot.rs @@ -8,15 +8,14 @@ use dom::TElement; use element_state::ElementState; use gecko::snapshot_helpers; -use gecko::wrapper::{AttrSelectorHelpers, GeckoElement}; +use gecko::wrapper::{NamespaceConstraintHelpers, GeckoElement}; use gecko_bindings::bindings; use gecko_bindings::structs::ServoElementSnapshot; use gecko_bindings::structs::ServoElementSnapshotFlags as Flags; use gecko_bindings::structs::ServoElementSnapshotTable; use restyle_hints::ElementSnapshot; -use selector_parser::SelectorImpl; -use selectors::parser::AttrSelector; -use string_cache::Atom; +use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint}; +use string_cache::{Atom, Namespace}; /// A snapshot of a Gecko element. pub type GeckoElementSnapshot = ServoElementSnapshot; @@ -48,11 +47,6 @@ impl SnapshotMap { impl GeckoElementSnapshot { #[inline] - fn is_html_element_in_html_document(&self) -> bool { - self.mIsHTMLElementInHTMLDocument - } - - #[inline] fn has_any(&self, flags: Flags) -> bool { (self.mContains as u8 & flags as u8) != 0 } @@ -60,76 +54,67 @@ impl GeckoElementSnapshot { fn as_ptr(&self) -> *const Self { self } -} - -impl ::selectors::MatchAttr for GeckoElementSnapshot { - type Impl = SelectorImpl; - - fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool { - unsafe { - bindings::Gecko_SnapshotHasAttr(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document())) - } - } - fn match_attr_equals(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_SnapshotAttrEquals(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr(), - /* ignoreCase = */ false) - } - } - - fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_SnapshotAttrEquals(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr(), - /* ignoreCase = */ true) - } - } - fn match_attr_includes(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_SnapshotAttrIncludes(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_dash(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_SnapshotAttrDashEquals(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_prefix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_SnapshotAttrHasPrefix(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_substring(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_SnapshotAttrHasSubstring(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_suffix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool { + /// selectors::Element::attr_matches + pub fn attr_matches(&self, + ns: &NamespaceConstraint<&Namespace>, + local_name: &Atom, + operation: &AttrSelectorOperation<&Atom>) + -> bool { unsafe { - bindings::Gecko_SnapshotAttrHasSuffix(self, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) + match *operation { + AttrSelectorOperation::Exists => { + bindings:: Gecko_SnapshotHasAttr(self, + ns.atom_or_null(), + local_name.as_ptr()) + } + AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => { + let ignore_case = match case_sensitivity { + CaseSensitivity::CaseSensitive => false, + CaseSensitivity::AsciiCaseInsensitive => true, + }; + // FIXME: case sensitivity for operators other than Equal + match operator { + AttrSelectorOperator::Equal => bindings::Gecko_SnapshotAttrEquals( + self, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ignore_case + ), + AttrSelectorOperator::Includes => bindings::Gecko_SnapshotAttrIncludes( + self, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::DashMatch => bindings::Gecko_SnapshotAttrDashEquals( + self, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::Prefix => bindings::Gecko_SnapshotAttrHasPrefix( + self, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::Suffix => bindings::Gecko_SnapshotAttrHasSuffix( + self, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::Substring => bindings::Gecko_SnapshotAttrHasSubstring( + self, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + } + } + } } } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 7aa879c3d11..3d8ab270658 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -64,8 +64,8 @@ use properties::style_structs::Font; use rule_tree::CascadeLevel as ServoCascadeLevel; use selector_parser::ElementExt; use selectors::Element; +use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; -use selectors::parser::{AttrSelector, NamespaceConstraint}; use shared_lock::Locked; use sink::Push; use std::cell::RefCell; @@ -655,6 +655,14 @@ impl<'le> TElement for GeckoElement<'le> { } } + fn each_class<F>(&self, callback: F) + where F: FnMut(&Atom) + { + snapshot_helpers::each_class(self.0, + callback, + Gecko_ClassOrClassList) + } + fn existing_style_for_restyle_damage<'a>(&'a self, _existing_values: &'a ComputedValues, pseudo: Option<&PseudoElement>) @@ -1082,6 +1090,8 @@ impl<'le> PresentationalHintsSynthesizer for GeckoElement<'le> { } impl<'le> ::selectors::Element for GeckoElement<'le> { + type Impl = SelectorImpl; + fn parent_element(&self) -> Option<Self> { let parent_node = self.as_node().parent_node(); parent_node.and_then(|n| n.as_element()) @@ -1136,6 +1146,68 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { None } + fn attr_matches(&self, + ns: &NamespaceConstraint<&Namespace>, + local_name: &Atom, + operation: &AttrSelectorOperation<&Atom>) + -> bool { + unsafe { + match *operation { + AttrSelectorOperation::Exists => { + bindings::Gecko_HasAttr(self.0, + ns.atom_or_null(), + local_name.as_ptr()) + } + AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => { + let ignore_case = match case_sensitivity { + CaseSensitivity::CaseSensitive => false, + CaseSensitivity::AsciiCaseInsensitive => true, + }; + // FIXME: case sensitivity for operators other than Equal + match operator { + AttrSelectorOperator::Equal => bindings::Gecko_AttrEquals( + self.0, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ignore_case + ), + AttrSelectorOperator::Includes => bindings::Gecko_AttrIncludes( + self.0, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::DashMatch => bindings::Gecko_AttrDashEquals( + self.0, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::Prefix => bindings::Gecko_AttrHasPrefix( + self.0, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::Suffix => bindings::Gecko_AttrHasSuffix( + self.0, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + AttrSelectorOperator::Substring => bindings::Gecko_AttrHasSubstring( + self.0, + ns.atom_or_null(), + local_name.as_ptr(), + expected_value.as_ptr(), + ), + } + } + } + } + } + fn is_root(&self) -> bool { unsafe { Gecko_IsRootElement(self.0) @@ -1322,18 +1394,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { Gecko_ClassOrClassList) } - fn each_class<F>(&self, callback: F) - where F: FnMut(&Atom) - { - if !self.may_have_class() { - return; - } - - snapshot_helpers::each_class(self.0, - callback, - Gecko_ClassOrClassList) - } - fn is_html_element_in_html_document(&self) -> bool { let node = self.as_node(); let node_info = node.node_info(); @@ -1343,97 +1403,16 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { } /// A few helpers to help with attribute selectors and snapshotting. -pub trait AttrSelectorHelpers { +pub trait NamespaceConstraintHelpers { /// Returns the namespace of the selector, or null otherwise. - fn ns_or_null(&self) -> *mut nsIAtom; - /// Returns the proper selector name depending on whether the requesting - /// element is an HTML element in an HTML document or not. - fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom; + fn atom_or_null(&self) -> *mut nsIAtom; } -impl AttrSelectorHelpers for AttrSelector<SelectorImpl> { - fn ns_or_null(&self) -> *mut nsIAtom { - match self.namespace { +impl<'a> NamespaceConstraintHelpers for NamespaceConstraint<&'a Namespace> { + fn atom_or_null(&self) -> *mut nsIAtom { + match *self { NamespaceConstraint::Any => ptr::null_mut(), - NamespaceConstraint::Specific(ref ns) => ns.url.0.as_ptr(), - } - } - - fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom { - if is_html_element_in_html_document { - self.lower_name.as_ptr() - } else { - self.name.as_ptr() - } - } -} - -impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { - type Impl = SelectorImpl; - - fn match_attr_has(&self, attr: &AttrSelector<Self::Impl>) -> bool { - unsafe { - bindings::Gecko_HasAttr(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document())) - } - } - fn match_attr_equals(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_AttrEquals(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr(), - /* ignoreCase = */ false) - } - } - fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_AttrEquals(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr(), - /* ignoreCase = */ false) - } - } - fn match_attr_includes(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_AttrIncludes(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_dash(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_AttrDashEquals(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_prefix(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_AttrHasPrefix(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_substring(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_AttrHasSubstring(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) - } - } - fn match_attr_suffix(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool { - unsafe { - bindings::Gecko_AttrHasSuffix(self.0, - attr.ns_or_null(), - attr.select_name(self.is_html_element_in_html_document()), - value.as_ptr()) + NamespaceConstraint::Specific(ref ns) => ns.0.as_ptr(), } } } diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index 23a3cc9f2c3..73bd39ec1b3 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -7,18 +7,20 @@ #![deny(missing_docs)] use Atom; +use LocalName; +use Namespace; use dom::TElement; use element_state::*; #[cfg(feature = "gecko")] use gecko_bindings::structs::nsRestyleHint; #[cfg(feature = "servo")] use heapsize::HeapSizeOf; -use selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap}; -use selectors::{Element, MatchAttr}; +use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue}; +use selectors::Element; +use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use selectors::matching::matches_selector; -use selectors::parser::{AttrSelector, Combinator, Component, Selector}; -use selectors::parser::{SelectorInner, SelectorMethods}; +use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorMethods}; use selectors::visitor::SelectorVisitor; use smallvec::SmallVec; use std::borrow::Borrow; @@ -170,7 +172,7 @@ impl HeapSizeOf for RestyleHint { /// still need to take the ElementWrapper approach for attribute-dependent /// style. So we do it the same both ways for now to reduce complexity, but it's /// worth measuring the performance impact (if any) of the mStateMask approach. -pub trait ElementSnapshot : Sized + MatchAttr<Impl=SelectorImpl> { +pub trait ElementSnapshot : Sized { /// The state of the snapshot, if any. fn state(&self) -> Option<ElementState>; @@ -242,90 +244,6 @@ impl<'a, E> ElementWrapper<'a, E> } } -impl<'a, E> MatchAttr for ElementWrapper<'a, E> - where E: TElement, -{ - type Impl = SelectorImpl; - - fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_has(attr), - _ => self.element.match_attr_has(attr) - } - } - - fn match_attr_equals(&self, - attr: &AttrSelector<SelectorImpl>, - value: &AttrValue) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_equals(attr, value), - _ => self.element.match_attr_equals(attr, value) - } - } - - fn match_attr_equals_ignore_ascii_case(&self, - attr: &AttrSelector<SelectorImpl>, - value: &AttrValue) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_equals_ignore_ascii_case(attr, value), - _ => self.element.match_attr_equals_ignore_ascii_case(attr, value) - } - } - - fn match_attr_includes(&self, - attr: &AttrSelector<SelectorImpl>, - value: &AttrValue) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_includes(attr, value), - _ => self.element.match_attr_includes(attr, value) - } - } - - fn match_attr_dash(&self, - attr: &AttrSelector<SelectorImpl>, - value: &AttrValue) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_dash(attr, value), - _ => self.element.match_attr_dash(attr, value) - } - } - - fn match_attr_prefix(&self, - attr: &AttrSelector<SelectorImpl>, - value: &AttrValue) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_prefix(attr, value), - _ => self.element.match_attr_prefix(attr, value) - } - } - - fn match_attr_substring(&self, - attr: &AttrSelector<SelectorImpl>, - value: &AttrValue) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_substring(attr, value), - _ => self.element.match_attr_substring(attr, value) - } - } - - fn match_attr_suffix(&self, - attr: &AttrSelector<SelectorImpl>, - value: &AttrValue) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.match_attr_suffix(attr, value), - _ => self.element.match_attr_suffix(attr, value) - } - } -} - #[cfg(feature = "gecko")] fn dir_selector_to_state(s: &[u16]) -> ElementState { // Jump through some hoops to deal with our Box<[u16]> thing. @@ -345,6 +263,8 @@ fn dir_selector_to_state(s: &[u16]) -> ElementState { impl<'a, E> Element for ElementWrapper<'a, E> where E: TElement, { + type Impl = SelectorImpl; + fn match_non_ts_pseudo_class<F>(&self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, @@ -450,6 +370,19 @@ impl<'a, E> Element for ElementWrapper<'a, E> self.element.get_namespace() } + fn attr_matches(&self, + ns: &NamespaceConstraint<&Namespace>, + local_name: &LocalName, + operation: &AttrSelectorOperation<&AttrValue>) + -> bool { + match self.snapshot() { + Some(snapshot) if snapshot.has_attrs() => { + snapshot.attr_matches(ns, local_name, operation) + } + _ => self.element.attr_matches(ns, local_name, operation) + } + } + fn get_id(&self) -> Option<Atom> { match self.snapshot() { Some(snapshot) if snapshot.has_attrs() @@ -474,15 +407,6 @@ impl<'a, E> Element for ElementWrapper<'a, E> self.element.is_root() } - fn each_class<F>(&self, callback: F) - where F: FnMut(&Atom) { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.each_class(callback), - _ => self.element.each_class(callback) - } - } - fn pseudo_element_originating_element(&self) -> Option<Self> { self.element.closest_non_native_anonymous_ancestor() .map(|e| ElementWrapper::new(e, self.snapshot_map)) @@ -504,13 +428,9 @@ fn is_attr_selector(sel: &Component<SelectorImpl>) -> bool { match *sel { Component::ID(_) | Component::Class(_) | - Component::AttrExists(_) | - Component::AttrEqual(_, _, _) | - Component::AttrIncludes(_, _) | - Component::AttrDashMatch(_, _) | - Component::AttrPrefixMatch(_, _) | - Component::AttrSubstringMatch(_, _) | - Component::AttrSuffixMatch(_, _) => true, + Component::AttributeInNoNamespaceExists { .. } | + Component::AttributeInNoNamespace { .. } | + Component::AttributeOther(_) => true, _ => false, } } diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index 6fd498ee9aa..fdc14b11e29 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -14,9 +14,10 @@ use element_state::ElementState; use fnv::FnvHashMap; use restyle_hints::ElementSnapshot; use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser}; -use selectors::{Element, MatchAttrGeneric}; +use selectors::Element; +use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::matching::{MatchingContext, MatchingMode}; -use selectors::parser::{AttrSelector, SelectorMethods}; +use selectors::parser::SelectorMethods; use selectors::visitor::SelectorVisitor; use std::borrow::Cow; use std::fmt; @@ -174,6 +175,7 @@ pub enum NonTSPseudoClass { ReadWrite, ReadOnly, ServoNonZeroBorder, + ServoCaseSensitiveTypeAttr(Atom), Target, Visited, } @@ -181,10 +183,18 @@ pub enum NonTSPseudoClass { impl ToCss for NonTSPseudoClass { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use self::NonTSPseudoClass::*; - if let Lang(ref lang) = *self { - dest.write_str(":lang(")?; - serialize_identifier(lang, dest)?; - return dest.write_str(")"); + match *self { + Lang(ref lang) => { + dest.write_str(":lang(")?; + serialize_identifier(lang, dest)?; + return dest.write_str(")") + } + ServoCaseSensitiveTypeAttr(ref value) => { + dest.write_str(":-servo-case-sensitive-type-attr(")?; + serialize_identifier(value, dest)?; + return dest.write_str(")") + } + _ => {} } dest.write_str(match *self { @@ -197,7 +207,6 @@ impl ToCss for NonTSPseudoClass { Fullscreen => ":fullscreen", Hover => ":hover", Indeterminate => ":indeterminate", - Lang(_) => unreachable!(), Link => ":link", PlaceholderShown => ":placeholder-shown", ReadWrite => ":read-write", @@ -205,6 +214,8 @@ impl ToCss for NonTSPseudoClass { ServoNonZeroBorder => ":-servo-nonzero-border", Target => ":target", Visited => ":visited", + Lang(_) | + ServoCaseSensitiveTypeAttr(_) => unreachable!(), }) } } @@ -243,7 +254,8 @@ impl NonTSPseudoClass { Lang(_) | Link | Visited | - ServoNonZeroBorder => ElementState::empty(), + ServoNonZeroBorder | + ServoCaseSensitiveTypeAttr(_) => ElementState::empty(), } } @@ -312,7 +324,15 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { -> Result<NonTSPseudoClass, ()> { use self::NonTSPseudoClass::*; let pseudo_class = match_ignore_ascii_case!{ &name, - "lang" => Lang(String::from(try!(parser.expect_ident_or_string())).into_boxed_str()), + "lang" => { + Lang(parser.expect_ident_or_string()?.into_owned().into_boxed_str()) + } + "-servo-case-sensitive-type-attr" => { + if !self.in_user_agent_stylesheet() { + return Err(()); + } + ServoCaseSensitiveTypeAttr(Atom::from(parser.expect_ident()?)) + } _ => return Err(()) }; @@ -519,10 +539,10 @@ impl ServoElementSnapshot { .map(|&(_, ref v)| v) } - fn get_attr_ignore_ns(&self, name: &LocalName) -> Option<&AttrValue> { + fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool + where F: FnMut(&AttrValue) -> bool { self.attrs.as_ref().unwrap().iter() - .find(|&&(ref ident, _)| ident.local_name == *name) - .map(|&(_, ref v)| v) + .any(|&(ref ident, ref v)| ident.local_name == *name && f(v)) } } @@ -555,19 +575,22 @@ impl ElementSnapshot for ServoElementSnapshot { } } -impl MatchAttrGeneric for ServoElementSnapshot { - type Impl = SelectorImpl; - - fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool - where F: Fn(&str) -> bool - { - use selectors::parser::NamespaceConstraint; - let html = self.is_html_element_in_html_document; - let local_name = if html { &attr.lower_name } else { &attr.name }; - match attr.namespace { - NamespaceConstraint::Specific(ref ns) => self.get_attr(&ns.url, local_name), - NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name), - }.map_or(false, |v| test(v)) +impl ServoElementSnapshot { + /// selectors::Element::attr_matches + pub fn attr_matches(&self, + ns: &NamespaceConstraint<&Namespace>, + local_name: &LocalName, + operation: &AttrSelectorOperation<&String>) + -> bool { + match *ns { + NamespaceConstraint::Specific(ref ns) => { + self.get_attr(ns, local_name) + .map_or(false, |value| value.eval_selector(operation)) + } + NamespaceConstraint::Any => { + self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation)) + } + } } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 644552050b2..a5c103c5a55 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -6,7 +6,7 @@ #![deny(missing_docs)] -use {Atom, LocalName}; +use {Atom, LocalName, Namespace}; use bit_vec::BitVec; use context::QuirksMode; use data::ComputedStyle; @@ -26,10 +26,11 @@ use properties::PropertyDeclarationBlock; use restyle_hints::{RestyleHint, DependencySet}; use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; use selector_parser::{SelectorImpl, PseudoElement, SnapshotMap}; +use selectors::attr::NamespaceConstraint; use selectors::bloom::BloomFilter; use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS}; use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; -use selectors::parser::{AttrSelector, Combinator, Component, Selector, SelectorInner, SelectorIter}; +use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorIter}; use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector}; use selectors::visitor::SelectorVisitor; use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; @@ -502,7 +503,7 @@ impl Stylist { /// Returns whether the given attribute might appear in an attribute /// selector of some rule in the stylist. pub fn might_have_attribute_dependency(&self, - local_name: &<SelectorImpl as ::selectors::SelectorImpl>::LocalName) + local_name: &LocalName) -> bool { #[cfg(feature = "servo")] let style_lower_name = local_name!("style"); @@ -1088,17 +1089,19 @@ struct AttributeAndStateDependencyVisitor<'a>(&'a mut Stylist); impl<'a> SelectorVisitor for AttributeAndStateDependencyVisitor<'a> { type Impl = SelectorImpl; - fn visit_attribute_selector(&mut self, selector: &AttrSelector<Self::Impl>) -> bool { + fn visit_attribute_selector(&mut self, _ns: &NamespaceConstraint<&Namespace>, + name: &LocalName, lower_name: &LocalName) + -> bool { #[cfg(feature = "servo")] let style_lower_name = local_name!("style"); #[cfg(feature = "gecko")] let style_lower_name = atom!("style"); - if selector.lower_name == style_lower_name { + if *lower_name == style_lower_name { self.0.style_attribute_dependency = true; } else { - self.0.attribute_dependencies.insert(&selector.name); - self.0.attribute_dependencies.insert(&selector.lower_name); + self.0.attribute_dependencies.insert(&name); + self.0.attribute_dependencies.insert(&lower_name); } true } @@ -1157,13 +1160,9 @@ impl SelectorVisitor for RevalidationVisitor { /// concerned. fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool { match *s { - Component::AttrExists(_) | - Component::AttrEqual(_, _, _) | - Component::AttrIncludes(_, _) | - Component::AttrDashMatch(_, _) | - Component::AttrPrefixMatch(_, _) | - Component::AttrSubstringMatch(_, _) | - Component::AttrSuffixMatch(_, _) | + Component::AttributeInNoNamespaceExists { .. } | + Component::AttributeInNoNamespace { .. } | + Component::AttributeOther(_) | Component::Empty | Component::FirstChild | Component::LastChild | diff --git a/python/tidy/servo_tidy/tidy.py b/python/tidy/servo_tidy/tidy.py index b8229e67d96..51e7e69f8d4 100644 --- a/python/tidy/servo_tidy/tidy.py +++ b/python/tidy/servo_tidy/tidy.py @@ -447,6 +447,7 @@ def check_rust(file_name, lines): prev_use = None prev_open_brace = False + multi_line_string = False current_indent = 0 prev_crate = {} prev_mod = {} @@ -464,6 +465,15 @@ def check_rust(file_name, lines): prev_indent = indent indent = len(original_line) - len(line) + # Hack for components/selectors/build.rs + if multi_line_string: + if line.startswith('"#'): + multi_line_string = False + else: + continue + if line.endswith('r#"'): + multi_line_string = True + is_attribute = re.search(r"#\[.*\]", line) is_comment = re.search(r"^//|^/\*|^\*", line) diff --git a/resources/presentational-hints.css b/resources/presentational-hints.css index 84157dcb4bd..63f42491dd3 100644 --- a/resources/presentational-hints.css +++ b/resources/presentational-hints.css @@ -18,11 +18,15 @@ br[clear=right i] { clear: right; } br[clear=all i], br[clear=both i] { clear: both; } -ol[type=1], li[type=1] { list-style-type: decimal; } -ol[type=a], li[type=a] { list-style-type: lower-alpha; } -ol[type=A], li[type=A] { list-style-type: upper-alpha; } -ol[type=i], li[type=i] { list-style-type: lower-roman; } -ol[type=I], li[type=I] { list-style-type: upper-roman; } +ol[type="1"], li[type="1"] { list-style-type: decimal; } +ol:-servo-case-sensitive-type-attr(a), +li:-servo-case-sensitive-type-attr(a) { list-style-type: lower-alpha; } +ol:-servo-case-sensitive-type-attr(A), +li:-servo-case-sensitive-type-attr(A) { list-style-type: upper-alpha; } +ol:-servo-case-sensitive-type-attr(i), +li:-servo-case-sensitive-type-attr(i) { list-style-type: lower-roman; } +ol:-servo-case-sensitive-type-attr(I), +li:-servo-case-sensitive-type-attr(I) { list-style-type: upper-roman; } ul[type=none i], li[type=none i] { list-style-type: none; } ul[type=disc i], li[type=disc i] { list-style-type: disc; } ul[type=circle i], li[type=circle i] { list-style-type: circle; } diff --git a/servo-tidy.toml b/servo-tidy.toml index 1b07e242ff9..55357dfe1ce 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -56,9 +56,6 @@ files = [ "./tests/wpt/mozilla/tests/css/fonts", "./tests/wpt/mozilla/tests/css/pre_with_tab.html", "./tests/wpt/mozilla/tests/mozilla/textarea_placeholder.html", - # Tidy complains about taking &String instead of &str, but they aren't - # equivalent given the way the traits are set up. - "./components/selectors/tree.rs", ] # Directories that are ignored for the non-WPT tidy check. directories = [ diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index 6d2a2d72bee..44c02f6c1f2 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -6,6 +6,7 @@ use cssparser::{self, Parser as CssParser, SourcePosition, SourceLocation}; use html5ever::{Namespace as NsAtom}; use media_queries::CSSErrorReporterTest; use parking_lot::RwLock; +use selectors::attr::*; use selectors::parser::*; use servo_atoms::Atom; use servo_url::ServoUrl; @@ -97,14 +98,14 @@ fn test_parse_stylesheet() { name: local_name!("input"), lower_name: local_name!("input"), }), - Component::AttrEqual(AttrSelector { - name: local_name!("type"), - lower_name: local_name!("type"), - namespace: NamespaceConstraint::Specific(Namespace { - prefix: None, - url: ns!() - }), - }, "hidden".to_owned(), CaseSensitivity::CaseInsensitive) + Component::AttributeInNoNamespace { + local_name: local_name!("type"), + local_name_lower: local_name!("type"), + operator: AttrSelectorOperator::Equal, + value: "hidden".to_owned(), + case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive, + never_matches: false, + } ]), (0 << 20) + (1 << 10) + (1 << 0) ), diff --git a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-024.htm.ini b/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-024.htm.ini deleted file mode 100644 index 8d25a735ed8..00000000000 --- a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-024.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[css3-selectors-lang-024.htm] - type: testharness - [A lang|= value will match a lang attribute value regardless of case differences.] - expected: FAIL - diff --git a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-035.htm.ini b/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-035.htm.ini deleted file mode 100644 index f1eb302573d..00000000000 --- a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-035.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[css3-selectors-lang-035.htm] - type: testharness - [A lang|= value will match a lang attribute value regardless of case differences in the script tag.] - expected: FAIL - diff --git a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-044.htm.ini b/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-044.htm.ini deleted file mode 100644 index 0a343c73563..00000000000 --- a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-044.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[css3-selectors-lang-044.htm] - type: testharness - [A lang= value will match a lang attribute value regardless of case differences.] - expected: FAIL - diff --git a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-055.htm.ini b/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-055.htm.ini deleted file mode 100644 index f8ff6b5f1f9..00000000000 --- a/tests/wpt/metadata-css/selectors-3_dev/html/css3-selectors-lang-055.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[css3-selectors-lang-055.htm] - type: testharness - [A lang= value will match a lang attribute value regardless of case differences in the script tag.] - expected: FAIL - diff --git a/tests/wpt/metadata/html/rendering/non-replaced-elements/lists/li-type-supported-xhtml.xhtml.ini b/tests/wpt/metadata/html/rendering/non-replaced-elements/lists/li-type-supported-xhtml.xhtml.ini deleted file mode 100644 index 3ac5a7343ca..00000000000 --- a/tests/wpt/metadata/html/rendering/non-replaced-elements/lists/li-type-supported-xhtml.xhtml.ini +++ /dev/null @@ -1,3 +0,0 @@ -[li-type-supported-xhtml.xhtml] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata/html/rendering/non-replaced-elements/lists/li-type-supported.html.ini b/tests/wpt/metadata/html/rendering/non-replaced-elements/lists/li-type-supported.html.ini deleted file mode 100644 index 1f631a1d1b8..00000000000 --- a/tests/wpt/metadata/html/rendering/non-replaced-elements/lists/li-type-supported.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[li-type-supported.html] - type: reftest - expected: FAIL |