From e11d387372b919edde209fee96f818264ead509b Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 12 Jun 2017 15:52:29 -0700 Subject: [PATCH] servo: Merge #17213 - ID and class selectors are ASCII case-insensitive in quirks mode (from servo:quirk-case); r=bholley https://bugzilla.mozilla.org/show_bug.cgi?id=1363778 Source-Repo: https://github.com/servo/servo Source-Revision: 1b077303237d5ecb8307f866e9172d0d8e6b132d --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : b310b4744e5d41ffeb8d516ed7f3b48c495f0328 --- servo/Cargo.lock | 8 +- servo/components/atoms/static_atoms.txt | 2 + servo/components/script/dom/element.rs | 31 ++-- servo/components/script/dom/htmlcollection.rs | 7 +- servo/components/script/dom/window.rs | 4 +- servo/components/script/layout_wrapper.rs | 28 ++-- servo/components/selectors/attr.rs | 4 +- servo/components/selectors/context.rs | 152 ++++++++++++++++++ servo/components/selectors/lib.rs | 1 + servo/components/selectors/matching.rs | 131 +-------------- servo/components/selectors/parser.rs | 5 +- servo/components/selectors/tree.rs | 12 +- servo/components/style/dom.rs | 3 + servo/components/style/gecko/snapshot.rs | 3 +- .../style/gecko/snapshot_helpers.rs | 8 +- servo/components/style/gecko/wrapper.rs | 40 +++-- .../style/gecko_string_cache/mod.rs | 80 ++++++--- .../style/invalidation/stylesheets.rs | 3 +- servo/components/style/lib.rs | 18 +++ servo/components/style/restyle_hints.rs | 37 +++-- servo/components/style/selector_map.rs | 135 ++++++++-------- .../components/style/servo/selector_parser.rs | 8 +- servo/components/style/stylist.rs | 39 +++-- servo/tests/unit/style/restyle_hints.rs | 4 +- servo/tests/unit/style/stylist.rs | 12 +- 25 files changed, 463 insertions(+), 312 deletions(-) create mode 100644 servo/components/selectors/context.rs diff --git a/servo/Cargo.lock b/servo/Cargo.lock index 3b97eec46b60..852654956254 100644 --- a/servo/Cargo.lock +++ b/servo/Cargo.lock @@ -1629,7 +1629,7 @@ dependencies = [ "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)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", - "string_cache 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "tendril 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2702,7 +2702,7 @@ dependencies = [ name = "servo_atoms" version = "0.0.1" dependencies = [ - "string_cache 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "string_cache" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3738,7 +3738,7 @@ dependencies = [ "checksum skeptic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd7d8dc1315094150052d0ab767840376335a98ac66ef313ff911cdf439a5b69" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum smallvec 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e40af10aafe98b4d8294ae8388d8a5cd0707c65d364872efe72d063ec44bee0" -"checksum string_cache 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c77392ab481a7b315078ae0cbfd827c7fcd7b0840235f0f9c24d8c7443593b5" +"checksum string_cache 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7c8ba7515dd502b75080d989b819d31fb72686a82320d8006f665003c42ef79" "checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" diff --git a/servo/components/atoms/static_atoms.txt b/servo/components/atoms/static_atoms.txt index a41aae29289f..56d949eff5d9 100644 --- a/servo/components/atoms/static_atoms.txt +++ b/servo/components/atoms/static_atoms.txt @@ -68,3 +68,5 @@ fullscreenchange fullscreenerror gattserverdisconnected onchange + +reftest-wait diff --git a/servo/components/script/dom/element.rs b/servo/components/script/dom/element.rs index 957eadb53e42..ebeef51de892 100644 --- a/servo/components/script/dom/element.rs +++ b/servo/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,7 @@ impl RawLayoutElementHelpers for Element { pub trait LayoutElementHelpers { #[allow(unsafe_code)] - unsafe fn has_class_for_layout(&self, name: &Atom) -> bool; + 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 +374,9 @@ pub trait LayoutElementHelpers { impl LayoutElementHelpers for LayoutJS { #[allow(unsafe_code)] #[inline] - unsafe fn has_class_for_layout(&self, name: &Atom) -> bool { + 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 +1159,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 +2498,12 @@ impl<'a> ::selectors::Element for Root { } } - fn get_id(&self) -> Option { - 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) -> bool { - Element::has_class(&**self, name) + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { + Element::has_class(&**self, name, case_sensitivity) } fn is_html_element_in_html_document(&self) -> bool { diff --git a/servo/components/script/dom/htmlcollection.rs b/servo/components/script/dom/htmlcollection.rs index aff4125a29fb..814ea766f53a 100644 --- a/servo/components/script/dom/htmlcollection.rs +++ b/servo/components/script/dom/htmlcollection.rs @@ -11,7 +11,7 @@ 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}; @@ -199,7 +199,10 @@ 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 = document_from_node(elem) + .quirks_mode() + .classes_and_ids_case_sensitivity(); + self.classes.iter().all(|class| elem.has_class(class, case_sensitivity)) } } let filter = ClassNameFilter { diff --git a/servo/components/script/dom/window.rs b/servo/components/script/dom/window.rs index bcc5a749d33c..6e89024887ab 100644 --- a/servo/components/script/dom/window.rs +++ b/servo/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/servo/components/script/layout_wrapper.rs b/servo/components/script/layout_wrapper.rs index 916b9f5a3296..1d28242e926d 100644 --- a/servo/components/script/layout_wrapper.rs +++ b/servo/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 { + unsafe { + (*self.element.id_attribute()).clone() + } + } + #[inline(always)] fn each_class(&self, mut callback: F) where F: FnMut(&Atom) { unsafe { @@ -779,16 +787,18 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { } #[inline] - fn get_id(&self) -> Option { + 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) } } @@ -1249,12 +1259,12 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { false } - fn get_id(&self) -> Option { - 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/servo/components/selectors/attr.rs b/servo/components/selectors/attr.rs index 274081ae9f75..da208c94b1fd 100644 --- a/servo/components/selectors/attr.rs +++ b/servo/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/servo/components/selectors/context.rs b/servo/components/selectors/context.rs new file mode 100644 index 000000000000..c40c4b68ab4f --- /dev/null +++ b/servo/components/selectors/context.rs @@ -0,0 +1,152 @@ +/* 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::CaseSensitivity; +use bloom::BloomFilter; + +bitflags! { + /// Set of flags that determine the different kind of elements affected by + /// the selector matching process. + /// + /// This is used to implement efficient sharing. + #[derive(Default)] + pub flags StyleRelations: usize { + /// Whether this element is affected by presentational hints. This is + /// computed externally (that is, in Servo). + const AFFECTED_BY_PRESENTATIONAL_HINTS = 1 << 0, + /// Whether this element has pseudo-element styles. Computed externally. + const AFFECTED_BY_PSEUDO_ELEMENTS = 1 << 1, + } +} + +/// What kind of selector matching mode we should use. +/// +/// There are two modes of selector matching. The difference is only noticeable +/// in presence of pseudo-elements. +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum MatchingMode { + /// Don't ignore any pseudo-element selectors. + Normal, + + /// Ignores any stateless pseudo-element selectors in the rightmost sequence + /// of simple selectors. + /// + /// This is useful, for example, to match against ::before when you aren't a + /// pseudo-element yourself. + /// + /// For example, in presence of `::before:hover`, it would never match, but + /// `::before` would be ignored as in "matching". + /// + /// It's required for all the selectors you match using this mode to have a + /// pseudo-element. + ForStatelessPseudoElement, +} + +/// The mode to use when matching unvisited and visited links. +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum VisitedHandlingMode { + /// All links are matched as if they are unvisted. + AllLinksUnvisited, + /// A element's "relevant link" is the element being matched if it is a link + /// or the nearest ancestor link. The relevant link is matched as though it + /// is visited, and all other links are matched as if they are unvisited. + RelevantLinkVisited, +} + +/// Which quirks mode is this document in. +/// +/// See: https://quirks.spec.whatwg.org/ +#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)] +pub enum QuirksMode { + /// Quirks mode. + Quirks, + /// Limited quirks mode. + LimitedQuirks, + /// No quirks mode. + NoQuirks, +} + +impl QuirksMode { + #[inline] + pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity { + match self { + QuirksMode::NoQuirks | + QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive, + QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive, + } + } +} + +/// Data associated with the matching process for a element. This context is +/// used across many selectors for an element, so it's not appropriate for +/// transient data that applies to only a single selector. +#[derive(Clone)] +pub struct MatchingContext<'a> { + /// Output that records certains relations between elements noticed during + /// matching (and also extended after matching). + pub relations: StyleRelations, + /// Input with the matching mode we should use when matching selectors. + pub matching_mode: MatchingMode, + /// Input with the bloom filter used to fast-reject selectors. + pub bloom_filter: Option<&'a BloomFilter>, + /// Input that controls how matching for links is handled. + pub visited_handling: VisitedHandlingMode, + /// Output that records whether we encountered a "relevant link" while + /// matching _any_ selector for this element. (This differs from + /// `RelevantLinkStatus` which tracks the status for the _current_ selector + /// only.) + pub relevant_link_found: bool, + + quirks_mode: QuirksMode, + classes_and_ids_case_sensitivity: CaseSensitivity, +} + +impl<'a> MatchingContext<'a> { + /// Constructs a new `MatchingContext`. + pub fn new(matching_mode: MatchingMode, + bloom_filter: Option<&'a BloomFilter>, + quirks_mode: QuirksMode) + -> Self + { + Self { + relations: StyleRelations::empty(), + matching_mode: matching_mode, + bloom_filter: bloom_filter, + visited_handling: VisitedHandlingMode::AllLinksUnvisited, + relevant_link_found: false, + quirks_mode: quirks_mode, + classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), + } + } + + /// Constructs a new `MatchingContext` for use in visited matching. + pub fn new_for_visited(matching_mode: MatchingMode, + bloom_filter: Option<&'a BloomFilter>, + visited_handling: VisitedHandlingMode, + quirks_mode: QuirksMode) + -> Self + { + Self { + relations: StyleRelations::empty(), + matching_mode: matching_mode, + bloom_filter: bloom_filter, + visited_handling: visited_handling, + relevant_link_found: false, + quirks_mode: quirks_mode, + classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), + } + } + + /// The quirks mode of the document. + #[inline] + pub fn quirks_mode(&self) -> QuirksMode { + self.quirks_mode + } + + /// The case-sensitivity for class and ID selectors + #[inline] + pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity { + self.classes_and_ids_case_sensitivity + } +} diff --git a/servo/components/selectors/lib.rs b/servo/components/selectors/lib.rs index 130475271fc9..16a7949cfa9c 100644 --- a/servo/components/selectors/lib.rs +++ b/servo/components/selectors/lib.rs @@ -15,6 +15,7 @@ extern crate smallvec; pub mod attr; pub mod bloom; +pub mod context; pub mod matching; pub mod parser; #[cfg(test)] mod size_of_tests; diff --git a/servo/components/selectors/matching.rs b/servo/components/selectors/matching.rs index 7cc4735b09ce..b9f4377f38d7 100644 --- a/servo/components/selectors/matching.rs +++ b/servo/components/selectors/matching.rs @@ -9,26 +9,13 @@ use parser::{Selector, SelectorImpl, SelectorIter, SelectorList}; use std::borrow::Borrow; use tree::Element; +pub use context::*; + // The bloom filter for descendant CSS selectors will have a <1% false // positive rate until it has this many selectors in it, then it will // rapidly increase. pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096; -bitflags! { - /// Set of flags that determine the different kind of elements affected by - /// the selector matching process. - /// - /// This is used to implement efficient sharing. - #[derive(Default)] - pub flags StyleRelations: usize { - /// Whether this element is affected by presentational hints. This is - /// computed externally (that is, in Servo). - const AFFECTED_BY_PRESENTATIONAL_HINTS = 1 << 0, - /// Whether this element has pseudo-element styles. Computed externally. - const AFFECTED_BY_PSEUDO_ELEMENTS = 1 << 1, - } -} - bitflags! { /// Set of flags that are set on either the element or its parent (depending /// on the flag) if the element could potentially match a selector. @@ -66,111 +53,6 @@ impl ElementSelectorFlags { } } -/// What kind of selector matching mode we should use. -/// -/// There are two modes of selector matching. The difference is only noticeable -/// in presence of pseudo-elements. -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum MatchingMode { - /// Don't ignore any pseudo-element selectors. - Normal, - - /// Ignores any stateless pseudo-element selectors in the rightmost sequence - /// of simple selectors. - /// - /// This is useful, for example, to match against ::before when you aren't a - /// pseudo-element yourself. - /// - /// For example, in presence of `::before:hover`, it would never match, but - /// `::before` would be ignored as in "matching". - /// - /// It's required for all the selectors you match using this mode to have a - /// pseudo-element. - ForStatelessPseudoElement, -} - -/// The mode to use when matching unvisited and visited links. -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum VisitedHandlingMode { - /// All links are matched as if they are unvisted. - AllLinksUnvisited, - /// A element's "relevant link" is the element being matched if it is a link - /// or the nearest ancestor link. The relevant link is matched as though it - /// is visited, and all other links are matched as if they are unvisited. - RelevantLinkVisited, -} - -/// Which quirks mode is this document in. -/// -/// See: https://quirks.spec.whatwg.org/ -#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)] -pub enum QuirksMode { - /// Quirks mode. - Quirks, - /// Limited quirks mode. - LimitedQuirks, - /// No quirks mode. - NoQuirks, -} - -/// Data associated with the matching process for a element. This context is -/// used across many selectors for an element, so it's not appropriate for -/// transient data that applies to only a single selector. -#[derive(Clone)] -pub struct MatchingContext<'a> { - /// Output that records certains relations between elements noticed during - /// matching (and also extended after matching). - pub relations: StyleRelations, - /// Input with the matching mode we should use when matching selectors. - pub matching_mode: MatchingMode, - /// Input with the bloom filter used to fast-reject selectors. - pub bloom_filter: Option<&'a BloomFilter>, - /// Input that controls how matching for links is handled. - pub visited_handling: VisitedHandlingMode, - /// Output that records whether we encountered a "relevant link" while - /// matching _any_ selector for this element. (This differs from - /// `RelevantLinkStatus` which tracks the status for the _current_ selector - /// only.) - pub relevant_link_found: bool, - /// The quirks mode of the document. - pub quirks_mode: QuirksMode, -} - -impl<'a> MatchingContext<'a> { - /// Constructs a new `MatchingContext`. - pub fn new(matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - quirks_mode: QuirksMode) - -> Self - { - Self { - relations: StyleRelations::empty(), - matching_mode: matching_mode, - bloom_filter: bloom_filter, - visited_handling: VisitedHandlingMode::AllLinksUnvisited, - relevant_link_found: false, - quirks_mode: quirks_mode, - } - } - - /// Constructs a new `MatchingContext` for use in visited matching. - pub fn new_for_visited(matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - visited_handling: VisitedHandlingMode, - quirks_mode: QuirksMode) - -> Self - { - Self { - relations: StyleRelations::empty(), - matching_mode: matching_mode, - bloom_filter: bloom_filter, - visited_handling: visited_handling, - relevant_link_found: false, - quirks_mode: quirks_mode, - } - } -} - /// Holds per-element data alongside a pointer to MatchingContext. pub struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> { /// Shared `MatchingContext`. @@ -208,7 +90,7 @@ impl<'a, 'b, Impl> LocalMatchingContext<'a, 'b, Impl> /// Updates offset of Selector to show new compound selector. /// To be able to correctly re-synthesize main SelectorIter. pub fn note_next_sequence(&mut self, selector_iter: &SelectorIter) { - if let QuirksMode::Quirks = self.shared.quirks_mode { + if let QuirksMode::Quirks = self.shared.quirks_mode() { self.offset = self.selector.len() - selector_iter.selector_length(); } } @@ -216,7 +98,7 @@ impl<'a, 'b, Impl> LocalMatchingContext<'a, 'b, Impl> /// Returns true if current compound selector matches :active and :hover quirk. /// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk pub fn active_hover_quirk_matches(&mut self) -> bool { - if self.shared.quirks_mode != QuirksMode::Quirks || + if self.shared.quirks_mode() != QuirksMode::Quirks || self.within_functional_pseudo_class_argument { return false; } @@ -666,12 +548,11 @@ fn matches_simple_selector( let ns = ::parser::namespace_empty_string::(); 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) + element.has_id(id, context.shared.classes_and_ids_case_sensitivity()) } Component::Class(ref class) => { - element.has_class(class) + element.has_class(class, context.shared.classes_and_ids_case_sensitivity()) } Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => { let is_html = element.is_html_element_in_html_document(); diff --git a/servo/components/selectors/parser.rs b/servo/components/selectors/parser.rs index 612e89ba60ea..c2d64f18db19 100644 --- a/servo/components/selectors/parser.rs +++ b/servo/components/selectors/parser.rs @@ -1385,7 +1385,10 @@ fn parse_attribute_flags<'i, 't, E>(input: &mut CssParser<'i, 't>) -> Result>> { 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/servo/components/selectors/tree.rs b/servo/components/selectors/tree.rs index 7a47f2b4ab1e..99c07c6bf3b4 100644 --- a/servo/components/selectors/tree.rs +++ b/servo/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,15 @@ pub trait Element: Sized + Debug { /// Whether this element is a `link`. fn is_link(&self) -> bool; - fn get_id(&self) -> Option<::Identifier>; + fn has_id(&self, + id: &::Identifier, + case_sensitivity: CaseSensitivity) + -> bool; - fn has_class(&self, name: &::ClassName) -> bool; + fn has_class(&self, + name: &::ClassName, + case_sensitivity: CaseSensitivity) + -> bool; /// Returns whether this element matches `:empty`. /// diff --git a/servo/components/style/dom.rs b/servo/components/style/dom.rs index bbb4b279bf3a..5a6cc5ea980f 100644 --- a/servo/components/style/dom.rs +++ b/servo/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; + /// Internal iterator for the classes of this element. fn each_class(&self, callback: F) where F: FnMut(&Atom); diff --git a/servo/components/style/gecko/snapshot.rs b/servo/components/style/gecko/snapshot.rs index b2f7907a64a0..d0060d5b7c79 100644 --- a/servo/components/style/gecko/snapshot.rs +++ b/servo/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/servo/components/style/gecko/snapshot_helpers.rs b/servo/components/style/gecko/snapshot_helpers.rs index f7a0efa48c05..70af6f7082ab 100644 --- a/servo/components/style/gecko/snapshot_helpers.rs +++ b/servo/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 = unsafe extern fn (T, *mut *mut nsIAtom, *mut *mut /// element has the class that `name` represents. pub fn has_class(item: T, name: &Atom, + case_sensitivity: CaseSensitivity, getter: ClassOrClassList) -> bool { unsafe { @@ -24,10 +28,10 @@ pub fn has_class(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/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs index 64068a875f65..17be81fe1466 100644 --- a/servo/components/style/gecko/wrapper.rs +++ b/servo/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 { + 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(&self, callback: F) where F: FnMut(&Atom) { @@ -1574,30 +1592,30 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag()) } - fn get_id(&self) -> Option { + fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool { if !self.has_id() { - return None; + return false } - let ptr = unsafe { - bindings::Gecko_AtomAttrValue(self.0, - atom!("id").as_ptr()) - }; + unsafe { + let ptr = bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()); - if ptr.is_null() { - None - } else { - Some(Atom::from(ptr)) + if ptr.is_null() { + false + } else { + case_sensitivity.eq_atom(WeakAtom::new(ptr), 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/servo/components/style/gecko_string_cache/mod.rs b/servo/components/style/gecko_string_cache/mod.rs index 79c4ef021387..6e1abc57059b 100644 --- a/servo/components/style/gecko_string_cache/mod.rs +++ b/servo/components/style/gecko_string_cache/mod.rs @@ -11,7 +11,7 @@ use gecko_bindings::bindings::Gecko_Atomize; use gecko_bindings::bindings::Gecko_Atomize16; use gecko_bindings::bindings::Gecko_ReleaseAtom; use gecko_bindings::structs::nsIAtom; -use nsstring::nsAString; +use nsstring::{nsAString, nsString}; use precomputed_hash::PrecomputedHash; use std::ascii::AsciiExt; use std::borrow::{Cow, Borrow}; @@ -176,6 +176,54 @@ impl WeakAtom { let const_ptr: *const nsIAtom = &self.0; const_ptr as *mut nsIAtom } + + /// Convert this atom to ASCII lower-case + pub fn to_ascii_lowercase(&self) -> Atom { + let slice = self.as_slice(); + match slice.iter().position(|&char16| (b'A' as u16) <= char16 && char16 <= (b'Z' as u16)) { + None => self.clone(), + Some(i) => { + let mut buffer: [u16; 64] = unsafe { mem::uninitialized() }; + let mut vec; + let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) { + buffer_prefix.copy_from_slice(slice); + buffer_prefix + } else { + vec = slice.to_vec(); + &mut vec + }; + for char16 in &mut mutable_slice[i..] { + if *char16 <= 0x7F { + *char16 = (*char16 as u8).to_ascii_lowercase() as u16 + } + } + Atom::from(&*mutable_slice) + } + } + } + + /// 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 { @@ -233,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 { @@ -321,6 +346,13 @@ impl<'a> From<&'a str> for Atom { } } +impl<'a> From<&'a [u16]> for Atom { + #[inline] + fn from(slice: &[u16]) -> Atom { + Atom::from(&*nsString::from(slice)) + } +} + impl<'a> From<&'a nsAString> for Atom { #[inline] fn from(string: &nsAString) -> Atom { diff --git a/servo/components/style/invalidation/stylesheets.rs b/servo/components/style/invalidation/stylesheets.rs index 2991867cb3ba..6d36d16a2cda 100644 --- a/servo/components/style/invalidation/stylesheets.rs +++ b/servo/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/servo/components/style/lib.rs b/servo/components/style/lib.rs index 66cde1211d3a..7c5945e3d79f 100644 --- a/servo/components/style/lib.rs +++ b/servo/components/style/lib.rs @@ -217,3 +217,21 @@ pub fn serialize_comma_separated_list(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/servo/components/style/restyle_hints.rs b/servo/components/style/restyle_hints.rs index 350608e118bd..0bdfdd4039ba 100644 --- a/servo/components/style/restyle_hints.rs +++ b/servo/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,21 @@ impl<'a, E> Element for ElementWrapper<'a, E> } } - fn get_id(&self) -> Option { + 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 +1009,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) { + pub fn note_selector(&mut self, selector_and_hashes: &SelectorAndHashes, + quirks_mode: QuirksMode) { let mut combinator = None; let mut iter = selector_and_hashes.selector.iter(); let mut index = 0; @@ -1073,7 +1077,7 @@ impl DependencySet { selector: selector_and_hashes.selector.clone(), selector_offset: sequence_start, hashes: hashes, - }); + }, quirks_mode); } combinator = iter.next_sequence(); @@ -1149,7 +1153,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 +1176,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/servo/components/style/selector_map.rs b/servo/components/style/selector_map.rs index 6a5733877a2c..9788860ebaaf 100644 --- a/servo/components/style/selector_map.rs +++ b/servo/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 { #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct SelectorMap { /// A hash from an ID to rules which contain that ID selector. - pub id_hash: FnvHashMap>, + pub id_hash: MaybeCaseInsensitiveHashMap>, /// A hash from a class name to rules which contain that class selector. - pub class_hash: FnvHashMap>, + pub class_hash: MaybeCaseInsensitiveHashMap>, /// A hash from local name to rules which contain that local name selector. pub local_name_hash: FnvHashMap>, /// Rules that don't have ID, class, or element selectors. @@ -86,8 +87,8 @@ impl SelectorMap { /// 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_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 { // 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 { |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 { rules_list } - fn get_matching_rules_from_hash( - element: &E, - hash: &FnvHashMap>, - key: &BorrowedStr, - matching_rules: &mut Vector, - context: &mut MatchingContext, - flags_setter: &mut F, - cascade_level: CascadeLevel) - where E: TElement, - Str: Borrow + Eq + Hash, - BorrowedStr: Eq + Hash, - Vector: VecLike, - 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(element: &E, rules: &[Rule], @@ -247,16 +222,16 @@ impl SelectorMap { impl SelectorMap { /// 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 SelectorMap { /// FIXME(bholley) This overlaps with SelectorMap::get_all_matching_rules, /// but that function is extremely hot and I'd rather not rearrange it. #[inline] - pub fn lookup(&self, element: E, f: &mut F) -> bool + pub fn lookup(&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 SelectorMap { 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 SelectorMap { #[inline] pub fn lookup_with_additional(&self, element: E, + quirks_mode: QuirksMode, additional_id: Option, additional_classes: &[Atom], f: &mut F) @@ -363,13 +339,13 @@ impl SelectorMap { 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 SelectorMap { // 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(map: &mut FnvHashMap>, 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(FnvHashMap); + +impl MaybeCaseInsensitiveHashMap { + /// 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 { + 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/servo/components/style/servo/selector_parser.rs b/servo/components/style/servo/selector_parser.rs index 289884c28f3c..681a4c54374c 100644 --- a/servo/components/style/servo/selector_parser.rs +++ b/servo/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(&self, mut callback: F) diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index 91de0ad34874..1a2aa59096f3 100644 --- a/servo/components/style/stylist.rs +++ b/servo/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)); + 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.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/servo/tests/unit/style/restyle_hints.rs b/servo/tests/unit/style/restyle_hints.rs index 88b7c9a6f4bc..5b12c030db5e 100644 --- a/servo/tests/unit/style/restyle_hints.rs +++ b/servo/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/servo/tests/unit/style/stylist.rs b/servo/tests/unit/style/stylist.rs index ee4ac0b53f71..1a28e5ea772b 100644 --- a/servo/tests/unit/style/stylist.rs +++ b/servo/tests/unit/style/stylist.rs @@ -56,7 +56,7 @@ fn get_mock_map(selectors: &[&str]) -> (SelectorMap, 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]