mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Merge pull request #3212 from cgaebel/style-resolution-bloom-filter
Added a bloom filter to CSS selector matching.
This commit is contained in:
commit
ad02534c10
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]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a"
|
source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf 0.0.0 (git+https://github.com/sfackler/rust-phf#fa5d803428dd760287330571c919c7c5e11b2e1b)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "string_cache_macros"
|
name = "string_cache_macros"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a"
|
source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "style"
|
name = "style"
|
||||||
|
@ -451,7 +451,7 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"azure 0.1.0 (git+https://github.com/servo/rust-azure#9c5567b79d8b87e8ef3b48c5842f453978035d21)",
|
"azure 0.1.0 (git+https://github.com/servo/rust-azure#9c5567b79d8b87e8ef3b48c5842f453978035d21)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)",
|
"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",
|
"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.start.i = *inline_start_margin_edge;
|
||||||
kid_base.position.size.inline = inline_size;
|
kid_base.position.size.inline = inline_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -186,15 +186,15 @@ enum WhitespaceStrippingMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An object that knows how to create flows.
|
/// An object that knows how to create flows.
|
||||||
pub struct FlowConstructor<'a, 'b> {
|
pub struct FlowConstructor<'a> {
|
||||||
/// The layout context.
|
/// 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.
|
/// Creates a new flow constructor.
|
||||||
pub fn new<'b>(layout_context: &'b LayoutContext)
|
pub fn new<'a>(layout_context: &'a LayoutContext<'a>)
|
||||||
-> FlowConstructor<'a, 'b> {
|
-> FlowConstructor<'a> {
|
||||||
FlowConstructor {
|
FlowConstructor {
|
||||||
layout_context: layout_context,
|
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.
|
// Construct Flow based on 'display', 'position', and 'float' values.
|
||||||
//
|
//
|
||||||
// CSS 2.1 Section 9.7
|
// 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 wrapper::{LayoutElement, LayoutNode, PostorderNodeMutTraversal, ThreadSafeLayoutNode};
|
||||||
|
|
||||||
use servo_util::atom::Atom;
|
use servo_util::atom::Atom;
|
||||||
|
use servo_util::bloom::BloomFilter;
|
||||||
use servo_util::cache::{Cache, LRUCache, SimpleHashCache};
|
use servo_util::cache::{Cache, LRUCache, SimpleHashCache};
|
||||||
use servo_util::namespace::Null;
|
use servo_util::namespace::Null;
|
||||||
use servo_util::smallvec::{SmallVec, SmallVec16};
|
use servo_util::smallvec::{SmallVec, SmallVec16};
|
||||||
|
@ -19,7 +20,9 @@ use servo_util::str::DOMString;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::hash::{Hash, sip};
|
use std::hash::{Hash, sip};
|
||||||
use std::slice::Items;
|
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;
|
use sync::Arc;
|
||||||
|
|
||||||
pub struct ApplicableDeclarations {
|
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();
|
let element = node.as_element();
|
||||||
if element.style_attribute().is_some() {
|
if element.style_attribute().is_some() {
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(StyleSharingCandidate {
|
Some(StyleSharingCandidate {
|
||||||
style: style.take_unwrap(),
|
style: style,
|
||||||
parent_style: parent_style.take_unwrap(),
|
parent_style: parent_style,
|
||||||
local_name: element.get_local_name().clone(),
|
local_name: element.get_local_name().clone(),
|
||||||
class: element.get_attr(&Null, "class")
|
class: element.get_attr(&Null, "class")
|
||||||
.map(|string| string.to_string()),
|
.map(|string| string.to_string()),
|
||||||
|
@ -278,16 +279,31 @@ pub enum StyleSharingResult<'ln> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MatchMethods {
|
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
|
/// Performs aux initialization, selector matching, cascading, and flow construction
|
||||||
/// sequentially.
|
/// sequentially.
|
||||||
fn recalc_style_for_subtree(&self,
|
fn recalc_style_for_subtree(&self,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
|
parent_bf: &mut Option<BloomFilter>,
|
||||||
applicable_declarations: &mut ApplicableDeclarations,
|
applicable_declarations: &mut ApplicableDeclarations,
|
||||||
parent: Option<LayoutNode>);
|
parent: Option<LayoutNode>);
|
||||||
|
|
||||||
fn match_node(&self,
|
fn match_node(&self,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
applicable_declarations: &mut ApplicableDeclarations,
|
applicable_declarations: &mut ApplicableDeclarations,
|
||||||
shareable: &mut bool);
|
shareable: &mut bool);
|
||||||
|
|
||||||
|
@ -403,20 +419,24 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
||||||
impl<'ln> MatchMethods for LayoutNode<'ln> {
|
impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||||
fn match_node(&self,
|
fn match_node(&self,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
applicable_declarations: &mut ApplicableDeclarations,
|
applicable_declarations: &mut ApplicableDeclarations,
|
||||||
shareable: &mut bool) {
|
shareable: &mut bool) {
|
||||||
let style_attribute = self.as_element().style_attribute().as_ref();
|
let style_attribute = self.as_element().style_attribute().as_ref();
|
||||||
|
|
||||||
applicable_declarations.normal_shareable =
|
applicable_declarations.normal_shareable =
|
||||||
stylist.push_applicable_declarations(self,
|
stylist.push_applicable_declarations(self,
|
||||||
|
parent_bf,
|
||||||
style_attribute,
|
style_attribute,
|
||||||
None,
|
None,
|
||||||
&mut applicable_declarations.normal);
|
&mut applicable_declarations.normal);
|
||||||
stylist.push_applicable_declarations(self,
|
stylist.push_applicable_declarations(self,
|
||||||
|
parent_bf,
|
||||||
None,
|
None,
|
||||||
Some(Before),
|
Some(Before),
|
||||||
&mut applicable_declarations.before);
|
&mut applicable_declarations.before);
|
||||||
stylist.push_applicable_declarations(self,
|
stylist.push_applicable_declarations(self,
|
||||||
|
parent_bf,
|
||||||
None,
|
None,
|
||||||
Some(After),
|
Some(After),
|
||||||
&mut applicable_declarations.after);
|
&mut applicable_declarations.after);
|
||||||
|
@ -455,9 +475,63 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||||
CannotShare(true)
|
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,
|
fn recalc_style_for_subtree(&self,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
|
parent_bf: &mut Option<BloomFilter>,
|
||||||
applicable_declarations: &mut ApplicableDeclarations,
|
applicable_declarations: &mut ApplicableDeclarations,
|
||||||
parent: Option<LayoutNode>) {
|
parent: Option<LayoutNode>) {
|
||||||
self.initialize_layout_data(layout_context.shared.layout_chan.clone());
|
self.initialize_layout_data(layout_context.shared.layout_chan.clone());
|
||||||
|
@ -471,7 +545,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||||
match sharing_result {
|
match sharing_result {
|
||||||
CannotShare(mut shareable) => {
|
CannotShare(mut shareable) => {
|
||||||
if self.is_element() {
|
if self.is_element() {
|
||||||
self.match_node(stylist, applicable_declarations, &mut shareable)
|
self.match_node(stylist, &*parent_bf, applicable_declarations, &mut shareable);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -490,11 +564,22 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||||
StyleWasShared(index) => layout_context.style_sharing_candidate_cache().touch(index),
|
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() {
|
for kid in self.children() {
|
||||||
kid.recalc_style_for_subtree(stylist,
|
kid.recalc_style_for_subtree(stylist,
|
||||||
layout_context,
|
layout_context,
|
||||||
applicable_declarations,
|
parent_bf,
|
||||||
Some(self.clone()))
|
applicable_declarations,
|
||||||
|
Some(self.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
match *parent_bf {
|
||||||
|
None => {},
|
||||||
|
Some(ref mut pbf) => self.remove_from_bloom_filter(pbf),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct flows.
|
// 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 servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
|
||||||
use gfx::font_cache_task::{FontCacheTask};
|
use gfx::font_cache_task::{FontCacheTask};
|
||||||
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
|
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
|
||||||
|
use servo_util::bloom::BloomFilter;
|
||||||
use servo_util::geometry::Au;
|
use servo_util::geometry::Au;
|
||||||
use servo_util::geometry;
|
use servo_util::geometry;
|
||||||
use servo_util::logical_geometry::LogicalPoint;
|
use servo_util::logical_geometry::LogicalPoint;
|
||||||
|
@ -57,6 +58,7 @@ use servo_util::workqueue::WorkQueue;
|
||||||
use std::comm::{channel, Sender, Receiver, Select};
|
use std::comm::{channel, Sender, Receiver, Select};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
use style;
|
||||||
use style::{AuthorOrigin, Stylesheet, Stylist};
|
use style::{AuthorOrigin, Stylesheet, Stylist};
|
||||||
use style::iter_font_face_rules;
|
use style::iter_font_face_rules;
|
||||||
use sync::{Arc, Mutex, MutexGuard};
|
use sync::{Arc, Mutex, MutexGuard};
|
||||||
|
@ -409,7 +411,12 @@ impl LayoutTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a layout context for use in building display lists, hit testing, &c.
|
// 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 {
|
SharedLayoutContext {
|
||||||
image_cache: rw_data.local_image_cache.clone(),
|
image_cache: rw_data.local_image_cache.clone(),
|
||||||
screen_size: rw_data.screen_size.clone(),
|
screen_size: rw_data.screen_size.clone(),
|
||||||
|
@ -718,7 +725,11 @@ impl LayoutTask {
|
||||||
rw_data.screen_size = current_screen_size;
|
rw_data.screen_size = current_screen_size;
|
||||||
|
|
||||||
// Create a layout context for use throughout the following passes.
|
// 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,
|
let mut layout_root = profile(time::LayoutStyleRecalcCategory,
|
||||||
self.time_profiler_chan.clone(),
|
self.time_profiler_chan.clone(),
|
||||||
|
@ -729,8 +740,11 @@ impl LayoutTask {
|
||||||
None => {
|
None => {
|
||||||
let layout_ctx = LayoutContext::new(&shared_layout_ctx);
|
let layout_ctx = LayoutContext::new(&shared_layout_ctx);
|
||||||
let mut applicable_declarations = ApplicableDeclarations::new();
|
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,
|
node.recalc_style_for_subtree(&*rw_data.stylist,
|
||||||
&layout_ctx,
|
&layout_ctx,
|
||||||
|
&mut parent_bf,
|
||||||
&mut applicable_declarations,
|
&mut applicable_declarations,
|
||||||
None)
|
None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,14 @@ use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_
|
||||||
use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode};
|
use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode};
|
||||||
|
|
||||||
use gfx::display_list::OpaqueNode;
|
use gfx::display_list::OpaqueNode;
|
||||||
|
use servo_util::bloom::BloomFilter;
|
||||||
use servo_util::time::{TimeProfilerChan, profile};
|
use servo_util::time::{TimeProfilerChan, profile};
|
||||||
use servo_util::time;
|
use servo_util::time;
|
||||||
use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
|
use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::atomics::{AtomicInt, Relaxed, SeqCst};
|
use std::sync::atomics::{AtomicInt, Relaxed, SeqCst};
|
||||||
|
use style;
|
||||||
use style::TNode;
|
use style::TNode;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -213,7 +215,91 @@ impl<'a> ParallelPreorderFlowTraversal for AssignISizesTraversal<'a> {
|
||||||
|
|
||||||
impl<'a> ParallelPostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'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>) {
|
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) {
|
||||||
let shared_layout_context = unsafe { &**proxy.user_data() };
|
let shared_layout_context = unsafe { &**proxy.user_data() };
|
||||||
let layout_context = LayoutContext::new(shared_layout_context);
|
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());
|
node.initialize_layout_data(layout_context.shared.layout_chan.clone());
|
||||||
|
|
||||||
// Get the parent node.
|
// Get the parent node.
|
||||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
|
let parent_opt = parent_node(&node, &layout_context);
|
||||||
let parent_opt = if opaque_node == layout_context.shared.reflow_root {
|
|
||||||
None
|
// Get the style bloom filter.
|
||||||
} else {
|
let bf = take_task_local_bloom_filter(parent_opt, &layout_context);
|
||||||
node.parent_node()
|
|
||||||
};
|
|
||||||
|
|
||||||
// First, check to see whether we can share a style with someone.
|
// First, check to see whether we can share a style with someone.
|
||||||
let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache();
|
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())
|
parent_opt.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Just needs to be wrapped in an option for `match_node`.
|
||||||
|
let some_bf = Some(bf);
|
||||||
|
|
||||||
// Otherwise, match and cascade selectors.
|
// Otherwise, match and cascade selectors.
|
||||||
match sharing_result {
|
match sharing_result {
|
||||||
CannotShare(mut shareable) => {
|
CannotShare(mut shareable) => {
|
||||||
|
@ -252,7 +339,7 @@ fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode,
|
||||||
if node.is_element() {
|
if node.is_element() {
|
||||||
// Perform the CSS selector matching.
|
// Perform the CSS selector matching.
|
||||||
let stylist = unsafe { &*layout_context.shared.stylist };
|
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.
|
// 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,
|
// 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
|
// 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
|
// 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),
|
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.
|
bf.map(|bf| put_task_local_bloom_filter(bf, &unsafe_layout_node));
|
||||||
construct_flows(unsafe_layout_node, &layout_context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode,
|
fn construct_flows<'a>(unsafe_layout_node: &mut UnsafeLayoutNode,
|
||||||
layout_context: &LayoutContext) {
|
parent_bf: &mut Option<BloomFilter>,
|
||||||
|
layout_context: &'a LayoutContext<'a>) {
|
||||||
loop {
|
loop {
|
||||||
// Get a real layout node.
|
// Get a real layout node.
|
||||||
let node: LayoutNode = unsafe {
|
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.
|
// 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.
|
// If this is the reflow root, we're done.
|
||||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
|
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
|
||||||
if layout_context.shared.reflow_root == opaque_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.
|
// Otherwise, enqueue the parent.
|
||||||
|
@ -352,6 +451,8 @@ fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode,
|
||||||
unsafe {
|
unsafe {
|
||||||
match *parent.borrow_layout_data_unchecked() {
|
match *parent.borrow_layout_data_unchecked() {
|
||||||
Some(ref parent_layout_data) => {
|
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);
|
let parent_layout_data: &mut LayoutDataWrapper = mem::transmute(parent_layout_data);
|
||||||
if parent_layout_data.data
|
if parent_layout_data.data
|
||||||
.parallel
|
.parallel
|
||||||
|
@ -359,7 +460,6 @@ fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode,
|
||||||
.fetch_sub(1, SeqCst) == 1 {
|
.fetch_sub(1, SeqCst) == 1 {
|
||||||
// We were the last child of our parent. Construct flows for our
|
// We were the last child of our parent. Construct flows for our
|
||||||
// parent.
|
// parent.
|
||||||
unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent)
|
|
||||||
} else {
|
} else {
|
||||||
// Get out of here and find another node to work on.
|
// Get out of here and find another node to work on.
|
||||||
break
|
break
|
||||||
|
@ -558,4 +658,3 @@ pub fn build_display_list_for_subtree(root: &mut FlowRef,
|
||||||
|
|
||||||
queue.data = ptr::null()
|
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>> {
|
fn prev_sibling(&self) -> Option<LayoutNode<'ln>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.node.prev_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
|
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),
|
Err(()) => Err(Syntax),
|
||||||
Ok(ref selectors) => {
|
Ok(ref selectors) => {
|
||||||
let root: &JSRef<Node> = NodeCast::from_ref(self);
|
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) => {
|
Ok(ref selectors) => {
|
||||||
let root = self.ancestors().last().unwrap_or(self.clone());
|
let root = self.ancestors().last().unwrap_or(self.clone());
|
||||||
for node in root.traverse_preorder() {
|
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();
|
let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap();
|
||||||
return Ok(Some(Temporary::from_rooted(elem)));
|
return Ok(Some(Temporary::from_rooted(elem)));
|
||||||
}
|
}
|
||||||
|
@ -631,7 +631,9 @@ impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> {
|
||||||
// Step 3.
|
// Step 3.
|
||||||
Ok(ref selectors) => {
|
Ok(ref selectors) => {
|
||||||
nodes = root.traverse_preorder().filter(
|
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();
|
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())
|
(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>> {
|
fn prev_sibling(&self) -> Option<JSRef<'a, Node>> {
|
||||||
(self as &NodeHelpers).prev_sibling().map(|node| *node.root())
|
(self as &NodeHelpers).prev_sibling().map(|node| *node.root())
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,8 @@ extern crate servo_util = "util";
|
||||||
// Public API
|
// Public API
|
||||||
pub use stylesheets::{Stylesheet, iter_font_face_rules};
|
pub use stylesheets::{Stylesheet, iter_font_face_rules};
|
||||||
pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
|
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::{cascade, cascade_anonymous};
|
||||||
pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs};
|
pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs};
|
||||||
pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes
|
pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes
|
||||||
|
@ -40,6 +41,7 @@ pub use properties::longhands;
|
||||||
pub use node::{TElement, TNode};
|
pub use node::{TElement, TNode};
|
||||||
pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str};
|
pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str};
|
||||||
pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace};
|
pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace};
|
||||||
|
pub use selectors::{SimpleSelector,LocalNameSelector};
|
||||||
pub use cssparser::{Color, RGBA};
|
pub use cssparser::{Color, RGBA};
|
||||||
|
|
||||||
mod stylesheets;
|
mod stylesheets;
|
||||||
|
|
|
@ -12,6 +12,8 @@ use servo_util::namespace::Namespace;
|
||||||
|
|
||||||
pub trait TNode<E:TElement> : Clone {
|
pub trait TNode<E:TElement> : Clone {
|
||||||
fn parent_node(&self) -> Option<Self>;
|
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 prev_sibling(&self) -> Option<Self>;
|
||||||
fn next_sibling(&self) -> Option<Self>;
|
fn next_sibling(&self) -> Option<Self>;
|
||||||
fn is_document(&self) -> bool;
|
fn is_document(&self) -> bool;
|
||||||
|
@ -32,4 +34,3 @@ pub trait TElement {
|
||||||
fn get_enabled_state(&self) -> bool;
|
fn get_enabled_state(&self) -> bool;
|
||||||
fn has_class(&self, name: &str) -> bool;
|
fn has_class(&self, name: &str) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use servo_util::atom::Atom;
|
use servo_util::atom::Atom;
|
||||||
|
use servo_util::bloom::BloomFilter;
|
||||||
use servo_util::namespace;
|
use servo_util::namespace;
|
||||||
use servo_util::smallvec::VecLike;
|
use servo_util::smallvec::VecLike;
|
||||||
use servo_util::sort;
|
use servo_util::sort;
|
||||||
|
@ -27,7 +28,7 @@ pub enum StylesheetOrigin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The definition of whitespace per CSS Selectors Level 3 § 4.
|
/// 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.
|
/// Map node attributes to Rules whose last simple selector starts with them.
|
||||||
///
|
///
|
||||||
|
@ -83,6 +84,7 @@ impl SelectorMap {
|
||||||
V:VecLike<DeclarationBlock>>(
|
V:VecLike<DeclarationBlock>>(
|
||||||
&self,
|
&self,
|
||||||
node: &N,
|
node: &N,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
matching_rules_list: &mut V,
|
matching_rules_list: &mut V,
|
||||||
shareable: &mut bool) {
|
shareable: &mut bool) {
|
||||||
if self.empty {
|
if self.empty {
|
||||||
|
@ -95,6 +97,7 @@ impl SelectorMap {
|
||||||
match element.get_id() {
|
match element.get_id() {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
SelectorMap::get_matching_rules_from_hash(node,
|
SelectorMap::get_matching_rules_from_hash(node,
|
||||||
|
parent_bf,
|
||||||
&self.id_hash,
|
&self.id_hash,
|
||||||
&id,
|
&id,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
|
@ -108,10 +111,11 @@ impl SelectorMap {
|
||||||
// FIXME: Store classes pre-split as atoms to make the loop below faster.
|
// FIXME: Store classes pre-split as atoms to make the loop below faster.
|
||||||
for class in class_attr.split(SELECTOR_WHITESPACE) {
|
for class in class_attr.split(SELECTOR_WHITESPACE) {
|
||||||
SelectorMap::get_matching_rules_from_hash(node,
|
SelectorMap::get_matching_rules_from_hash(node,
|
||||||
&self.class_hash,
|
parent_bf,
|
||||||
&Atom::from_slice(class),
|
&self.class_hash,
|
||||||
matching_rules_list,
|
&Atom::from_slice(class),
|
||||||
shareable);
|
matching_rules_list,
|
||||||
|
shareable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
|
@ -123,12 +127,14 @@ impl SelectorMap {
|
||||||
&self.local_name_hash
|
&self.local_name_hash
|
||||||
};
|
};
|
||||||
SelectorMap::get_matching_rules_from_hash(node,
|
SelectorMap::get_matching_rules_from_hash(node,
|
||||||
|
parent_bf,
|
||||||
local_name_hash,
|
local_name_hash,
|
||||||
element.get_local_name(),
|
element.get_local_name(),
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
shareable);
|
shareable);
|
||||||
|
|
||||||
SelectorMap::get_matching_rules(node,
|
SelectorMap::get_matching_rules(node,
|
||||||
|
parent_bf,
|
||||||
self.universal_rules.as_slice(),
|
self.universal_rules.as_slice(),
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
shareable);
|
shareable);
|
||||||
|
@ -145,13 +151,14 @@ impl SelectorMap {
|
||||||
N:TNode<E>,
|
N:TNode<E>,
|
||||||
V:VecLike<DeclarationBlock>>(
|
V:VecLike<DeclarationBlock>>(
|
||||||
node: &N,
|
node: &N,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
hash: &HashMap<Atom, Vec<Rule>>,
|
hash: &HashMap<Atom, Vec<Rule>>,
|
||||||
key: &Atom,
|
key: &Atom,
|
||||||
matching_rules: &mut V,
|
matching_rules: &mut V,
|
||||||
shareable: &mut bool) {
|
shareable: &mut bool) {
|
||||||
match hash.find(key) {
|
match hash.find(key) {
|
||||||
Some(rules) => {
|
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 => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
@ -162,11 +169,12 @@ impl SelectorMap {
|
||||||
N:TNode<E>,
|
N:TNode<E>,
|
||||||
V:VecLike<DeclarationBlock>>(
|
V:VecLike<DeclarationBlock>>(
|
||||||
node: &N,
|
node: &N,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
rules: &[Rule],
|
rules: &[Rule],
|
||||||
matching_rules: &mut V,
|
matching_rules: &mut V,
|
||||||
shareable: &mut bool) {
|
shareable: &mut bool) {
|
||||||
for rule in rules.iter() {
|
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());
|
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 {
|
pub struct Stylist {
|
||||||
element_map: PerPseudoElementSelectorMap,
|
element_map: PerPseudoElementSelectorMap,
|
||||||
before_map: PerPseudoElementSelectorMap,
|
before_map: PerPseudoElementSelectorMap,
|
||||||
|
@ -336,6 +349,7 @@ impl Stylist {
|
||||||
V:VecLike<DeclarationBlock>>(
|
V:VecLike<DeclarationBlock>>(
|
||||||
&self,
|
&self,
|
||||||
element: &N,
|
element: &N,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
style_attribute: Option<&PropertyDeclarationBlock>,
|
style_attribute: Option<&PropertyDeclarationBlock>,
|
||||||
pseudo_element: Option<PseudoElement>,
|
pseudo_element: Option<PseudoElement>,
|
||||||
applicable_declarations: &mut V)
|
applicable_declarations: &mut V)
|
||||||
|
@ -354,10 +368,11 @@ impl Stylist {
|
||||||
|
|
||||||
// Step 1: Normal rules.
|
// Step 1: Normal rules.
|
||||||
map.user_agent.normal.get_all_matching_rules(element,
|
map.user_agent.normal.get_all_matching_rules(element,
|
||||||
|
parent_bf,
|
||||||
applicable_declarations,
|
applicable_declarations,
|
||||||
&mut shareable);
|
&mut shareable);
|
||||||
map.user.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, applicable_declarations, &mut shareable);
|
map.author.normal.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable);
|
||||||
|
|
||||||
// Step 2: Normal style attributes.
|
// Step 2: Normal style attributes.
|
||||||
style_attribute.map(|sa| {
|
style_attribute.map(|sa| {
|
||||||
|
@ -367,6 +382,7 @@ impl Stylist {
|
||||||
|
|
||||||
// Step 3: Author-supplied `!important` rules.
|
// Step 3: Author-supplied `!important` rules.
|
||||||
map.author.important.get_all_matching_rules(element,
|
map.author.important.get_all_matching_rules(element,
|
||||||
|
parent_bf,
|
||||||
applicable_declarations,
|
applicable_declarations,
|
||||||
&mut shareable);
|
&mut shareable);
|
||||||
|
|
||||||
|
@ -378,9 +394,11 @@ impl Stylist {
|
||||||
|
|
||||||
// Step 5: User and UA `!important` rules.
|
// Step 5: User and UA `!important` rules.
|
||||||
map.user.important.get_all_matching_rules(element,
|
map.user.important.get_all_matching_rules(element,
|
||||||
|
parent_bf,
|
||||||
applicable_declarations,
|
applicable_declarations,
|
||||||
&mut shareable);
|
&mut shareable);
|
||||||
map.user_agent.important.get_all_matching_rules(element,
|
map.user_agent.important.get_all_matching_rules(element,
|
||||||
|
parent_bf,
|
||||||
applicable_declarations,
|
applicable_declarations,
|
||||||
&mut shareable);
|
&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|
|
get_selector_list_selectors(selector_list).iter().any(|selector|
|
||||||
selector.pseudo_element.is_none() &&
|
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.
|
/// 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
|
/// 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>>(
|
N:TNode<E>>(
|
||||||
selector: &CompoundSelector,
|
selector: &CompoundSelector,
|
||||||
element: &N,
|
element: &N,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
shareable: &mut bool)
|
shareable: &mut bool)
|
||||||
-> bool {
|
-> bool {
|
||||||
match matches_compound_selector_internal(selector, element, shareable) {
|
match matches_compound_selector_internal(selector, element, parent_bf, shareable) {
|
||||||
Matched => true,
|
Matched => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
|
@ -523,17 +541,82 @@ enum SelectorMatchingResult {
|
||||||
NotMatchedGlobally,
|
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,
|
fn matches_compound_selector_internal<E:TElement,
|
||||||
N:TNode<E>>(
|
N:TNode<E>>(
|
||||||
selector: &CompoundSelector,
|
selector: &CompoundSelector,
|
||||||
element: &N,
|
element: &N,
|
||||||
|
parent_bf: &Option<BloomFilter>,
|
||||||
shareable: &mut bool)
|
shareable: &mut bool)
|
||||||
-> SelectorMatchingResult {
|
-> SelectorMatchingResult {
|
||||||
if !selector.simple_selectors.iter().all(|simple_selector| {
|
match can_fast_reject(selector, element, parent_bf, shareable) {
|
||||||
matches_simple_selector(simple_selector, element, shareable)
|
None => {},
|
||||||
}) {
|
Some(result) => return result,
|
||||||
return NotMatchedAndRestartFromClosestLaterSibling
|
};
|
||||||
}
|
|
||||||
match selector.next {
|
match selector.next {
|
||||||
None => Matched,
|
None => Matched,
|
||||||
Some((ref next_selector, combinator)) => {
|
Some((ref next_selector, combinator)) => {
|
||||||
|
@ -557,6 +640,7 @@ fn matches_compound_selector_internal<E:TElement,
|
||||||
if node.is_element() {
|
if node.is_element() {
|
||||||
let result = matches_compound_selector_internal(&**next_selector,
|
let result = matches_compound_selector_internal(&**next_selector,
|
||||||
&node,
|
&node,
|
||||||
|
parent_bf,
|
||||||
shareable);
|
shareable);
|
||||||
match (result, combinator) {
|
match (result, combinator) {
|
||||||
// Return the status immediately.
|
// 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
|
/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
|
||||||
/// `main/css/matching.rs`.)
|
/// `main/css/matching.rs`.)
|
||||||
#[inline]
|
#[inline]
|
||||||
fn matches_simple_selector<E:TElement,
|
pub fn matches_simple_selector<E:TElement,
|
||||||
N:TNode<E>>(
|
N:TNode<E>>(
|
||||||
selector: &SimpleSelector,
|
selector: &SimpleSelector,
|
||||||
element: &N,
|
element: &N,
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub struct Selector {
|
||||||
pub specificity: u32,
|
pub specificity: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deriving(PartialEq, Clone)]
|
#[deriving(Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum PseudoElement {
|
pub enum PseudoElement {
|
||||||
Before,
|
Before,
|
||||||
After,
|
After,
|
||||||
|
@ -54,7 +54,7 @@ pub enum Combinator {
|
||||||
LaterSibling, // ~
|
LaterSibling, // ~
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deriving(PartialEq, Clone)]
|
#[deriving(Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum SimpleSelector {
|
pub enum SimpleSelector {
|
||||||
IDSelector(Atom),
|
IDSelector(Atom),
|
||||||
ClassSelector(Atom),
|
ClassSelector(Atom),
|
||||||
|
@ -92,20 +92,20 @@ pub enum SimpleSelector {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deriving(PartialEq, Clone)]
|
#[deriving(Eq, PartialEq, Clone, Hash)]
|
||||||
pub struct LocalNameSelector {
|
pub struct LocalNameSelector {
|
||||||
pub name: Atom,
|
pub name: Atom,
|
||||||
pub lower_name: Atom,
|
pub lower_name: Atom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deriving(PartialEq, Clone)]
|
#[deriving(Eq, PartialEq, Clone, Hash)]
|
||||||
pub struct AttrSelector {
|
pub struct AttrSelector {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub lower_name: String,
|
pub lower_name: String,
|
||||||
pub namespace: NamespaceConstraint,
|
pub namespace: NamespaceConstraint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deriving(PartialEq, Clone)]
|
#[deriving(Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum NamespaceConstraint {
|
pub enum NamespaceConstraint {
|
||||||
AnyNamespace,
|
AnyNamespace,
|
||||||
SpecificNamespace(Namespace),
|
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
|
* 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/. */
|
* 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)]
|
#![deny(unused_imports, unused_variable)]
|
||||||
|
|
||||||
|
@ -29,8 +29,10 @@ extern crate std_time = "time";
|
||||||
extern crate string_cache;
|
extern crate string_cache;
|
||||||
|
|
||||||
pub mod atom;
|
pub mod atom;
|
||||||
|
pub mod bloom;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod debug_utils;
|
pub mod debug_utils;
|
||||||
|
pub mod fnv;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
pub mod logical_geometry;
|
pub mod logical_geometry;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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 {
|
pub enum Namespace {
|
||||||
Null,
|
Null,
|
||||||
HTML,
|
HTML,
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub enum TimeProfilerCategory {
|
||||||
CompositingCategory,
|
CompositingCategory,
|
||||||
LayoutQueryCategory,
|
LayoutQueryCategory,
|
||||||
LayoutPerformCategory,
|
LayoutPerformCategory,
|
||||||
|
LayoutMaxSelectorMatchesCategory,
|
||||||
LayoutStyleRecalcCategory,
|
LayoutStyleRecalcCategory,
|
||||||
LayoutSelectorMatchCategory,
|
LayoutSelectorMatchCategory,
|
||||||
LayoutTreeBuilderCategory,
|
LayoutTreeBuilderCategory,
|
||||||
|
@ -66,6 +67,7 @@ impl TimeProfilerCategory {
|
||||||
buckets.insert(CompositingCategory, vec!());
|
buckets.insert(CompositingCategory, vec!());
|
||||||
buckets.insert(LayoutQueryCategory, vec!());
|
buckets.insert(LayoutQueryCategory, vec!());
|
||||||
buckets.insert(LayoutPerformCategory, vec!());
|
buckets.insert(LayoutPerformCategory, vec!());
|
||||||
|
buckets.insert(LayoutMaxSelectorMatchesCategory, vec!());
|
||||||
buckets.insert(LayoutStyleRecalcCategory, vec!());
|
buckets.insert(LayoutStyleRecalcCategory, vec!());
|
||||||
buckets.insert(LayoutSelectorMatchCategory, vec!());
|
buckets.insert(LayoutSelectorMatchCategory, vec!());
|
||||||
buckets.insert(LayoutTreeBuilderCategory, 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]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a"
|
source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf 0.0.0 (git+https://github.com/sfackler/rust-phf#fa5d803428dd760287330571c919c7c5e11b2e1b)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "string_cache_macros"
|
name = "string_cache_macros"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/servo/string-cache#6d5a6669d05fcc20fe0436a7f9a63310e2bc522a"
|
source = "git+https://github.com/servo/string-cache#3e5a5178088729772d95fe8f1450511497e08289"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "style"
|
name = "style"
|
||||||
|
@ -487,7 +487,7 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"azure 0.1.0 (git+https://github.com/servo/rust-azure#9c5567b79d8b87e8ef3b48c5842f453978035d21)",
|
"azure 0.1.0 (git+https://github.com/servo/rust-azure#9c5567b79d8b87e8ef3b48c5842f453978035d21)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)",
|
"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",
|
"task_info 0.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -495,4 +495,3 @@ dependencies = [
|
||||||
name = "xlib"
|
name = "xlib"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/servo/rust-xlib#79904fb42ff8a0e888f70fae336fbf6c11f1e6c8"
|
source = "git+https://github.com/servo/rust-xlib#79904fb42ff8a0e888f70fae336fbf6c11f1e6c8"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue