diff options
-rw-r--r-- | components/atoms/static_atoms.txt | 2 | ||||
-rw-r--r-- | components/script/dom/element.rs | 43 | ||||
-rw-r--r-- | components/script/dom/htmlcollection.rs | 11 | ||||
-rw-r--r-- | components/script/dom/window.rs | 4 | ||||
-rw-r--r-- | components/script/layout_wrapper.rs | 39 | ||||
-rw-r--r-- | components/selectors/attr.rs | 4 | ||||
-rw-r--r-- | components/selectors/matching.rs | 17 | ||||
-rw-r--r-- | components/selectors/parser.rs | 5 | ||||
-rw-r--r-- | components/selectors/tree.rs | 19 | ||||
-rw-r--r-- | components/style/dom.rs | 3 | ||||
-rw-r--r-- | components/style/gecko/snapshot.rs | 3 | ||||
-rw-r--r-- | components/style/gecko/snapshot_helpers.rs | 8 | ||||
-rw-r--r-- | components/style/gecko/wrapper.rs | 40 | ||||
-rw-r--r-- | components/style/gecko_string_cache/mod.rs | 46 | ||||
-rw-r--r-- | components/style/invalidation/stylesheets.rs | 3 | ||||
-rw-r--r-- | components/style/lib.rs | 18 | ||||
-rw-r--r-- | components/style/restyle_hints.rs | 41 | ||||
-rw-r--r-- | components/style/selector_map.rs | 135 | ||||
-rw-r--r-- | components/style/servo/selector_parser.rs | 8 | ||||
-rw-r--r-- | components/style/stylist.rs | 41 | ||||
-rw-r--r-- | tests/unit/style/restyle_hints.rs | 4 | ||||
-rw-r--r-- | tests/unit/style/stylist.rs | 12 |
22 files changed, 314 insertions, 192 deletions
diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index a41aae29289..56d949eff5d 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -68,3 +68,5 @@ fullscreenchange fullscreenerror gattserverdisconnected onchange + +reftest-wait diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 957eadb53e4..ce848be5e17 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -85,7 +85,7 @@ 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::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity}; use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode}; use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS}; use selectors::matching::{RelevantLinkStatus, matches_selector_list}; @@ -97,6 +97,7 @@ use std::convert::TryFrom; use std::default::Default; use std::fmt; use std::rc::Rc; +use style::CaseSensitivityExt; use style::applicable_declarations::ApplicableDeclarationBlock; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::context::{QuirksMode, ReflowGoal}; @@ -345,7 +346,9 @@ impl RawLayoutElementHelpers for Element { pub trait LayoutElementHelpers { #[allow(unsafe_code)] - unsafe fn has_class_for_layout(&self, name: &Atom) -> bool; + unsafe fn in_quirks_mode_document_for_layout(&self) -> bool; + #[allow(unsafe_code)] + unsafe fn has_class_for_layout(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool; #[allow(unsafe_code)] unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]>; @@ -373,9 +376,15 @@ pub trait LayoutElementHelpers { impl LayoutElementHelpers for LayoutJS<Element> { #[allow(unsafe_code)] #[inline] - unsafe fn has_class_for_layout(&self, name: &Atom) -> bool { + unsafe fn in_quirks_mode_document_for_layout(&self) -> bool { + self.upcast::<Node>().owner_doc_for_layout().quirks_mode() == QuirksMode::Quirks + } + + #[allow(unsafe_code)] + #[inline] + unsafe fn has_class_for_layout(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { get_attr_for_layout(&*self.unsafe_get(), &ns!(), &local_name!("class")).map_or(false, |attr| { - attr.value_tokens_forever().unwrap().iter().any(|atom| atom == name) + attr.value_tokens_forever().unwrap().iter().any(|atom| case_sensitivity.eq_atom(atom, name)) }) } @@ -1158,16 +1167,10 @@ impl Element { }) } - pub fn has_class(&self, name: &Atom) -> bool { - let quirks_mode = document_from_node(self).quirks_mode(); - let is_equal = |lhs: &Atom, rhs: &Atom| { - match quirks_mode { - QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => lhs == rhs, - QuirksMode::Quirks => lhs.eq_ignore_ascii_case(&rhs), - } - }; - self.get_attribute(&ns!(), &local_name!("class")) - .map_or(false, |attr| attr.value().as_tokens().iter().any(|atom| is_equal(name, atom))) + pub fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { + self.get_attribute(&ns!(), &local_name!("class")).map_or(false, |attr| { + attr.value().as_tokens().iter().any(|atom| case_sensitivity.eq_atom(name, atom)) + }) } pub fn set_atomic_attribute(&self, local_name: &LocalName, value: DOMString) { @@ -2503,12 +2506,16 @@ impl<'a> ::selectors::Element for Root<Element> { } } - fn get_id(&self) -> Option<Atom> { - self.id_attribute.borrow().clone() + fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool { + self.id_attribute.borrow().as_ref().map_or(false, |atom| case_sensitivity.eq_atom(id, atom)) + } + + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { + Element::has_class(&**self, name, case_sensitivity) } - fn has_class(&self, name: &Atom) -> bool { - Element::has_class(&**self, name) + fn in_quirks_mode_document(&self) -> bool { + document_from_node(&**self).quirks_mode() == QuirksMode::Quirks } fn is_html_element_in_html_document(&self) -> bool { diff --git a/components/script/dom/htmlcollection.rs b/components/script/dom/htmlcollection.rs index aff4125a29f..7c3cad306c4 100644 --- a/components/script/dom/htmlcollection.rs +++ b/components/script/dom/htmlcollection.rs @@ -11,12 +11,14 @@ use dom::bindings::str::DOMString; use dom::bindings::trace::JSTraceable; use dom::bindings::xmlname::namespace_from_domstring; use dom::element::Element; -use dom::node::Node; +use dom::node::{Node, document_from_node}; use dom::window::Window; use dom_struct::dom_struct; use html5ever::{LocalName, QualName}; +use selectors::attr::CaseSensitivity; use servo_atoms::Atom; use std::cell::Cell; +use style::context::QuirksMode; use style::str::split_html_space_chars; pub trait CollectionFilter : JSTraceable { @@ -199,7 +201,12 @@ impl HTMLCollection { } impl CollectionFilter for ClassNameFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { - self.classes.iter().all(|class| elem.has_class(class)) + let case_sensitivity = match document_from_node(elem).quirks_mode() { + QuirksMode::NoQuirks | + QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive, + QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive, + }; + self.classes.iter().all(|class| elem.has_class(class, case_sensitivity)) } } let filter = ClassNameFilter { diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index bcc5a749d33..6e89024887a 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -84,7 +84,7 @@ use script_traits::{ConstellationControlMsg, DocumentState, LoadData, MozBrowser use script_traits::{ScriptMsg as ConstellationMsg, ScrollState, TimerEvent, TimerEventId}; use script_traits::{TimerSchedulerMsg, UntrustedNodeAddress, WindowSizeData, WindowSizeType}; use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; -use servo_atoms::Atom; +use selectors::attr::CaseSensitivity; use servo_config::opts; use servo_config::prefs::PREFS; use servo_geometry::{f32_rect_to_au_rect, max_rect}; @@ -1365,7 +1365,7 @@ impl Window { // See http://testthewebforward.org/docs/reftests.html let html_element = document.GetDocumentElement(); let reftest_wait = html_element.map_or(false, |elem| { - elem.has_class(&Atom::from("reftest-wait")) + elem.has_class(&atom!("reftest-wait"), CaseSensitivity::CaseSensitive) }); let ready_state = document.ReadyState(); diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 916b9f5a329..7b4b450c63e 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -49,7 +49,7 @@ use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, Truste use script_layout_interface::{OpaqueStyleAndLayoutData, StyleData}; 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::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity}; use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus}; use selectors::matching::VisitedHandlingMode; use servo_atoms::Atom; @@ -61,6 +61,7 @@ use std::marker::PhantomData; use std::mem::transmute; use std::sync::atomic::Ordering; use style; +use style::CaseSensitivityExt; use style::applicable_declarations::ApplicableDeclarationBlock; use style::attr::AttrValue; use style::computed_values::display; @@ -414,6 +415,13 @@ impl<'le> TElement for ServoLayoutElement<'le> { self.get_attr(namespace, attr).is_some() } + #[inline] + fn get_id(&self) -> Option<Atom> { + unsafe { + (*self.element.id_attribute()).clone() + } + } + #[inline(always)] fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) { unsafe { @@ -779,16 +787,24 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { } #[inline] - fn get_id(&self) -> Option<Atom> { + fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool { unsafe { - (*self.element.id_attribute()).clone() + (*self.element.id_attribute()) + .as_ref() + .map_or(false, |atom| case_sensitivity.eq_atom(atom, id)) } } #[inline] - fn has_class(&self, name: &Atom) -> bool { + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { unsafe { - self.element.has_class_for_layout(name) + self.element.has_class_for_layout(name, case_sensitivity) + } + } + + fn in_quirks_mode_document(&self) -> bool { + unsafe { + self.element.in_quirks_mode_document_for_layout() } } @@ -1194,6 +1210,11 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { true } + fn in_quirks_mode_document(&self) -> bool { + debug!("ServoThreadSafeLayoutElement::in_quirks_mode_document called"); + false + } + #[inline] fn get_local_name(&self) -> &LocalName { self.element.get_local_name() @@ -1249,12 +1270,12 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { false } - fn get_id(&self) -> Option<Atom> { - debug!("ServoThreadSafeLayoutElement::get_id called"); - None + fn has_id(&self, _id: &Atom, _case_sensitivity: CaseSensitivity) -> bool { + debug!("ServoThreadSafeLayoutElement::has_id called"); + false } - fn has_class(&self, _name: &Atom) -> bool { + fn has_class(&self, _name: &Atom, _case_sensitivity: CaseSensitivity) -> bool { debug!("ServoThreadSafeLayoutElement::has_class called"); false } diff --git a/components/selectors/attr.rs b/components/selectors/attr.rs index 274081ae9f7..da208c94b1f 100644 --- a/components/selectors/attr.rs +++ b/components/selectors/attr.rs @@ -127,7 +127,7 @@ 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. + CaseSensitive, AsciiCaseInsensitive, AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, } @@ -150,7 +150,7 @@ impl ParsedCaseSensitivity { #[derive(Eq, PartialEq, Clone, Copy, Debug)] pub enum CaseSensitivity { - CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive. + CaseSensitive, AsciiCaseInsensitive, } diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 7cc4735b09c..ea78a80bbf0 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -2,7 +2,7 @@ * 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 attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint, CaseSensitivity}; use bloom::{BLOOM_HASH_MASK, BloomFilter}; use parser::{AncestorHashes, Combinator, Component, LocalName}; use parser::{Selector, SelectorImpl, SelectorIter, SelectorList}; @@ -666,12 +666,21 @@ fn matches_simple_selector<E, F>( 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) => { - element.get_id().map_or(false, |attr| attr == *id) + let case_sensitivity = if element.in_quirks_mode_document() { + CaseSensitivity::AsciiCaseInsensitive + } else { + CaseSensitivity::CaseSensitive + }; + element.has_id(id, case_sensitivity) } Component::Class(ref class) => { - element.has_class(class) + let case_sensitivity = if element.in_quirks_mode_document() { + CaseSensitivity::AsciiCaseInsensitive + } else { + CaseSensitivity::CaseSensitive + }; + element.has_class(class, case_sensitivity) } Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => { let is_html = element.is_html_element_in_html_document(); diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 612e89ba60e..c2d64f18db1 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -1385,7 +1385,10 @@ fn parse_attribute_flags<'i, 't, E>(input: &mut CssParser<'i, 't>) -> Result<ParsedCaseSensitivity, ParseError<'i, SelectorParseError<'i, E>>> { match input.next() { - Err(_) => Ok(ParsedCaseSensitivity::CaseSensitive), + Err(_) => { + // Selectors spec says language-defined, but HTML says sensitive. + Ok(ParsedCaseSensitivity::CaseSensitive) + } Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => { Ok(ParsedCaseSensitivity::AsciiCaseInsensitive) } diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index 7a47f2b4ab1..97114de031d 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -5,7 +5,7 @@ //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency //! between layout and style. -use attr::{AttrSelectorOperation, NamespaceConstraint}; +use attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity}; use matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus}; use parser::SelectorImpl; use std::fmt::Debug; @@ -63,9 +63,20 @@ pub trait Element: Sized + Debug { /// Whether this element is a `link`. fn is_link(&self) -> bool; - fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>; - - fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool; + /// Whether this element is in a document that is in quirks mode. + /// + /// https://dom.spec.whatwg.org/#concept-document-quirks + fn in_quirks_mode_document(&self) -> bool; + + fn has_id(&self, + id: &<Self::Impl as SelectorImpl>::Identifier, + case_sensitivity: CaseSensitivity) + -> bool; + + fn has_class(&self, + name: &<Self::Impl as SelectorImpl>::ClassName, + case_sensitivity: CaseSensitivity) + -> bool; /// Returns whether this element matches `:empty`. /// diff --git a/components/style/dom.rs b/components/style/dom.rs index bbb4b279bf3..5a6cc5ea980 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -376,6 +376,9 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// Whether this element has an attribute with a given namespace. fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool; + /// The ID for this element. + fn get_id(&self) -> Option<Atom>; + /// Internal iterator for the classes of this element. fn each_class<F>(&self, callback: F) where F: FnMut(&Atom); diff --git a/components/style/gecko/snapshot.rs b/components/style/gecko/snapshot.rs index b2f7907a64a..d0060d5b7c7 100644 --- a/components/style/gecko/snapshot.rs +++ b/components/style/gecko/snapshot.rs @@ -164,13 +164,14 @@ impl ElementSnapshot for GeckoElementSnapshot { } #[inline] - fn has_class(&self, name: &Atom) -> bool { + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { if !self.has_any(Flags::MaybeClass) { return false; } snapshot_helpers::has_class(self.as_ptr(), name, + case_sensitivity, bindings::Gecko_SnapshotClassOrClassList) } diff --git a/components/style/gecko/snapshot_helpers.rs b/components/style/gecko/snapshot_helpers.rs index f7a0efa48c0..70af6f7082a 100644 --- a/components/style/gecko/snapshot_helpers.rs +++ b/components/style/gecko/snapshot_helpers.rs @@ -4,7 +4,10 @@ //! Element an snapshot common logic. +use CaseSensitivityExt; use gecko_bindings::structs::nsIAtom; +use gecko_string_cache::WeakAtom; +use selectors::attr::CaseSensitivity; use std::{ptr, slice}; use string_cache::Atom; @@ -16,6 +19,7 @@ pub type ClassOrClassList<T> = unsafe extern fn (T, *mut *mut nsIAtom, *mut *mut /// element has the class that `name` represents. pub fn has_class<T>(item: T, name: &Atom, + case_sensitivity: CaseSensitivity, getter: ClassOrClassList<T>) -> bool { unsafe { @@ -24,10 +28,10 @@ pub fn has_class<T>(item: T, let length = getter(item, &mut class, &mut list); match length { 0 => false, - 1 => name.as_ptr() == class, + 1 => case_sensitivity.eq_atom(name, WeakAtom::new(class)), n => { let classes = slice::from_raw_parts(list, n as usize); - classes.iter().any(|ptr| name.as_ptr() == *ptr) + classes.iter().any(|ptr| case_sensitivity.eq_atom(name, WeakAtom::new(*ptr))) } } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 64068a875f6..abebcd2691c 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -14,6 +14,7 @@ //! style system it's kind of pointless in the Stylo case, and only Servo forces //! the separation between the style system implementation and everything else. +use CaseSensitivityExt; use app_units::Au; use applicable_declarations::ApplicableDeclarationBlock; use atomic_refcell::AtomicRefCell; @@ -744,6 +745,23 @@ impl<'le> TElement for GeckoElement<'le> { } } + fn get_id(&self) -> Option<Atom> { + if !self.has_id() { + return None + } + + let ptr = unsafe { + bindings::Gecko_AtomAttrValue(self.0, + atom!("id").as_ptr()) + }; + + if ptr.is_null() { + None + } else { + Some(Atom::from(ptr)) + } + } + fn each_class<F>(&self, callback: F) where F: FnMut(&Atom) { @@ -1574,30 +1592,22 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag()) } - fn get_id(&self) -> Option<Atom> { - if !self.has_id() { - return None; - } - - let ptr = unsafe { - bindings::Gecko_AtomAttrValue(self.0, - atom!("id").as_ptr()) - }; + fn in_quirks_mode_document(&self) -> bool { + self.as_node().owner_doc().mCompatMode == structs::nsCompatibility::eCompatibility_NavQuirks + } - if ptr.is_null() { - None - } else { - Some(Atom::from(ptr)) - } + fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool { + self.get_id().map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)) } - fn has_class(&self, name: &Atom) -> bool { + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { if !self.may_have_class() { return false; } snapshot_helpers::has_class(self.0, name, + case_sensitivity, Gecko_ClassOrClassList) } diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs index f9bc9c3a809..6e1abc57059 100644 --- a/components/style/gecko_string_cache/mod.rs +++ b/components/style/gecko_string_cache/mod.rs @@ -201,6 +201,29 @@ impl WeakAtom { } } } + + /// Return whether two atoms are ASCII-case-insensitive matches + pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { + if self == other { + return true; + } + + let a = self.as_slice(); + let b = other.as_slice(); + a.len() == b.len() && a.iter().zip(b).all(|(&a16, &b16)| { + if a16 <= 0x7F && b16 <= 0x7F { + (a16 as u8).eq_ignore_ascii_case(&(b16 as u8)) + } else { + a16 == b16 + } + }) + } + + /// Return whether this atom is an ASCII-case-insensitive match for the given string + pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool { + self.chars().map(|r| r.map(|c: char| c.to_ascii_lowercase())) + .eq(other.chars().map(|c: char| Ok(c.to_ascii_lowercase()))) + } } impl fmt::Debug for WeakAtom { @@ -258,29 +281,6 @@ impl Atom { mem::forget(self); ptr } - - /// Return whether two atoms are ASCII-case-insensitive matches - pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { - if self == other { - return true; - } - - let a = self.as_slice(); - let b = other.as_slice(); - a.len() == b.len() && a.iter().zip(b).all(|(&a16, &b16)| { - if a16 <= 0x7F && b16 <= 0x7F { - (a16 as u8).eq_ignore_ascii_case(&(b16 as u8)) - } else { - a16 == b16 - } - }) - } - - /// Return whether this atom is an ASCII-case-insensitive match for the given string - pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool { - self.chars().map(|r| r.map(|c: char| c.to_ascii_lowercase())) - .eq(other.chars().map(|c: char| Ok(c.to_ascii_lowercase()))) - } } impl Hash for Atom { diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 2991867cb3b..6d36d16a2cd 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -12,6 +12,7 @@ use data::StoredRestyleHint; use dom::{TElement, TNode}; use fnv::FnvHashSet; use selector_parser::SelectorImpl; +use selectors::attr::CaseSensitivity; use selectors::parser::{Component, Selector}; use shared_lock::SharedRwLockReadGuard; use stylesheets::{CssRule, Stylesheet}; @@ -37,7 +38,7 @@ impl InvalidationScope { { match *self { InvalidationScope::Class(ref class) => { - element.has_class(class) + element.has_class(class, CaseSensitivity::CaseSensitive) } InvalidationScope::ID(ref id) => { match element.get_id() { diff --git a/components/style/lib.rs b/components/style/lib.rs index 66cde1211d3..7c5945e3d79 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -217,3 +217,21 @@ pub fn serialize_comma_separated_list<W, T>(dest: &mut W, Ok(()) } + +#[cfg(feature = "gecko")] use gecko_string_cache::WeakAtom; +#[cfg(feature = "servo")] use servo_atoms::Atom as WeakAtom; + +/// Extension methods for selectors::attr::CaseSensitivity +pub trait CaseSensitivityExt { + /// Return whether two atoms compare equal according to this case sensitivity. + fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool; +} + +impl CaseSensitivityExt for selectors::attr::CaseSensitivity { + fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool { + match self { + selectors::attr::CaseSensitivity::CaseSensitive => a == b, + selectors::attr::CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), + } + } +} diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index 350608e118b..0461a9e1456 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -7,9 +7,10 @@ #![deny(missing_docs)] use Atom; +use CaseSensitivityExt; use LocalName; use Namespace; -use context::{SharedStyleContext, ThreadLocalStyleContext}; +use context::{SharedStyleContext, ThreadLocalStyleContext, QuirksMode}; use dom::TElement; use element_state::*; #[cfg(feature = "gecko")] @@ -19,7 +20,7 @@ use heapsize::HeapSizeOf; use selector_map::{SelectorMap, SelectorMapEntry}; use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue}; use selectors::Element; -use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; +use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity}; use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode}; use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode, matches_selector}; use selectors::parser::{AncestorHashes, Combinator, Component}; @@ -549,7 +550,7 @@ pub trait ElementSnapshot : Sized { /// Whether this snapshot contains the class `name`. Should only be called /// if `has_attrs()` returns true. - fn has_class(&self, name: &Atom) -> bool; + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool; /// A callback that should be called for each class of the snapshot. Should /// only be called if `has_attrs()` returns true. @@ -820,19 +821,25 @@ impl<'a, E> Element for ElementWrapper<'a, E> } } - fn get_id(&self) -> Option<Atom> { + fn in_quirks_mode_document(&self) -> bool { + self.element.in_quirks_mode_document() + } + + fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool { match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.id_attr(), - _ => self.element.get_id() + Some(snapshot) if snapshot.has_attrs() => { + snapshot.id_attr().map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)) + } + _ => self.element.has_id(id, case_sensitivity) } } - fn has_class(&self, name: &Atom) -> bool { + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() - => snapshot.has_class(name), - _ => self.element.has_class(name) + Some(snapshot) if snapshot.has_attrs() => { + snapshot.has_class(name, case_sensitivity) + } + _ => self.element.has_class(name, case_sensitivity) } } @@ -1006,7 +1013,8 @@ pub enum HintComputationContext<'a, E: 'a> impl DependencySet { /// Adds a selector to this `DependencySet`. - pub fn note_selector(&mut self, selector_and_hashes: &SelectorAndHashes<SelectorImpl>) { + pub fn note_selector(&mut self, selector_and_hashes: &SelectorAndHashes<SelectorImpl>, + quirks_mode: QuirksMode) { let mut combinator = None; let mut iter = selector_and_hashes.selector.iter(); let mut index = 0; @@ -1073,7 +1081,7 @@ impl DependencySet { selector: selector_and_hashes.selector.clone(), selector_offset: sequence_start, hashes: hashes, - }); + }, quirks_mode); } combinator = iter.next_sequence(); @@ -1149,7 +1157,7 @@ impl DependencySet { } snapshot.each_class(|c| { - if !el.has_class(c) { + if !el.has_class(c, CaseSensitivity::CaseSensitive) { additional_classes.push(c.clone()) } }); @@ -1172,8 +1180,9 @@ impl DependencySet { *el }; - self.dependencies - .lookup_with_additional(lookup_element, additional_id, &additional_classes, &mut |dep| { + self.dependencies.lookup_with_additional( + lookup_element, shared_context.quirks_mode, additional_id, &additional_classes, + &mut |dep| { trace!("scanning dependency: {:?}", dep); if !dep.sensitivities.sensitive_to(attrs_changed, diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 6a5733877a2..9788860ebaa 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -7,6 +7,7 @@ use {Atom, LocalName}; use applicable_declarations::ApplicableDeclarationBlock; +use context::QuirksMode; use dom::TElement; use fnv::FnvHashMap; use pdqsort::sort_by; @@ -16,8 +17,8 @@ use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlag use selectors::parser::{AncestorHashes, Component, Combinator, SelectorAndHashes, SelectorIter}; use selectors::parser::LocalName as LocalNameSelector; use smallvec::VecLike; -use std::borrow::Borrow; use std::collections::HashMap; +use std::collections::hash_map; use std::hash::Hash; use stylist::Rule; @@ -66,9 +67,9 @@ impl SelectorMapEntry for SelectorAndHashes<SelectorImpl> { #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct SelectorMap<T: SelectorMapEntry> { /// A hash from an ID to rules which contain that ID selector. - pub id_hash: FnvHashMap<Atom, Vec<T>>, + pub id_hash: MaybeCaseInsensitiveHashMap<Atom, Vec<T>>, /// A hash from a class name to rules which contain that class selector. - pub class_hash: FnvHashMap<Atom, Vec<T>>, + pub class_hash: MaybeCaseInsensitiveHashMap<Atom, Vec<T>>, /// A hash from local name to rules which contain that local name selector. pub local_name_hash: FnvHashMap<LocalName, Vec<T>>, /// Rules that don't have ID, class, or element selectors. @@ -86,8 +87,8 @@ impl<T: SelectorMapEntry> SelectorMap<T> { /// Trivially constructs an empty `SelectorMap`. pub fn new() -> Self { SelectorMap { - id_hash: HashMap::default(), - class_hash: HashMap::default(), + id_hash: MaybeCaseInsensitiveHashMap::new(), + class_hash: MaybeCaseInsensitiveHashMap::new(), local_name_hash: HashMap::default(), other: Vec::new(), count: 0, @@ -115,6 +116,7 @@ impl SelectorMap<Rule> { rule_hash_target: &E, matching_rules_list: &mut V, context: &mut MatchingContext, + quirks_mode: QuirksMode, flags_setter: &mut F, cascade_level: CascadeLevel) where E: TElement, @@ -128,32 +130,35 @@ impl SelectorMap<Rule> { // At the end, we're going to sort the rules that we added, so remember where we began. let init_len = matching_rules_list.len(); if let Some(id) = rule_hash_target.get_id() { - SelectorMap::get_matching_rules_from_hash(element, - &self.id_hash, - &id, - matching_rules_list, - context, - flags_setter, - cascade_level) + if let Some(rules) = self.id_hash.get(&id, quirks_mode) { + SelectorMap::get_matching_rules(element, + rules, + matching_rules_list, + context, + flags_setter, + cascade_level) + } } rule_hash_target.each_class(|class| { - SelectorMap::get_matching_rules_from_hash(element, - &self.class_hash, - class, - matching_rules_list, - context, - flags_setter, - cascade_level); + if let Some(rules) = self.class_hash.get(&class, quirks_mode) { + SelectorMap::get_matching_rules(element, + rules, + matching_rules_list, + context, + flags_setter, + cascade_level) + } }); - SelectorMap::get_matching_rules_from_hash(element, - &self.local_name_hash, - rule_hash_target.get_local_name(), - matching_rules_list, - context, - flags_setter, - cascade_level); + if let Some(rules) = self.local_name_hash.get(rule_hash_target.get_local_name()) { + SelectorMap::get_matching_rules(element, + rules, + matching_rules_list, + context, + flags_setter, + cascade_level) + } SelectorMap::get_matching_rules(element, &self.other, @@ -167,12 +172,6 @@ impl SelectorMap<Rule> { |block| (block.specificity, block.source_order())); } - /// Check whether we have rules for the given id - #[inline] - pub fn has_rules_for_id(&self, id: &Atom) -> bool { - self.id_hash.get(id).is_some() - } - /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in /// `self` sorted by specificity and source order. pub fn get_universal_rules(&self, @@ -196,30 +195,6 @@ impl SelectorMap<Rule> { rules_list } - fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>( - element: &E, - hash: &FnvHashMap<Str, Vec<Rule>>, - key: &BorrowedStr, - matching_rules: &mut Vector, - context: &mut MatchingContext, - flags_setter: &mut F, - cascade_level: CascadeLevel) - where E: TElement, - Str: Borrow<BorrowedStr> + Eq + Hash, - BorrowedStr: Eq + Hash, - Vector: VecLike<ApplicableDeclarationBlock>, - F: FnMut(&E, ElementSelectorFlags), - { - if let Some(rules) = hash.get(key) { - SelectorMap::get_matching_rules(element, - rules, - matching_rules, - context, - flags_setter, - cascade_level) - } - } - /// Adds rules in `rules` that match `element` to the `matching_rules` list. fn get_matching_rules<E, V, F>(element: &E, rules: &[Rule], @@ -247,16 +222,16 @@ impl SelectorMap<Rule> { impl<T: SelectorMapEntry> SelectorMap<T> { /// Inserts into the correct hash, trying id, class, and localname. - pub fn insert(&mut self, entry: T) { + pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) { self.count += 1; if let Some(id_name) = get_id_name(entry.selector()) { - find_push(&mut self.id_hash, id_name, entry); + self.id_hash.entry(id_name, quirks_mode).or_insert_with(Vec::new).push(entry); return; } if let Some(class_name) = get_class_name(entry.selector()) { - find_push(&mut self.class_hash, class_name, entry); + self.class_hash.entry(class_name, quirks_mode).or_insert_with(Vec::new).push(entry); return; } @@ -293,13 +268,13 @@ impl<T: SelectorMapEntry> SelectorMap<T> { /// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules, /// but that function is extremely hot and I'd rather not rearrange it. #[inline] - pub fn lookup<E, F>(&self, element: E, f: &mut F) -> bool + pub fn lookup<E, F>(&self, element: E, quirks_mode: QuirksMode, f: &mut F) -> bool where E: TElement, F: FnMut(&T) -> bool { // Id. if let Some(id) = element.get_id() { - if let Some(v) = self.id_hash.get(&id) { + if let Some(v) = self.id_hash.get(&id, quirks_mode) { for entry in v.iter() { if !f(&entry) { return false; @@ -312,7 +287,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> { let mut done = false; element.each_class(|class| { if !done { - if let Some(v) = self.class_hash.get(class) { + if let Some(v) = self.class_hash.get(class, quirks_mode) { for entry in v.iter() { if !f(&entry) { done = true; @@ -355,6 +330,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> { #[inline] pub fn lookup_with_additional<E, F>(&self, element: E, + quirks_mode: QuirksMode, additional_id: Option<Atom>, additional_classes: &[Atom], f: &mut F) @@ -363,13 +339,13 @@ impl<T: SelectorMapEntry> SelectorMap<T> { F: FnMut(&T) -> bool { // Do the normal lookup. - if !self.lookup(element, f) { + if !self.lookup(element, quirks_mode, f) { return false; } // Check the additional id. if let Some(id) = additional_id { - if let Some(v) = self.id_hash.get(&id) { + if let Some(v) = self.id_hash.get(&id, quirks_mode) { for entry in v.iter() { if !f(&entry) { return false; @@ -380,7 +356,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> { // Check the additional classes. for class in additional_classes { - if let Some(v) = self.class_hash.get(class) { + if let Some(v) = self.class_hash.get(class, quirks_mode) { for entry in v.iter() { if !f(&entry) { return false; @@ -472,3 +448,32 @@ fn find_push<Str: Eq + Hash, V>(map: &mut FnvHashMap<Str, Vec<V>>, value: V) { map.entry(key).or_insert_with(Vec::new).push(value) } + +/// Wrapper for FnvHashMap that does ASCII-case-insensitive lookup in quirks mode. +#[derive(Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct MaybeCaseInsensitiveHashMap<K: Hash + Eq, V>(FnvHashMap<K, V>); + +impl<V> MaybeCaseInsensitiveHashMap<Atom, V> { + /// Empty map + pub fn new() -> Self { + MaybeCaseInsensitiveHashMap(FnvHashMap::default()) + } + + /// HashMap::entry + pub fn entry(&mut self, mut key: Atom, quirks_mode: QuirksMode) -> hash_map::Entry<Atom, V> { + if quirks_mode == QuirksMode::Quirks { + key = key.to_ascii_lowercase() + } + self.0.entry(key) + } + + /// HashMap::get + pub fn get(&self, key: &Atom, quirks_mode: QuirksMode) -> Option<&V> { + if quirks_mode == QuirksMode::Quirks { + self.0.get(&key.to_ascii_lowercase()) + } else { + self.0.get(key) + } + } +} diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index 289884c28f3..681a4c54374 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -6,7 +6,7 @@ //! Servo's selector parser. -use {Atom, Prefix, Namespace, LocalName}; +use {Atom, Prefix, Namespace, LocalName, CaseSensitivityExt}; use attr::{AttrIdentifier, AttrValue}; use cssparser::{Parser as CssParser, ToCss, serialize_identifier}; use dom::{OpaqueNode, TElement, TNode}; @@ -15,7 +15,7 @@ use fnv::FnvHashMap; use restyle_hints::ElementSnapshot; use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser}; use selectors::Element; -use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; +use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity}; use selectors::parser::{SelectorMethods, SelectorParseError}; use selectors::visitor::SelectorVisitor; use std::ascii::AsciiExt; @@ -584,9 +584,9 @@ impl ElementSnapshot for ServoElementSnapshot { self.get_attr(&ns!(), &local_name!("id")).map(|v| v.as_atom().clone()) } - fn has_class(&self, name: &Atom) -> bool { + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { self.get_attr(&ns!(), &local_name!("class")) - .map_or(false, |v| v.as_tokens().iter().any(|atom| atom == name)) + .map_or(false, |v| v.as_tokens().iter().any(|atom| case_sensitivity.eq_atom(atom, name))) } fn each_class<F>(&self, mut callback: F) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 91de0ad3487..1a2aa59096f 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -477,15 +477,18 @@ impl Stylist { self.element_map.borrow_for_origin(&stylesheet.origin) }; - map.insert(Rule::new(selector_and_hashes.selector.clone(), - selector_and_hashes.hashes.clone(), - locked.clone(), - self.rules_source_order)); - - self.dependencies.note_selector(selector_and_hashes); + map.insert( + Rule::new(selector_and_hashes.selector.clone(), + selector_and_hashes.hashes.clone(), + locked.clone(), + self.rules_source_order), + self.quirks_mode); + + self.dependencies.note_selector(selector_and_hashes, self.quirks_mode); if needs_revalidation(&selector_and_hashes.selector) { self.selectors_for_cache_revalidation.insert( - RevalidationSelectorAndHashes::new(&selector_and_hashes)); + RevalidationSelectorAndHashes::new(&selector_and_hashes), + self.quirks_mode); } selector_and_hashes.selector.visit(&mut AttributeAndStateDependencyVisitor { attribute_dependencies: &mut self.attribute_dependencies, @@ -946,6 +949,7 @@ impl Stylist { element, applicable_declarations, &mut matching_context, + self.quirks_mode, &mut dummy_flag_setter, CascadeLevel::XBL); } @@ -1006,6 +1010,7 @@ impl Stylist { &rule_hash_target, applicable_declarations, context, + self.quirks_mode, flags_setter, CascadeLevel::UANormal); debug!("UA normal: {:?}", context.relations); @@ -1045,6 +1050,7 @@ impl Stylist { &rule_hash_target, applicable_declarations, context, + self.quirks_mode, flags_setter, CascadeLevel::UserNormal); debug!("user normal: {:?}", context.relations); @@ -1066,6 +1072,7 @@ impl Stylist { &rule_hash_target, applicable_declarations, context, + self.quirks_mode, flags_setter, CascadeLevel::AuthorNormal); debug!("author normal: {:?}", context.relations); @@ -1176,15 +1183,17 @@ impl Stylist { // the lookups, which means that the bitvecs are comparable. We verify // this in the caller by asserting that the bitvecs are same-length. let mut results = BitVec::new(); - self.selectors_for_cache_revalidation.lookup(*element, &mut |selector_and_hashes| { - results.push(matches_selector(&selector_and_hashes.selector, - selector_and_hashes.selector_offset, - &selector_and_hashes.hashes, - element, - &mut matching_context, - flags_setter)); - true - }); + self.selectors_for_cache_revalidation.lookup( + *element, self.quirks_mode, &mut |selector_and_hashes| { + results.push(matches_selector(&selector_and_hashes.selector, + selector_and_hashes.selector_offset, + &selector_and_hashes.hashes, + element, + &mut matching_context, + flags_setter)); + true + } + ); results } diff --git a/tests/unit/style/restyle_hints.rs b/tests/unit/style/restyle_hints.rs index 88b7c9a6f4b..5b12c030db5 100644 --- a/tests/unit/style/restyle_hints.rs +++ b/tests/unit/style/restyle_hints.rs @@ -2,6 +2,8 @@ * 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 style::context::QuirksMode; + #[test] fn smoke_restyle_hints() { use cssparser::{Parser, ParserInput}; @@ -23,6 +25,6 @@ fn smoke_restyle_hints() { assert_eq!((selectors.0).len(), 1); let selector = (selectors.0).first().unwrap(); - dependencies.note_selector(selector); + dependencies.note_selector(selector, QuirksMode::NoQuirks); assert_eq!(dependencies.len(), 1); } diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index ee4ac0b53f7..1a28e5ea772 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -56,7 +56,7 @@ fn get_mock_map(selectors: &[&str]) -> (SelectorMap<Rule>, SharedRwLock) { for rules in selector_rules.into_iter() { for rule in rules.into_iter() { - map.insert(rule) + map.insert(rule, QuirksMode::NoQuirks) } } @@ -217,11 +217,11 @@ fn test_get_local_name() { fn test_insert() { let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]); let mut selector_map = SelectorMap::new(); - selector_map.insert(rules_list[1][0].clone()); - assert_eq!(1, selector_map.id_hash.get(&Atom::from("top")).unwrap()[0].source_order); - selector_map.insert(rules_list[0][0].clone()); - assert_eq!(0, selector_map.class_hash.get(&Atom::from("foo")).unwrap()[0].source_order); - assert!(selector_map.class_hash.get(&Atom::from("intro")).is_none()); + selector_map.insert(rules_list[1][0].clone(), QuirksMode::NoQuirks); + assert_eq!(1, selector_map.id_hash.get(&Atom::from("top"), QuirksMode::NoQuirks).unwrap()[0].source_order); + selector_map.insert(rules_list[0][0].clone(), QuirksMode::NoQuirks); + assert_eq!(0, selector_map.class_hash.get(&Atom::from("foo"), QuirksMode::NoQuirks).unwrap()[0].source_order); + assert!(selector_map.class_hash.get(&Atom::from("intro"), QuirksMode::NoQuirks).is_none()); } #[test] |