mirror of
https://github.com/servo/servo.git
synced 2025-08-12 08:55:32 +01:00
layout: Implement CSS transitions per CSS-TRANSITIONS § 2.
Transition events are not yet supported, and the only animatable properties are `top`, `right`, `bottom`, and `left`. However, all other features of transitions are supported. There are no automated tests at present because I'm not sure how best to test it, but three manual tests are included.
This commit is contained in:
parent
c1cc31b9d6
commit
66dd8c8a6c
31 changed files with 1603 additions and 224 deletions
|
@ -61,9 +61,13 @@ git = "https://github.com/servo/string-cache"
|
|||
[dependencies.png]
|
||||
git = "https://github.com/servo/rust-png"
|
||||
|
||||
[dependencies.clock_ticks]
|
||||
git = "https://github.com/tomaka/clock_ticks"
|
||||
|
||||
[dependencies]
|
||||
encoding = "0.2"
|
||||
url = "0.2.16"
|
||||
bitflags = "*"
|
||||
rustc-serialize = "0.3"
|
||||
libc = "*"
|
||||
|
||||
|
|
104
components/layout/animation.rs
Normal file
104
components/layout/animation.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS transitions and animations.
|
||||
|
||||
use flow::{self, Flow};
|
||||
use incremental::{self, RestyleDamage};
|
||||
|
||||
use clock_ticks;
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use layout_task::{LayoutTask, LayoutTaskData};
|
||||
use msg::constellation_msg::{Msg, PipelineId};
|
||||
use script::layout_interface::Animation;
|
||||
use std::mem;
|
||||
use std::sync::mpsc::Sender;
|
||||
use style::animation::{GetMod, PropertyAnimation};
|
||||
use style::properties::ComputedValues;
|
||||
|
||||
/// Inserts transitions into the queue of running animations as applicable for the given style
|
||||
/// difference. This is called from the layout worker threads.
|
||||
pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>,
|
||||
node: OpaqueNode,
|
||||
old_style: &ComputedValues,
|
||||
new_style: &mut ComputedValues) {
|
||||
for i in range(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.into_iter() {
|
||||
// Set the property to the initial value.
|
||||
property_animation.update(new_style, 0.0);
|
||||
|
||||
// Kick off the animation.
|
||||
let now = clock_ticks::precise_time_s();
|
||||
let animation_style = new_style.get_animation();
|
||||
let start_time = now + animation_style.transition_delay.0.get_mod(i).seconds();
|
||||
new_animations_sender.send(Animation {
|
||||
node: node.id(),
|
||||
property_animation: property_animation,
|
||||
start_time: start_time,
|
||||
end_time: start_time +
|
||||
animation_style.transition_duration.0.get_mod(i).seconds(),
|
||||
}).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes any new animations that were discovered after style recalculation.
|
||||
pub fn process_new_animations(rw_data: &mut LayoutTaskData, pipeline_id: PipelineId) {
|
||||
while let Ok(animation) = rw_data.new_animations_receiver.try_recv() {
|
||||
rw_data.running_animations.push(animation)
|
||||
}
|
||||
|
||||
let animations_are_running = !rw_data.running_animations.is_empty();
|
||||
rw_data.constellation_chan
|
||||
.0
|
||||
.send(Msg::ChangeRunningAnimationsState(pipeline_id, animations_are_running))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Recalculates style for an animation. This does *not* run with the DOM lock held.
|
||||
pub fn recalc_style_for_animation(flow: &mut Flow, animation: &Animation) {
|
||||
let mut damage = RestyleDamage::empty();
|
||||
flow.mutate_fragments(&mut |fragment| {
|
||||
if fragment.node.id() != animation.node {
|
||||
return
|
||||
}
|
||||
|
||||
let now = clock_ticks::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 = fragment.style.clone();
|
||||
animation.property_animation.update(&mut *new_style.make_unique(), progress);
|
||||
damage.insert(incremental::compute_damage(&Some(fragment.style.clone()), &new_style));
|
||||
fragment.style = new_style
|
||||
});
|
||||
|
||||
let base = flow::mut_base(flow);
|
||||
base.restyle_damage.insert(damage);
|
||||
for kid in base.children.iter_mut() {
|
||||
recalc_style_for_animation(kid, animation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles animation updates.
|
||||
pub fn tick_all_animations(layout_task: &LayoutTask, rw_data: &mut LayoutTaskData) {
|
||||
let running_animations = mem::replace(&mut rw_data.running_animations, Vec::new());
|
||||
let now = clock_ticks::precise_time_s();
|
||||
for running_animation in running_animations.into_iter() {
|
||||
layout_task.tick_animation(running_animation, rw_data);
|
||||
|
||||
if now < running_animation.end_time {
|
||||
// Keep running the animation if it hasn't expired.
|
||||
rw_data.running_animations.push(running_animation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,19 +10,20 @@ use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};
|
|||
|
||||
use geom::{Rect, Size2D};
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use gfx::font_context::FontContext;
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use script::layout_interface::LayoutChan;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use gfx::font_context::FontContext;
|
||||
use msg::constellation_msg::ConstellationChan;
|
||||
use net::local_image_cache::LocalImageCache;
|
||||
use util::geometry::Au;
|
||||
use script::layout_interface::{Animation, LayoutChan};
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use std::boxed;
|
||||
use std::cell::Cell;
|
||||
use std::ptr;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use style::selector_matching::Stylist;
|
||||
use url::Url;
|
||||
use util::geometry::Au;
|
||||
|
||||
struct LocalLayoutContext {
|
||||
font_context: FontContext,
|
||||
|
@ -32,7 +33,8 @@ struct LocalLayoutContext {
|
|||
|
||||
thread_local!(static LOCAL_CONTEXT_KEY: Cell<*mut LocalLayoutContext> = Cell::new(ptr::null_mut()));
|
||||
|
||||
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext {
|
||||
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext)
|
||||
-> *mut LocalLayoutContext {
|
||||
LOCAL_CONTEXT_KEY.with(|ref r| {
|
||||
if r.get().is_null() {
|
||||
let context = box LocalLayoutContext {
|
||||
|
@ -51,6 +53,7 @@ fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *
|
|||
})
|
||||
}
|
||||
|
||||
/// Layout information shared among all workers. This must be thread-safe.
|
||||
pub struct SharedLayoutContext {
|
||||
/// The local image cache.
|
||||
pub image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>,
|
||||
|
@ -76,7 +79,7 @@ pub struct SharedLayoutContext {
|
|||
pub stylist: *const Stylist,
|
||||
|
||||
/// The root node at which we're starting the layout.
|
||||
pub reflow_root: OpaqueNode,
|
||||
pub reflow_root: Option<OpaqueNode>,
|
||||
|
||||
/// The URL.
|
||||
pub url: Url,
|
||||
|
@ -87,9 +90,14 @@ pub struct SharedLayoutContext {
|
|||
/// Starts at zero, and increased by one every time a layout completes.
|
||||
/// This can be used to easily check for invalid stale data.
|
||||
pub generation: u32,
|
||||
|
||||
/// A channel on which new animations that have been triggered by style recalculation can be
|
||||
/// sent.
|
||||
pub new_animations_sender: Sender<Animation>,
|
||||
}
|
||||
|
||||
pub struct SharedLayoutContextWrapper(pub *const SharedLayoutContext);
|
||||
|
||||
unsafe impl Send for SharedLayoutContextWrapper {}
|
||||
|
||||
pub struct LayoutContext<'a> {
|
||||
|
|
|
@ -6,29 +6,33 @@
|
|||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use animation;
|
||||
use context::SharedLayoutContext;
|
||||
use css::node_style::StyledNode;
|
||||
use incremental::{self, RestyleDamage};
|
||||
use data::{LayoutDataAccess, LayoutDataWrapper};
|
||||
use incremental::{self, RestyleDamage};
|
||||
use opaque_node::OpaqueNodeMethods;
|
||||
use wrapper::{LayoutElement, LayoutNode, TLayoutNode};
|
||||
|
||||
use script::dom::node::NodeTypeId;
|
||||
use script::layout_interface::Animation;
|
||||
use selectors::bloom::BloomFilter;
|
||||
use util::cache::{LRUCache, SimpleHashCache};
|
||||
use util::smallvec::{SmallVec, SmallVec16};
|
||||
use util::arc_ptr_eq;
|
||||
use std::borrow::ToOwned;
|
||||
use std::mem;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::slice::Iter;
|
||||
use string_cache::{Atom, Namespace};
|
||||
use selectors::parser::PseudoElement;
|
||||
use style::selector_matching::{Stylist, DeclarationBlock};
|
||||
use style::node::{TElement, TNode};
|
||||
use style::properties::{ComputedValues, cascade};
|
||||
use selectors::matching::{CommonStyleAffectingAttributeMode, CommonStyleAffectingAttributes};
|
||||
use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
|
||||
use selectors::parser::PseudoElement;
|
||||
use std::borrow::ToOwned;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use std::slice::Iter;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Sender;
|
||||
use string_cache::{Atom, Namespace};
|
||||
use style::node::{TElement, TNode};
|
||||
use style::properties::{ComputedValues, cascade};
|
||||
use style::selector_matching::{Stylist, DeclarationBlock};
|
||||
use util::arc_ptr_eq;
|
||||
use util::cache::{LRUCache, SimpleHashCache};
|
||||
use util::smallvec::{SmallVec, SmallVec16};
|
||||
|
||||
pub struct ApplicableDeclarations {
|
||||
pub normal: SmallVec16<DeclarationBlock>,
|
||||
|
@ -399,7 +403,8 @@ pub trait MatchMethods {
|
|||
layout_context: &SharedLayoutContext,
|
||||
parent: Option<LayoutNode>,
|
||||
applicable_declarations: &ApplicableDeclarations,
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache);
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>);
|
||||
}
|
||||
|
||||
trait PrivateMatchMethods {
|
||||
|
@ -408,8 +413,9 @@ trait PrivateMatchMethods {
|
|||
parent_style: Option<&Arc<ComputedValues>>,
|
||||
applicable_declarations: &[DeclarationBlock],
|
||||
style: &mut Option<Arc<ComputedValues>>,
|
||||
applicable_declarations_cache: &mut
|
||||
ApplicableDeclarationsCache,
|
||||
applicable_declarations_cache:
|
||||
&mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>,
|
||||
shareable: bool)
|
||||
-> RestyleDamage;
|
||||
|
||||
|
@ -425,11 +431,12 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
|||
parent_style: Option<&Arc<ComputedValues>>,
|
||||
applicable_declarations: &[DeclarationBlock],
|
||||
style: &mut Option<Arc<ComputedValues>>,
|
||||
applicable_declarations_cache: &mut
|
||||
ApplicableDeclarationsCache,
|
||||
applicable_declarations_cache:
|
||||
&mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>,
|
||||
shareable: bool)
|
||||
-> RestyleDamage {
|
||||
let this_style;
|
||||
let mut this_style;
|
||||
let cacheable;
|
||||
match parent_style {
|
||||
Some(ref parent_style) => {
|
||||
|
@ -444,7 +451,7 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
|||
Some(&***parent_style),
|
||||
cached_computed_values);
|
||||
cacheable = is_cacheable;
|
||||
this_style = Arc::new(the_style);
|
||||
this_style = the_style
|
||||
}
|
||||
None => {
|
||||
let (the_style, is_cacheable) = cascade(layout_context.screen_size,
|
||||
|
@ -453,22 +460,39 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
|||
None,
|
||||
None);
|
||||
cacheable = is_cacheable;
|
||||
this_style = Arc::new(the_style);
|
||||
this_style = the_style
|
||||
}
|
||||
};
|
||||
|
||||
// Trigger transitions if necessary. This will reset `this_style` back to its old value if
|
||||
// it did trigger a transition.
|
||||
match *style {
|
||||
None => {
|
||||
// This is a newly-created node; we've nothing to transition from!
|
||||
}
|
||||
Some(ref style) => {
|
||||
let node = OpaqueNodeMethods::from_layout_node(self);
|
||||
animation::start_transitions_if_applicable(new_animations_sender,
|
||||
node,
|
||||
&**style,
|
||||
&mut this_style);
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
// Calculate style difference and write.
|
||||
let damage = incremental::compute_damage(style, &*this_style);
|
||||
// Write in the final style and return the damage done to our caller.
|
||||
*style = Some(this_style);
|
||||
damage
|
||||
}
|
||||
|
||||
|
||||
fn share_style_with_candidate_if_possible(&self,
|
||||
parent_node: Option<LayoutNode>,
|
||||
candidate: &StyleSharingCandidate)
|
||||
|
@ -615,7 +639,8 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
layout_context: &SharedLayoutContext,
|
||||
parent: Option<LayoutNode>,
|
||||
applicable_declarations: &ApplicableDeclarations,
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>) {
|
||||
// Get our parent's style. This must be unsafe so that we don't touch the parent's
|
||||
// borrow flags.
|
||||
//
|
||||
|
@ -625,8 +650,12 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
None => None,
|
||||
Some(parent_node) => {
|
||||
let parent_layout_data_ref = parent_node.borrow_layout_data_unchecked();
|
||||
let parent_layout_data = (&*parent_layout_data_ref).as_ref().expect("no parent data!?");
|
||||
let parent_style = parent_layout_data.shared_data.style.as_ref().expect("parent hasn't been styled yet!");
|
||||
let parent_layout_data = (&*parent_layout_data_ref).as_ref()
|
||||
.expect("no parent data!?");
|
||||
let parent_style = parent_layout_data.shared_data
|
||||
.style
|
||||
.as_ref()
|
||||
.expect("parent hasn't been styled yet!");
|
||||
Some(parent_style)
|
||||
}
|
||||
};
|
||||
|
@ -651,6 +680,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
applicable_declarations.normal.as_slice(),
|
||||
&mut layout_data.shared_data.style,
|
||||
applicable_declarations_cache,
|
||||
new_animations_sender,
|
||||
applicable_declarations.normal_shareable);
|
||||
if applicable_declarations.before.len() > 0 {
|
||||
damage = damage | self.cascade_node_pseudo_element(
|
||||
|
@ -659,6 +689,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
&*applicable_declarations.before,
|
||||
&mut layout_data.data.before_style,
|
||||
applicable_declarations_cache,
|
||||
new_animations_sender,
|
||||
false);
|
||||
}
|
||||
if applicable_declarations.after.len() > 0 {
|
||||
|
@ -668,6 +699,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
&*applicable_declarations.after,
|
||||
&mut layout_data.data.after_style,
|
||||
applicable_declarations_cache,
|
||||
new_animations_sender,
|
||||
false);
|
||||
}
|
||||
layout_data.data.restyle_damage = damage;
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use animation;
|
||||
use construct::ConstructionResult;
|
||||
use context::{SharedLayoutContext, SharedLayoutContextWrapper};
|
||||
use css::node_style::StyledNode;
|
||||
use display_list_builder::ToGfxColor;
|
||||
use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
|
||||
use flow_ref::FlowRef;
|
||||
|
@ -49,11 +49,10 @@ use profile::time::{self, ProfilerMetadata, profile};
|
|||
use profile::time::{TimerMetadataFrameType, TimerMetadataReflowType};
|
||||
use script::dom::bindings::js::LayoutJS;
|
||||
use script::dom::node::{LayoutData, Node};
|
||||
use script::layout_interface::ReflowQueryType;
|
||||
use script::layout_interface::{ContentBoxResponse, ContentBoxesResponse};
|
||||
use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse};
|
||||
use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC};
|
||||
use script::layout_interface::{MouseOverResponse, Msg};
|
||||
use script::layout_interface::{Reflow, ReflowGoal, ScriptLayoutChan, TrustedNodeAddress};
|
||||
use script::layout_interface::{MouseOverResponse, Msg, Reflow, ReflowGoal, ReflowQueryType};
|
||||
use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress};
|
||||
use script_traits::{ConstellationControlMsg, CompositorEvent, OpaqueScriptLayoutChannel};
|
||||
use script_traits::{ScriptControlChan, UntrustedNodeAddress};
|
||||
use std::borrow::ToOwned;
|
||||
|
@ -70,7 +69,7 @@ use style::selector_matching::Stylist;
|
|||
use style::stylesheets::{Origin, Stylesheet, iter_font_face_rules};
|
||||
use url::Url;
|
||||
use util::cursor::Cursor;
|
||||
use util::geometry::Au;
|
||||
use util::geometry::{Au, MAX_RECT};
|
||||
use util::logical_geometry::LogicalPoint;
|
||||
use util::mem::HeapSizeOf;
|
||||
use util::opts;
|
||||
|
@ -83,6 +82,9 @@ use util::workqueue::WorkQueue;
|
|||
///
|
||||
/// This needs to be protected by a mutex so we can do fast RPCs.
|
||||
pub struct LayoutTaskData {
|
||||
/// The root of the flow tree.
|
||||
pub root_flow: Option<FlowRef>,
|
||||
|
||||
/// The local image cache.
|
||||
pub local_image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>,
|
||||
|
||||
|
@ -113,6 +115,16 @@ pub struct LayoutTaskData {
|
|||
|
||||
/// A queued response for the content boxes of a node.
|
||||
pub content_boxes_response: Vec<Rect<Au>>,
|
||||
|
||||
/// The list of currently-running animations.
|
||||
pub running_animations: Vec<Animation>,
|
||||
|
||||
/// Receives newly-discovered animations.
|
||||
pub new_animations_receiver: Receiver<Animation>,
|
||||
|
||||
/// A channel on which new animations that have been triggered by style recalculation can be
|
||||
/// sent.
|
||||
pub new_animations_sender: Sender<Animation>,
|
||||
}
|
||||
|
||||
/// Information needed by the layout task.
|
||||
|
@ -129,7 +141,7 @@ pub struct LayoutTask {
|
|||
/// The port on which we receive messages from the constellation
|
||||
pub pipeline_port: Receiver<LayoutControlMsg>,
|
||||
|
||||
//// The channel to send messages to ourself.
|
||||
/// The channel on which we or others can send messages to ourselves.
|
||||
pub chan: LayoutChan,
|
||||
|
||||
/// The channel on which messages can be sent to the constellation.
|
||||
|
@ -192,39 +204,37 @@ impl ImageResponder<UntrustedNodeAddress> for LayoutImageResponder {
|
|||
impl LayoutTaskFactory for LayoutTask {
|
||||
/// Spawns a new layout task.
|
||||
fn create(_phantom: Option<&mut LayoutTask>,
|
||||
id: PipelineId,
|
||||
url: Url,
|
||||
chan: OpaqueScriptLayoutChannel,
|
||||
pipeline_port: Receiver<LayoutControlMsg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
failure_msg: Failure,
|
||||
script_chan: ScriptControlChan,
|
||||
paint_chan: PaintChan,
|
||||
resource_task: ResourceTask,
|
||||
img_cache_task: ImageCacheTask,
|
||||
font_cache_task: FontCacheTask,
|
||||
time_profiler_chan: time::ProfilerChan,
|
||||
mem_profiler_chan: mem::ProfilerChan,
|
||||
shutdown_chan: Sender<()>) {
|
||||
id: PipelineId,
|
||||
url: Url,
|
||||
chan: OpaqueScriptLayoutChannel,
|
||||
pipeline_port: Receiver<LayoutControlMsg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
failure_msg: Failure,
|
||||
script_chan: ScriptControlChan,
|
||||
paint_chan: PaintChan,
|
||||
resource_task: ResourceTask,
|
||||
img_cache_task: ImageCacheTask,
|
||||
font_cache_task: FontCacheTask,
|
||||
time_profiler_chan: time::ProfilerChan,
|
||||
memory_profiler_chan: mem::ProfilerChan,
|
||||
shutdown_chan: Sender<()>) {
|
||||
let ConstellationChan(con_chan) = constellation_chan.clone();
|
||||
spawn_named_with_send_on_failure("LayoutTask", task_state::LAYOUT, move || {
|
||||
{ // Ensures layout task is destroyed before we send shutdown message
|
||||
let sender = chan.sender();
|
||||
let layout =
|
||||
LayoutTask::new(
|
||||
id,
|
||||
url,
|
||||
chan.receiver(),
|
||||
LayoutChan(sender),
|
||||
pipeline_port,
|
||||
constellation_chan,
|
||||
script_chan,
|
||||
paint_chan,
|
||||
resource_task,
|
||||
img_cache_task,
|
||||
font_cache_task,
|
||||
time_profiler_chan,
|
||||
mem_profiler_chan);
|
||||
let layout = LayoutTask::new(id,
|
||||
url,
|
||||
chan.receiver(),
|
||||
LayoutChan(sender),
|
||||
pipeline_port,
|
||||
constellation_chan,
|
||||
script_chan,
|
||||
paint_chan,
|
||||
resource_task,
|
||||
img_cache_task,
|
||||
font_cache_task,
|
||||
time_profiler_chan,
|
||||
memory_profiler_chan);
|
||||
layout.start();
|
||||
}
|
||||
shutdown_chan.send(()).unwrap();
|
||||
|
@ -297,6 +307,10 @@ impl LayoutTask {
|
|||
let reporter_name = format!("layout-reporter-{}", id.0);
|
||||
mem_profiler_chan.send(mem::ProfilerMsg::RegisterReporter(reporter_name.clone(), reporter));
|
||||
|
||||
|
||||
// Create the channel on which new animations can be sent.
|
||||
let (new_animations_sender, new_animations_receiver) = channel();
|
||||
|
||||
LayoutTask {
|
||||
id: id,
|
||||
url: url,
|
||||
|
@ -315,6 +329,7 @@ impl LayoutTask {
|
|||
first_reflow: Cell::new(true),
|
||||
rw_data: Arc::new(Mutex::new(
|
||||
LayoutTaskData {
|
||||
root_flow: None,
|
||||
local_image_cache: local_image_cache,
|
||||
constellation_chan: constellation_chan,
|
||||
screen_size: screen_size,
|
||||
|
@ -325,6 +340,9 @@ impl LayoutTask {
|
|||
generation: 0,
|
||||
content_box_response: Rect::zero(),
|
||||
content_boxes_response: Vec::new(),
|
||||
running_animations: Vec::new(),
|
||||
new_animations_receiver: new_animations_receiver,
|
||||
new_animations_sender: new_animations_sender,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
@ -341,7 +359,7 @@ impl LayoutTask {
|
|||
fn build_shared_layout_context(&self,
|
||||
rw_data: &LayoutTaskData,
|
||||
screen_size_changed: bool,
|
||||
reflow_root: &LayoutNode,
|
||||
reflow_root: Option<&LayoutNode>,
|
||||
url: &Url)
|
||||
-> SharedLayoutContext {
|
||||
SharedLayoutContext {
|
||||
|
@ -353,9 +371,10 @@ impl LayoutTask {
|
|||
font_cache_task: self.font_cache_task.clone(),
|
||||
stylist: &*rw_data.stylist,
|
||||
url: (*url).clone(),
|
||||
reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root),
|
||||
reflow_root: reflow_root.map(|node| OpaqueNodeMethods::from_layout_node(node)),
|
||||
dirty: Rect::zero(),
|
||||
generation: rw_data.generation,
|
||||
new_animations_sender: rw_data.new_animations_sender.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,8 +408,12 @@ impl LayoutTask {
|
|||
match port_to_read {
|
||||
PortToRead::Pipeline => {
|
||||
match self.pipeline_port.recv().unwrap() {
|
||||
LayoutControlMsg::TickAnimationsMsg => {
|
||||
self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data)
|
||||
}
|
||||
LayoutControlMsg::ExitNowMsg(exit_type) => {
|
||||
self.handle_request_helper(Msg::ExitNow(exit_type), possibly_locked_rw_data)
|
||||
self.handle_request_helper(Msg::ExitNow(exit_type),
|
||||
possibly_locked_rw_data)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -434,8 +457,12 @@ impl LayoutTask {
|
|||
LayoutTaskData>>)
|
||||
-> bool {
|
||||
match request {
|
||||
Msg::AddStylesheet(sheet, mq) => self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data),
|
||||
Msg::LoadStylesheet(url, mq) => self.handle_load_stylesheet(url, mq, possibly_locked_rw_data),
|
||||
Msg::AddStylesheet(sheet, mq) => {
|
||||
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data)
|
||||
}
|
||||
Msg::LoadStylesheet(url, mq) => {
|
||||
self.handle_load_stylesheet(url, mq, possibly_locked_rw_data)
|
||||
}
|
||||
Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data),
|
||||
Msg::GetRPC(response_chan) => {
|
||||
response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as
|
||||
|
@ -443,10 +470,11 @@ impl LayoutTask {
|
|||
},
|
||||
Msg::Reflow(data) => {
|
||||
profile(time::ProfilerCategory::LayoutPerform,
|
||||
self.profiler_metadata(&*data),
|
||||
self.profiler_metadata(&data.reflow_info),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| self.handle_reflow(&*data, possibly_locked_rw_data));
|
||||
},
|
||||
Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data),
|
||||
Msg::ReapLayoutData(dead_layout_data) => {
|
||||
unsafe {
|
||||
self.handle_reap_layout_data(dead_layout_data)
|
||||
|
@ -486,9 +514,9 @@ impl LayoutTask {
|
|||
reports_chan.send(reports);
|
||||
}
|
||||
|
||||
/// Enters a quiescent state in which no new messages except for `layout_interface::Msg::ReapLayoutData` will be
|
||||
/// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given
|
||||
/// response channel.
|
||||
/// Enters a quiescent state in which no new messages except for
|
||||
/// `layout_interface::Msg::ReapLayoutData` will be processed until an `ExitNowMsg` is
|
||||
/// received. A pong is immediately sent on the given response channel.
|
||||
fn prepare_to_exit<'a>(&'a self,
|
||||
response_chan: Sender<()>,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
|
||||
|
@ -586,7 +614,6 @@ impl LayoutTask {
|
|||
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
|
||||
}
|
||||
|
||||
/// Retrieves the flow tree root from the root node.
|
||||
fn try_get_layout_root(&self, node: LayoutNode) -> Option<FlowRef> {
|
||||
let mut layout_data_ref = node.mutate_layout_data();
|
||||
let layout_data =
|
||||
|
@ -698,7 +725,7 @@ impl LayoutTask {
|
|||
data: &Reflow,
|
||||
layout_root: &mut FlowRef,
|
||||
shared_layout_context: &mut SharedLayoutContext,
|
||||
rw_data: &mut RWGuard<'a>) {
|
||||
rw_data: &mut LayoutTaskData) {
|
||||
let writing_mode = flow::base(&**layout_root).writing_mode;
|
||||
profile(time::ProfilerCategory::LayoutDispListBuild,
|
||||
self.profiler_metadata(data),
|
||||
|
@ -714,7 +741,6 @@ impl LayoutTask {
|
|||
flow::mut_base(&mut **layout_root).clip =
|
||||
ClippingRegion::from_rect(&data.page_clip_rect);
|
||||
|
||||
let rw_data = &mut **rw_data;
|
||||
match rw_data.parallel_traversal {
|
||||
None => {
|
||||
sequential::build_display_list_for_subtree(layout_root, shared_layout_context);
|
||||
|
@ -767,7 +793,7 @@ impl LayoutTask {
|
|||
|
||||
/// The high-level routine that performs layout tasks.
|
||||
fn handle_reflow<'a>(&'a self,
|
||||
data: &Reflow,
|
||||
data: &ScriptReflow,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
|
||||
// FIXME: Isolate this transmutation into a "bridge" module.
|
||||
// FIXME(rust#16366): The following line had to be moved because of a
|
||||
|
@ -779,8 +805,7 @@ impl LayoutTask {
|
|||
transmute(&mut node)
|
||||
};
|
||||
|
||||
debug!("layout: received layout request for: {}", data.url.serialize());
|
||||
debug!("layout: parsed Node tree");
|
||||
debug!("layout: received layout request for: {}", data.reflow_info.url.serialize());
|
||||
if log_enabled!(log::DEBUG) {
|
||||
node.dump();
|
||||
}
|
||||
|
@ -796,7 +821,6 @@ impl LayoutTask {
|
|||
// TODO: Calculate the "actual viewport":
|
||||
// http://www.w3.org/TR/css-device-adapt/#actual-viewport
|
||||
let viewport_size = data.window_size.initial_viewport;
|
||||
|
||||
let old_screen_size = rw_data.screen_size;
|
||||
let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()),
|
||||
Au::from_frac32_px(viewport_size.height.get()));
|
||||
|
@ -804,23 +828,19 @@ impl LayoutTask {
|
|||
|
||||
// Handle conditions where the entire flow tree is invalid.
|
||||
let screen_size_changed = current_screen_size != old_screen_size;
|
||||
|
||||
if screen_size_changed {
|
||||
let device = Device::new(MediaType::Screen, data.window_size.initial_viewport);
|
||||
rw_data.stylist.set_device(device);
|
||||
}
|
||||
|
||||
let needs_dirtying = rw_data.stylist.update();
|
||||
|
||||
// If the entire flow tree is invalid, then it will be reflowed anyhow.
|
||||
let needs_dirtying = rw_data.stylist.update();
|
||||
let needs_reflow = screen_size_changed && !needs_dirtying;
|
||||
|
||||
unsafe {
|
||||
if needs_dirtying {
|
||||
LayoutTask::dirty_all_nodes(node);
|
||||
}
|
||||
}
|
||||
|
||||
if needs_reflow {
|
||||
match self.try_get_layout_root(*node) {
|
||||
None => {}
|
||||
|
@ -833,13 +853,14 @@ impl LayoutTask {
|
|||
// Create a layout context for use throughout the following passes.
|
||||
let mut shared_layout_context = self.build_shared_layout_context(&*rw_data,
|
||||
screen_size_changed,
|
||||
node,
|
||||
&data.url);
|
||||
Some(&node),
|
||||
&data.reflow_info.url);
|
||||
|
||||
let mut layout_root = profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
// Recalculate CSS styles and rebuild flows and fragments.
|
||||
profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||
self.profiler_metadata(&data.reflow_info),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
// Perform CSS selector matching and flow construction.
|
||||
let rw_data = &mut *rw_data;
|
||||
match rw_data.parallel_traversal {
|
||||
|
@ -847,39 +868,105 @@ impl LayoutTask {
|
|||
sequential::traverse_dom_preorder(*node, &shared_layout_context);
|
||||
}
|
||||
Some(ref mut traversal) => {
|
||||
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal)
|
||||
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal);
|
||||
}
|
||||
}
|
||||
|
||||
self.get_layout_root((*node).clone())
|
||||
});
|
||||
|
||||
// Retrieve the (possibly rebuilt) root flow.
|
||||
rw_data.root_flow = Some(self.get_layout_root((*node).clone()));
|
||||
|
||||
// Kick off animations if any were triggered.
|
||||
animation::process_new_animations(&mut *rw_data, self.id);
|
||||
|
||||
// Perform post-style recalculation layout passes.
|
||||
self.perform_post_style_recalc_layout_passes(&data.reflow_info,
|
||||
&mut rw_data,
|
||||
&mut shared_layout_context);
|
||||
|
||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||
match data.query_type {
|
||||
ReflowQueryType::ContentBoxQuery(node) => {
|
||||
self.process_content_box_request(node, &mut root_flow, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::ContentBoxesQuery(node) => {
|
||||
self.process_content_boxes_request(node, &mut root_flow, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::NoQuery => {}
|
||||
}
|
||||
|
||||
|
||||
// Tell script that we're done.
|
||||
//
|
||||
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
|
||||
// either select or a filtered recv() that only looks for messages of a given type.
|
||||
data.script_join_chan.send(()).unwrap();
|
||||
let ScriptControlChan(ref chan) = data.script_chan;
|
||||
chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap();
|
||||
}
|
||||
|
||||
fn tick_all_animations<'a>(&'a self,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a,
|
||||
LayoutTaskData>>) {
|
||||
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
animation::tick_all_animations(self, &mut rw_data)
|
||||
}
|
||||
|
||||
pub fn tick_animation<'a>(&'a self, animation: Animation, rw_data: &mut LayoutTaskData) {
|
||||
// FIXME(#5466, pcwalton): These data are lies.
|
||||
let reflow_info = Reflow {
|
||||
goal: ReflowGoal::ForDisplay,
|
||||
url: Url::parse("http://animation.com/").unwrap(),
|
||||
iframe: false,
|
||||
page_clip_rect: MAX_RECT,
|
||||
};
|
||||
|
||||
// Perform an abbreviated style recalc that operates without access to the DOM.
|
||||
let mut layout_context = self.build_shared_layout_context(&*rw_data,
|
||||
false,
|
||||
None,
|
||||
&reflow_info.url);
|
||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||
profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||
self.profiler_metadata(&reflow_info),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| animation::recalc_style_for_animation(root_flow.deref_mut(), &animation));
|
||||
|
||||
self.perform_post_style_recalc_layout_passes(&reflow_info,
|
||||
&mut *rw_data,
|
||||
&mut layout_context);
|
||||
}
|
||||
|
||||
fn perform_post_style_recalc_layout_passes<'a>(&'a self,
|
||||
data: &Reflow,
|
||||
rw_data: &mut LayoutTaskData,
|
||||
layout_context: &mut SharedLayoutContext) {
|
||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||
profile(time::ProfilerCategory::LayoutRestyleDamagePropagation,
|
||||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
if opts::get().nonincremental_layout || layout_root.compute_layout_damage()
|
||||
.contains(REFLOW_ENTIRE_DOCUMENT) {
|
||||
layout_root.reflow_entire_document()
|
||||
if opts::get().nonincremental_layout || root_flow.deref_mut()
|
||||
.compute_layout_damage()
|
||||
.contains(REFLOW_ENTIRE_DOCUMENT) {
|
||||
root_flow.deref_mut().reflow_entire_document()
|
||||
}
|
||||
});
|
||||
|
||||
// Verification of the flow tree, which ensures that all nodes were either marked as leaves
|
||||
// or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to
|
||||
// memory safety but is a useful debugging tool.)
|
||||
self.verify_flow_tree(&mut layout_root);
|
||||
self.verify_flow_tree(&mut root_flow);
|
||||
|
||||
if opts::get().trace_layout {
|
||||
layout_debug::begin_trace(layout_root.clone());
|
||||
layout_debug::begin_trace(root_flow.clone());
|
||||
}
|
||||
|
||||
// Resolve generated content.
|
||||
profile(time::ProfilerCategory::LayoutGeneratedContent,
|
||||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
sequential::resolve_generated_content(&mut layout_root, &shared_layout_context)
|
||||
});
|
||||
|| sequential::resolve_generated_content(&mut root_flow, &layout_context));
|
||||
|
||||
// Perform the primary layout passes over the flow tree to compute the locations of all
|
||||
// the boxes.
|
||||
|
@ -887,18 +974,17 @@ impl LayoutTask {
|
|||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
let rw_data = &mut *rw_data;
|
||||
match rw_data.parallel_traversal {
|
||||
None => {
|
||||
// Sequential mode.
|
||||
self.solve_constraints(&mut layout_root, &shared_layout_context)
|
||||
self.solve_constraints(&mut root_flow, &layout_context)
|
||||
}
|
||||
Some(_) => {
|
||||
// Parallel mode.
|
||||
self.solve_constraints_parallel(data,
|
||||
rw_data,
|
||||
&mut layout_root,
|
||||
&mut shared_layout_context);
|
||||
&mut root_flow,
|
||||
&mut *layout_context);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -907,23 +993,13 @@ impl LayoutTask {
|
|||
match data.goal {
|
||||
ReflowGoal::ForDisplay => {
|
||||
self.build_display_list_for_reflow(data,
|
||||
&mut layout_root,
|
||||
&mut shared_layout_context,
|
||||
&mut rw_data);
|
||||
&mut root_flow,
|
||||
&mut *layout_context,
|
||||
rw_data);
|
||||
}
|
||||
ReflowGoal::ForScriptQuery => {}
|
||||
}
|
||||
|
||||
match data.query_type {
|
||||
ReflowQueryType::ContentBoxQuery(node) => {
|
||||
self.process_content_box_request(node, &mut layout_root, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::ContentBoxesQuery(node) => {
|
||||
self.process_content_boxes_request(node, &mut layout_root, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::NoQuery => {}
|
||||
}
|
||||
|
||||
self.first_reflow.set(false);
|
||||
|
||||
if opts::get().trace_layout {
|
||||
|
@ -931,18 +1007,10 @@ impl LayoutTask {
|
|||
}
|
||||
|
||||
if opts::get().dump_flow_tree {
|
||||
layout_root.dump();
|
||||
root_flow.dump();
|
||||
}
|
||||
|
||||
rw_data.generation += 1;
|
||||
|
||||
// Tell script that we're done.
|
||||
//
|
||||
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
|
||||
// either select or a filtered recv() that only looks for messages of a given type.
|
||||
data.script_join_chan.send(()).unwrap();
|
||||
let ScriptControlChan(ref chan) = data.script_chan;
|
||||
chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap();
|
||||
}
|
||||
|
||||
unsafe fn dirty_all_nodes(node: &mut LayoutNode) {
|
||||
|
|
|
@ -25,40 +25,45 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[macro_use]extern crate bitflags;
|
||||
extern crate azure;
|
||||
extern crate cssparser;
|
||||
extern crate canvas;
|
||||
extern crate geom;
|
||||
extern crate gfx;
|
||||
extern crate layout_traits;
|
||||
extern crate script;
|
||||
extern crate script_traits;
|
||||
extern crate "rustc-serialize" as rustc_serialize;
|
||||
extern crate png;
|
||||
extern crate style;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
#[macro_use]
|
||||
#[no_link]
|
||||
extern crate "plugins" as servo_plugins;
|
||||
extern crate net;
|
||||
extern crate msg;
|
||||
|
||||
#[macro_use]
|
||||
extern crate profile;
|
||||
extern crate selectors;
|
||||
|
||||
#[macro_use]
|
||||
extern crate util;
|
||||
|
||||
extern crate string_cache;
|
||||
|
||||
extern crate "rustc-serialize" as rustc_serialize;
|
||||
extern crate alloc;
|
||||
extern crate azure;
|
||||
extern crate canvas;
|
||||
extern crate clock_ticks;
|
||||
extern crate collections;
|
||||
extern crate cssparser;
|
||||
extern crate encoding;
|
||||
extern crate geom;
|
||||
extern crate gfx;
|
||||
extern crate layout_traits;
|
||||
extern crate libc;
|
||||
extern crate msg;
|
||||
extern crate net;
|
||||
extern crate png;
|
||||
extern crate script;
|
||||
extern crate script_traits;
|
||||
extern crate selectors;
|
||||
extern crate string_cache;
|
||||
extern crate style;
|
||||
extern crate url;
|
||||
|
||||
// Listed first because of macro definitions
|
||||
pub mod layout_debug;
|
||||
|
||||
pub mod animation;
|
||||
pub mod block;
|
||||
pub mod construct;
|
||||
pub mod context;
|
||||
|
|
|
@ -180,7 +180,8 @@ impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
|
|||
node.cascade_node(self.layout_context.shared,
|
||||
parent_opt,
|
||||
&applicable_declarations,
|
||||
self.layout_context.applicable_declarations_cache());
|
||||
self.layout_context.applicable_declarations_cache(),
|
||||
&self.layout_context.shared.new_animations_sender);
|
||||
}
|
||||
|
||||
// Add ourselves to the LRU cache.
|
||||
|
|
|
@ -333,11 +333,16 @@ impl<'ln> LayoutNode<'ln> {
|
|||
/// While doing a reflow, the node at the root has no parent, as far as we're
|
||||
/// concerned. This method returns `None` at the reflow root.
|
||||
pub fn layout_parent_node(self, shared: &SharedLayoutContext) -> Option<LayoutNode<'ln>> {
|
||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&self);
|
||||
if opaque_node == shared.reflow_root {
|
||||
None
|
||||
} else {
|
||||
self.parent_node()
|
||||
match shared.reflow_root {
|
||||
None => panic!("layout_parent_node(): This layout has no access to the DOM!"),
|
||||
Some(reflow_root) => {
|
||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&self);
|
||||
if opaque_node == reflow_root {
|
||||
None
|
||||
} else {
|
||||
self.parent_node()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue