Auto merge of #9110 - bholley:hoist_matching_and_animation, r=SimonSapin

Hoist the rest of css/matching.rs (and its dependencies) into style/.

Slowly but surely.

This goes on top of #9103.

Fixes #9103.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/9110)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-01-04 23:34:35 +05:30
commit f3075d1319
20 changed files with 762 additions and 865 deletions

View file

@ -6,54 +6,16 @@
use flow::{self, Flow};
use gfx::display_list::OpaqueNode;
use incremental::{self, RestyleDamage};
use incremental::RestyleDamage;
use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId};
use script::layout_interface::Animation;
use script_traits::LayoutMsg as ConstellationMsg;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::{Arc, Mutex};
use style::animation::{GetMod, PropertyAnimation};
use style::properties::ComputedValues;
use std::sync::mpsc::Receiver;
use style::animation::update_style_for_animation;
use time;
/// Inserts transitions into the queue of running animations as applicable for the given style
/// difference. This is called from the layout worker threads. Returns true if any animations were
/// kicked off and false otherwise.
pub fn start_transitions_if_applicable(new_animations_sender: &Mutex<Sender<Animation>>,
node: OpaqueNode,
old_style: &ComputedValues,
new_style: &mut ComputedValues)
-> bool {
let mut had_animations = false;
for i in 0..new_style.get_animation().transition_property.0.len() {
// Create any property animations, if applicable.
let property_animations = PropertyAnimation::from_transition(i, old_style, new_style);
for property_animation in property_animations {
// Set the property to the initial value.
property_animation.update(new_style, 0.0);
// Kick off the animation.
let now = time::precise_time_s();
let animation_style = new_style.get_animation();
let start_time =
now + (animation_style.transition_delay.0.get_mod(i).seconds() as f64);
new_animations_sender.lock().unwrap().send(Animation {
node: node,
property_animation: property_animation,
start_time: start_time,
end_time: start_time +
(animation_style.transition_duration.0.get_mod(i).seconds() as f64),
}).unwrap();
had_animations = true
}
}
had_animations
}
/// Processes any new animations that were discovered after style recalculation.
/// Also expire any old animations that have completed, inserting them into `expired_animations`.
pub fn update_animation_state(constellation_chan: &ConstellationChan<ConstellationMsg>,
@ -139,26 +101,3 @@ pub fn recalc_style_for_animations(flow: &mut Flow,
recalc_style_for_animations(kid, animations)
}
}
/// Updates a single animation and associated style based on the current time. If `damage` is
/// provided, inserts the appropriate restyle damage.
pub fn update_style_for_animation(animation: &Animation,
style: &mut Arc<ComputedValues>,
damage: Option<&mut RestyleDamage>) {
let now = time::precise_time_s();
let mut progress = (now - animation.start_time) / animation.duration();
if progress > 1.0 {
progress = 1.0
}
if progress <= 0.0 {
return
}
let mut new_style = (*style).clone();
animation.property_animation.update(&mut *Arc::make_mut(&mut new_style), progress);
if let Some(damage) = damage {
damage.insert(incremental::compute_damage(&Some((*style).clone()), &new_style));
}
*style = new_style
}

View file

@ -1,432 +0,0 @@
/* 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/. */
//! High-level interface to CSS selector matching.
#![allow(unsafe_code)]
use animation;
use context::SharedLayoutContext;
use data::PrivateLayoutData;
use incremental::{self, RestyleDamage};
use msg::ParseErrorReporter;
use script::layout_interface::Animation;
use selectors::bloom::BloomFilter;
use selectors::parser::PseudoElement;
use selectors::{Element};
use std::mem::transmute;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use style::data::PrivateStyleData;
use style::dom::{TElement, TNode};
use style::matching::{ApplicableDeclarations, ApplicableDeclarationsCache};
use style::matching::{StyleSharingCandidate, StyleSharingCandidateCache};
use style::properties::{ComputedValues, cascade};
use style::selector_matching::{DeclarationBlock, Stylist};
use util::arc_ptr_eq;
use util::opts;
/// The results of attempting to share a style.
pub enum StyleSharingResult {
/// We didn't find anybody to share the style with.
CannotShare,
/// The node's style can be shared. The integer specifies the index in the LRU cache that was
/// hit and the damage that was done.
StyleWasShared(usize, RestyleDamage),
}
pub trait ElementMatchMethods<'le, ConcreteElement: TElement<'le>> {
fn match_element(&self,
stylist: &Stylist,
parent_bf: Option<&BloomFilter>,
applicable_declarations: &mut ApplicableDeclarations)
-> bool;
/// Attempts to share a style with another node. This method is unsafe because it depends on
/// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to
/// guarantee that at the type system level yet.
unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache:
&mut StyleSharingCandidateCache,
parent: Option<ConcreteElement::ConcreteNode>)
-> StyleSharingResult;
}
pub trait MatchMethods<'ln, ConcreteNode: TNode<'ln>> {
/// 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);
unsafe fn cascade_node(&self,
layout_context: &SharedLayoutContext,
parent: Option<ConcreteNode>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
new_animations_sender: &Mutex<Sender<Animation>>);
}
trait PrivateMatchMethods {
fn cascade_node_pseudo_element(&self,
layout_context: &SharedLayoutContext,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache:
&mut ApplicableDeclarationsCache,
new_animations_sender: &Mutex<Sender<Animation>>,
shareable: bool,
animate_properties: bool)
-> RestyleDamage;
fn update_animations_for_cascade(&self,
layout_context: &SharedLayoutContext,
style: &mut Option<Arc<ComputedValues>>)
-> bool;
}
trait PrivateElementMatchMethods<'le, ConcreteElement: TElement<'le>> {
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<ConcreteElement::ConcreteNode>,
candidate: &StyleSharingCandidate)
-> Option<Arc<ComputedValues>>;
}
impl<'ln, ConcreteNode> PrivateMatchMethods for ConcreteNode
where ConcreteNode: TNode<'ln> {
fn cascade_node_pseudo_element(&self,
layout_context: &SharedLayoutContext,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache:
&mut ApplicableDeclarationsCache,
new_animations_sender: &Mutex<Sender<Animation>>,
shareable: bool,
animate_properties: bool)
-> RestyleDamage {
let mut cacheable = true;
if animate_properties {
cacheable = !self.update_animations_for_cascade(layout_context, style) && cacheable;
}
let mut this_style;
match parent_style {
Some(ref parent_style) => {
let cache_entry = applicable_declarations_cache.find(applicable_declarations);
let cached_computed_values = match cache_entry {
None => None,
Some(ref style) => Some(&**style),
};
let (the_style, is_cacheable) = cascade(layout_context.style_context.viewport_size,
applicable_declarations,
shareable,
Some(&***parent_style),
cached_computed_values,
layout_context.style_context.error_reporter.clone());
cacheable = cacheable && is_cacheable;
this_style = the_style
}
None => {
let (the_style, is_cacheable) = cascade(layout_context.style_context.viewport_size,
applicable_declarations,
shareable,
None,
None,
layout_context.style_context.error_reporter.clone());
cacheable = cacheable && is_cacheable;
this_style = the_style
}
};
// Trigger transitions if necessary. This will reset `this_style` back to its old value if
// it did trigger a transition.
if animate_properties {
if let Some(ref style) = *style {
let animations_started =
animation::start_transitions_if_applicable(new_animations_sender,
self.opaque(),
&**style,
&mut this_style);
cacheable = cacheable && !animations_started
}
}
// Calculate style difference.
let this_style = Arc::new(this_style);
let damage = incremental::compute_damage(style, &*this_style);
// Cache the resolved style if it was cacheable.
if cacheable {
applicable_declarations_cache.insert(applicable_declarations.to_vec(),
this_style.clone());
}
// Write in the final style and return the damage done to our caller.
*style = Some(this_style);
damage
}
fn update_animations_for_cascade(&self,
layout_context: &SharedLayoutContext,
style: &mut Option<Arc<ComputedValues>>)
-> bool {
let style = match *style {
None => return false,
Some(ref mut style) => style,
};
// Finish any expired transitions.
let this_opaque = self.opaque();
let had_animations_to_expire;
{
let all_expired_animations = layout_context.style_context.expired_animations.read().unwrap();
let animations_to_expire = all_expired_animations.get(&this_opaque);
had_animations_to_expire = animations_to_expire.is_some();
if let Some(ref animations) = animations_to_expire {
for animation in *animations {
animation.property_animation.update(&mut *Arc::make_mut(style), 1.0);
}
}
}
if had_animations_to_expire {
layout_context.style_context.expired_animations.write().unwrap().remove(&this_opaque);
}
// Merge any running transitions into the current style, and cancel them.
let had_running_animations = layout_context.style_context
.running_animations
.read()
.unwrap()
.get(&this_opaque)
.is_some();
if had_running_animations {
let mut all_running_animations = layout_context.style_context.running_animations.write().unwrap();
for running_animation in all_running_animations.get(&this_opaque).unwrap() {
animation::update_style_for_animation(running_animation, style, None);
}
all_running_animations.remove(&this_opaque);
}
had_animations_to_expire || had_running_animations
}
}
impl<'le, ConcreteElement> PrivateElementMatchMethods<'le, ConcreteElement>
for ConcreteElement
where ConcreteElement: TElement<'le> {
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<ConcreteElement::ConcreteNode>,
candidate: &StyleSharingCandidate)
-> Option<Arc<ComputedValues>> {
let parent_node = match parent_node {
Some(ref parent_node) if parent_node.as_element().is_some() => parent_node,
Some(_) | None => return None,
};
let parent_data: Option<&PrivateStyleData> = unsafe {
parent_node.borrow_data_unchecked().map(|d| &*d)
};
if let Some(parent_data_ref) = parent_data {
// Check parent style.
let parent_style = (*parent_data_ref).style.as_ref().unwrap();
if !arc_ptr_eq(parent_style, &candidate.parent_style) {
return None
}
// Check tag names, classes, etc.
if !candidate.can_share_style_with(self) {
return None
}
return Some(candidate.style.clone())
}
None
}
}
impl<'le, ConcreteElement> ElementMatchMethods<'le, ConcreteElement>
for ConcreteElement
where ConcreteElement: TElement<'le> {
fn match_element(&self,
stylist: &Stylist,
parent_bf: Option<&BloomFilter>,
applicable_declarations: &mut ApplicableDeclarations)
-> bool {
let style_attribute = self.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(PseudoElement::Before),
&mut applicable_declarations.before);
stylist.push_applicable_declarations(self,
parent_bf,
None,
Some(PseudoElement::After),
&mut applicable_declarations.after);
applicable_declarations.normal_shareable &&
applicable_declarations.before.is_empty() &&
applicable_declarations.after.is_empty()
}
unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache:
&mut StyleSharingCandidateCache,
parent: Option<ConcreteElement::ConcreteNode>)
-> StyleSharingResult {
if opts::get().disable_share_style_cache {
return StyleSharingResult::CannotShare
}
if self.style_attribute().is_some() {
return StyleSharingResult::CannotShare
}
if self.get_attr(&ns!(), &atom!("id")).is_some() {
return StyleSharingResult::CannotShare
}
for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
match self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
Some(shared_style) => {
// Yay, cache hit. Share the style.
let node = self.as_node();
let style = &mut node.mutate_data().unwrap().style;
let damage = incremental::compute_damage(style, &*shared_style);
*style = Some(shared_style);
return StyleSharingResult::StyleWasShared(i, damage)
}
None => {}
}
}
StyleSharingResult::CannotShare
}
}
impl<'ln, ConcreteNode> MatchMethods<'ln, ConcreteNode>
for ConcreteNode
where ConcreteNode: TNode<'ln> {
// 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:
// - `SimpleSelector::LocalName`
// - `SimpleSelector::Namepace`
// - `SimpleSelector::ID`
// - `SimpleSelector::Class`
fn insert_into_bloom_filter(&self, bf: &mut BloomFilter) {
// Only elements are interesting.
if let Some(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.each_class(|class| bf.insert(class));
}
}
fn remove_from_bloom_filter(&self, bf: &mut BloomFilter) {
// Only elements are interesting.
if let Some(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.each_class(|class| bf.remove(class));
}
}
unsafe fn cascade_node(&self,
layout_context: &SharedLayoutContext,
parent: Option<ConcreteNode>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
new_animations_sender: &Mutex<Sender<Animation>>) {
// Get our parent's style. This must be unsafe so that we don't touch the parent's
// borrow flags.
//
// FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
// enforced safe, race-free access to the parent style.
let parent_style = match parent {
None => None,
Some(parent_node) => {
let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap();
Some(parent_style)
}
};
let mut data_ref = self.mutate_data().unwrap();
let mut data = &mut *data_ref;
if self.is_text_node() {
// Text nodes get a copy of the parent style. This ensures
// that during fragment construction any non-inherited
// CSS properties (such as vertical-align) are correctly
// set on the fragment(s).
let cloned_parent_style = parent_style.unwrap().clone();
data.style = Some(cloned_parent_style);
} else {
let mut damage = self.cascade_node_pseudo_element(
layout_context,
parent_style,
&applicable_declarations.normal,
&mut data.style,
applicable_declarations_cache,
new_animations_sender,
applicable_declarations.normal_shareable,
true);
if !applicable_declarations.before.is_empty() {
damage = damage | self.cascade_node_pseudo_element(
layout_context,
Some(data.style.as_ref().unwrap()),
&*applicable_declarations.before,
&mut data.before_style,
applicable_declarations_cache,
new_animations_sender,
false,
false);
}
if !applicable_declarations.after.is_empty() {
damage = damage | self.cascade_node_pseudo_element(
layout_context,
Some(data.style.as_ref().unwrap()),
&*applicable_declarations.after,
&mut data.after_style,
applicable_declarations_cache,
new_animations_sender,
false,
false);
}
// FIXME(bholley): This is the only dependency in this file on non-style
// stuff.
let layout_data: &mut PrivateLayoutData = transmute(data);
layout_data.restyle_damage = damage;
}
}
}

View file

@ -35,7 +35,7 @@ use flow_ref::{self, FlowRef, WeakFlowRef};
use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
use gfx::display_list::{ClippingRegion, DisplayList};
use gfx_traits::LayerId;
use incremental::{self, RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage};
use incremental::{RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage};
use inline::InlineFlow;
use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo};
use msg::compositor_msg::LayerType;
@ -48,6 +48,7 @@ use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::{fmt, mem, raw};
use style::computed_values::{clear, display, empty_cells, float, position, text_align};
use style::dom::TRestyleDamage;
use style::properties::{self, ComputedValues};
use style::values::computed::LengthOrPercentageOrAuto;
use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, TableFlow};
@ -1046,7 +1047,7 @@ impl BaseFlow {
}
// New flows start out as fully damaged.
let mut damage = incremental::rebuild_and_reflow();
let mut damage = RestyleDamage::rebuild_and_reflow();
damage.remove(RECONSTRUCT_FLOW);
BaseFlow {

View file

@ -18,7 +18,7 @@ use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode};
use gfx::text::glyph::CharIndex;
use gfx::text::text_run::{TextRun, TextRunSlice};
use gfx_traits::LayerId;
use incremental::{self, RECONSTRUCT_FLOW, RestyleDamage};
use incremental::{RECONSTRUCT_FLOW, RestyleDamage};
use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFragmentContext, InlineFragmentNodeInfo};
use inline::{InlineMetrics, LAST_FRAGMENT_OF_ELEMENT};
use ipc_channel::ipc::IpcSender;
@ -40,6 +40,7 @@ use style::computed_values::content::ContentItem;
use style::computed_values::{border_collapse, clear, display, mix_blend_mode, overflow_wrap};
use style::computed_values::{overflow_x, position, text_decoration, transform_style};
use style::computed_values::{white_space, word_break, z_index};
use style::dom::TRestyleDamage;
use style::properties::ComputedValues;
use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto};
use style::values::computed::{LengthOrPercentageOrNone};
@ -833,7 +834,7 @@ impl Fragment {
self.border_box.start,
size);
let mut restyle_damage = incremental::rebuild_and_reflow();
let mut restyle_damage = RestyleDamage::rebuild_and_reflow();
restyle_damage.remove(RECONSTRUCT_FLOW);
Fragment {

View file

@ -13,12 +13,13 @@ use flow::{InorderFlowTraversal};
use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, ImmutableFlowUtils};
use fragment::{Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
use gfx::display_list::OpaqueNode;
use incremental::{self, RESOLVE_GENERATED_CONTENT};
use incremental::{RESOLVE_GENERATED_CONTENT, RestyleDamage};
use smallvec::SmallVec;
use std::collections::{HashMap, LinkedList};
use std::sync::Arc;
use style::computed_values::content::ContentItem;
use style::computed_values::{display, list_style_type};
use style::dom::TRestyleDamage;
use style::properties::ComputedValues;
use text::TextRunScanner;
use wrapper::PseudoElementType;
@ -431,7 +432,7 @@ fn render_text(layout_context: &LayoutContext,
fragments.push_back(Fragment::from_opaque_node_and_style(node,
pseudo,
style,
incremental::rebuild_and_reflow(),
RestyleDamage::rebuild_and_reflow(),
info));
// FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen
// due to text run splitting.

View file

@ -6,6 +6,7 @@ use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, IS_ABSO
use std::fmt;
use std::sync::Arc;
use style::computed_values::float;
use style::dom::TRestyleDamage;
use style::properties::ComputedValues;
bitflags! {
@ -47,6 +48,18 @@ bitflags! {
}
}
impl TRestyleDamage for RestyleDamage {
fn compute(old: &Option<Arc<ComputedValues>>, new: &ComputedValues) -> RestyleDamage { compute_damage(old, new) }
/// Returns a bitmask that represents a flow that needs to be rebuilt and reflowed.
///
/// Use this instead of `RestyleDamage::all()` because `RestyleDamage::all()` will result in
/// unnecessary sequential resolution of generated content.
fn rebuild_and_reflow() -> RestyleDamage {
REPAINT | BUBBLE_ISIZES | REFLOW_OUT_OF_FLOW | REFLOW | RECONSTRUCT_FLOW
}
}
impl RestyleDamage {
/// Supposing a flow has the given `position` property and this damage, returns the damage that
@ -130,18 +143,10 @@ macro_rules! add_if_not_equal(
})
);
/// Returns a bitmask that represents a flow that needs to be rebuilt and reflowed.
///
/// Use this instead of `RestyleDamage::all()` because `RestyleDamage::all()` will result in
/// unnecessary sequential resolution of generated content.
pub fn rebuild_and_reflow() -> RestyleDamage {
REPAINT | BUBBLE_ISIZES | REFLOW_OUT_OF_FLOW | REFLOW | RECONSTRUCT_FLOW
}
pub fn compute_damage(old: &Option<Arc<ComputedValues>>, new: &ComputedValues) -> RestyleDamage {
let old: &ComputedValues =
match old.as_ref() {
None => return rebuild_and_reflow(),
None => return RestyleDamage::rebuild_and_reflow(),
Some(cv) => &**cv,
};
@ -195,7 +200,7 @@ pub fn compute_damage(old: &Option<Arc<ComputedValues>>, new: &ComputedValues) -
// If the layer requirements of this flow have changed due to the value
// of the transform, then reflow is required to rebuild the layers.
if old.transform_requires_layer() != new.transform_requires_layer() {
damage.insert(rebuild_and_reflow());
damage.insert(RestyleDamage::rebuild_and_reflow());
}
// FIXME: test somehow that we checked every CSS property
@ -256,7 +261,7 @@ impl<'a> LayoutDamageComputation for &'a mut Flow {
fn reflow_entire_document(self) {
let self_base = flow::mut_base(self);
self_base.restyle_damage.insert(rebuild_and_reflow());
self_base.restyle_damage.insert(RestyleDamage::rebuild_and_reflow());
self_base.restyle_damage.remove(RECONSTRUCT_FLOW);
for kid in self_base.children.iter_mut() {
kid.reflow_entire_document();

View file

@ -69,6 +69,7 @@ use style::dom::{TDocument, TElement, TNode};
use style::media_queries::{Device, MediaType};
use style::selector_matching::{Stylist, USER_OR_USER_AGENT_STYLESHEETS};
use style::stylesheets::{CSSRuleIteratorExt, Stylesheet};
use traversal::RecalcStyleAndConstructFlows;
use url::Url;
use util::geometry::MAX_RECT;
use util::ipc::OptionalIpcSender;
@ -1024,10 +1025,12 @@ impl LayoutTask {
// Perform CSS selector matching and flow construction.
match self.parallel_traversal {
None => {
sequential::traverse_dom_preorder(node, &shared_layout_context);
sequential::traverse_dom_preorder::<ServoLayoutNode, RecalcStyleAndConstructFlows>(
node, &shared_layout_context);
}
Some(ref mut traversal) => {
parallel::traverse_dom_preorder(node, &shared_layout_context, traversal);
parallel::traverse_dom_preorder::<ServoLayoutNode, RecalcStyleAndConstructFlows>(
node, &shared_layout_context, traversal);
}
}
});

View file

@ -96,7 +96,3 @@ mod table_wrapper;
mod text;
mod traversal;
mod wrapper;
mod css {
pub mod matching;
}

View file

@ -19,8 +19,7 @@ use style::dom::UnsafeNode;
use traversal::PostorderNodeMutTraversal;
use traversal::{AssignBSizesAndStoreOverflow, AssignISizes, BubbleISizes};
use traversal::{BuildDisplayList, ComputeAbsolutePositions};
use traversal::{ConstructFlows, RecalcStyleForNode};
use traversal::{PostorderDomTraversal, PreorderDomTraversal};
use traversal::{DomTraversal, DomTraversalContext};
use util::opts;
use util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
use wrapper::LayoutNode;
@ -73,105 +72,6 @@ pub type ChunkedFlowTraversalFunction =
pub type FlowTraversalFunction = extern "Rust" fn(UnsafeFlow, &SharedLayoutContext);
/// A parallel top-down DOM traversal.
pub trait ParallelPreorderDomTraversal<'ln, ConcreteLayoutNode>
: PreorderDomTraversal<'ln, ConcreteLayoutNode>
where ConcreteLayoutNode: LayoutNode<'ln> {
fn run_parallel(&self,
nodes: UnsafeNodeList,
proxy: &mut WorkerProxy<SharedLayoutContext, UnsafeNodeList>);
#[inline(always)]
fn run_parallel_helper(
&self,
unsafe_nodes: UnsafeNodeList,
proxy: &mut WorkerProxy<SharedLayoutContext, UnsafeNodeList>,
top_down_func: ChunkedDomTraversalFunction,
bottom_up_func: DomTraversalFunction) {
let mut discovered_child_nodes = Vec::new();
for unsafe_node in *unsafe_nodes.0 {
// Get a real layout node.
let node = unsafe { ConcreteLayoutNode::from_unsafe(&unsafe_node) };
// Perform the appropriate traversal.
self.process(node);
let child_count = node.children_count();
// Reset the count of children.
{
let data = node.mutate_data().unwrap();
data.parallel.children_count.store(child_count as isize,
Ordering::Relaxed);
}
// Possibly enqueue the children.
if child_count != 0 {
for kid in node.children() {
discovered_child_nodes.push(kid.to_unsafe())
}
} else {
// If there were no more children, start walking back up.
bottom_up_func(unsafe_nodes.1, unsafe_node, proxy)
}
}
for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) {
proxy.push(WorkUnit {
fun: top_down_func,
data: (box chunk.iter().cloned().collect(), unsafe_nodes.1),
});
}
}
}
/// A parallel bottom-up DOM traversal.
trait ParallelPostorderDomTraversal<'ln, ConcreteLayoutNode>
: PostorderDomTraversal<'ln, ConcreteLayoutNode>
where ConcreteLayoutNode: LayoutNode<'ln> {
fn root(&self) -> OpaqueNode;
/// Process current node and potentially traverse its ancestors.
///
/// If we are the last child that finished processing, recursively process
/// our parent. Else, stop. Also, stop at the root.
///
/// Thus, if we start with all the leaves of a tree, we end up traversing
/// the whole tree bottom-up because each parent will be processed exactly
/// once (by the last child that finishes processing).
///
/// The only communication between siblings is that they both
/// fetch-and-subtract the parent's children count.
fn run_parallel(&self, unsafe_node: UnsafeNode) {
// Get a real layout node.
let mut node = unsafe { ConcreteLayoutNode::from_unsafe(&unsafe_node) };
loop {
// Perform the appropriate operation.
self.process(node);
let parent = match node.layout_parent_node(self.root()) {
None => break,
Some(parent) => parent,
};
let parent_data = unsafe {
&*parent.borrow_data_unchecked().unwrap()
};
if parent_data
.parallel
.children_count
.fetch_sub(1, Ordering::Relaxed) != 1 {
// Get out of here and find another node to work on.
break
}
// We were the last child of our parent. Construct flows for our parent.
node = parent;
}
}
}
/// Information that we need stored in each flow.
pub struct FlowParallelInfo {
/// The number of children that still need work done.
@ -332,59 +232,102 @@ impl<'a> ParallelPreorderFlowTraversal for ComputeAbsolutePositions<'a> {
impl<'a> ParallelPostorderFlowTraversal for BuildDisplayList<'a> {}
impl<'a, 'ln, ConcreteLayoutNode> ParallelPostorderDomTraversal<'ln, ConcreteLayoutNode>
for ConstructFlows<'a>
where ConcreteLayoutNode: LayoutNode<'ln> {
fn root(&self) -> OpaqueNode {
self.root
}
}
impl<'a, 'ln, ConcreteLayoutNode> ParallelPreorderDomTraversal<'ln, ConcreteLayoutNode>
for RecalcStyleForNode<'a>
where ConcreteLayoutNode: LayoutNode<'ln> {
fn run_parallel(&self,
unsafe_nodes: UnsafeNodeList,
proxy: &mut WorkerProxy<SharedLayoutContext, UnsafeNodeList>) {
// Not exactly sure why we need UFCS here, but we seem to.
<RecalcStyleForNode<'a> as ParallelPreorderDomTraversal<'ln, ConcreteLayoutNode>>
::run_parallel_helper(self, unsafe_nodes, proxy,
recalc_style::<'ln, ConcreteLayoutNode>,
construct_flows::<'ln, ConcreteLayoutNode>)
}
}
fn recalc_style<'ln, ConcreteLayoutNode>(unsafe_nodes: UnsafeNodeList,
/// A parallel top-down DOM traversal.
#[inline(always)]
fn top_down_dom<'ln, N, T>(unsafe_nodes: UnsafeNodeList,
proxy: &mut WorkerProxy<SharedLayoutContext, UnsafeNodeList>)
where ConcreteLayoutNode: LayoutNode<'ln> {
where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> {
let shared_layout_context = proxy.user_data();
let layout_context = LayoutContext::new(shared_layout_context);
let recalc_style_for_node_traversal = RecalcStyleForNode {
let traversal_context = DomTraversalContext {
layout_context: &layout_context,
root: unsafe_nodes.1,
};
// The UFCS is necessary here to select the proper set of generic routines which
// will eventually cast the UnsafeNode to a concrete LayoutNode implementation.
<RecalcStyleForNode as ParallelPreorderDomTraversal<'ln, ConcreteLayoutNode>>
::run_parallel(&recalc_style_for_node_traversal, unsafe_nodes, proxy)
let mut discovered_child_nodes = Vec::new();
for unsafe_node in *unsafe_nodes.0 {
// Get a real layout node.
let node = unsafe { N::from_unsafe(&unsafe_node) };
// Perform the appropriate traversal.
T::process_preorder(&traversal_context, node);
let child_count = node.children_count();
// Reset the count of children.
{
let data = node.mutate_data().unwrap();
data.parallel.children_count.store(child_count as isize,
Ordering::Relaxed);
}
fn construct_flows<'ln, ConcreteLayoutNode: LayoutNode<'ln>>(
root: OpaqueNode,
// Possibly enqueue the children.
if child_count != 0 {
for kid in node.children() {
discovered_child_nodes.push(kid.to_unsafe())
}
} else {
// If there were no more children, start walking back up.
bottom_up_dom::<N, T>(unsafe_nodes.1, unsafe_node, proxy)
}
}
for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) {
proxy.push(WorkUnit {
fun: top_down_dom::<N, T>,
data: (box chunk.iter().cloned().collect(), unsafe_nodes.1),
});
}
}
/// Process current node and potentially traverse its ancestors.
///
/// If we are the last child that finished processing, recursively process
/// our parent. Else, stop. Also, stop at the root.
///
/// Thus, if we start with all the leaves of a tree, we end up traversing
/// the whole tree bottom-up because each parent will be processed exactly
/// once (by the last child that finishes processing).
///
/// The only communication between siblings is that they both
/// fetch-and-subtract the parent's children count.
fn bottom_up_dom<'ln, N, T>(root: OpaqueNode,
unsafe_node: UnsafeNode,
proxy: &mut WorkerProxy<SharedLayoutContext, UnsafeNodeList>) {
proxy: &mut WorkerProxy<SharedLayoutContext, UnsafeNodeList>)
where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> {
let shared_layout_context = proxy.user_data();
let layout_context = LayoutContext::new(shared_layout_context);
let construct_flows_traversal = ConstructFlows {
let traversal_context = DomTraversalContext {
layout_context: &layout_context,
root: root,
};
// The UFCS is necessary here to select the proper set of generic routines which
// will eventually cast the UnsafeNode to a concrete LayoutNode implementation.
<ConstructFlows as ParallelPostorderDomTraversal<'ln, ConcreteLayoutNode>>
::run_parallel(&construct_flows_traversal, unsafe_node)
// Get a real layout node.
let mut node = unsafe { N::from_unsafe(&unsafe_node) };
loop {
// Perform the appropriate operation.
T::process_postorder(&traversal_context, node);
let parent = match node.layout_parent_node(traversal_context.root) {
None => break,
Some(parent) => parent,
};
let parent_data = unsafe {
&*parent.borrow_data_unchecked().unwrap()
};
if parent_data
.parallel
.children_count
.fetch_sub(1, Ordering::Relaxed) != 1 {
// Get out of here and find another node to work on.
break
}
// We were the last child of our parent. Construct flows for our parent.
node = parent;
}
}
fn assign_inline_sizes(unsafe_flows: UnsafeFlowList,
@ -441,12 +384,14 @@ fn run_queue_with_custom_work_data_type<To, F>(
queue.run(shared_layout_context);
}
pub fn traverse_dom_preorder<'ln, ConcreteLayoutNode: LayoutNode<'ln>>(root: ConcreteLayoutNode,
pub fn traverse_dom_preorder<'ln, N, T>(
root: N,
shared_layout_context: &SharedLayoutContext,
queue: &mut WorkQueue<SharedLayoutContext, WorkQueueData>) {
queue: &mut WorkQueue<SharedLayoutContext, WorkQueueData>)
where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> {
run_queue_with_custom_work_data_type(queue, |queue| {
queue.push(WorkUnit {
fun: recalc_style::<'ln, ConcreteLayoutNode>,
fun: top_down_dom::<N, T>,
data: (box vec![root.to_unsafe()], root.opaque()),
});
}, shared_layout_context);

View file

@ -14,39 +14,33 @@ use fragment::FragmentBorderBoxIterator;
use generated_content::ResolveGeneratedContent;
use traversal::PostorderNodeMutTraversal;
use traversal::{AssignBSizesAndStoreOverflow, AssignISizes};
use traversal::{BubbleISizes, ConstructFlows, RecalcStyleForNode};
use traversal::{BuildDisplayList, ComputeAbsolutePositions};
use traversal::{PostorderDomTraversal, PreorderDomTraversal};
use traversal::{BubbleISizes, BuildDisplayList, ComputeAbsolutePositions};
use traversal::{DomTraversal, DomTraversalContext};
use util::opts;
use wrapper::LayoutNode;
pub fn traverse_dom_preorder<'le, N>(root: N,
pub fn traverse_dom_preorder<'ln, N, T>(root: N,
shared_layout_context: &SharedLayoutContext)
where N: LayoutNode<'le> {
fn doit<'le, N>(node: N,
recalc_style: RecalcStyleForNode,
construct_flows: ConstructFlows)
where N: LayoutNode<'le> {
recalc_style.process(node);
where N: LayoutNode<'ln>,
T: DomTraversal<'ln, N> {
fn doit<'a, 'ln, N, T>(context: &'a DomTraversalContext<'a>, node: N)
where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> {
T::process_preorder(context, node);
for kid in node.children() {
doit(kid, recalc_style, construct_flows);
doit::<N, T>(context, kid);
}
construct_flows.process(node);
T::process_postorder(context, node);
}
let layout_context = LayoutContext::new(shared_layout_context);
let recalc_style = RecalcStyleForNode {
layout_context: &layout_context,
root: root.opaque(),
};
let construct_flows = ConstructFlows {
let traversal_context = DomTraversalContext {
layout_context: &layout_context,
root: root.opaque(),
};
doit::<'le, N>(root, recalc_style, construct_flows);
doit::<N, T>(&traversal_context, root);
}
pub fn resolve_generated_content(root: &mut FlowRef, shared_layout_context: &SharedLayoutContext) {

View file

@ -6,18 +6,17 @@
use construct::FlowConstructor;
use context::LayoutContext;
use css::matching::{ElementMatchMethods, MatchMethods, StyleSharingResult};
use flow::{PostorderFlowTraversal, PreorderFlowTraversal};
use flow::{self, Flow};
use gfx::display_list::OpaqueNode;
use incremental::{self, BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage};
use incremental::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage};
use script::layout_interface::ReflowGoal;
use selectors::bloom::BloomFilter;
use std::cell::RefCell;
use std::mem;
use style::context::StyleContext;
use style::dom::UnsafeNode;
use style::matching::ApplicableDeclarations;
use style::dom::{TRestyleDamage, UnsafeNode};
use style::matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult};
use util::opts;
use util::tid::tid;
use wrapper::{LayoutNode, ThreadSafeLayoutNode};
@ -118,17 +117,30 @@ fn insert_ancestors_into_bloom_filter<'ln, N>(bf: &mut Box<BloomFilter>,
debug!("[{}] Inserted {} ancestors.", tid(), ancestors);
}
/// A top-down traversal.
pub trait PreorderDomTraversal<'ln, ConcreteLayoutNode: LayoutNode<'ln>> {
/// The operation to perform. Return true to continue or false to stop.
fn process(&self, node: ConcreteLayoutNode);
#[derive(Copy, Clone)]
pub struct DomTraversalContext<'a> {
pub layout_context: &'a LayoutContext<'a>,
pub root: OpaqueNode,
}
/// A bottom-up traversal, with a optional in-order pass.
pub trait PostorderDomTraversal<'ln, ConcreteLayoutNode: LayoutNode<'ln>> {
/// The operation to perform. Return true to continue or false to stop.
fn process(&self, node: ConcreteLayoutNode);
pub trait DomTraversal<'ln, N: LayoutNode<'ln>> {
fn process_preorder<'a>(context: &'a DomTraversalContext<'a>, node: N);
fn process_postorder<'a>(context: &'a DomTraversalContext<'a>, node: N);
}
/// FIXME(bholley): I added this now to demonstrate the usefulness of the new design.
/// This is currently unused, but will be used shortly.
#[allow(dead_code)]
pub struct RecalcStyleOnly;
impl<'ln, N: LayoutNode<'ln>> DomTraversal<'ln, N> for RecalcStyleOnly {
fn process_preorder<'a>(context: &'a DomTraversalContext<'a>, node: N) { recalc_style_at(context, node); }
fn process_postorder<'a>(_: &'a DomTraversalContext<'a>, _: N) {}
}
pub struct RecalcStyleAndConstructFlows;
impl<'ln, N: LayoutNode<'ln>> DomTraversal<'ln, N> for RecalcStyleAndConstructFlows {
fn process_preorder<'a>(context: &'a DomTraversalContext<'a>, node: N) { recalc_style_at(context, node); }
fn process_postorder<'a>(context: &'a DomTraversalContext<'a>, node: N) { construct_flows_at(context, node); }
}
/// A bottom-up, parallelizable traversal.
@ -139,18 +151,9 @@ pub trait PostorderNodeMutTraversal<'ln, ConcreteThreadSafeLayoutNode: ThreadSaf
/// The recalc-style-for-node traversal, which styles each node and must run before
/// layout computation. This computes the styles applied to each node.
#[derive(Copy, Clone)]
pub struct RecalcStyleForNode<'a> {
pub layout_context: &'a LayoutContext<'a>,
pub root: OpaqueNode,
}
impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
for RecalcStyleForNode<'a>
where ConcreteLayoutNode: LayoutNode<'ln> {
#[inline]
#[allow(unsafe_code)]
fn process(&self, node: ConcreteLayoutNode) {
fn recalc_style_at<'a, 'ln, N: LayoutNode<'ln>> (context: &'a DomTraversalContext<'a>, node: N) {
// Initialize layout data.
//
// FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
@ -158,10 +161,10 @@ impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
node.initialize_data();
// Get the parent node.
let parent_opt = node.layout_parent_node(self.root);
let parent_opt = node.layout_parent_node(context.root);
// Get the style bloom filter.
let mut bf = take_task_local_bloom_filter(parent_opt, self.root, self.layout_context);
let mut bf = take_task_local_bloom_filter(parent_opt, context.root, context.layout_context);
let nonincremental_layout = opts::get().nonincremental_layout;
if nonincremental_layout || node.is_dirty() {
@ -174,7 +177,7 @@ impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
// Check to see whether we can share a style with someone.
let style_sharing_candidate_cache =
&mut self.layout_context.style_sharing_candidate_cache();
&mut context.layout_context.style_sharing_candidate_cache();
let sharing_result = match node.as_element() {
Some(element) => {
@ -194,7 +197,7 @@ impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
let shareable_element = match node.as_element() {
Some(element) => {
// Perform the CSS selector matching.
let stylist = unsafe { &*self.layout_context.shared_context().stylist.0 };
let stylist = unsafe { &*context.layout_context.shared_context().stylist.0 };
if element.match_element(stylist,
Some(&*bf),
&mut applicable_declarations) {
@ -205,8 +208,7 @@ impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
},
None => {
if node.has_changed() {
node.to_threadsafe().set_restyle_damage(
incremental::rebuild_and_reflow())
node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow())
}
None
},
@ -214,11 +216,11 @@ impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
// Perform the CSS cascade.
unsafe {
node.cascade_node(self.layout_context.shared,
node.cascade_node(&context.layout_context.shared.style_context,
parent_opt,
&applicable_declarations,
&mut self.layout_context.applicable_declarations_cache(),
&self.layout_context.shared_context().new_animations_sender);
&mut context.layout_context.applicable_declarations_cache(),
&context.layout_context.shared_context().new_animations_sender);
}
// Add ourselves to the LRU cache.
@ -228,7 +230,7 @@ impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
}
StyleSharingResult::StyleWasShared(index, damage) => {
style_sharing_candidate_cache.touch(index);
node.to_threadsafe().set_restyle_damage(damage);
node.set_restyle_damage(damage);
}
}
}
@ -241,23 +243,13 @@ impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode>
node.insert_into_bloom_filter(&mut *bf);
// NB: flow construction updates the bloom filter on the way up.
put_task_local_bloom_filter(bf, &unsafe_layout_node, self.layout_context);
}
put_task_local_bloom_filter(bf, &unsafe_layout_node, context.layout_context);
}
/// The flow construction traversal, which builds flows for styled nodes.
#[derive(Copy, Clone)]
pub struct ConstructFlows<'a> {
pub layout_context: &'a LayoutContext<'a>,
pub root: OpaqueNode,
}
impl<'a, 'ln, ConcreteLayoutNode> PostorderDomTraversal<'ln, ConcreteLayoutNode>
for ConstructFlows<'a>
where ConcreteLayoutNode: LayoutNode<'ln> {
#[inline]
#[allow(unsafe_code)]
fn process(&self, node: ConcreteLayoutNode) {
fn construct_flows_at<'a, 'ln, N: LayoutNode<'ln>>(context: &'a DomTraversalContext<'a>, node: N) {
// Construct flows for this node.
{
let tnode = node.to_threadsafe();
@ -265,7 +257,7 @@ impl<'a, 'ln, ConcreteLayoutNode> PostorderDomTraversal<'ln, ConcreteLayoutNode>
// Always reconstruct if incremental layout is turned off.
let nonincremental_layout = opts::get().nonincremental_layout;
if nonincremental_layout || node.has_dirty_descendants() {
let mut flow_constructor = FlowConstructor::new(self.layout_context);
let mut flow_constructor = FlowConstructor::new(context.layout_context);
if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
flow_constructor.process(&tnode);
debug!("Constructed flow for {:x}: {:x}",
@ -294,9 +286,9 @@ impl<'a, 'ln, ConcreteLayoutNode> PostorderDomTraversal<'ln, ConcreteLayoutNode>
});
assert_eq!(old_node, unsafe_layout_node);
assert_eq!(old_generation, self.layout_context.shared_context().generation);
assert_eq!(old_generation, context.layout_context.shared_context().generation);
match node.layout_parent_node(self.root) {
match node.layout_parent_node(context.root) {
None => {
debug!("[{}] - {:X}, and deleting BF.", tid(), unsafe_layout_node.0);
// If this is the reflow root, eat the task-local bloom filter.
@ -305,11 +297,10 @@ impl<'a, 'ln, ConcreteLayoutNode> PostorderDomTraversal<'ln, ConcreteLayoutNode>
// Otherwise, put it back, but remove this node.
node.remove_from_bloom_filter(&mut *bf);
let unsafe_parent = parent.to_unsafe();
put_task_local_bloom_filter(bf, &unsafe_parent, self.layout_context);
put_task_local_bloom_filter(bf, &unsafe_parent, context.layout_context);
},
};
}
}
/// The bubble-inline-sizes traversal, the first part of layout computation. This computes
/// preferred and intrinsic inline-sizes and bubbles them up the tree.

View file

@ -132,6 +132,7 @@ impl<'ln> ServoLayoutNode<'ln> {
impl<'ln> TNode<'ln> for ServoLayoutNode<'ln> {
type ConcreteElement = ServoLayoutElement<'ln>;
type ConcreteDocument = ServoLayoutDocument<'ln>;
type ConcreteRestyleDamage = RestyleDamage;
fn to_unsafe(&self) -> UnsafeNode {
unsafe {
@ -237,6 +238,14 @@ impl<'ln> TNode<'ln> for ServoLayoutNode<'ln> {
unsafe { self.mutate_layout_data().map(|d| transmute(d)) }
}
fn restyle_damage(self) -> RestyleDamage {
self.borrow_layout_data().unwrap().restyle_damage
}
fn set_restyle_damage(self, damage: RestyleDamage) {
self.mutate_layout_data().unwrap().restyle_damage = damage;
}
fn parent_node(&self) -> Option<ServoLayoutNode<'ln>> {
unsafe {
self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node))
@ -714,16 +723,9 @@ pub trait ThreadSafeLayoutNode<'ln> : Clone + Copy + Sized {
fn is_ignorable_whitespace(&self) -> bool;
/// Get the description of how to account for recent style changes.
/// This is a simple bitfield and fine to copy by value.
fn restyle_damage(self) -> RestyleDamage {
self.borrow_layout_data().unwrap().restyle_damage
}
fn restyle_damage(self) -> RestyleDamage;
/// Set the restyle damage field.
fn set_restyle_damage(self, damage: RestyleDamage) {
self.mutate_layout_data().unwrap().restyle_damage = damage;
}
fn set_restyle_damage(self, damage: RestyleDamage);
/// Returns the layout data flags for this node.
fn flags(self) -> LayoutDataFlags;
@ -909,6 +911,14 @@ impl<'ln> ThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> {
}
}
fn restyle_damage(self) -> RestyleDamage {
self.node.restyle_damage()
}
fn set_restyle_damage(self, damage: RestyleDamage) {
self.node.set_restyle_damage(damage)
}
fn flags(self) -> LayoutDataFlags {
unsafe {
(*self.node.borrow_layout_data_unchecked().unwrap()).flags

View file

@ -1790,6 +1790,7 @@ dependencies = [
"smallvec 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"style_traits 0.0.1",
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
]

View file

@ -38,5 +38,6 @@ string_cache = "0.2"
euclid = {version = "0.4", features = ["plugins"]}
serde = "0.6"
serde_macros = "0.6"
time = "0.1"
url = "0.5.2"

View file

@ -4,7 +4,7 @@
use app_units::Au;
use cssparser::{Color, RGBA};
use dom::OpaqueNode;
use dom::{OpaqueNode, TRestyleDamage};
use euclid::point::Point2D;
use properties::ComputedValues;
use properties::longhands::background_position::computed_value::T as BackgroundPosition;
@ -26,6 +26,9 @@ use properties::longhands::visibility::computed_value::T as Visibility;
use properties::longhands::z_index::computed_value::T as ZIndex;
use std::cmp::Ordering;
use std::iter::repeat;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use time;
use util::bezier::Bezier;
use values::CSSFloat;
use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
@ -922,3 +925,62 @@ impl<T> GetMod for Vec<T> {
&(*self)[i % self.len()]
}
}
/// Inserts transitions into the queue of running animations as applicable for the given style
/// difference. This is called from the layout worker threads. Returns true if any animations were
/// kicked off and false otherwise.
pub fn start_transitions_if_applicable(new_animations_sender: &Mutex<Sender<Animation>>,
node: OpaqueNode,
old_style: &ComputedValues,
new_style: &mut ComputedValues)
-> bool {
let mut had_animations = false;
for i in 0..new_style.get_animation().transition_property.0.len() {
// Create any property animations, if applicable.
let property_animations = PropertyAnimation::from_transition(i, old_style, new_style);
for property_animation in property_animations {
// Set the property to the initial value.
property_animation.update(new_style, 0.0);
// Kick off the animation.
let now = time::precise_time_s();
let animation_style = new_style.get_animation();
let start_time =
now + (animation_style.transition_delay.0.get_mod(i).seconds() as f64);
new_animations_sender.lock().unwrap().send(Animation {
node: node,
property_animation: property_animation,
start_time: start_time,
end_time: start_time +
(animation_style.transition_duration.0.get_mod(i).seconds() as f64),
}).unwrap();
had_animations = true
}
}
had_animations
}
/// Updates a single animation and associated style based on the current time. If `damage` is
/// provided, inserts the appropriate restyle damage.
pub fn update_style_for_animation<ConcreteRestyleDamage: TRestyleDamage>(animation: &Animation,
style: &mut Arc<ComputedValues>,
damage: Option<&mut ConcreteRestyleDamage>) {
let now = time::precise_time_s();
let mut progress = (now - animation.start_time) / animation.duration();
if progress > 1.0 {
progress = 1.0
}
if progress <= 0.0 {
return
}
let mut new_style = (*style).clone();
animation.property_animation.update(&mut *Arc::make_mut(&mut new_style), progress);
if let Some(damage) = damage {
*damage = *damage | ConcreteRestyleDamage::compute(&Some((*style).clone()), &new_style);
}
*style = new_style
}

View file

@ -5,13 +5,15 @@
#![allow(unsafe_code)]
use data::PrivateStyleData;
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock};
use restyle_hints::{ElementSnapshot, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
use selectors::matching::DeclarationBlock;
use selectors::states::ElementState;
use smallvec::VecLike;
use std::cell::{Ref, RefMut};
use std::marker::PhantomData;
use std::ops::BitOr;
use std::sync::Arc;
use string_cache::{Atom, Namespace};
/// Opaque type stored in type-unsafe work queues for parallel layout.
@ -39,10 +41,15 @@ impl OpaqueNode {
}
}
pub trait TRestyleDamage : BitOr<Output=Self> + Copy {
fn compute(old: &Option<Arc<ComputedValues>>, new: &ComputedValues) -> Self;
fn rebuild_and_reflow() -> Self;
}
pub trait TNode<'ln> : Sized + Copy + Clone {
type ConcreteElement: TElement<'ln, ConcreteNode = Self, ConcreteDocument = Self::ConcreteDocument>;
type ConcreteDocument: TDocument<'ln, ConcreteNode = Self, ConcreteElement = Self::ConcreteElement>;
type ConcreteRestyleDamage: TRestyleDamage;
fn to_unsafe(&self) -> UnsafeNode;
unsafe fn from_unsafe(n: &UnsafeNode) -> Self;
@ -134,6 +141,12 @@ pub trait TNode<'ln> : Sized + Copy + Clone {
#[inline(always)]
fn mutate_data(&self) -> Option<RefMut<PrivateStyleData>>;
/// Get the description of how to account for recent style changes.
fn restyle_damage(self) -> Self::ConcreteRestyleDamage;
/// Set the restyle damage field.
fn set_restyle_damage(self, damage: Self::ConcreteRestyleDamage);
fn parent_node(&self) -> Option<Self>;
fn first_child(&self) -> Option<Self>;

View file

@ -40,6 +40,7 @@ extern crate smallvec;
#[macro_use(atom, ns)] extern crate string_cache;
#[macro_use]
extern crate style_traits;
extern crate time;
extern crate url;
extern crate util;

View file

@ -2,22 +2,31 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::{TElement, TNode};
use properties::{ComputedValues, PropertyDeclaration};
use selector_matching::DeclarationBlock;
#![allow(unsafe_code)]
use animation::{self, Animation};
use context::SharedStyleContext;
use data::PrivateStyleData;
use dom::{TElement, TNode, TRestyleDamage};
use properties::{ComputedValues, PropertyDeclaration, cascade};
use selector_matching::{DeclarationBlock, Stylist};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::{CommonStyleAffectingAttributeMode, CommonStyleAffectingAttributes};
use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
use selectors::parser::PseudoElement;
use smallvec::SmallVec;
use std::hash::{Hash, Hasher};
use std::slice::Iter;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use string_cache::{Atom, Namespace};
use util::arc_ptr_eq;
use util::cache::{LRUCache, SimpleHashCache};
use util::opts;
use util::vec::ForgetfulSink;
/// Pieces of layout/css/matching.rs, which will eventually be merged
/// into this file.
/// High-level interface to CSS selector matching.
fn create_common_style_affecting_attributes_from_element<'le, E: TElement<'le>>(element: &E)
-> CommonStyleAffectingAttributes {
@ -341,4 +350,358 @@ impl StyleSharingCandidateCache {
}
}
/// The results of attempting to share a style.
pub enum StyleSharingResult<ConcreteRestyleDamage: TRestyleDamage> {
/// We didn't find anybody to share the style with.
CannotShare,
/// The node's style can be shared. The integer specifies the index in the LRU cache that was
/// hit and the damage that was done.
StyleWasShared(usize, ConcreteRestyleDamage),
}
trait PrivateMatchMethods<'ln>: TNode<'ln> {
fn cascade_node_pseudo_element(&self,
context: &SharedStyleContext,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache:
&mut ApplicableDeclarationsCache,
new_animations_sender: &Mutex<Sender<Animation>>,
shareable: bool,
animate_properties: bool)
-> Self::ConcreteRestyleDamage {
let mut cacheable = true;
if animate_properties {
cacheable = !self.update_animations_for_cascade(context, style) && cacheable;
}
let mut this_style;
match parent_style {
Some(ref parent_style) => {
let cache_entry = applicable_declarations_cache.find(applicable_declarations);
let cached_computed_values = match cache_entry {
None => None,
Some(ref style) => Some(&**style),
};
let (the_style, is_cacheable) = cascade(context.viewport_size,
applicable_declarations,
shareable,
Some(&***parent_style),
cached_computed_values,
context.error_reporter.clone());
cacheable = cacheable && is_cacheable;
this_style = the_style
}
None => {
let (the_style, is_cacheable) = cascade(context.viewport_size,
applicable_declarations,
shareable,
None,
None,
context.error_reporter.clone());
cacheable = cacheable && is_cacheable;
this_style = the_style
}
};
// Trigger transitions if necessary. This will reset `this_style` back to its old value if
// it did trigger a transition.
if animate_properties {
if let Some(ref style) = *style {
let animations_started =
animation::start_transitions_if_applicable(new_animations_sender,
self.opaque(),
&**style,
&mut this_style);
cacheable = cacheable && !animations_started
}
}
// Calculate style difference.
let this_style = Arc::new(this_style);
let damage = Self::ConcreteRestyleDamage::compute(style, &*this_style);
// Cache the resolved style if it was cacheable.
if cacheable {
applicable_declarations_cache.insert(applicable_declarations.to_vec(),
this_style.clone());
}
// Write in the final style and return the damage done to our caller.
*style = Some(this_style);
damage
}
fn update_animations_for_cascade(&self,
context: &SharedStyleContext,
style: &mut Option<Arc<ComputedValues>>)
-> bool {
let style = match *style {
None => return false,
Some(ref mut style) => style,
};
// Finish any expired transitions.
let this_opaque = self.opaque();
let had_animations_to_expire;
{
let all_expired_animations = context.expired_animations.read().unwrap();
let animations_to_expire = all_expired_animations.get(&this_opaque);
had_animations_to_expire = animations_to_expire.is_some();
if let Some(ref animations) = animations_to_expire {
for animation in *animations {
animation.property_animation.update(&mut *Arc::make_mut(style), 1.0);
}
}
}
if had_animations_to_expire {
context.expired_animations.write().unwrap().remove(&this_opaque);
}
// Merge any running transitions into the current style, and cancel them.
let had_running_animations = context.running_animations
.read()
.unwrap()
.get(&this_opaque)
.is_some();
if had_running_animations {
let mut all_running_animations = context.running_animations.write().unwrap();
for running_animation in all_running_animations.get(&this_opaque).unwrap() {
animation::update_style_for_animation::<Self::ConcreteRestyleDamage>(running_animation, style, None);
}
all_running_animations.remove(&this_opaque);
}
had_animations_to_expire || had_running_animations
}
}
impl<'ln, N: TNode<'ln>> PrivateMatchMethods<'ln> for N {}
trait PrivateElementMatchMethods<'le>: TElement<'le> {
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<Self::ConcreteNode>,
candidate: &StyleSharingCandidate)
-> Option<Arc<ComputedValues>> {
let parent_node = match parent_node {
Some(ref parent_node) if parent_node.as_element().is_some() => parent_node,
Some(_) | None => return None,
};
let parent_data: Option<&PrivateStyleData> = unsafe {
parent_node.borrow_data_unchecked().map(|d| &*d)
};
if let Some(parent_data_ref) = parent_data {
// Check parent style.
let parent_style = (*parent_data_ref).style.as_ref().unwrap();
if !arc_ptr_eq(parent_style, &candidate.parent_style) {
return None
}
// Check tag names, classes, etc.
if !candidate.can_share_style_with(self) {
return None
}
return Some(candidate.style.clone())
}
None
}
}
impl<'le, E: TElement<'le>> PrivateElementMatchMethods<'le> for E {}
pub trait ElementMatchMethods<'le> : TElement<'le> {
fn match_element(&self,
stylist: &Stylist,
parent_bf: Option<&BloomFilter>,
applicable_declarations: &mut ApplicableDeclarations)
-> bool {
let style_attribute = self.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(PseudoElement::Before),
&mut applicable_declarations.before);
stylist.push_applicable_declarations(self,
parent_bf,
None,
Some(PseudoElement::After),
&mut applicable_declarations.after);
applicable_declarations.normal_shareable &&
applicable_declarations.before.is_empty() &&
applicable_declarations.after.is_empty()
}
/// Attempts to share a style with another node. This method is unsafe because it depends on
/// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to
/// guarantee that at the type system level yet.
unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache:
&mut StyleSharingCandidateCache,
parent: Option<Self::ConcreteNode>)
-> StyleSharingResult<<Self::ConcreteNode as TNode<'le>>::ConcreteRestyleDamage> {
if opts::get().disable_share_style_cache {
return StyleSharingResult::CannotShare
}
if self.style_attribute().is_some() {
return StyleSharingResult::CannotShare
}
if self.get_attr(&ns!(), &atom!("id")).is_some() {
return StyleSharingResult::CannotShare
}
for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
match self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
Some(shared_style) => {
// Yay, cache hit. Share the style.
let node = self.as_node();
let style = &mut node.mutate_data().unwrap().style;
let damage = <<Self as TElement<'le>>::ConcreteNode as TNode<'le>>
::ConcreteRestyleDamage::compute(style, &*shared_style);
*style = Some(shared_style);
return StyleSharingResult::StyleWasShared(i, damage)
}
None => {}
}
}
StyleSharingResult::CannotShare
}
}
impl<'le, E: TElement<'le>> ElementMatchMethods<'le> for E {}
pub trait MatchMethods<'ln> : TNode<'ln> {
// 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:
// - `SimpleSelector::LocalName`
// - `SimpleSelector::Namepace`
// - `SimpleSelector::ID`
// - `SimpleSelector::Class`
/// 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) {
// Only elements are interesting.
if let Some(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.each_class(|class| bf.insert(class));
}
}
/// 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) {
// Only elements are interesting.
if let Some(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.each_class(|class| bf.remove(class));
}
}
unsafe fn cascade_node(&self,
context: &SharedStyleContext,
parent: Option<Self>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
new_animations_sender: &Mutex<Sender<Animation>>) {
// Get our parent's style. This must be unsafe so that we don't touch the parent's
// borrow flags.
//
// FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
// enforced safe, race-free access to the parent style.
let parent_style = match parent {
None => None,
Some(parent_node) => {
let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap();
Some(parent_style)
}
};
if self.is_text_node() {
// Text nodes get a copy of the parent style. This ensures
// that during fragment construction any non-inherited
// CSS properties (such as vertical-align) are correctly
// set on the fragment(s).
let mut data_ref = self.mutate_data().unwrap();
let mut data = &mut *data_ref;
let cloned_parent_style = parent_style.unwrap().clone();
data.style = Some(cloned_parent_style);
} else {
let mut damage;
{
let mut data_ref = self.mutate_data().unwrap();
let mut data = &mut *data_ref;
damage = self.cascade_node_pseudo_element(
context,
parent_style,
&applicable_declarations.normal,
&mut data.style,
applicable_declarations_cache,
new_animations_sender,
applicable_declarations.normal_shareable,
true);
if !applicable_declarations.before.is_empty() {
damage = damage | self.cascade_node_pseudo_element(
context,
Some(data.style.as_ref().unwrap()),
&*applicable_declarations.before,
&mut data.before_style,
applicable_declarations_cache,
new_animations_sender,
false,
false);
}
if !applicable_declarations.after.is_empty() {
damage = damage | self.cascade_node_pseudo_element(
context,
Some(data.style.as_ref().unwrap()),
&*applicable_declarations.after,
&mut data.after_style,
applicable_declarations_cache,
new_animations_sender,
false,
false);
}
}
// This method needs to borrow the data as mutable, so make sure data_ref goes out of
// scope first.
self.set_restyle_damage(damage);
}
}
}
impl<'ln, N: TNode<'ln>> MatchMethods<'ln> for N {}

1
ports/cef/Cargo.lock generated
View file

@ -1742,6 +1742,7 @@ dependencies = [
"smallvec 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"style_traits 0.0.1",
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
]

1
ports/gonk/Cargo.lock generated
View file

@ -1708,6 +1708,7 @@ dependencies = [
"smallvec 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"style_traits 0.0.1",
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
]