mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Added a bloom filter to CSS selector matching.
This commit is contained in:
parent
d161d0ad47
commit
acd83ff47b
20 changed files with 817 additions and 78 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
398
components/util/bloom.rs
Normal file
398
components/util/bloom.rs
Normal file
|
@ -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;
|
||||
})
|
||||
}
|
||||
}
|
45
components/util/fnv.rs
Normal file
45
components/util/fnv.rs
Normal file
|
@ -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)
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!());
|
||||
|
|
|
@ -288,4 +288,3 @@ impl<QueueData: Send, WorkData: Send> WorkQueue<QueueData, WorkData> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
9
ports/cef/Cargo.lock
generated
9
ports/cef/Cargo.lock
generated
|
@ -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"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue