aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock8
-rw-r--r--components/atoms/static_atoms.txt2
-rw-r--r--components/script/dom/element.rs31
-rw-r--r--components/script/dom/htmlcollection.rs7
-rw-r--r--components/script/dom/window.rs4
-rw-r--r--components/script/layout_wrapper.rs28
-rw-r--r--components/selectors/attr.rs4
-rw-r--r--components/selectors/context.rs152
-rw-r--r--components/selectors/lib.rs1
-rw-r--r--components/selectors/matching.rs131
-rw-r--r--components/selectors/parser.rs5
-rw-r--r--components/selectors/tree.rs12
-rw-r--r--components/style/dom.rs3
-rw-r--r--components/style/gecko/snapshot.rs3
-rw-r--r--components/style/gecko/snapshot_helpers.rs8
-rw-r--r--components/style/gecko/wrapper.rs40
-rw-r--r--components/style/gecko_string_cache/mod.rs80
-rw-r--r--components/style/invalidation/stylesheets.rs3
-rw-r--r--components/style/lib.rs18
-rw-r--r--components/style/restyle_hints.rs37
-rw-r--r--components/style/selector_map.rs135
-rw-r--r--components/style/servo/selector_parser.rs8
-rw-r--r--components/style/stylist.rs41
-rw-r--r--tests/unit/style/restyle_hints.rs4
-rw-r--r--tests/unit/style/stylist.rs12
25 files changed, 464 insertions, 313 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3b97eec46b6..85265495625 100644
--- a/Cargo.lock
+++ b/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/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..ebeef51de89 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,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<Element> {
#[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<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) -> 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/components/script/dom/htmlcollection.rs b/components/script/dom/htmlcollection.rs
index aff4125a29f..814ea766f53 100644
--- a/components/script/dom/htmlcollection.rs
+++ b/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/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..1d28242e926 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,18 @@ 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)
}
}
@@ -1249,12 +1259,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/context.rs b/components/selectors/context.rs
new file mode 100644
index 00000000000..c40c4b68ab4
--- /dev/null
+++ b/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/components/selectors/lib.rs b/components/selectors/lib.rs
index 130475271fc..16a7949cfa9 100644
--- a/components/selectors/lib.rs
+++ b/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/components/selectors/matching.rs b/components/selectors/matching.rs
index 7cc4735b09c..b9f4377f38d 100644
--- a/components/selectors/matching.rs
+++ b/components/selectors/matching.rs
@@ -9,27 +9,14 @@ 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.
pub flags ElementSelectorFlags: usize {
@@ -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<Impl>) {
- 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<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)
+ 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/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..99c07c6bf3b 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,15 @@ 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_id(&self,
+ id: &<Self::Impl as SelectorImpl>::Identifier,
+ case_sensitivity: CaseSensitivity)
+ -> bool;
- fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> 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..17be81fe146 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,30 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag())
}
- fn get_id(&self) -> Option<Atom> {
+ 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/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs
index 79c4ef02138..6e1abc57059 100644
--- a/components/style/gecko_string_cache/mod.rs
+++ b/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/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..0bdfdd4039b 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,21 @@ impl<'a, E> Element for ElementWrapper<'a, E>
}
}
- fn get_id(&self) -> Option<Atom> {
+ 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<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 +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/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]