diff options
-rw-r--r-- | Cargo.lock | 8 | ||||
-rw-r--r-- | components/layout/block.rs | 1 | ||||
-rw-r--r-- | components/layout/construct.rs | 13 | ||||
-rw-r--r-- | components/layout/css/matching.rs | 104 | ||||
-rw-r--r-- | components/layout/layout_task.rs | 18 | ||||
-rw-r--r-- | components/layout/parallel.rs | 133 | ||||
-rw-r--r-- | components/layout/wrapper.rs | 6 | ||||
-rw-r--r-- | components/script/dom/element.rs | 2 | ||||
-rw-r--r-- | components/script/dom/node.rs | 10 | ||||
-rw-r--r-- | components/style/lib.rs | 4 | ||||
-rw-r--r-- | components/style/node.rs | 3 | ||||
-rw-r--r-- | components/style/selector_matching.rs | 122 | ||||
-rw-r--r-- | components/style/selectors.rs | 10 | ||||
-rw-r--r-- | components/util/bloom.rs | 398 | ||||
-rw-r--r-- | components/util/fnv.rs | 45 | ||||
-rw-r--r-- | components/util/lib.rs | 4 | ||||
-rw-r--r-- | components/util/namespace.rs | 2 | ||||
-rw-r--r-- | components/util/time.rs | 2 | ||||
-rw-r--r-- | components/util/workqueue.rs | 1 | ||||
-rw-r--r-- | ports/cef/Cargo.lock | 9 |
20 files changed, 817 insertions, 78 deletions
diff --git a/Cargo.lock b/Cargo.lock index b79a663c2f8..57f5b658e50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,17 +409,17 @@ source = "git+https://github.com/servo/rust-stb-image#f5022de4ad6bb474a03493d1f2 [[package]] name = "string_cache" version = "0.0.0" -source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a" +source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289" dependencies = [ "phf 0.0.0 (git+https://github.com/sfackler/rust-phf#fa5d803428dd760287330571c919c7c5e11b2e1b)", "phf_mac 0.0.0 (git+https://github.com/sfackler/rust-phf#fa5d803428dd760287330571c919c7c5e11b2e1b)", - "string_cache_macros 0.0.0 (git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a)", + "string_cache_macros 0.0.0 (git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289)", ] [[package]] name = "string_cache_macros" version = "0.0.0" -source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a" +source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289" [[package]] name = "style" @@ -451,7 +451,7 @@ version = "0.0.1" dependencies = [ "azure 0.1.0 (git+https://github.com/servo/rust-azure#9c5567b79d8b87e8ef3b48c5842f453978035d21)", "geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)", - "string_cache 0.0.0 (git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a)", + "string_cache 0.0.0 (git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289)", "task_info 0.0.1", ] diff --git a/components/layout/block.rs b/components/layout/block.rs index 4a5395b7582..26564e9f84a 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -2449,4 +2449,3 @@ fn propagate_column_inline_sizes_to_child(kid: &mut Flow, kid_base.position.start.i = *inline_start_margin_edge; kid_base.position.size.inline = inline_size; } - diff --git a/components/layout/construct.rs b/components/layout/construct.rs index a1631a5858d..05231a091e4 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -186,15 +186,15 @@ enum WhitespaceStrippingMode { } /// An object that knows how to create flows. -pub struct FlowConstructor<'a, 'b> { +pub struct FlowConstructor<'a> { /// The layout context. - pub layout_context: &'b LayoutContext<'b>, + pub layout_context: &'a LayoutContext<'a>, } -impl<'a, 'b> FlowConstructor<'a, 'b> { +impl<'a> FlowConstructor<'a> { /// Creates a new flow constructor. - pub fn new<'b>(layout_context: &'b LayoutContext) - -> FlowConstructor<'a, 'b> { + pub fn new<'a>(layout_context: &'a LayoutContext<'a>) + -> FlowConstructor<'a> { FlowConstructor { layout_context: layout_context, } @@ -826,7 +826,7 @@ impl<'a, 'b> FlowConstructor<'a, 'b> { } } -impl<'a, 'b> PostorderNodeMutTraversal for FlowConstructor<'a, 'b> { +impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { // Construct Flow based on 'display', 'position', and 'float' values. // // CSS 2.1 Section 9.7 @@ -1109,4 +1109,3 @@ impl FlowConstructionUtils for FlowRef { } } } - diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs index c1d766c32ca..7abc683f4ad 100644 --- a/components/layout/css/matching.rs +++ b/components/layout/css/matching.rs @@ -12,6 +12,7 @@ use util::{LayoutDataAccess, LayoutDataWrapper}; use wrapper::{LayoutElement, LayoutNode, PostorderNodeMutTraversal, ThreadSafeLayoutNode}; use servo_util::atom::Atom; +use servo_util::bloom::BloomFilter; use servo_util::cache::{Cache, LRUCache, SimpleHashCache}; use servo_util::namespace::Null; use servo_util::smallvec::{SmallVec, SmallVec16}; @@ -19,7 +20,9 @@ use servo_util::str::DOMString; use std::mem; use std::hash::{Hash, sip}; use std::slice::Items; -use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode, cascade}; +use style; +use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode}; +use style::cascade; use sync::Arc; pub struct ApplicableDeclarations { @@ -211,16 +214,14 @@ impl StyleSharingCandidate { } }; - let mut style = Some(style); - let mut parent_style = Some(parent_style); let element = node.as_element(); if element.style_attribute().is_some() { return None } Some(StyleSharingCandidate { - style: style.take_unwrap(), - parent_style: parent_style.take_unwrap(), + style: style, + parent_style: parent_style, local_name: element.get_local_name().clone(), class: element.get_attr(&Null, "class") .map(|string| string.to_string()), @@ -278,16 +279,31 @@ pub enum StyleSharingResult<'ln> { } pub trait MatchMethods { + /// Inserts and removes the matching `Descendant` selectors from a bloom + /// filter. This is used to speed up CSS selector matching to remove + /// unnecessary tree climbs for `Descendant` queries. + /// + /// A bloom filter of the local names, namespaces, IDs, and classes is kept. + /// Therefore, each node must have its matching selectors inserted _after_ + /// its own selector matching and _before_ its children start. + fn insert_into_bloom_filter(&self, bf: &mut BloomFilter); + + /// After all the children are done css selector matching, this must be + /// called to reset the bloom filter after an `insert`. + fn remove_from_bloom_filter(&self, bf: &mut BloomFilter); + /// Performs aux initialization, selector matching, cascading, and flow construction /// sequentially. fn recalc_style_for_subtree(&self, stylist: &Stylist, layout_context: &LayoutContext, + parent_bf: &mut Option<BloomFilter>, applicable_declarations: &mut ApplicableDeclarations, parent: Option<LayoutNode>); fn match_node(&self, stylist: &Stylist, + parent_bf: &Option<BloomFilter>, applicable_declarations: &mut ApplicableDeclarations, shareable: &mut bool); @@ -403,20 +419,24 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> { impl<'ln> MatchMethods for LayoutNode<'ln> { fn match_node(&self, stylist: &Stylist, + parent_bf: &Option<BloomFilter>, applicable_declarations: &mut ApplicableDeclarations, shareable: &mut bool) { let style_attribute = self.as_element().style_attribute().as_ref(); applicable_declarations.normal_shareable = stylist.push_applicable_declarations(self, + parent_bf, style_attribute, None, &mut applicable_declarations.normal); stylist.push_applicable_declarations(self, + parent_bf, None, Some(Before), &mut applicable_declarations.before); stylist.push_applicable_declarations(self, + parent_bf, None, Some(After), &mut applicable_declarations.after); @@ -455,9 +475,63 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { CannotShare(true) } + // The below two functions are copy+paste because I can't figure out how to + // write a function which takes a generic function. I don't think it can + // be done. + // + // Ideally, I'd want something like: + // + // > fn with_really_simple_selectors(&self, f: <H: Hash>|&H|); + + + // In terms of `SimpleSelector`s, these two functions will insert and remove: + // - `LocalNameSelector` + // - `NamepaceSelector` + // - `IDSelector` + // - `ClassSelector` + + fn insert_into_bloom_filter(&self, bf: &mut BloomFilter) { + // Only elements are interesting. + if !self.is_element() { return; } + let element = self.as_element(); + + bf.insert(element.get_local_name()); + bf.insert(element.get_namespace()); + element.get_id().map(|id| bf.insert(&id)); + + // TODO: case-sensitivity depends on the document type and quirks mode + element + .get_attr(&Null, "class") + .map(|attr| { + for c in attr.split(style::SELECTOR_WHITESPACE) { + bf.insert(&c); + } + }); + } + + fn remove_from_bloom_filter(&self, bf: &mut BloomFilter) { + // Only elements are interesting. + if !self.is_element() { return; } + let element = self.as_element(); + + bf.remove(element.get_local_name()); + bf.remove(element.get_namespace()); + element.get_id().map(|id| bf.remove(&id)); + + // TODO: case-sensitivity depends on the document type and quirks mode + element + .get_attr(&Null, "class") + .map(|attr| { + for c in attr.split(style::SELECTOR_WHITESPACE) { + bf.remove(&c); + } + }); + } + fn recalc_style_for_subtree(&self, stylist: &Stylist, layout_context: &LayoutContext, + parent_bf: &mut Option<BloomFilter>, applicable_declarations: &mut ApplicableDeclarations, parent: Option<LayoutNode>) { self.initialize_layout_data(layout_context.shared.layout_chan.clone()); @@ -471,7 +545,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { match sharing_result { CannotShare(mut shareable) => { if self.is_element() { - self.match_node(stylist, applicable_declarations, &mut shareable) + self.match_node(stylist, &*parent_bf, applicable_declarations, &mut shareable); } unsafe { @@ -490,11 +564,22 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { StyleWasShared(index) => layout_context.style_sharing_candidate_cache().touch(index), } + match *parent_bf { + None => {}, + Some(ref mut pbf) => self.insert_into_bloom_filter(pbf), + } + for kid in self.children() { kid.recalc_style_for_subtree(stylist, - layout_context, - applicable_declarations, - Some(self.clone())) + layout_context, + parent_bf, + applicable_declarations, + Some(self.clone())); + } + + match *parent_bf { + None => {}, + Some(ref mut pbf) => self.remove_from_bloom_filter(pbf), } // Construct flows. @@ -555,4 +640,3 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { } } } - diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 62c6781557c..36db0d8c44e 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -45,6 +45,7 @@ use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, Failu use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use gfx::font_cache_task::{FontCacheTask}; use servo_net::local_image_cache::{ImageResponder, LocalImageCache}; +use servo_util::bloom::BloomFilter; use servo_util::geometry::Au; use servo_util::geometry; use servo_util::logical_geometry::LogicalPoint; @@ -57,6 +58,7 @@ use servo_util::workqueue::WorkQueue; use std::comm::{channel, Sender, Receiver, Select}; use std::mem; use std::ptr; +use style; use style::{AuthorOrigin, Stylesheet, Stylist}; use style::iter_font_face_rules; use sync::{Arc, Mutex, MutexGuard}; @@ -409,7 +411,12 @@ impl LayoutTask { } // Create a layout context for use in building display lists, hit testing, &c. - fn build_shared_layout_context(&self, rw_data: &LayoutTaskData, reflow_root: &LayoutNode, url: &Url) -> SharedLayoutContext { + fn build_shared_layout_context( + &self, + rw_data: &LayoutTaskData, + reflow_root: &LayoutNode, + url: &Url) + -> SharedLayoutContext { SharedLayoutContext { image_cache: rw_data.local_image_cache.clone(), screen_size: rw_data.screen_size.clone(), @@ -718,7 +725,11 @@ impl LayoutTask { rw_data.screen_size = current_screen_size; // Create a layout context for use throughout the following passes. - let mut shared_layout_ctx = self.build_shared_layout_context(rw_data.deref(), node, &data.url); + let mut shared_layout_ctx = + self.build_shared_layout_context( + rw_data.deref(), + node, + &data.url); let mut layout_root = profile(time::LayoutStyleRecalcCategory, self.time_profiler_chan.clone(), @@ -729,8 +740,11 @@ impl LayoutTask { None => { let layout_ctx = LayoutContext::new(&shared_layout_ctx); let mut applicable_declarations = ApplicableDeclarations::new(); + let mut parent_bf = Some(BloomFilter::new( + style::RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE)); node.recalc_style_for_subtree(&*rw_data.stylist, &layout_ctx, + &mut parent_bf, &mut applicable_declarations, None) } diff --git a/components/layout/parallel.rs b/components/layout/parallel.rs index a2786e8ba91..fbf85053a4f 100644 --- a/components/layout/parallel.rs +++ b/components/layout/parallel.rs @@ -20,12 +20,14 @@ use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_ use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode}; use gfx::display_list::OpaqueNode; +use servo_util::bloom::BloomFilter; use servo_util::time::{TimeProfilerChan, profile}; use servo_util::time; use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy}; use std::mem; use std::ptr; use std::sync::atomics::{AtomicInt, Relaxed, SeqCst}; +use style; use style::TNode; #[allow(dead_code)] @@ -213,7 +215,91 @@ impl<'a> ParallelPreorderFlowTraversal for AssignISizesTraversal<'a> { impl<'a> ParallelPostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {} -fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, +/// A pair of the bloom filter used for css selector matching, and the node to +/// which it applies. This is used to efficiently do `Descendant` selector +/// matches. Thanks to the bloom filter, we can avoid walking up the tree +/// looking for ancestors that aren't there in the majority of cases. +/// +/// As we walk down the DOM tree a task-local bloom filter is built of all the +/// CSS `SimpleSelector`s which are part of a `Descendant` compound selector +/// (i.e. paired with a `Descendant` combinator, in the `next` field of a +/// `CompoundSelector`. +/// +/// Before a `Descendant` selector match is tried, it's compared against the +/// bloom filter. If the bloom filter can exclude it, the selector is quickly +/// rejected. +/// +/// When done styling a node, all selectors previously inserted into the filter +/// are removed. +/// +/// Since a work-stealing queue is used for styling, sometimes, the bloom filter +/// will no longer be the for the parent of the node we're currently on. When +/// this happens, the task local bloom filter will be thrown away and rebuilt. +local_data_key!(style_bloom: (BloomFilter, UnsafeLayoutNode)) + +/// Returns the task local bloom filter. +/// +/// If one does not exist, a new one will be made for you. If it is out of date, +/// it will be thrown out and a new one will be made for you. +fn take_task_local_bloom_filter( + parent_node: Option<LayoutNode>, + layout_context: &LayoutContext) + -> BloomFilter { + + let new_bloom = + |p: Option<LayoutNode>| -> BloomFilter { + let mut bf = BloomFilter::new(style::RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE); + p.map(|p| insert_ancestors_into_bloom_filter(&mut bf, p, layout_context)); + bf + }; + + match (parent_node, style_bloom.replace(None)) { + // Root node. Needs new bloom filter. + (None, _ ) => new_bloom(None), + // No bloom filter for this thread yet. + (Some(p), None) => new_bloom(Some(p)), + // Found cached bloom filter. + (Some(p), Some((bf, old_node))) => { + // Hey, the cached parent is our parent! We can reuse the bloom filter. + if old_node == layout_node_to_unsafe_layout_node(&p) { + bf + // Oh no. the cached parent is stale. I guess we need a new one... + } else { + new_bloom(Some(p)) + } + }, + } +} + +fn put_task_local_bloom_filter(bf: BloomFilter, unsafe_node: &UnsafeLayoutNode) { + match style_bloom.replace(Some((bf, *unsafe_node))) { + None => {}, + Some(_) => fail!("Putting into a never-taken task-local bloom filter"), + } +} + +/// "Ancestors" in this context is inclusive of ourselves. +fn insert_ancestors_into_bloom_filter( + bf: &mut BloomFilter, mut n: LayoutNode, layout_context: &LayoutContext) { + loop { + n.insert_into_bloom_filter(bf); + n = match parent_node(&n, layout_context) { + None => return, + Some(p) => p, + }; + } +} + +fn parent_node<'ln>(node: &LayoutNode<'ln>, layout_context: &LayoutContext) -> Option<LayoutNode<'ln>> { + let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(node); + if opaque_node == layout_context.shared.reflow_root { + None + } else { + node.parent_node() + } +} + +fn recalc_style_for_node(mut unsafe_layout_node: UnsafeLayoutNode, proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) { let shared_layout_context = unsafe { &**proxy.user_data() }; let layout_context = LayoutContext::new(shared_layout_context); @@ -230,12 +316,10 @@ fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, node.initialize_layout_data(layout_context.shared.layout_chan.clone()); // Get the parent node. - let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node); - let parent_opt = if opaque_node == layout_context.shared.reflow_root { - None - } else { - node.parent_node() - }; + let parent_opt = parent_node(&node, &layout_context); + + // Get the style bloom filter. + let bf = take_task_local_bloom_filter(parent_opt, &layout_context); // First, check to see whether we can share a style with someone. let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache(); @@ -244,6 +328,9 @@ fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, parent_opt.clone()) }; + // Just needs to be wrapped in an option for `match_node`. + let some_bf = Some(bf); + // Otherwise, match and cascade selectors. match sharing_result { CannotShare(mut shareable) => { @@ -252,7 +339,7 @@ fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, if node.is_element() { // Perform the CSS selector matching. let stylist = unsafe { &*layout_context.shared.stylist }; - node.match_node(stylist, &mut applicable_declarations, &mut shareable); + node.match_node(stylist, &some_bf, &mut applicable_declarations, &mut shareable); } // Perform the CSS cascade. @@ -285,6 +372,13 @@ fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, } } + // It can be `None` now. + let mut bf = some_bf; + + // Before running the children, we need to insert our nodes into the bloom + // filter. + bf.as_mut().map(|bf| node.insert_into_bloom_filter(bf)); + // It's *very* important that this block is in a separate scope to the block above, // to avoid a data race that can occur (github issue #2308). The block above issues // a borrow on the node layout data. That borrow must be dropped before the child @@ -299,19 +393,21 @@ fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, data: layout_node_to_unsafe_layout_node(&kid), }); } - return + } else { + // If we got here, we're a leaf. Start construction of flows for this node. + construct_flows(&mut unsafe_layout_node, &mut bf, &layout_context); } - // If we got here, we're a leaf. Start construction of flows for this node. - construct_flows(unsafe_layout_node, &layout_context) + bf.map(|bf| put_task_local_bloom_filter(bf, &unsafe_layout_node)); } -fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode, - layout_context: &LayoutContext) { +fn construct_flows<'a>(unsafe_layout_node: &mut UnsafeLayoutNode, + parent_bf: &mut Option<BloomFilter>, + layout_context: &'a LayoutContext<'a>) { loop { // Get a real layout node. let node: LayoutNode = unsafe { - layout_node_from_unsafe_layout_node(&unsafe_layout_node) + layout_node_from_unsafe_layout_node(&*unsafe_layout_node) }; // Construct flows for this node. @@ -340,7 +436,10 @@ fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode, // If this is the reflow root, we're done. let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node); if layout_context.shared.reflow_root == opaque_node { - break + *parent_bf = None; + break; + } else { + parent_bf.as_mut().map(|parent_bf| node.remove_from_bloom_filter(parent_bf)); } // Otherwise, enqueue the parent. @@ -352,6 +451,8 @@ fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode, unsafe { match *parent.borrow_layout_data_unchecked() { Some(ref parent_layout_data) => { + *unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent); + let parent_layout_data: &mut LayoutDataWrapper = mem::transmute(parent_layout_data); if parent_layout_data.data .parallel @@ -359,7 +460,6 @@ fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode, .fetch_sub(1, SeqCst) == 1 { // We were the last child of our parent. Construct flows for our // parent. - unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent) } else { // Get out of here and find another node to work on. break @@ -558,4 +658,3 @@ pub fn build_display_list_for_subtree(root: &mut FlowRef, queue.data = ptr::null() } - diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 8f948a9a128..bdabc3b5840 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -231,6 +231,12 @@ impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> { } } + fn tnode_first_child(&self) -> Option<LayoutNode<'ln>> { + unsafe { + self.node.first_child_ref().map(|node| self.new_with_this_lifetime(&node)) + } + } + fn prev_sibling(&self) -> Option<LayoutNode<'ln>> { unsafe { self.node.prev_sibling_ref().map(|node| self.new_with_this_lifetime(&node)) diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 2150e67f0eb..b0aad2f9bbf 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -801,7 +801,7 @@ impl<'a> ElementMethods for JSRef<'a, Element> { Err(()) => Err(Syntax), Ok(ref selectors) => { let root: &JSRef<Node> = NodeCast::from_ref(self); - Ok(matches(selectors, root)) + Ok(matches(selectors, root, &mut None)) } } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index f419798a110..ee173eee1f2 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -610,7 +610,7 @@ impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> { Ok(ref selectors) => { let root = self.ancestors().last().unwrap_or(self.clone()); for node in root.traverse_preorder() { - if node.is_element() && matches(selectors, &node) { + if node.is_element() && matches(selectors, &node, &mut None) { let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap(); return Ok(Some(Temporary::from_rooted(elem))); } @@ -631,7 +631,9 @@ impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> { // Step 3. Ok(ref selectors) => { nodes = root.traverse_preorder().filter( - |node| node.is_element() && matches(selectors, node)).collect() + // TODO(cgaebel): Is it worth it to build a bloom filter here + // (instead of passing `None`)? Probably. + |node| node.is_element() && matches(selectors, node, &mut None)).collect() } } let window = window_from_node(self).root(); @@ -1988,6 +1990,10 @@ impl<'a> style::TNode<JSRef<'a, Element>> for JSRef<'a, Node> { (self as &NodeHelpers).parent_node().map(|node| *node.root()) } + fn tnode_first_child(&self) -> Option<JSRef<'a, Node>> { + (self as &NodeHelpers).first_child().map(|node| *node.root()) + } + fn prev_sibling(&self) -> Option<JSRef<'a, Node>> { (self as &NodeHelpers).prev_sibling().map(|node| *node.root()) } diff --git a/components/style/lib.rs b/components/style/lib.rs index 869905d4a3f..107889cb57a 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -31,7 +31,8 @@ extern crate servo_util = "util"; // Public API pub use stylesheets::{Stylesheet, iter_font_face_rules}; pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin}; -pub use selector_matching::{DeclarationBlock, matches}; +pub use selector_matching::{DeclarationBlock, matches,matches_simple_selector}; +pub use selector_matching::{RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE,SELECTOR_WHITESPACE}; pub use properties::{cascade, cascade_anonymous}; pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs}; pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes @@ -40,6 +41,7 @@ pub use properties::longhands; pub use node::{TElement, TNode}; pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str}; pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace}; +pub use selectors::{SimpleSelector,LocalNameSelector}; pub use cssparser::{Color, RGBA}; mod stylesheets; diff --git a/components/style/node.rs b/components/style/node.rs index cdd7cabf8e7..bd71aa6fd79 100644 --- a/components/style/node.rs +++ b/components/style/node.rs @@ -12,6 +12,8 @@ use servo_util::namespace::Namespace; pub trait TNode<E:TElement> : Clone { fn parent_node(&self) -> Option<Self>; + /// Name is prefixed to avoid a conflict with TLayoutNode. + fn tnode_first_child(&self) -> Option<Self>; fn prev_sibling(&self) -> Option<Self>; fn next_sibling(&self) -> Option<Self>; fn is_document(&self) -> bool; @@ -32,4 +34,3 @@ pub trait TElement { fn get_enabled_state(&self) -> bool; fn has_class(&self, name: &str) -> bool; } - diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 766e2b8bce1..a0ddf7dd31d 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -10,6 +10,7 @@ use sync::Arc; use url::Url; use servo_util::atom::Atom; +use servo_util::bloom::BloomFilter; use servo_util::namespace; use servo_util::smallvec::VecLike; use servo_util::sort; @@ -27,7 +28,7 @@ pub enum StylesheetOrigin { } /// The definition of whitespace per CSS Selectors Level 3 § 4. -static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C']; +pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C']; /// Map node attributes to Rules whose last simple selector starts with them. /// @@ -83,6 +84,7 @@ impl SelectorMap { V:VecLike<DeclarationBlock>>( &self, node: &N, + parent_bf: &Option<BloomFilter>, matching_rules_list: &mut V, shareable: &mut bool) { if self.empty { @@ -95,6 +97,7 @@ impl SelectorMap { match element.get_id() { Some(id) => { SelectorMap::get_matching_rules_from_hash(node, + parent_bf, &self.id_hash, &id, matching_rules_list, @@ -108,10 +111,11 @@ impl SelectorMap { // FIXME: Store classes pre-split as atoms to make the loop below faster. for class in class_attr.split(SELECTOR_WHITESPACE) { SelectorMap::get_matching_rules_from_hash(node, - &self.class_hash, - &Atom::from_slice(class), - matching_rules_list, - shareable); + parent_bf, + &self.class_hash, + &Atom::from_slice(class), + matching_rules_list, + shareable); } } None => {} @@ -123,12 +127,14 @@ impl SelectorMap { &self.local_name_hash }; SelectorMap::get_matching_rules_from_hash(node, + parent_bf, local_name_hash, element.get_local_name(), matching_rules_list, shareable); SelectorMap::get_matching_rules(node, + parent_bf, self.universal_rules.as_slice(), matching_rules_list, shareable); @@ -145,13 +151,14 @@ impl SelectorMap { N:TNode<E>, V:VecLike<DeclarationBlock>>( node: &N, + parent_bf: &Option<BloomFilter>, hash: &HashMap<Atom, Vec<Rule>>, key: &Atom, matching_rules: &mut V, shareable: &mut bool) { match hash.find(key) { Some(rules) => { - SelectorMap::get_matching_rules(node, rules.as_slice(), matching_rules, shareable) + SelectorMap::get_matching_rules(node, parent_bf, rules.as_slice(), matching_rules, shareable) } None => {} } @@ -162,11 +169,12 @@ impl SelectorMap { N:TNode<E>, V:VecLike<DeclarationBlock>>( node: &N, + parent_bf: &Option<BloomFilter>, rules: &[Rule], matching_rules: &mut V, shareable: &mut bool) { for rule in rules.iter() { - if matches_compound_selector(&*rule.selector, node, shareable) { + if matches_compound_selector(&*rule.selector, node, parent_bf, shareable) { matching_rules.vec_push(rule.declarations.clone()); } } @@ -247,6 +255,11 @@ impl SelectorMap { } } +// 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: uint = 4096; + pub struct Stylist { element_map: PerPseudoElementSelectorMap, before_map: PerPseudoElementSelectorMap, @@ -336,6 +349,7 @@ impl Stylist { V:VecLike<DeclarationBlock>>( &self, element: &N, + parent_bf: &Option<BloomFilter>, style_attribute: Option<&PropertyDeclarationBlock>, pseudo_element: Option<PseudoElement>, applicable_declarations: &mut V) @@ -354,10 +368,11 @@ impl Stylist { // Step 1: Normal rules. map.user_agent.normal.get_all_matching_rules(element, + parent_bf, applicable_declarations, &mut shareable); - map.user.normal.get_all_matching_rules(element, applicable_declarations, &mut shareable); - map.author.normal.get_all_matching_rules(element, applicable_declarations, &mut shareable); + map.user.normal.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); + map.author.normal.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); // Step 2: Normal style attributes. style_attribute.map(|sa| { @@ -367,6 +382,7 @@ impl Stylist { // Step 3: Author-supplied `!important` rules. map.author.important.get_all_matching_rules(element, + parent_bf, applicable_declarations, &mut shareable); @@ -378,9 +394,11 @@ impl Stylist { // Step 5: User and UA `!important` rules. map.user.important.get_all_matching_rules(element, + parent_bf, applicable_declarations, &mut shareable); map.user_agent.important.get_all_matching_rules(element, + parent_bf, applicable_declarations, &mut shareable); @@ -449,13 +467,12 @@ impl DeclarationBlock { } } -pub fn matches<E:TElement, N:TNode<E>>(selector_list: &SelectorList, element: &N) -> bool { +pub fn matches<E:TElement, N:TNode<E>>(selector_list: &SelectorList, element: &N, parent_bf: &Option<BloomFilter>) -> bool { get_selector_list_selectors(selector_list).iter().any(|selector| selector.pseudo_element.is_none() && - matches_compound_selector(&*selector.compound_selectors, element, &mut false)) + matches_compound_selector(&*selector.compound_selectors, element, parent_bf, &mut false)) } - /// Determines whether the given element matches the given single or compound selector. /// /// NB: If you add support for any new kinds of selectors to this routine, be sure to set @@ -466,9 +483,10 @@ fn matches_compound_selector<E:TElement, N:TNode<E>>( selector: &CompoundSelector, element: &N, + parent_bf: &Option<BloomFilter>, shareable: &mut bool) -> bool { - match matches_compound_selector_internal(selector, element, shareable) { + match matches_compound_selector_internal(selector, element, parent_bf, shareable) { Matched => true, _ => false } @@ -523,17 +541,82 @@ enum SelectorMatchingResult { NotMatchedGlobally, } +/// Quickly figures out whether or not the compound selector is worth doing more +/// work on. If the simple selectors don't match, or there's a child selector +/// that does not appear in the bloom parent bloom filter, we can exit early. +fn can_fast_reject<E: TElement, N: TNode<E>>( + mut selector: &CompoundSelector, + element: &N, + parent_bf: &Option<BloomFilter>, + shareable: &mut bool) -> Option<SelectorMatchingResult> { + if !selector.simple_selectors.iter().all(|simple_selector| { + matches_simple_selector(simple_selector, element, shareable) }) { + return Some(NotMatchedAndRestartFromClosestLaterSibling); + } + + let bf: &BloomFilter = + match *parent_bf { + None => return None, + Some(ref bf) => bf, + }; + + // See if the bloom filter can exclude any of the descendant selectors, and + // reject if we can. + loop { + match selector.next { + None => break, + Some((ref cs, Descendant)) => selector = &**cs, + Some((ref cs, _)) => { + selector = &**cs; + continue; + } + }; + + for ss in selector.simple_selectors.iter() { + match *ss { + LocalNameSelector(LocalNameSelector { ref name, ref lower_name }) => { + if bf.definitely_excludes(name) + && bf.definitely_excludes(lower_name) { + return Some(NotMatchedGlobally); + } + }, + NamespaceSelector(ref namespace) => { + if bf.definitely_excludes(namespace) { + return Some(NotMatchedGlobally); + } + }, + IDSelector(ref id) => { + if bf.definitely_excludes(id) { + return Some(NotMatchedGlobally); + } + }, + ClassSelector(ref class) => { + if bf.definitely_excludes(&class.as_slice()) { + return Some(NotMatchedGlobally); + } + }, + _ => {}, + } + } + + } + + // Can't fast reject. + return None; +} + fn matches_compound_selector_internal<E:TElement, N:TNode<E>>( selector: &CompoundSelector, element: &N, + parent_bf: &Option<BloomFilter>, shareable: &mut bool) -> SelectorMatchingResult { - if !selector.simple_selectors.iter().all(|simple_selector| { - matches_simple_selector(simple_selector, element, shareable) - }) { - return NotMatchedAndRestartFromClosestLaterSibling - } + match can_fast_reject(selector, element, parent_bf, shareable) { + None => {}, + Some(result) => return result, + }; + match selector.next { None => Matched, Some((ref next_selector, combinator)) => { @@ -557,6 +640,7 @@ fn matches_compound_selector_internal<E:TElement, if node.is_element() { let result = matches_compound_selector_internal(&**next_selector, &node, + parent_bf, shareable); match (result, combinator) { // Return the status immediately. @@ -596,7 +680,7 @@ fn matches_compound_selector_internal<E:TElement, /// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in /// `main/css/matching.rs`.) #[inline] -fn matches_simple_selector<E:TElement, +pub fn matches_simple_selector<E:TElement, N:TNode<E>>( selector: &SimpleSelector, element: &N, diff --git a/components/style/selectors.rs b/components/style/selectors.rs index 44b098fa329..c398c09877e 100644 --- a/components/style/selectors.rs +++ b/components/style/selectors.rs @@ -31,7 +31,7 @@ pub struct Selector { pub specificity: u32, } -#[deriving(PartialEq, Clone)] +#[deriving(Eq, PartialEq, Clone, Hash)] pub enum PseudoElement { Before, After, @@ -54,7 +54,7 @@ pub enum Combinator { LaterSibling, // ~ } -#[deriving(PartialEq, Clone)] +#[deriving(Eq, PartialEq, Clone, Hash)] pub enum SimpleSelector { IDSelector(Atom), ClassSelector(Atom), @@ -92,20 +92,20 @@ pub enum SimpleSelector { // ... } -#[deriving(PartialEq, Clone)] +#[deriving(Eq, PartialEq, Clone, Hash)] pub struct LocalNameSelector { pub name: Atom, pub lower_name: Atom, } -#[deriving(PartialEq, Clone)] +#[deriving(Eq, PartialEq, Clone, Hash)] pub struct AttrSelector { pub name: String, pub lower_name: String, pub namespace: NamespaceConstraint, } -#[deriving(PartialEq, Clone)] +#[deriving(Eq, PartialEq, Clone, Hash)] pub enum NamespaceConstraint { AnyNamespace, SpecificNamespace(Namespace), diff --git a/components/util/bloom.rs b/components/util/bloom.rs new file mode 100644 index 00000000000..0019092663f --- /dev/null +++ b/components/util/bloom.rs @@ -0,0 +1,398 @@ +/* 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/. */ + +//! Simple counting bloom filters. + +extern crate rand; + +use fnv::{FnvState, hash}; +use rand::Rng; +use std::hash::Hash; +use std::iter; +use std::num; +use std::uint; + +// Just a quick and dirty xxhash embedding. + +/// A counting bloom filter. +/// +/// A bloom filter is a probabilistic data structure which allows you to add and +/// remove elements from a set, query the set for whether it may contain an +/// element or definitely exclude it, and uses much less ram than an equivalent +/// hashtable. +#[deriving(Clone)] +pub struct BloomFilter { + buf: Vec<uint>, + number_of_insertions: uint, +} + +// Here's where some of the magic numbers came from: +// +// m = number of elements in the filter +// n = size of the filter +// k = number of hash functions +// +// p = Pr[false positive] = 0.01 false positive rate +// +// if we have an estimation of the number of elements in the bloom filter, we +// know m. +// +// p = (1 - exp(-kn/m))^k +// k = (m/n)ln2 +// lnp = -(m/n)(ln2)^2 +// m = -nlnp/(ln2)^2 +// => n = -m(ln2)^2/lnp +// ~= 10*m +// +// k = (m/n)ln2 = 10ln2 ~= 7 + +static NUMBER_OF_HASHES: uint = 7; + +static BITS_PER_BUCKET: uint = 4; +static BUCKETS_PER_WORD: uint = uint::BITS / BITS_PER_BUCKET; + +/// Returns a tuple of (array index, lsr shift amount) to get to the bits you +/// need. Don't forget to mask with 0xF! +fn bucket_index_to_array_index(bucket_index: uint) -> (uint, uint) { + let arr_index = bucket_index / BUCKETS_PER_WORD; + let shift_amount = (bucket_index % BUCKETS_PER_WORD) * BITS_PER_BUCKET; + (arr_index, shift_amount) +} + +// Key Stretching +// ============== +// +// Siphash is expensive. Instead of running it `NUMBER_OF_HASHES`, which would +// be a pretty big hit on performance, we just use it to see a non-cryptographic +// random number generator. This stretches the hash to get us our +// `NUMBER_OF_HASHES` array indicies. +// +// A hash is a `u64` and comes from SipHash. +// A shash is a `uint` stretched hash which comes from the XorShiftRng. + +fn to_rng(hash: u64) -> rand::XorShiftRng { + let bottom = (hash & 0xFFFFFFFF) as u32; + let top = ((hash >> 32) & 0xFFFFFFFF) as u32; + rand::SeedableRng::from_seed([ 0x97830e05, 0x113ba7bb, bottom, top ]) +} + +fn stretch<'a>(r: &'a mut rand::XorShiftRng) + -> iter::Take<rand::Generator<'a, uint, rand::XorShiftRng>> { + r.gen_iter().take(NUMBER_OF_HASHES) +} + +impl BloomFilter { + /// This bloom filter is tuned to have ~1% false positive rate. In exchange + /// for this guarantee, you need to have a reasonable upper bound on the + /// number of elements that will ever be inserted into it. If you guess too + /// low, your false positive rate will suffer. If you guess too high, you'll + /// use more memory than is really necessary. + pub fn new(expected_number_of_insertions: uint) -> BloomFilter { + let size_in_buckets = 10 * expected_number_of_insertions; + + let size_in_words = size_in_buckets / BUCKETS_PER_WORD; + + let nonzero_size = if size_in_words == 0 { 1 } else { size_in_words }; + + let num_words = + num::checked_next_power_of_two(nonzero_size) + .unwrap(); + + BloomFilter { + buf: Vec::from_elem(num_words, 0), + number_of_insertions: 0, + } + } + + /// Since the array length must be a power of two, this will return a + /// bitmask that can be `&`ed with a number to bring it into the range of + /// the array. + fn mask(&self) -> uint { + (self.buf.len()*BUCKETS_PER_WORD) - 1 // guaranteed to be a power of two + } + + /// Converts a stretched hash into a bucket index. + fn shash_to_bucket_index(&self, shash: uint) -> uint { + shash & self.mask() + } + + /// Converts a stretched hash into an array and bit index. See the comment + /// on `bucket_index_to_array_index` for details about the return value. + fn shash_to_array_index(&self, shash: uint) -> (uint, uint) { + bucket_index_to_array_index(self.shash_to_bucket_index(shash)) + } + + /// Gets the value at a given bucket. + fn bucket_get(&self, a_idx: uint, shift_amount: uint) -> uint { + let array_val = self.buf[a_idx]; + (array_val >> shift_amount) & 0xF + } + + /// Sets the value at a given bucket. This will not bounds check, but that's + /// ok because you've called `bucket_get` first, anyhow. + fn bucket_set(&mut self, a_idx: uint, shift_amount: uint, new_val: uint) { + // We can avoid bounds checking here since in order to do a bucket_set + // we have to had done a `bucket_get` at the same index for it to make + // sense. + let old_val = self.buf.as_mut_slice().get_mut(a_idx).unwrap(); + let mask = (1 << BITS_PER_BUCKET) - 1; // selects the right-most bucket + let select_in_bucket = mask << shift_amount; // selects the correct bucket + let select_out_of_bucket = !select_in_bucket; // selects everything except the correct bucket + let new_array_val = (new_val << shift_amount) // move the new_val into the right spot + | (*old_val & select_out_of_bucket); // mask out the old value, and or it with the new one + *old_val = new_array_val; + } + + /// Insert a stretched hash into the bloom filter, remembering to saturate + /// the counter instead of overflowing. + fn insert_shash(&mut self, shash: uint) { + let (a_idx, shift_amount) = self.shash_to_array_index(shash); + let b_val = self.bucket_get(a_idx, shift_amount); + + + // saturate the count. + if b_val == 0xF { + return; + } + + let new_val = b_val + 1; + + self.bucket_set(a_idx, shift_amount, new_val); + } + + /// Insert a hashed value into the bloom filter. + fn insert_hashed(&mut self, hash: u64) { + self.number_of_insertions += 1; + for h in stretch(&mut to_rng(hash)) { + self.insert_shash(h); + } + } + + /// Inserts a value into the bloom filter. Note that the bloom filter isn't + /// parameterized over the values it holds. That's because it can hold + /// values of different types, as long as it can get a hash out of them. + pub fn insert<H: Hash<FnvState>>(&mut self, h: &H) { + self.insert_hashed(hash(h)) + } + + /// Removes a stretched hash from the bloom filter, taking care not to + /// decrememnt saturated counters. + /// + /// It is an error to remove never-inserted elements. + fn remove_shash(&mut self, shash: uint) { + let (a_idx, shift_amount) = self.shash_to_array_index(shash); + let b_val = self.bucket_get(a_idx, shift_amount); + assert!(b_val != 0, "Removing an element that was never inserted."); + + // can't do anything if the counter saturated. + if b_val == 0xF { return; } + + self.bucket_set(a_idx, shift_amount, b_val - 1); + } + + /// Removes a hashed value from the bloom filter. + fn remove_hashed(&mut self, hash: u64) { + self.number_of_insertions -= 1; + for h in stretch(&mut to_rng(hash)) { + self.remove_shash(h); + } + } + + /// Removes a value from the bloom filter. + /// + /// Be careful of adding and removing lots of elements, especially for + /// long-lived bloom filters. The counters in each bucket will saturate if + /// 16 or more elements hash to it, and then stick there. This will hurt + /// your false positive rate. To fix this, you might consider refreshing the + /// bloom filter by `clear`ing it, and then reinserting elements at regular, + /// long intervals. + /// + /// It is an error to remove never-inserted elements. + pub fn remove<H: Hash<FnvState>>(&mut self, h: &H) { + self.remove_hashed(hash(h)) + } + + /// Returns `true` if the bloom filter cannot possibly contain the given + /// stretched hash. + fn definitely_excludes_shash(&self, shash: uint) -> bool { + let (a_idx, shift_amount) = self.shash_to_array_index(shash); + self.bucket_get(a_idx, shift_amount) == 0 + } + + /// A hash is definitely excluded iff none of the stretched hashes are in + /// the bloom filter. + fn definitely_excludes_hashed(&self, hash: u64) -> bool { + let mut ret = false; + + // Doing `.any` is slower than this branch-free version. + for shash in stretch(&mut to_rng(hash)) { + ret |= self.definitely_excludes_shash(shash); + } + + ret + } + + /// A bloom filter can tell you whether or not a value has definitely never + /// been inserted. Note that bloom filters can give false positives. + pub fn definitely_excludes<H: Hash<FnvState>>(&self, h: &H) -> bool { + self.definitely_excludes_hashed(hash(h)) + } + + /// A bloom filter can tell you if an element /may/ be in it. It cannot be + /// certain. But, assuming correct usage, this query will have a low false + /// positive rate. + pub fn may_include<H: Hash<FnvState>>(&self, h: &H) -> bool { + !self.definitely_excludes(h) + } + + /// Returns the number of elements ever inserted into the bloom filter - the + /// number of elements removed. + pub fn number_of_insertions(&self) -> uint { + self.number_of_insertions + } + + /// Returns the number of bytes of memory the bloom filter uses. + pub fn size(&self) -> uint { + self.buf.len() * uint::BYTES + } + + /// Removes all elements from the bloom filter. This is both more efficient + /// and has better false-positive properties than repeatedly calling `remove` + /// on every element. + pub fn clear(&mut self) { + self.number_of_insertions = 0; + for x in self.buf.as_mut_slice().mut_iter() { + *x = 0u; + } + } +} + +#[test] +fn create_and_insert_some_stuff() { + use std::iter::range; + + let mut bf = BloomFilter::new(1000); + + for i in range(0u, 1000) { + bf.insert(&i); + } + + assert_eq!(bf.number_of_insertions(), 1000); + + for i in range(0u, 1000) { + assert!(bf.may_include(&i)); + } + + let false_positives = + range(1001u, 2000).filter(|i| bf.may_include(&i)).count(); + + assert!(false_positives < 10) // 1%. + + for i in range(0u, 100) { + bf.remove(&i); + } + + assert_eq!(bf.number_of_insertions(), 900); + + for i in range(100u, 1000) { + assert!(bf.may_include(&i)); + } + + let false_positives = range(0u, 100).filter(|i| bf.may_include(&i)).count(); + + assert!(false_positives < 2); // 2%. + + bf.clear(); + + assert_eq!(bf.number_of_insertions(), 0); + + for i in range(0u, 2000) { + assert!(bf.definitely_excludes(&i)); + } +} + +#[cfg(test)] +mod bench { + extern crate test; + + use std::hash::hash; + use std::iter; + use super::BloomFilter; + + #[bench] + fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) { + b.iter(|| { + let mut bf = BloomFilter::new(1000); + for i in iter::range(0u, 1000) { + bf.insert(&i); + } + for i in iter::range(0u, 100) { + bf.remove(&i); + } + for i in iter::range(100u, 200) { + test::black_box(bf.may_include(&i)); + } + }); + } + + #[bench] + fn may_include(b: &mut test::Bencher) { + let mut bf = BloomFilter::new(1000); + + for i in iter::range(0u, 1000) { + bf.insert(&i); + } + + let mut i = 0u; + + b.bench_n(1000, |b| { + b.iter(|| { + test::black_box(bf.may_include(&i)); + i += 1; + }); + }); + } + + #[bench] + fn insert(b: &mut test::Bencher) { + let mut bf = BloomFilter::new(1000); + + b.bench_n(1000, |b| { + let mut i = 0u; + + b.iter(|| { + test::black_box(bf.insert(&i)); + i += 1; + }); + }); + } + + #[bench] + fn remove(b: &mut test::Bencher) { + let mut bf = BloomFilter::new(1000); + for i in range(0u, 1000) { + bf.insert(&i); + } + + b.bench_n(1000, |b| { + let mut i = 0u; + + b.iter(|| { + bf.remove(&i); + i += 1; + }); + }); + + test::black_box(bf.may_include(&0u)); + } + + #[bench] + fn hash_a_uint(b: &mut test::Bencher) { + let mut i = 0u; + b.iter(|| { + test::black_box(hash(&i)); + i += 1; + }) + } +} diff --git a/components/util/fnv.rs b/components/util/fnv.rs new file mode 100644 index 00000000000..a14f3ea2bec --- /dev/null +++ b/components/util/fnv.rs @@ -0,0 +1,45 @@ +/* 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/. */ + +//! This file stolen wholesale from rustc/src/librustc/util/nodemap.rs + +pub use std::hash::{Hash, Hasher, Writer}; + +/// A speedy hash algorithm for node ids and def ids. The hashmap in +/// libcollections by default uses SipHash which isn't quite as speedy as we +/// want. In the compiler we're not really worried about DOS attempts, so we +/// just default to a non-cryptographic hash. +/// +/// This uses FNV hashing, as described here: +/// http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +#[deriving(Clone)] +pub struct FnvHasher; + +pub struct FnvState(u64); + +impl Hasher<FnvState> for FnvHasher { + fn hash<T: Hash<FnvState>>(&self, t: &T) -> u64 { + let mut state = FnvState(0xcbf29ce484222325); + t.hash(&mut state); + let FnvState(ret) = state; + return ret; + } +} + +impl Writer for FnvState { + fn write(&mut self, bytes: &[u8]) { + let FnvState(mut hash) = *self; + for byte in bytes.iter() { + hash = hash ^ (*byte as u64); + hash = hash * 0x100000001b3; + } + *self = FnvState(hash); + } +} + +#[inline(always)] +pub fn hash<T: Hash<FnvState>>(t: &T) -> u64 { + let s = FnvHasher; + s.hash(t) +} diff --git a/components/util/lib.rs b/components/util/lib.rs index 0d59b21124d..0c186fdb690 100644 --- a/components/util/lib.rs +++ b/components/util/lib.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#![feature(macro_rules,unsafe_destructor)] +#![feature(default_type_params,macro_rules,unsafe_destructor)] #![deny(unused_imports, unused_variable)] @@ -29,8 +29,10 @@ extern crate std_time = "time"; extern crate string_cache; pub mod atom; +pub mod bloom; pub mod cache; pub mod debug_utils; +pub mod fnv; pub mod geometry; pub mod logical_geometry; pub mod memory; diff --git a/components/util/namespace.rs b/components/util/namespace.rs index 1f32ae83017..8824accae00 100644 --- a/components/util/namespace.rs +++ b/components/util/namespace.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#[deriving(PartialEq, Clone, Encodable)] +#[deriving(Eq, PartialEq, Clone, Encodable, Hash)] pub enum Namespace { Null, HTML, diff --git a/components/util/time.rs b/components/util/time.rs index 4f282aa2648..bb483ca2a99 100644 --- a/components/util/time.rs +++ b/components/util/time.rs @@ -38,6 +38,7 @@ pub enum TimeProfilerCategory { CompositingCategory, LayoutQueryCategory, LayoutPerformCategory, + LayoutMaxSelectorMatchesCategory, LayoutStyleRecalcCategory, LayoutSelectorMatchCategory, LayoutTreeBuilderCategory, @@ -66,6 +67,7 @@ impl TimeProfilerCategory { buckets.insert(CompositingCategory, vec!()); buckets.insert(LayoutQueryCategory, vec!()); buckets.insert(LayoutPerformCategory, vec!()); + buckets.insert(LayoutMaxSelectorMatchesCategory, vec!()); buckets.insert(LayoutStyleRecalcCategory, vec!()); buckets.insert(LayoutSelectorMatchCategory, vec!()); buckets.insert(LayoutTreeBuilderCategory, vec!()); diff --git a/components/util/workqueue.rs b/components/util/workqueue.rs index 5b27b4a5dab..f7823448243 100644 --- a/components/util/workqueue.rs +++ b/components/util/workqueue.rs @@ -288,4 +288,3 @@ impl<QueueData: Send, WorkData: Send> WorkQueue<QueueData, WorkData> { } } } - diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index dad6d3e58d2..89de2206488 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -445,17 +445,17 @@ source = "git+https://github.com/servo/rust-stb-image#f5022de4ad6bb474a03493d1f2 [[package]] name = "string_cache" version = "0.0.0" -source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a" +source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289" dependencies = [ "phf 0.0.0 (git+https://github.com/sfackler/rust-phf#fa5d803428dd760287330571c919c7c5e11b2e1b)", "phf_mac 0.0.0 (git+https://github.com/sfackler/rust-phf#fa5d803428dd760287330571c919c7c5e11b2e1b)", - "string_cache_macros 0.0.0 (git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a)", + "string_cache_macros 0.0.0 (git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289)", ] [[package]] name = "string_cache_macros" version = "0.0.0" -source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a" +source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289" [[package]] name = "style" @@ -487,7 +487,7 @@ version = "0.0.1" dependencies = [ "azure 0.1.0 (git+https://github.com/servo/rust-azure#9c5567b79d8b87e8ef3b48c5842f453978035d21)", "geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)", - "string_cache 0.0.0 (git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a)", + "string_cache 0.0.0 (git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289)", "task_info 0.0.1", ] @@ -495,4 +495,3 @@ dependencies = [ name = "xlib" version = "0.1.0" source = "git+https://github.com/servo/rust-xlib#79904fb42ff8a0e888f70fae336fbf6c11f1e6c8" - |