diff options
-rw-r--r-- | components/layout_thread/dom_wrapper.rs | 9 | ||||
-rw-r--r-- | components/script/dom/element.rs | 4 | ||||
-rw-r--r-- | components/selectors/context.rs | 9 | ||||
-rw-r--r-- | components/selectors/lib.rs | 4 | ||||
-rw-r--r-- | components/selectors/matching.rs | 81 | ||||
-rw-r--r-- | components/selectors/nth_index_cache.rs | 56 | ||||
-rw-r--r-- | components/selectors/tree.rs | 18 | ||||
-rw-r--r-- | components/style/context.rs | 2 | ||||
-rw-r--r-- | components/style/gecko/wrapper.rs | 6 | ||||
-rw-r--r-- | components/style/invalidation/element/element_wrapper.rs | 6 | ||||
-rw-r--r-- | components/style/sharing/checks.rs | 2 | ||||
-rw-r--r-- | components/style/sharing/mod.rs | 2 | ||||
-rw-r--r-- | components/style/stylist.rs | 3 |
13 files changed, 163 insertions, 39 deletions
diff --git a/components/layout_thread/dom_wrapper.rs b/components/layout_thread/dom_wrapper.rs index a9596f099a2..962fe09a726 100644 --- a/components/layout_thread/dom_wrapper.rs +++ b/components/layout_thread/dom_wrapper.rs @@ -630,6 +630,10 @@ fn as_element<'le>(node: LayoutJS<Node>) -> Option<ServoLayoutElement<'le>> { impl<'le> ::selectors::Element for ServoLayoutElement<'le> { type Impl = SelectorImpl; + fn opaque(&self) -> ::selectors::OpaqueElement { + ::selectors::OpaqueElement::new(self.as_node().opaque().0 as *const ()) + } + fn parent_element(&self) -> Option<ServoLayoutElement<'le>> { unsafe { self.element.upcast().parent_node_ref().and_then(as_element) @@ -1168,6 +1172,11 @@ impl<'le> ThreadSafeLayoutElement for ServoThreadSafeLayoutElement<'le> { impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { type Impl = SelectorImpl; + fn opaque(&self) -> ::selectors::OpaqueElement { + ::selectors::OpaqueElement::new(self.as_node().opaque().0 as *const ()) + } + + fn parent_element(&self) -> Option<Self> { warn!("ServoThreadSafeLayoutElement::parent_element called"); None diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 76990dc9aa6..cad8c87900a 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -2500,6 +2500,10 @@ impl VirtualMethods for Element { impl<'a> ::selectors::Element for Root<Element> { type Impl = SelectorImpl; + fn opaque(&self) -> ::selectors::OpaqueElement { + ::selectors::OpaqueElement::new(self.reflector().get_jsobject().get()) + } + fn parent_element(&self) -> Option<Root<Element>> { self.upcast::<Node>().GetParentElement() } diff --git a/components/selectors/context.rs b/components/selectors/context.rs index 46991d349ce..f0da3b14ef6 100644 --- a/components/selectors/context.rs +++ b/components/selectors/context.rs @@ -4,6 +4,7 @@ use attr::CaseSensitivity; use bloom::BloomFilter; +use nth_index_cache::NthIndexCache; /// What kind of selector matching mode we should use. /// @@ -69,12 +70,6 @@ impl QuirksMode { } } -/// A cache to speed up matching of nth-index-like selectors. -/// -/// NB: This is just a dummy type right now, it will be fleshed out in later patches. -#[derive(Default)] -pub struct NthIndexCache(usize); - /// 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. @@ -84,7 +79,7 @@ pub struct MatchingContext<'a> { /// Input with the bloom filter used to fast-reject selectors. pub bloom_filter: Option<&'a BloomFilter>, /// An optional cache to speed up nth-index-like selectors. - nth_index_cache: Option<&'a mut NthIndexCache>, + pub nth_index_cache: Option<&'a mut NthIndexCache>, /// Input that controls how matching for links is handled. pub visited_handling: VisitedHandlingMode, /// Output that records whether we encountered a "relevant link" while diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index 2184dfc64e7..0805a53041f 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -24,6 +24,7 @@ pub mod bloom; mod builder; pub mod context; pub mod matching; +mod nth_index_cache; pub mod parser; #[cfg(test)] mod size_of_tests; #[cfg(any(test, feature = "gecko_like_types"))] pub mod gecko_like_types; @@ -31,5 +32,6 @@ pub mod sink; mod tree; pub mod visitor; +pub use nth_index_cache::NthIndexCache; pub use parser::{SelectorImpl, Parser, SelectorList}; -pub use tree::Element; +pub use tree::{Element, OpaqueElement}; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index afff6cd1e92..ff0dd40b484 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -4,6 +4,7 @@ use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint}; use bloom::{BLOOM_HASH_MASK, BloomFilter}; +use nth_index_cache::NthIndexCacheInner; use parser::{AncestorHashes, Combinator, Component, LocalName}; use parser::{Selector, SelectorImpl, SelectorIter, SelectorList}; use std::borrow::Borrow; @@ -787,7 +788,20 @@ fn matches_generic_nth_child<E, F>(element: &E, HAS_SLOW_SELECTOR_LATER_SIBLINGS }); - let index = nth_child_index(element, is_of_type, is_from_end); + // Grab a reference to the appropriate cache. + let mut cache = context.shared.nth_index_cache.as_mut().map(|c| { + c.get(is_of_type, is_from_end) + }); + + // Lookup or compute the index. + let index = if let Some(i) = cache.as_mut().and_then(|c| c.lookup(element.opaque())) { + i + } else { + let i = nth_child_index(element, is_of_type, is_from_end, cache.as_mut().map(|s| &mut **s)); + cache.as_mut().map(|c| c.insert(element.opaque(), i)); + i + }; + debug_assert_eq!(index, nth_child_index(element, is_of_type, is_from_end, None), "invalid cache"); // Is there a non-negative integer n such that An+B=index? match index.checked_sub(b) { @@ -800,40 +814,59 @@ fn matches_generic_nth_child<E, F>(element: &E, } #[inline] +fn same_type<E: Element>(a: &E, b: &E) -> bool { + a.get_local_name() == b.get_local_name() && + a.get_namespace() == b.get_namespace() +} + +#[inline] fn nth_child_index<E>( element: &E, is_of_type: bool, is_from_end: bool, + mut cache: Option<&mut NthIndexCacheInner>, ) -> i32 where E: Element, { - let mut index: i32 = 1; - let mut next_sibling = if is_from_end { - element.next_sibling_element() - } else { - element.prev_sibling_element() - }; - - loop { - let sibling = match next_sibling { - None => break, - Some(next_sibling) => next_sibling - }; + // The traversal mostly processes siblings left to right. So when we walk + // siblings to the right when computing NthLast/NthLastOfType we're unlikely + // to get cache hits along the way. As such, we take the hit of walking the + // siblings to the left checking the cache in the is_from_end case (this + // matches what Gecko does). The indices-from-the-left is handled during the + // regular look further below. + if let Some(ref mut c) = cache { + if is_from_end && !c.is_empty() { + let mut index: i32 = 1; + let mut curr = element.clone(); + while let Some(e) = curr.prev_sibling_element() { + curr = e; + if !is_of_type || same_type(element, &curr) { + if let Some(i) = c.lookup(curr.opaque()) { + return i - index; + } + index += 1; + } + } + } + } - if is_of_type { - if element.get_local_name() == sibling.get_local_name() && - element.get_namespace() == sibling.get_namespace() { - index += 1; + let mut index: i32 = 1; + let mut curr = element.clone(); + let next = |e: E| if is_from_end { e.next_sibling_element() } else { e.prev_sibling_element() }; + while let Some(e) = next(curr) { + curr = e; + if !is_of_type || same_type(element, &curr) { + // If we're computing indices from the left, check each element in the + // cache. We handle the indices-from-the-right case at the top of this + // function. + if !is_from_end { + if let Some(i) = cache.as_mut().and_then(|c| c.lookup(curr.opaque())) { + return i + index + } } - } else { - index += 1; + index += 1; } - next_sibling = if is_from_end { - sibling.next_sibling_element() - } else { - sibling.prev_sibling_element() - }; } index diff --git a/components/selectors/nth_index_cache.rs b/components/selectors/nth_index_cache.rs new file mode 100644 index 00000000000..ebae7a6033a --- /dev/null +++ b/components/selectors/nth_index_cache.rs @@ -0,0 +1,56 @@ +/* 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 fnv::FnvHashMap; +use tree::OpaqueElement; + +/// A cache to speed up matching of nth-index-like selectors. +/// +/// See [1] for some discussion around the design tradeoffs. +/// +/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1401855#c3 +#[derive(Default)] +pub struct NthIndexCache { + nth: NthIndexCacheInner, + nth_last: NthIndexCacheInner, + nth_of_type: NthIndexCacheInner, + nth_last_of_type: NthIndexCacheInner, +} + +impl NthIndexCache { + /// Gets the appropriate cache for the given parameters. + pub fn get( + &mut self, + is_of_type: bool, + is_from_end: bool + ) -> &mut NthIndexCacheInner { + match (is_of_type, is_from_end) { + (false, false) => &mut self.nth, + (false, true) => &mut self.nth_last, + (true, false) => &mut self.nth_of_type, + (true, true) => &mut self.nth_last_of_type, + } + } +} + +/// The concrete per-pseudo-class cache. +#[derive(Default)] +pub struct NthIndexCacheInner(FnvHashMap<OpaqueElement, i32>); + +impl NthIndexCacheInner { + /// Does a lookup for a given element in the cache. + pub fn lookup(&mut self, el: OpaqueElement) -> Option<i32> { + self.0.get(&el).map(|x| *x) + } + + /// Inserts an entry into the cache. + pub fn insert(&mut self, element: OpaqueElement, index: i32) { + self.0.insert(element, index); + } + + /// Returns whether the cache is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index 23238237b2f..958311ad0d8 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -8,11 +8,27 @@ use attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity}; use matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus}; use parser::SelectorImpl; +use servo_arc::NonZeroPtrMut; use std::fmt::Debug; -pub trait Element: Sized + Debug { +/// Opaque representation of an Element, for identity comparisons. We use +/// NonZeroPtrMut to get the NonZero optimization. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct OpaqueElement(pub NonZeroPtrMut<()>); + +impl OpaqueElement { + /// Creates a new OpaqueElement from an arbitrarily-typed pointer. + pub fn new<T>(ptr: *const T) -> Self { + OpaqueElement(NonZeroPtrMut::new(ptr as *const () as *mut ())) + } +} + +pub trait Element: Sized + Clone + Debug { type Impl: SelectorImpl; + /// Converts self into an opaque representation. + fn opaque(&self) -> OpaqueElement; + fn parent_element(&self) -> Option<Self>; /// The parent of a given pseudo-element, after matching a pseudo-element diff --git a/components/style/context.rs b/components/style/context.rs index ff2551c0b54..dadd80d343b 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -23,7 +23,7 @@ use properties::ComputedValues; use rule_cache::RuleCache; use rule_tree::StrongRuleNode; use selector_parser::{EAGER_PSEUDO_COUNT, SnapshotMap}; -use selectors::context::NthIndexCache; +use selectors::NthIndexCache; use selectors::matching::ElementSelectorFlags; use servo_arc::Arc; #[cfg(feature = "servo")] use servo_atoms::Atom; diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 641aafbb31a..655b6759ca0 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -76,7 +76,7 @@ use properties::animated_properties::TransitionProperty; use properties::style_structs::Font; use rule_tree::CascadeLevel as ServoCascadeLevel; use selector_parser::{AttrValue, ElementExt, PseudoClassStringArg}; -use selectors::Element; +use selectors::{Element, OpaqueElement}; use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint}; use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext}; use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode}; @@ -1682,6 +1682,10 @@ impl<'le> PresentationalHintsSynthesizer for GeckoElement<'le> { impl<'le> ::selectors::Element for GeckoElement<'le> { type Impl = SelectorImpl; + fn opaque(&self) -> OpaqueElement { + OpaqueElement::new(self.0) + } + fn parent_element(&self) -> Option<Self> { // FIXME(emilio): This will need to jump across if the parent node is a // shadow root to get the shadow host. diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index 2bb2c3857dc..705da44840c 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -9,7 +9,7 @@ use {Atom, CaseSensitivityExt, LocalName, Namespace}; use dom::TElement; use element_state::ElementState; use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue}; -use selectors::Element; +use selectors::{Element, OpaqueElement}; use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext}; use selectors::matching::RelevantLinkStatus; @@ -258,6 +258,10 @@ impl<'a, E> Element for ElementWrapper<'a, E> self.element.is_link() } + fn opaque(&self) -> OpaqueElement { + self.element.opaque() + } + fn parent_element(&self) -> Option<Self> { self.element.parent_element() .map(|e| ElementWrapper::new(e, self.snapshot_map)) diff --git a/components/style/sharing/checks.rs b/components/style/sharing/checks.rs index 2120bbc6f8c..20020b9497b 100644 --- a/components/style/sharing/checks.rs +++ b/components/style/sharing/checks.rs @@ -10,7 +10,7 @@ use Atom; use bloom::StyleBloom; use context::{SelectorFlagsMap, SharedStyleContext}; use dom::TElement; -use selectors::context::NthIndexCache; +use selectors::NthIndexCache; use sharing::{StyleSharingCandidate, StyleSharingTarget}; /// Determines whether a target and a candidate have compatible parents for sharing. diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 8e2c9763b1d..e99da60f4b9 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -75,7 +75,7 @@ use matching::MatchMethods; use owning_ref::OwningHandle; use properties::ComputedValues; use rule_tree::StrongRuleNode; -use selectors::context::NthIndexCache; +use selectors::NthIndexCache; use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; use servo_arc::{Arc, NonZeroPtrMut}; use smallbitvec::SmallBitVec; diff --git a/components/style/stylist.rs b/components/style/stylist.rs index b398ff851bc..5962117fbad 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -28,9 +28,10 @@ use properties::IS_LINK; use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; use selector_map::{PrecomputedHashMap, SelectorMap, SelectorMapEntry}; use selector_parser::{SelectorImpl, PerPseudoElementMap, PseudoElement}; +use selectors::NthIndexCache; use selectors::attr::NamespaceConstraint; use selectors::bloom::{BloomFilter, NonCountingBloomFilter}; -use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode, NthIndexCache}; +use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; use selectors::matching::VisitedHandlingMode; use selectors::parser::{AncestorHashes, Combinator, Component, Selector}; use selectors::parser::{SelectorIter, SelectorMethods}; |