Auto merge of #6031 - glennw:reftest-race-conditions, r=larsberg,jdm

The basic idea is it's safe to output an image for reftest by testing:
 - That the compositor doesn't have any animations active.
 - That the compositor is not waiting on any outstanding paint messages to arrive.
 - That the script tasks are "idle" and therefore won't cause reflow.
    - This currently means page loaded, onload fired, reftest-wait not active, first reflow triggered.
    - It could easily be expanded to handle pending timers etc.
 - That the "epoch" that the layout tasks have last laid out after script went idle, is reflected by the compositor in all visible layers for that pipeline.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/6031)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2015-05-13 18:37:54 -05:00
commit 5e61ebaa05
17 changed files with 363 additions and 390 deletions

View file

@ -4,7 +4,7 @@
use compositor_layer::{CompositorData, CompositorLayer, WantsScrollEventsFlag}; use compositor_layer::{CompositorData, CompositorLayer, WantsScrollEventsFlag};
use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver}; use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver};
use compositor_task::{CompositorTask, LayerProperties, Msg}; use compositor_task::{CompositorTask, Msg};
use constellation::SendableFrameTree; use constellation::SendableFrameTree;
use pipeline::CompositionPipeline; use pipeline::CompositionPipeline;
use scrolling::ScrollingTimerProxy; use scrolling::ScrollingTimerProxy;
@ -14,7 +14,7 @@ use windowing::{MouseWindowEvent, WindowEvent, WindowMethods, WindowNavigateMsg}
use geom::point::{Point2D, TypedPoint2D}; use geom::point::{Point2D, TypedPoint2D};
use geom::rect::{Rect, TypedRect}; use geom::rect::{Rect, TypedRect};
use geom::scale_factor::ScaleFactor; use geom::scale_factor::ScaleFactor;
use geom::size::{Size2D, TypedSize2D}; use geom::size::TypedSize2D;
use gfx::color; use gfx::color;
use gfx::paint_task::Msg as PaintMsg; use gfx::paint_task::Msg as PaintMsg;
use gfx::paint_task::PaintRequest; use gfx::paint_task::PaintRequest;
@ -26,7 +26,7 @@ use layers::rendergl::RenderContext;
use layers::rendergl; use layers::rendergl;
use layers::scene::Scene; use layers::scene::Scene;
use msg::compositor_msg::{Epoch, FrameTreeId, LayerId}; use msg::compositor_msg::{Epoch, FrameTreeId, LayerId};
use msg::compositor_msg::{ReadyState, PaintState, ScrollPolicy}; use msg::compositor_msg::{LayerProperties, ScrollPolicy};
use msg::constellation_msg::AnimationState; use msg::constellation_msg::AnimationState;
use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{ConstellationChan, NavigationDirection}; use msg::constellation_msg::{ConstellationChan, NavigationDirection};
@ -36,7 +36,6 @@ use png;
use profile_traits::mem; use profile_traits::mem;
use profile_traits::time::{self, ProfilerCategory, profile}; use profile_traits::time::{self, ProfilerCategory, profile};
use script_traits::{ConstellationControlMsg, ScriptControlChan}; use script_traits::{ConstellationControlMsg, ScriptControlChan};
use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::mem as std_mem; use std::mem as std_mem;
@ -49,6 +48,15 @@ use url::Url;
use util::geometry::{PagePx, ScreenPx, ViewportPx}; use util::geometry::{PagePx, ScreenPx, ViewportPx};
use util::opts; use util::opts;
/// Holds the state when running reftests that determines when it is
/// safe to save the output image.
#[derive(Copy, Clone, PartialEq)]
enum ReadyState {
Unknown,
WaitingForConstellationReply,
ReadyToSaveImage,
}
/// NB: Never block on the constellation, because sometimes the constellation blocks on us. /// NB: Never block on the constellation, because sometimes the constellation blocks on us.
pub struct IOCompositor<Window: WindowMethods> { pub struct IOCompositor<Window: WindowMethods> {
/// The application window. /// The application window.
@ -102,9 +110,6 @@ pub struct IOCompositor<Window: WindowMethods> {
/// the compositor. /// the compositor.
shutdown_state: ShutdownState, shutdown_state: ShutdownState,
/// Tracks outstanding paint_msg's sent to the paint tasks.
outstanding_paint_msgs: u32,
/// Tracks the last composite time. /// Tracks the last composite time.
last_composite_time: u64, last_composite_time: u64,
@ -139,6 +144,10 @@ pub struct IOCompositor<Window: WindowMethods> {
/// Has a Quit event been seen? /// Has a Quit event been seen?
has_seen_quit_event: bool, has_seen_quit_event: bool,
/// Used by the logic that determines when it is safe to output an
/// image for the reftest framework.
ready_to_save_state: ReadyState,
} }
pub struct ScrollEvent { pub struct ScrollEvent {
@ -169,11 +178,8 @@ struct PipelineDetails {
/// The pipeline associated with this PipelineDetails object. /// The pipeline associated with this PipelineDetails object.
pipeline: Option<CompositionPipeline>, pipeline: Option<CompositionPipeline>,
/// The status of this pipeline's ScriptTask. /// The current layout epoch that this pipeline wants to draw.
ready_state: ReadyState, current_epoch: Epoch,
/// The status of this pipeline's PaintTask.
paint_state: PaintState,
/// Whether animations are running /// Whether animations are running
animations_running: bool, animations_running: bool,
@ -186,15 +192,14 @@ impl PipelineDetails {
fn new() -> PipelineDetails { fn new() -> PipelineDetails {
PipelineDetails { PipelineDetails {
pipeline: None, pipeline: None,
ready_state: ReadyState::Blank, current_epoch: Epoch(0),
paint_state: PaintState::Painting,
animations_running: false, animations_running: false,
animation_callbacks_running: false, animation_callbacks_running: false,
} }
} }
} }
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq, Debug)]
enum CompositeTarget { enum CompositeTarget {
/// Normal composition to a window /// Normal composition to a window
Window, Window,
@ -274,9 +279,9 @@ impl<Window: WindowMethods> IOCompositor<Window> {
time_profiler_chan: time_profiler_chan, time_profiler_chan: time_profiler_chan,
mem_profiler_chan: mem_profiler_chan, mem_profiler_chan: mem_profiler_chan,
fragment_point: None, fragment_point: None,
outstanding_paint_msgs: 0,
last_composite_time: 0, last_composite_time: 0,
has_seen_quit_event: false, has_seen_quit_event: false,
ready_to_save_state: ReadyState::Unknown,
} }
} }
@ -322,14 +327,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
return false; return false;
} }
(Msg::ChangeReadyState(pipeline_id, ready_state), ShutdownState::NotShuttingDown) => {
self.change_ready_state(pipeline_id, ready_state);
}
(Msg::ChangePaintState(pipeline_id, paint_state), ShutdownState::NotShuttingDown) => {
self.change_paint_state(pipeline_id, paint_state);
}
(Msg::ChangeRunningAnimationsState(pipeline_id, animation_state), (Msg::ChangeRunningAnimationsState(pipeline_id, animation_state),
ShutdownState::NotShuttingDown) => { ShutdownState::NotShuttingDown) => {
self.change_running_animations_state(pipeline_id, animation_state); self.change_running_animations_state(pipeline_id, animation_state);
@ -343,10 +340,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.change_page_url(pipeline_id, url); self.change_page_url(pipeline_id, url);
} }
(Msg::PaintMsgDiscarded, ShutdownState::NotShuttingDown) => {
self.remove_outstanding_paint_msg();
}
(Msg::SetFrameTree(frame_tree, response_chan, new_constellation_chan), (Msg::SetFrameTree(frame_tree, response_chan, new_constellation_chan),
ShutdownState::NotShuttingDown) => { ShutdownState::NotShuttingDown) => {
self.set_frame_tree(&frame_tree, response_chan, new_constellation_chan); self.set_frame_tree(&frame_tree, response_chan, new_constellation_chan);
@ -354,13 +347,15 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.get_title_for_main_frame(); self.get_title_for_main_frame();
} }
(Msg::CreateOrUpdateBaseLayer(layer_properties), ShutdownState::NotShuttingDown) => { (Msg::InitializeLayersForPipeline(pipeline_id, epoch, properties), ShutdownState::NotShuttingDown) => {
self.create_or_update_base_layer(layer_properties); self.get_or_create_pipeline_details(pipeline_id).current_epoch = epoch;
for (index, layer_properties) in properties.iter().enumerate() {
if index == 0 {
self.create_or_update_base_layer(pipeline_id, *layer_properties);
} else {
self.create_or_update_descendant_layer(pipeline_id, *layer_properties);
}
} }
(Msg::CreateOrUpdateDescendantLayer(layer_properties),
ShutdownState::NotShuttingDown) => {
self.create_or_update_descendant_layer(layer_properties);
} }
(Msg::GetGraphicsMetadata(chan), ShutdownState::NotShuttingDown) => { (Msg::GetGraphicsMetadata(chan), ShutdownState::NotShuttingDown) => {
@ -381,7 +376,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
epoch, epoch,
frame_tree_id); frame_tree_id);
} }
self.remove_outstanding_paint_msg();
} }
(Msg::ScrollFragmentPoint(pipeline_id, layer_id, point), (Msg::ScrollFragmentPoint(pipeline_id, layer_id, point),
@ -446,6 +440,16 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.constrain_viewport(pipeline_id, constraints); self.constrain_viewport(pipeline_id, constraints);
} }
(Msg::IsReadyToSaveImageReply(is_ready), ShutdownState::NotShuttingDown) => {
assert!(self.ready_to_save_state == ReadyState::WaitingForConstellationReply);
if is_ready {
self.ready_to_save_state = ReadyState::ReadyToSaveImage;
} else {
self.ready_to_save_state = ReadyState::Unknown;
}
self.composite_if_necessary(CompositingReason::Headless);
}
// When we are shutting_down, we need to avoid performing operations // When we are shutting_down, we need to avoid performing operations
// such as Paint that may crash because we have begun tearing down // such as Paint that may crash because we have begun tearing down
// the rest of our resources. // the rest of our resources.
@ -455,31 +459,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
true true
} }
fn change_ready_state(&mut self, pipeline_id: PipelineId, ready_state: ReadyState) {
self.get_or_create_pipeline_details(pipeline_id).ready_state = ready_state;
self.window.set_ready_state(self.get_earliest_pipeline_ready_state());
// If we're painting in headless mode, schedule a recomposite.
if let CompositeTarget::PngFile = self.composite_target {
self.composite_if_necessary(CompositingReason::Headless)
}
}
fn get_earliest_pipeline_ready_state(&self) -> ReadyState {
if self.pipeline_details.len() == 0 {
return ReadyState::Blank;
}
return self.pipeline_details.values().fold(ReadyState::FinishedLoading,
|v, ref details| {
cmp::min(v, details.ready_state)
});
}
fn change_paint_state(&mut self, pipeline_id: PipelineId, paint_state: PaintState) {
self.get_or_create_pipeline_details(pipeline_id).paint_state = paint_state;
self.window.set_paint_state(paint_state);
}
/// Sets or unsets the animations-running flag for the given pipeline, and schedules a /// Sets or unsets the animations-running flag for the given pipeline, and schedules a
/// recomposite if necessary. /// recomposite if necessary.
fn change_running_animations_state(&mut self, fn change_running_animations_state(&mut self,
@ -539,54 +518,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.window.set_page_url(url); self.window.set_page_url(url);
} }
fn all_pipelines_in_idle_paint_state(&self) -> bool {
if self.pipeline_details.len() == 0 {
return false;
}
return self.pipeline_details.values().all(|ref details| {
// If a pipeline exists and has a root layer that has
// zero size, it will never be painted. In this case,
// consider it as idle to avoid hangs in reftests.
if let Some(ref pipeline) = details.pipeline {
if let Some(root_layer) = self.find_pipeline_root_layer(pipeline.id) {
if root_layer.bounds.borrow().size == Size2D::zero() {
return true;
}
}
}
details.paint_state == PaintState::Idle
});
}
fn has_paint_msg_tracking(&self) -> bool {
// only track PaintMsg's if the compositor outputs to a file.
opts::get().output_file.is_some()
}
fn has_outstanding_paint_msgs(&self) -> bool {
self.has_paint_msg_tracking() && self.outstanding_paint_msgs > 0
}
fn add_outstanding_paint_msg(&mut self, count: u32) {
// return early if not tracking paint_msg's
if !self.has_paint_msg_tracking() {
return;
}
debug!("add_outstanding_paint_msg {:?}", self.outstanding_paint_msgs);
self.outstanding_paint_msgs += count;
}
fn remove_outstanding_paint_msg(&mut self) {
if !self.has_paint_msg_tracking() {
return;
}
if self.outstanding_paint_msgs > 0 {
self.outstanding_paint_msgs -= 1;
} else {
debug!("too many repaint msgs completed");
}
}
fn set_frame_tree(&mut self, fn set_frame_tree(&mut self,
frame_tree: &SendableFrameTree, frame_tree: &SendableFrameTree,
response_chan: Sender<()>, response_chan: Sender<()>,
@ -616,15 +547,14 @@ impl<Window: WindowMethods> IOCompositor<Window> {
frame_rect: Option<TypedRect<PagePx, f32>>) frame_rect: Option<TypedRect<PagePx, f32>>)
-> Rc<Layer<CompositorData>> { -> Rc<Layer<CompositorData>> {
let layer_properties = LayerProperties { let layer_properties = LayerProperties {
pipeline_id: pipeline.id,
epoch: Epoch(0),
id: LayerId::null(), id: LayerId::null(),
rect: Rect::zero(), rect: Rect::zero(),
background_color: color::transparent(), background_color: color::transparent(),
scroll_policy: ScrollPolicy::Scrollable, scroll_policy: ScrollPolicy::Scrollable,
}; };
let root_layer = CompositorData::new_layer(layer_properties, let root_layer = CompositorData::new_layer(pipeline.id,
layer_properties,
WantsScrollEventsFlag::WantsScrollEvents, WantsScrollEventsFlag::WantsScrollEvents,
opts::get().tile_size); opts::get().tile_size);
@ -661,8 +591,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.find_layer_with_pipeline_and_layer_id(pipeline_id, LayerId::null()) self.find_layer_with_pipeline_and_layer_id(pipeline_id, LayerId::null())
} }
fn update_layer_if_exists(&mut self, properties: LayerProperties) -> bool { fn update_layer_if_exists(&mut self, pipeline_id: PipelineId, properties: LayerProperties) -> bool {
match self.find_layer_with_pipeline_and_layer_id(properties.pipeline_id, properties.id) { match self.find_layer_with_pipeline_and_layer_id(pipeline_id, properties.id) {
Some(existing_layer) => { Some(existing_layer) => {
existing_layer.update_layer(properties); existing_layer.update_layer(properties);
true true
@ -671,8 +601,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
} }
} }
fn create_or_update_base_layer(&mut self, layer_properties: LayerProperties) { fn create_or_update_base_layer(&mut self, pipeline_id: PipelineId, layer_properties: LayerProperties) {
let pipeline_id = layer_properties.pipeline_id;
let root_layer = match self.find_pipeline_root_layer(pipeline_id) { let root_layer = match self.find_pipeline_root_layer(pipeline_id) {
Some(root_layer) => root_layer, Some(root_layer) => root_layer,
None => { None => {
@ -683,11 +612,12 @@ impl<Window: WindowMethods> IOCompositor<Window> {
} }
}; };
let need_new_base_layer = !self.update_layer_if_exists(layer_properties); let need_new_base_layer = !self.update_layer_if_exists(pipeline_id, layer_properties);
if need_new_base_layer { if need_new_base_layer {
root_layer.update_layer_except_bounds(layer_properties); root_layer.update_layer_except_bounds(layer_properties);
let base_layer = CompositorData::new_layer( let base_layer = CompositorData::new_layer(
pipeline_id,
layer_properties, layer_properties,
WantsScrollEventsFlag::DoesntWantScrollEvents, WantsScrollEventsFlag::DoesntWantScrollEvents,
opts::get().tile_size); opts::get().tile_size);
@ -699,27 +629,28 @@ impl<Window: WindowMethods> IOCompositor<Window> {
root_layer.children().insert(0, base_layer); root_layer.children().insert(0, base_layer);
} }
self.scroll_layer_to_fragment_point_if_necessary(layer_properties.pipeline_id, self.scroll_layer_to_fragment_point_if_necessary(pipeline_id,
layer_properties.id); layer_properties.id);
self.send_buffer_requests_for_all_layers(); self.send_buffer_requests_for_all_layers();
} }
fn create_or_update_descendant_layer(&mut self, layer_properties: LayerProperties) { fn create_or_update_descendant_layer(&mut self, pipeline_id: PipelineId, layer_properties: LayerProperties) {
if !self.update_layer_if_exists(layer_properties) { if !self.update_layer_if_exists(pipeline_id, layer_properties) {
self.create_descendant_layer(layer_properties); self.create_descendant_layer(pipeline_id, layer_properties);
} }
self.scroll_layer_to_fragment_point_if_necessary(layer_properties.pipeline_id, self.scroll_layer_to_fragment_point_if_necessary(pipeline_id,
layer_properties.id); layer_properties.id);
self.send_buffer_requests_for_all_layers(); self.send_buffer_requests_for_all_layers();
} }
fn create_descendant_layer(&self, layer_properties: LayerProperties) { fn create_descendant_layer(&self, pipeline_id: PipelineId, layer_properties: LayerProperties) {
let root_layer = match self.find_pipeline_root_layer(layer_properties.pipeline_id) { let root_layer = match self.find_pipeline_root_layer(pipeline_id) {
Some(root_layer) => root_layer, Some(root_layer) => root_layer,
None => return, // This pipeline is in the process of shutting down. None => return, // This pipeline is in the process of shutting down.
}; };
let new_layer = CompositorData::new_layer(layer_properties, let new_layer = CompositorData::new_layer(pipeline_id,
layer_properties,
WantsScrollEventsFlag::DoesntWantScrollEvents, WantsScrollEventsFlag::DoesntWantScrollEvents,
root_layer.tile_size); root_layer.tile_size);
root_layer.add_child(new_layer); root_layer.add_child(new_layer);
@ -806,8 +737,17 @@ impl<Window: WindowMethods> IOCompositor<Window> {
// has already drawn the most recently painted buffer, and missing a frame. // has already drawn the most recently painted buffer, and missing a frame.
if frame_tree_id == self.frame_tree_id { if frame_tree_id == self.frame_tree_id {
if let Some(layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) { if let Some(layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
let requested_epoch = layer.extra_data.borrow().requested_epoch;
if requested_epoch == epoch {
self.assign_painted_buffers_to_layer(layer, new_layer_buffer_set, epoch); self.assign_painted_buffers_to_layer(layer, new_layer_buffer_set, epoch);
return return
} else {
debug!("assign_painted_buffers epoch mismatch {:?} {:?} req={:?} actual={:?}",
pipeline_id,
layer_id,
requested_epoch,
epoch);
}
} }
} }
@ -830,7 +770,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
// FIXME(pcwalton): This is going to cause problems with inconsistent frames since // FIXME(pcwalton): This is going to cause problems with inconsistent frames since
// we only composite one layer at a time. // we only composite one layer at a time.
assert!(layer.add_buffers(self, new_layer_buffer_set, epoch)); layer.add_buffers(self, new_layer_buffer_set, epoch);
self.composite_if_necessary(CompositingReason::NewPaintedBuffers); self.composite_if_necessary(CompositingReason::NewPaintedBuffers);
} }
@ -1112,9 +1052,11 @@ impl<Window: WindowMethods> IOCompositor<Window> {
let mut results: HashMap<PipelineId, Vec<PaintRequest>> = HashMap::new(); let mut results: HashMap<PipelineId, Vec<PaintRequest>> = HashMap::new();
for (layer, mut layer_requests) in requests.into_iter() { for (layer, mut layer_requests) in requests.into_iter() {
let vec = match results.entry(layer.get_pipeline_id()) { let pipeline_id = layer.get_pipeline_id();
Occupied(mut entry) => { let current_epoch = self.pipeline_details.get(&pipeline_id).unwrap().current_epoch;
*entry.get_mut() = Vec::new(); layer.extra_data.borrow_mut().requested_epoch = current_epoch;
let vec = match results.entry(pipeline_id) {
Occupied(entry) => {
entry.into_mut() entry.into_mut()
} }
Vacant(entry) => { Vacant(entry) => {
@ -1132,7 +1074,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
buffer_requests: layer_requests, buffer_requests: layer_requests,
scale: scale.get(), scale: scale.get(),
layer_id: layer.extra_data.borrow().id, layer_id: layer.extra_data.borrow().id,
epoch: layer.extra_data.borrow().epoch, epoch: layer.extra_data.borrow().requested_epoch,
}); });
} }
@ -1189,41 +1131,96 @@ impl<Window: WindowMethods> IOCompositor<Window> {
let pipeline_requests = let pipeline_requests =
self.convert_buffer_requests_to_pipeline_requests_map(layers_and_requests); self.convert_buffer_requests_to_pipeline_requests_map(layers_and_requests);
let mut num_paint_msgs_sent = 0;
for (pipeline_id, requests) in pipeline_requests.into_iter() { for (pipeline_id, requests) in pipeline_requests.into_iter() {
num_paint_msgs_sent += 1;
let msg = PaintMsg::Paint(requests, self.frame_tree_id); let msg = PaintMsg::Paint(requests, self.frame_tree_id);
let _ = self.get_pipeline(pipeline_id).paint_chan.send(msg); let _ = self.get_pipeline(pipeline_id).paint_chan.send(msg);
} }
self.add_outstanding_paint_msg(num_paint_msgs_sent);
true true
} }
fn is_ready_to_paint_image_output(&self) -> bool { /// Check if a layer (or its children) have any outstanding paint
if !self.got_load_complete_message { /// results to arrive yet.
return false; fn does_layer_have_outstanding_paint_messages(&self, layer: &Rc<Layer<CompositorData>>) -> bool {
} let layer_data = layer.extra_data.borrow();
let current_epoch = self.pipeline_details.get(&layer_data.pipeline_id).unwrap().current_epoch;
if self.get_earliest_pipeline_ready_state() != ReadyState::FinishedLoading {
return false;
}
if self.has_outstanding_paint_msgs() {
return false;
}
if !self.all_pipelines_in_idle_paint_state() {
return false;
}
if self.frame_tree_id == FrameTreeId(0) {
return false;
}
// Only check layers that have requested the current epoch, as there may be
// layers that are not visible in the current viewport, and therefore
// have not requested a paint of the current epoch.
// If a layer has sent a request for the current epoch, but it hasn't
// arrived yet then this layer is waiting for a paint message.
if layer_data.requested_epoch == current_epoch && layer_data.painted_epoch != current_epoch {
return true; return true;
} }
for child in layer.children().iter() {
if self.does_layer_have_outstanding_paint_messages(child) {
return true;
}
}
false
}
/// Query the constellation to see if the current compositor
/// output matches the current frame tree output, and if the
/// associated script tasks are idle.
fn is_ready_to_paint_image_output(&mut self) -> bool {
match self.ready_to_save_state {
ReadyState::Unknown => {
// Unsure if the output image is stable.
// Check if any layers are waiting for paints to complete
// of their current epoch request. If so, early exit
// from this check.
match self.scene.root {
Some(ref root_layer) => {
if self.does_layer_have_outstanding_paint_messages(root_layer) {
return false;
}
}
None => {
return false;
}
}
// Collect the currently painted epoch of each pipeline that is
// complete (i.e. has *all* layers painted to the requested epoch).
// This gets sent to the constellation for comparison with the current
// frame tree.
let mut pipeline_epochs = HashMap::new();
for (id, details) in self.pipeline_details.iter() {
// If animations are currently running, then don't bother checking
// with the constellation if the output image is stable.
if details.animations_running || details.animation_callbacks_running {
return false;
}
pipeline_epochs.insert(*id, details.current_epoch);
}
// Pass the pipeline/epoch states to the constellation and check
// if it's safe to output the image.
let ConstellationChan(ref chan) = self.constellation_chan;
chan.send(ConstellationMsg::IsReadyToSaveImage(pipeline_epochs)).unwrap();
self.ready_to_save_state = ReadyState::WaitingForConstellationReply;
false
}
ReadyState::WaitingForConstellationReply => {
// If waiting on a reply from the constellation to the last
// query if the image is stable, then assume not ready yet.
false
}
ReadyState::ReadyToSaveImage => {
// Constellation has replied at some point in the past
// that the current output image is stable and ready
// for saving.
true
}
}
}
fn composite(&mut self) { fn composite(&mut self) {
let target = self.composite_target; let target = self.composite_target;
self.composite_specific_target(target); self.composite_specific_target(target);

View file

@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use compositor_task::LayerProperties;
use compositor::IOCompositor; use compositor::IOCompositor;
use windowing::{MouseWindowEvent, WindowMethods}; use windowing::{MouseWindowEvent, WindowMethods};
@ -18,7 +17,7 @@ use layers::geometry::LayerPixel;
use layers::layers::{Layer, LayerBufferSet}; use layers::layers::{Layer, LayerBufferSet};
use script_traits::CompositorEvent::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent}; use script_traits::CompositorEvent::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent};
use script_traits::{ScriptControlChan, ConstellationControlMsg}; use script_traits::{ScriptControlChan, ConstellationControlMsg};
use msg::compositor_msg::{Epoch, LayerId, ScrollPolicy}; use msg::compositor_msg::{Epoch, LayerId, LayerProperties, ScrollPolicy};
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use std::rc::Rc; use std::rc::Rc;
@ -36,9 +35,11 @@ pub struct CompositorData {
/// Whether an ancestor layer that receives scroll events moves this layer. /// Whether an ancestor layer that receives scroll events moves this layer.
pub scroll_policy: ScrollPolicy, pub scroll_policy: ScrollPolicy,
/// A monotonically increasing counter that keeps track of the current epoch. /// The epoch that has been requested for this layer (via send_buffer_requests).
/// add_buffer() calls that don't match the current epoch will be ignored. pub requested_epoch: Epoch,
pub epoch: Epoch,
/// The last accepted painted buffer for this layer (via assign_pained_buffers).
pub painted_epoch: Epoch,
/// The scroll offset originating from this scrolling root. This allows scrolling roots /// The scroll offset originating from this scrolling root. This allows scrolling roots
/// to track their current scroll position even while their content_offset does not change. /// to track their current scroll position even while their content_offset does not change.
@ -46,16 +47,18 @@ pub struct CompositorData {
} }
impl CompositorData { impl CompositorData {
pub fn new_layer(layer_properties: LayerProperties, pub fn new_layer(pipeline_id: PipelineId,
layer_properties: LayerProperties,
wants_scroll_events: WantsScrollEventsFlag, wants_scroll_events: WantsScrollEventsFlag,
tile_size: usize) tile_size: usize)
-> Rc<Layer<CompositorData>> { -> Rc<Layer<CompositorData>> {
let new_compositor_data = CompositorData { let new_compositor_data = CompositorData {
pipeline_id: layer_properties.pipeline_id, pipeline_id: pipeline_id,
id: layer_properties.id, id: layer_properties.id,
wants_scroll_events: wants_scroll_events, wants_scroll_events: wants_scroll_events,
scroll_policy: layer_properties.scroll_policy, scroll_policy: layer_properties.scroll_policy,
epoch: layer_properties.epoch, requested_epoch: Epoch(0),
painted_epoch: Epoch(0),
scroll_offset: TypedPoint2D(0., 0.), scroll_offset: TypedPoint2D(0., 0.),
}; };
@ -76,7 +79,6 @@ pub trait CompositorLayer {
compositor: &IOCompositor<Window>, compositor: &IOCompositor<Window>,
new_buffers: Box<LayerBufferSet>, new_buffers: Box<LayerBufferSet>,
epoch: Epoch) epoch: Epoch)
-> bool
where Window: WindowMethods; where Window: WindowMethods;
/// Destroys all layer tiles, sending the buffers back to the painter to be destroyed or /// Destroys all layer tiles, sending the buffers back to the painter to be destroyed or
@ -178,7 +180,6 @@ pub enum ScrollEventResult {
impl CompositorLayer for Layer<CompositorData> { impl CompositorLayer for Layer<CompositorData> {
fn update_layer_except_bounds(&self, layer_properties: LayerProperties) { fn update_layer_except_bounds(&self, layer_properties: LayerProperties) {
self.extra_data.borrow_mut().epoch = layer_properties.epoch;
self.extra_data.borrow_mut().scroll_policy = layer_properties.scroll_policy; self.extra_data.borrow_mut().scroll_policy = layer_properties.scroll_policy;
*self.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color); *self.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color);
@ -205,17 +206,9 @@ impl CompositorLayer for Layer<CompositorData> {
compositor: &IOCompositor<Window>, compositor: &IOCompositor<Window>,
new_buffers: Box<LayerBufferSet>, new_buffers: Box<LayerBufferSet>,
epoch: Epoch) epoch: Epoch)
-> bool
where Window: WindowMethods { where Window: WindowMethods {
if self.extra_data.borrow().epoch != epoch { self.extra_data.borrow_mut().painted_epoch = epoch;
debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}", assert!(self.extra_data.borrow().painted_epoch == self.extra_data.borrow().requested_epoch);
self.extra_data.borrow().epoch,
epoch,
self.get_pipeline_id());
let pipeline = compositor.get_pipeline(self.get_pipeline_id());
let _ = pipeline.paint_chan.send(PaintMsg::UnusedBuffer(new_buffers.buffers));
return false;
}
for buffer in new_buffers.buffers.into_iter().rev() { for buffer in new_buffers.buffers.into_iter().rev() {
self.add_buffer(buffer); self.add_buffer(buffer);
@ -226,8 +219,6 @@ impl CompositorLayer for Layer<CompositorData> {
let pipeline = compositor.get_pipeline(self.get_pipeline_id()); let pipeline = compositor.get_pipeline(self.get_pipeline_id());
let _ = pipeline.paint_chan.send(PaintMsg::UnusedBuffer(unused_buffers)); let _ = pipeline.paint_chan.send(PaintMsg::UnusedBuffer(unused_buffers));
} }
true
} }
fn clear<Window>(&self, compositor: &IOCompositor<Window>) where Window: WindowMethods { fn clear<Window>(&self, compositor: &IOCompositor<Window>) where Window: WindowMethods {

View file

@ -11,14 +11,12 @@ use compositor;
use headless; use headless;
use windowing::{WindowEvent, WindowMethods}; use windowing::{WindowEvent, WindowMethods};
use azure::azure_hl::{SourceSurfaceMethods, Color};
use geom::point::Point2D; use geom::point::Point2D;
use geom::rect::Rect; use geom::rect::Rect;
use geom::size::Size2D;
use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata}; use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata};
use layers::layers::LayerBufferSet; use layers::layers::LayerBufferSet;
use msg::compositor_msg::{Epoch, LayerId, LayerMetadata, FrameTreeId, ReadyState}; use msg::compositor_msg::{Epoch, LayerId, LayerProperties, FrameTreeId};
use msg::compositor_msg::{PaintListener, PaintState, ScriptListener, ScrollPolicy}; use msg::compositor_msg::{PaintListener, ScriptListener};
use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId}; use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId};
use msg::constellation_msg::{Key, KeyState, KeyModifiers}; use msg::constellation_msg::{Key, KeyState, KeyModifiers};
use profile_traits::mem; use profile_traits::mem;
@ -65,11 +63,6 @@ impl CompositorReceiver for Receiver<Msg> {
/// Implementation of the abstract `ScriptListener` interface. /// Implementation of the abstract `ScriptListener` interface.
impl ScriptListener for Box<CompositorProxy+'static+Send> { impl ScriptListener for Box<CompositorProxy+'static+Send> {
fn set_ready_state(&mut self, pipeline_id: PipelineId, ready_state: ReadyState) {
let msg = Msg::ChangeReadyState(pipeline_id, ready_state);
self.send(msg);
}
fn scroll_fragment_point(&mut self, fn scroll_fragment_point(&mut self,
pipeline_id: PipelineId, pipeline_id: PipelineId,
layer_id: LayerId, layer_id: LayerId,
@ -96,33 +89,6 @@ impl ScriptListener for Box<CompositorProxy+'static+Send> {
} }
} }
/// Information about each layer that the compositor keeps.
#[derive(Clone, Copy)]
pub struct LayerProperties {
pub pipeline_id: PipelineId,
pub epoch: Epoch,
pub id: LayerId,
pub rect: Rect<f32>,
pub background_color: Color,
pub scroll_policy: ScrollPolicy,
}
impl LayerProperties {
fn new(pipeline_id: PipelineId, epoch: Epoch, metadata: &LayerMetadata) -> LayerProperties {
LayerProperties {
pipeline_id: pipeline_id,
epoch: epoch,
id: metadata.id,
rect: Rect(Point2D(metadata.position.origin.x as f32,
metadata.position.origin.y as f32),
Size2D(metadata.position.size.width as f32,
metadata.position.size.height as f32)),
background_color: metadata.background_color,
scroll_policy: metadata.scroll_policy,
}
}
}
/// Implementation of the abstract `PaintListener` interface. /// Implementation of the abstract `PaintListener` interface.
impl PaintListener for Box<CompositorProxy+'static+Send> { impl PaintListener for Box<CompositorProxy+'static+Send> {
fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata> { fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata> {
@ -141,29 +107,12 @@ impl PaintListener for Box<CompositorProxy+'static+Send> {
fn initialize_layers_for_pipeline(&mut self, fn initialize_layers_for_pipeline(&mut self,
pipeline_id: PipelineId, pipeline_id: PipelineId,
metadata: Vec<LayerMetadata>, properties: Vec<LayerProperties>,
epoch: Epoch) { epoch: Epoch) {
// FIXME(#2004, pcwalton): This assumes that the first layer determines the page size, and // FIXME(#2004, pcwalton): This assumes that the first layer determines the page size, and
// that all other layers are immediate children of it. This is sufficient to handle // that all other layers are immediate children of it. This is sufficient to handle
// `position: fixed` but will not be sufficient to handle `overflow: scroll` or transforms. // `position: fixed` but will not be sufficient to handle `overflow: scroll` or transforms.
let mut first = true; self.send(Msg::InitializeLayersForPipeline(pipeline_id, epoch, properties));
for metadata in metadata.iter() {
let layer_properties = LayerProperties::new(pipeline_id, epoch, metadata);
if first {
self.send(Msg::CreateOrUpdateBaseLayer(layer_properties));
first = false
} else {
self.send(Msg::CreateOrUpdateDescendantLayer(layer_properties));
}
}
}
fn paint_msg_discarded(&mut self) {
self.send(Msg::PaintMsgDiscarded);
}
fn set_paint_state(&mut self, pipeline_id: PipelineId, paint_state: PaintState) {
self.send(Msg::ChangePaintState(pipeline_id, paint_state))
} }
} }
@ -184,30 +133,21 @@ pub enum Msg {
/// The headless compositor returns `None`. /// The headless compositor returns `None`.
GetGraphicsMetadata(Sender<Option<NativeGraphicsMetadata>>), GetGraphicsMetadata(Sender<Option<NativeGraphicsMetadata>>),
/// Tells the compositor to create the root layer for a pipeline if necessary (i.e. if no layer /// Tells the compositor to create or update the layers for a pipeline if necessary
/// with that ID exists). /// (i.e. if no layer with that ID exists).
CreateOrUpdateBaseLayer(LayerProperties), InitializeLayersForPipeline(PipelineId, Epoch, Vec<LayerProperties>),
/// Tells the compositor to create a descendant layer for a pipeline if necessary (i.e. if no
/// layer with that ID exists).
CreateOrUpdateDescendantLayer(LayerProperties),
/// Alerts the compositor that the specified layer's rect has changed. /// Alerts the compositor that the specified layer's rect has changed.
SetLayerRect(PipelineId, LayerId, Rect<f32>), SetLayerRect(PipelineId, LayerId, Rect<f32>),
/// Scroll a page in a window /// Scroll a page in a window
ScrollFragmentPoint(PipelineId, LayerId, Point2D<f32>), ScrollFragmentPoint(PipelineId, LayerId, Point2D<f32>),
/// Requests that the compositor assign the painted buffers to the given layers. /// Requests that the compositor assign the painted buffers to the given layers.
AssignPaintedBuffers(PipelineId, Epoch, Vec<(LayerId, Box<LayerBufferSet>)>, FrameTreeId), AssignPaintedBuffers(PipelineId, Epoch, Vec<(LayerId, Box<LayerBufferSet>)>, FrameTreeId),
/// Alerts the compositor to the current status of page loading.
ChangeReadyState(PipelineId, ReadyState),
/// Alerts the compositor to the current status of painting.
ChangePaintState(PipelineId, PaintState),
/// Alerts the compositor that the current page has changed its title. /// Alerts the compositor that the current page has changed its title.
ChangePageTitle(PipelineId, Option<String>), ChangePageTitle(PipelineId, Option<String>),
/// Alerts the compositor that the current page has changed its URL. /// Alerts the compositor that the current page has changed its URL.
ChangePageUrl(PipelineId, Url), ChangePageUrl(PipelineId, Url),
/// Alerts the compositor that the given pipeline has changed whether it is running animations. /// Alerts the compositor that the given pipeline has changed whether it is running animations.
ChangeRunningAnimationsState(PipelineId, AnimationState), ChangeRunningAnimationsState(PipelineId, AnimationState),
/// Alerts the compositor that a `PaintMsg` has been discarded.
PaintMsgDiscarded,
/// Replaces the current frame tree, typically called during main frame navigation. /// Replaces the current frame tree, typically called during main frame navigation.
SetFrameTree(SendableFrameTree, Sender<()>, ConstellationChan), SetFrameTree(SendableFrameTree, Sender<()>, ConstellationChan),
/// The load of a page has completed. /// The load of a page has completed.
@ -226,6 +166,8 @@ pub enum Msg {
PaintTaskExited(PipelineId), PaintTaskExited(PipelineId),
/// Alerts the compositor that the viewport has been constrained in some manner /// Alerts the compositor that the viewport has been constrained in some manner
ViewportConstrained(PipelineId, ViewportConstraints), ViewportConstrained(PipelineId, ViewportConstraints),
/// A reply to the compositor asking if the output image is stable.
IsReadyToSaveImageReply(bool),
} }
impl Debug for Msg { impl Debug for Msg {
@ -234,17 +176,13 @@ impl Debug for Msg {
Msg::Exit(..) => write!(f, "Exit"), Msg::Exit(..) => write!(f, "Exit"),
Msg::ShutdownComplete(..) => write!(f, "ShutdownComplete"), Msg::ShutdownComplete(..) => write!(f, "ShutdownComplete"),
Msg::GetGraphicsMetadata(..) => write!(f, "GetGraphicsMetadata"), Msg::GetGraphicsMetadata(..) => write!(f, "GetGraphicsMetadata"),
Msg::CreateOrUpdateBaseLayer(..) => write!(f, "CreateOrUpdateBaseLayer"), Msg::InitializeLayersForPipeline(..) => write!(f, "InitializeLayersForPipeline"),
Msg::CreateOrUpdateDescendantLayer(..) => write!(f, "CreateOrUpdateDescendantLayer"),
Msg::SetLayerRect(..) => write!(f, "SetLayerRect"), Msg::SetLayerRect(..) => write!(f, "SetLayerRect"),
Msg::ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"), Msg::ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"),
Msg::AssignPaintedBuffers(..) => write!(f, "AssignPaintedBuffers"), Msg::AssignPaintedBuffers(..) => write!(f, "AssignPaintedBuffers"),
Msg::ChangeReadyState(..) => write!(f, "ChangeReadyState"),
Msg::ChangePaintState(..) => write!(f, "ChangePaintState"),
Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"), Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"),
Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"), Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
Msg::ChangePageUrl(..) => write!(f, "ChangePageUrl"), Msg::ChangePageUrl(..) => write!(f, "ChangePageUrl"),
Msg::PaintMsgDiscarded(..) => write!(f, "PaintMsgDiscarded"),
Msg::SetFrameTree(..) => write!(f, "SetFrameTree"), Msg::SetFrameTree(..) => write!(f, "SetFrameTree"),
Msg::LoadComplete => write!(f, "LoadComplete"), Msg::LoadComplete => write!(f, "LoadComplete"),
Msg::ScrollTimeout(..) => write!(f, "ScrollTimeout"), Msg::ScrollTimeout(..) => write!(f, "ScrollTimeout"),
@ -254,6 +192,7 @@ impl Debug for Msg {
Msg::CreatePng(..) => write!(f, "CreatePng"), Msg::CreatePng(..) => write!(f, "CreatePng"),
Msg::PaintTaskExited(..) => write!(f, "PaintTaskExited"), Msg::PaintTaskExited(..) => write!(f, "PaintTaskExited"),
Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"), Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"),
Msg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"),
} }
} }
} }

View file

@ -16,11 +16,12 @@ use compositor_task::Msg as CompositorMsg;
use devtools_traits::{DevtoolsControlChan, DevtoolsControlMsg}; use devtools_traits::{DevtoolsControlChan, DevtoolsControlMsg};
use geom::point::Point2D; use geom::point::Point2D;
use geom::rect::{Rect, TypedRect}; use geom::rect::{Rect, TypedRect};
use geom::size::Size2D;
use geom::scale_factor::ScaleFactor; use geom::scale_factor::ScaleFactor;
use gfx::font_cache_task::FontCacheTask; use gfx::font_cache_task::FontCacheTask;
use layout_traits::{LayoutControlMsg, LayoutTaskFactory}; use layout_traits::{LayoutControlChan, LayoutControlMsg, LayoutTaskFactory};
use libc; use libc;
use msg::compositor_msg::LayerId; use msg::compositor_msg::{Epoch, LayerId};
use msg::constellation_msg::AnimationState; use msg::constellation_msg::AnimationState;
use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{FrameId, PipelineExitType, PipelineId}; use msg::constellation_msg::{FrameId, PipelineExitType, PipelineId};
@ -34,7 +35,7 @@ use net_traits::storage_task::{StorageTask, StorageTaskMsg};
use profile_traits::mem; use profile_traits::mem;
use profile_traits::time; use profile_traits::time;
use script_traits::{CompositorEvent, ConstellationControlMsg}; use script_traits::{CompositorEvent, ConstellationControlMsg};
use script_traits::{ScriptControlChan, ScriptTaskFactory}; use script_traits::{ScriptControlChan, ScriptState, ScriptTaskFactory};
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{self, Write}; use std::io::{self, Write};
@ -438,6 +439,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
debug!("constellation got viewport-constrained event message"); debug!("constellation got viewport-constrained event message");
self.handle_viewport_constrained_msg(pipeline_id, constraints); self.handle_viewport_constrained_msg(pipeline_id, constraints);
} }
ConstellationMsg::IsReadyToSaveImage(pipeline_states) => {
let is_ready = self.handle_is_ready_to_save_image(pipeline_states);
self.compositor_proxy.send(CompositorMsg::IsReadyToSaveImageReply(is_ready));
}
} }
true true
} }
@ -938,6 +943,87 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
self.compositor_proxy.send(CompositorMsg::ViewportConstrained(pipeline_id, constraints)); self.compositor_proxy.send(CompositorMsg::ViewportConstrained(pipeline_id, constraints));
} }
/// Checks the state of all script and layout pipelines to see if they are idle
/// and compares the current layout state to what the compositor has. This is used
/// to check if the output image is "stable" and can be written as a screenshot
/// for reftests.
fn handle_is_ready_to_save_image(&mut self,
pipeline_states: HashMap<PipelineId, Epoch>) -> bool {
// If there is no root frame yet, the initial page has
// not loaded, so there is nothing to save yet.
if self.root_frame_id.is_none() {
return false;
}
// If there are pending changes to the current frame
// tree, the image is not stable yet.
if self.pending_frames.len() > 0 {
return false;
}
// Step through the current frame tree, checking that the script
// task is idle, and that the current epoch of the layout task
// matches what the compositor has painted. If all these conditions
// are met, then the output image should not change and a reftest
// screenshot can safely be written.
for frame in self.current_frame_tree_iter(self.root_frame_id) {
let pipeline = self.pipeline(frame.current);
// Synchronously query the script task for this pipeline
// to see if it is idle.
let ScriptControlChan(ref script_chan) = pipeline.script_chan;
let (sender, receiver) = channel();
let msg = ConstellationControlMsg::GetCurrentState(sender, frame.current);
script_chan.send(msg).unwrap();
if receiver.recv().unwrap() == ScriptState::DocumentLoading {
return false;
}
// Check the visible rectangle for this pipeline. If the constellation
// hasn't received a rectangle for this pipeline yet, then assume
// that the output image isn't stable yet.
match pipeline.rect {
Some(rect) => {
// If the rectangle for this pipeline is zero sized, it will
// never be painted. In this case, don't query the layout
// task as it won't contribute to the final output image.
if rect.size == Size2D::zero() {
continue;
}
// Get the epoch that the compositor has drawn for this pipeline.
let compositor_epoch = pipeline_states.get(&frame.current);
match compositor_epoch {
Some(compositor_epoch) => {
// Synchronously query the layout task to see if the current
// epoch matches what the compositor has drawn. If they match
// (and script is idle) then this pipeline won't change again
// and can be considered stable.
let (sender, receiver) = channel();
let LayoutControlChan(ref layout_chan) = pipeline.layout_chan;
layout_chan.send(LayoutControlMsg::GetCurrentEpoch(sender)).unwrap();
let layout_task_epoch = receiver.recv().unwrap();
if layout_task_epoch != *compositor_epoch {
return false;
}
}
None => {
// The compositor doesn't know about this pipeline yet.
// Assume it hasn't rendered yet.
return false;
}
}
}
None => {
return false;
}
}
}
// All script tasks are idle and layout epochs match compositor, so output image!
true
}
// Close a frame (and all children) // Close a frame (and all children)
fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) { fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) {
let frame = self.frames.remove(&frame_id).unwrap(); let frame = self.frames.remove(&frame_id).unwrap();

View file

@ -92,16 +92,12 @@ impl CompositorEventListener for NullCompositor {
// we'll notice and think about whether it needs a response, like // we'll notice and think about whether it needs a response, like
// SetFrameTree. // SetFrameTree.
Msg::CreateOrUpdateBaseLayer(..) | Msg::InitializeLayersForPipeline(..) |
Msg::CreateOrUpdateDescendantLayer(..) |
Msg::SetLayerRect(..) | Msg::SetLayerRect(..) |
Msg::AssignPaintedBuffers(..) | Msg::AssignPaintedBuffers(..) |
Msg::ChangeReadyState(..) |
Msg::ChangePaintState(..) |
Msg::ChangeRunningAnimationsState(..) | Msg::ChangeRunningAnimationsState(..) |
Msg::ScrollFragmentPoint(..) | Msg::ScrollFragmentPoint(..) |
Msg::LoadComplete | Msg::LoadComplete |
Msg::PaintMsgDiscarded(..) |
Msg::ScrollTimeout(..) | Msg::ScrollTimeout(..) |
Msg::RecompositeAfterScroll | Msg::RecompositeAfterScroll |
Msg::ChangePageTitle(..) | Msg::ChangePageTitle(..) |
@ -110,7 +106,8 @@ impl CompositorEventListener for NullCompositor {
Msg::SetCursor(..) | Msg::SetCursor(..) |
Msg::ViewportConstrained(..) => {} Msg::ViewportConstrained(..) => {}
Msg::CreatePng(..) | Msg::CreatePng(..) |
Msg::PaintTaskExited(..) => {} Msg::PaintTaskExited(..) |
Msg::IsReadyToSaveImageReply(..) => {}
} }
true true
} }

View file

@ -11,7 +11,6 @@ use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D; use geom::size::TypedSize2D;
use layers::geometry::DevicePixel; use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata; use layers::platform::surface::NativeGraphicsMetadata;
use msg::compositor_msg::{PaintState, ReadyState};
use msg::constellation_msg::{Key, KeyState, KeyModifiers}; use msg::constellation_msg::{Key, KeyState, KeyModifiers};
use script_traits::MouseButton; use script_traits::MouseButton;
use url::Url; use url::Url;
@ -100,10 +99,6 @@ pub trait WindowMethods {
/// Presents the window to the screen (perhaps by page flipping). /// Presents the window to the screen (perhaps by page flipping).
fn present(&self); fn present(&self);
/// Sets the ready state of the current page.
fn set_ready_state(&self, ready_state: ReadyState);
/// Sets the paint state of the current page.
fn set_paint_state(&self, paint_state: PaintState);
/// Sets the page title for the current page. /// Sets the page title for the current page.
fn set_page_title(&self, title: Option<String>); fn set_page_title(&self, title: Option<String>);
/// Sets the load data for the current page. /// Sets the load data for the current page.

View file

@ -20,8 +20,8 @@ use layers::platform::surface::{NativeGraphicsMetadata, NativePaintingGraphicsCo
use layers::platform::surface::NativeSurface; use layers::platform::surface::NativeSurface;
use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet}; use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet};
use layers; use layers;
use msg::compositor_msg::{Epoch, FrameTreeId, PaintState, LayerId}; use msg::compositor_msg::{Epoch, FrameTreeId, LayerId};
use msg::compositor_msg::{LayerMetadata, PaintListener, ScrollPolicy}; use msg::compositor_msg::{LayerProperties, PaintListener, ScrollPolicy};
use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{ConstellationChan, Failure, PipelineId}; use msg::constellation_msg::{ConstellationChan, Failure, PipelineId};
use msg::constellation_msg::PipelineExitType; use msg::constellation_msg::PipelineExitType;
@ -67,7 +67,7 @@ pub struct PaintRequest {
} }
pub enum Msg { pub enum Msg {
PaintInit(Arc<StackingContext>), PaintInit(Epoch, Arc<StackingContext>),
Paint(Vec<PaintRequest>, FrameTreeId), Paint(Vec<PaintRequest>, FrameTreeId),
UnusedBuffer(Vec<Box<LayerBuffer>>), UnusedBuffer(Vec<Box<LayerBuffer>>),
PaintPermissionGranted, PaintPermissionGranted,
@ -112,8 +112,8 @@ pub struct PaintTask<C> {
/// Permission to send paint messages to the compositor /// Permission to send paint messages to the compositor
paint_permission: bool, paint_permission: bool,
/// A counter for epoch messages /// The current epoch counter is passed by the layout task
epoch: Epoch, current_epoch: Option<Epoch>,
/// A data structure to store unused LayerBuffers /// A data structure to store unused LayerBuffers
buffer_map: BufferMap, buffer_map: BufferMap,
@ -165,7 +165,7 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
native_graphics_context: native_graphics_context, native_graphics_context: native_graphics_context,
root_stacking_context: None, root_stacking_context: None,
paint_permission: false, paint_permission: false,
epoch: Epoch(0), current_epoch: None,
buffer_map: BufferMap::new(10000000), buffer_map: BufferMap::new(10000000),
worker_threads: worker_threads, worker_threads: worker_threads,
used_buffer_count: 0, used_buffer_count: 0,
@ -197,7 +197,8 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
let mut waiting_for_compositor_buffers_to_exit = false; let mut waiting_for_compositor_buffers_to_exit = false;
loop { loop {
match self.port.recv().unwrap() { match self.port.recv().unwrap() {
Msg::PaintInit(stacking_context) => { Msg::PaintInit(epoch, stacking_context) => {
self.current_epoch = Some(epoch);
self.root_stacking_context = Some(stacking_context.clone()); self.root_stacking_context = Some(stacking_context.clone());
if !self.paint_permission { if !self.paint_permission {
@ -207,7 +208,6 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
continue; continue;
} }
self.epoch.next();
self.initialize_layers(); self.initialize_layers();
} }
Msg::Paint(requests, frame_tree_id) => { Msg::Paint(requests, frame_tree_id) => {
@ -215,30 +215,26 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
debug!("PaintTask: paint ready msg"); debug!("PaintTask: paint ready msg");
let ConstellationChan(ref mut c) = self.constellation_chan; let ConstellationChan(ref mut c) = self.constellation_chan;
c.send(ConstellationMsg::PainterReady(self.id)).unwrap(); c.send(ConstellationMsg::PainterReady(self.id)).unwrap();
self.compositor.paint_msg_discarded();
continue; continue;
} }
let mut replies = Vec::new(); let mut replies = Vec::new();
self.compositor.set_paint_state(self.id, PaintState::Painting);
for PaintRequest { buffer_requests, scale, layer_id, epoch } for PaintRequest { buffer_requests, scale, layer_id, epoch }
in requests.into_iter() { in requests.into_iter() {
if self.epoch == epoch { if self.current_epoch == Some(epoch) {
self.paint(&mut replies, buffer_requests, scale, layer_id); self.paint(&mut replies, buffer_requests, scale, layer_id);
} else { } else {
debug!("painter epoch mismatch: {:?} != {:?}", self.epoch, epoch); debug!("painter epoch mismatch: {:?} != {:?}", self.current_epoch, epoch);
} }
} }
self.compositor.set_paint_state(self.id, PaintState::Idle);
for reply in replies.iter() { for reply in replies.iter() {
let &(_, ref buffer_set) = reply; let &(_, ref buffer_set) = reply;
self.used_buffer_count += (*buffer_set).buffers.len(); self.used_buffer_count += (*buffer_set).buffers.len();
} }
debug!("PaintTask: returning surfaces"); debug!("PaintTask: returning surfaces");
self.compositor.assign_painted_buffers(self.id, self.epoch, replies, frame_tree_id); self.compositor.assign_painted_buffers(self.id, self.current_epoch.unwrap(), replies, frame_tree_id);
} }
Msg::UnusedBuffer(unused_buffers) => { Msg::UnusedBuffer(unused_buffers) => {
debug!("PaintTask: Received {} unused buffers", unused_buffers.len()); debug!("PaintTask: Received {} unused buffers", unused_buffers.len());
@ -258,7 +254,6 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
self.paint_permission = true; self.paint_permission = true;
if self.root_stacking_context.is_some() { if self.root_stacking_context.is_some() {
self.epoch.next();
self.initialize_layers(); self.initialize_layers();
} }
} }
@ -379,11 +374,11 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
Some(ref root_stacking_context) => root_stacking_context, Some(ref root_stacking_context) => root_stacking_context,
}; };
let mut metadata = Vec::new(); let mut properties = Vec::new();
build(&mut metadata, &**root_stacking_context, &ZERO_POINT); build(&mut properties, &**root_stacking_context, &ZERO_POINT);
self.compositor.initialize_layers_for_pipeline(self.id, metadata, self.epoch); self.compositor.initialize_layers_for_pipeline(self.id, properties, self.current_epoch.unwrap());
fn build(metadata: &mut Vec<LayerMetadata>, fn build(properties: &mut Vec<LayerProperties>,
stacking_context: &StackingContext, stacking_context: &StackingContext,
page_position: &Point2D<Au>) { page_position: &Point2D<Au>) {
let page_position = stacking_context.bounds.origin + *page_position; let page_position = stacking_context.bounds.origin + *page_position;
@ -392,20 +387,20 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
// the compositor is concerned. // the compositor is concerned.
let overflow_relative_page_position = page_position + stacking_context.overflow.origin; let overflow_relative_page_position = page_position + stacking_context.overflow.origin;
let layer_position = let layer_position =
Rect(Point2D(overflow_relative_page_position.x.to_nearest_px() as i32, Rect(Point2D(overflow_relative_page_position.x.to_nearest_px() as f32,
overflow_relative_page_position.y.to_nearest_px() as i32), overflow_relative_page_position.y.to_nearest_px() as f32),
Size2D(stacking_context.overflow.size.width.to_nearest_px() as i32, Size2D(stacking_context.overflow.size.width.to_nearest_px() as f32,
stacking_context.overflow.size.height.to_nearest_px() as i32)); stacking_context.overflow.size.height.to_nearest_px() as f32));
metadata.push(LayerMetadata { properties.push(LayerProperties {
id: paint_layer.id, id: paint_layer.id,
position: layer_position, rect: layer_position,
background_color: paint_layer.background_color, background_color: paint_layer.background_color,
scroll_policy: paint_layer.scroll_policy, scroll_policy: paint_layer.scroll_policy,
}) })
} }
for kid in stacking_context.display_list.children.iter() { for kid in stacking_context.display_list.children.iter() {
build(metadata, &**kid, &page_position) build(properties, &**kid, &page_position)
} }
} }
} }

View file

@ -39,7 +39,7 @@ use gfx::paint_task::Msg as PaintMsg;
use gfx::paint_task::{PaintChan, PaintLayer}; use gfx::paint_task::{PaintChan, PaintLayer};
use layout_traits::{LayoutControlMsg, LayoutTaskFactory}; use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
use log; use log;
use msg::compositor_msg::ScrollPolicy; use msg::compositor_msg::{Epoch, ScrollPolicy};
use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, PipelineId}; use msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, PipelineId};
use profile_traits::mem::{self, Report, ReportsChan}; use profile_traits::mem::{self, Report, ReportsChan};
@ -124,6 +124,9 @@ pub struct LayoutTaskData {
/// A channel on which new animations that have been triggered by style recalculation can be /// A channel on which new animations that have been triggered by style recalculation can be
/// sent. /// sent.
pub new_animations_sender: Sender<Animation>, pub new_animations_sender: Sender<Animation>,
/// A counter for epoch messages
epoch: Epoch,
} }
/// Information needed by the layout task. /// Information needed by the layout task.
@ -329,6 +332,7 @@ impl LayoutTask {
running_animations: Vec::new(), running_animations: Vec::new(),
new_animations_receiver: new_animations_receiver, new_animations_receiver: new_animations_receiver,
new_animations_sender: new_animations_sender, new_animations_sender: new_animations_sender,
epoch: Epoch(0),
})), })),
} }
} }
@ -405,6 +409,10 @@ impl LayoutTask {
LayoutControlMsg::TickAnimations => { LayoutControlMsg::TickAnimations => {
self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data) self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data)
} }
LayoutControlMsg::GetCurrentEpoch(sender) => {
self.handle_request_helper(Msg::GetCurrentEpoch(sender),
possibly_locked_rw_data)
}
LayoutControlMsg::ExitNow(exit_type) => { LayoutControlMsg::ExitNow(exit_type) => {
self.handle_request_helper(Msg::ExitNow(exit_type), self.handle_request_helper(Msg::ExitNow(exit_type),
possibly_locked_rw_data) possibly_locked_rw_data)
@ -509,8 +517,11 @@ impl LayoutTask {
Msg::CollectReports(reports_chan) => { Msg::CollectReports(reports_chan) => {
self.collect_reports(reports_chan, possibly_locked_rw_data); self.collect_reports(reports_chan, possibly_locked_rw_data);
}, },
Msg::GetCurrentEpoch(sender) => {
let rw_data = self.lock_rw_data(possibly_locked_rw_data);
sender.send(rw_data.epoch).unwrap();
},
Msg::PrepareToExit(response_chan) => { Msg::PrepareToExit(response_chan) => {
debug!("layout: PrepareToExitMsg received");
self.prepare_to_exit(response_chan, possibly_locked_rw_data); self.prepare_to_exit(response_chan, possibly_locked_rw_data);
return false return false
}, },
@ -825,7 +836,8 @@ impl LayoutTask {
debug!("Layout done!"); debug!("Layout done!");
self.paint_chan.send(PaintMsg::PaintInit(stacking_context)); rw_data.epoch.next();
self.paint_chan.send(PaintMsg::PaintInit(rw_data.epoch, stacking_context));
} }
}); });
} }

View file

@ -17,6 +17,7 @@ extern crate util;
use gfx::font_cache_task::FontCacheTask; use gfx::font_cache_task::FontCacheTask;
use gfx::paint_task::PaintChan; use gfx::paint_task::PaintChan;
use msg::compositor_msg::Epoch;
use msg::constellation_msg::{ConstellationChan, Failure, PipelineId, PipelineExitType}; use msg::constellation_msg::{ConstellationChan, Failure, PipelineId, PipelineExitType};
use profile_traits::mem; use profile_traits::mem;
use profile_traits::time; use profile_traits::time;
@ -28,6 +29,7 @@ use std::sync::mpsc::{Sender, Receiver};
/// Messages sent to the layout task from the constellation /// Messages sent to the layout task from the constellation
pub enum LayoutControlMsg { pub enum LayoutControlMsg {
ExitNow(PipelineExitType), ExitNow(PipelineExitType),
GetCurrentEpoch(Sender<Epoch>),
TickAnimations, TickAnimations,
} }

View file

@ -13,27 +13,8 @@ use std::fmt;
use constellation_msg::PipelineId; use constellation_msg::PipelineId;
/// The status of the painter.
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum PaintState {
Idle,
Painting,
}
#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug, Copy)]
pub enum ReadyState {
/// Informs the compositor that nothing has been done yet. Used for setting status
Blank,
/// Informs the compositor that a page is loading. Used for setting status
Loading,
/// Informs the compositor that a page is performing layout. Used for setting status
PerformingLayout,
/// Informs the compositor that a page is finished loading. Used for setting status
FinishedLoading,
}
/// A newtype struct for denoting the age of messages; prevents race conditions. /// A newtype struct for denoting the age of messages; prevents race conditions.
#[derive(PartialEq, Eq, Debug, Copy, Clone)] #[derive(PartialEq, Eq, Debug, Copy, Clone, PartialOrd, Ord)]
pub struct Epoch(pub u32); pub struct Epoch(pub u32);
impl Epoch { impl Epoch {
@ -82,11 +63,11 @@ pub enum ScrollPolicy {
/// All layer-specific information that the painting task sends to the compositor other than the /// All layer-specific information that the painting task sends to the compositor other than the
/// buffer contents of the layer itself. /// buffer contents of the layer itself.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct LayerMetadata { pub struct LayerProperties {
/// An opaque ID. This is usually the address of the flow and index of the box within it. /// An opaque ID. This is usually the address of the flow and index of the box within it.
pub id: LayerId, pub id: LayerId,
/// The position and size of the layer in pixels. /// The position and size of the layer in pixels.
pub position: Rect<i32>, pub rect: Rect<f32>,
/// The background color of the layer. /// The background color of the layer.
pub background_color: Color, pub background_color: Color,
/// The scrolling policy of this layer. /// The scrolling policy of this layer.
@ -102,7 +83,7 @@ pub trait PaintListener {
/// creating and/or destroying paint layers as necessary. /// creating and/or destroying paint layers as necessary.
fn initialize_layers_for_pipeline(&mut self, fn initialize_layers_for_pipeline(&mut self,
pipeline_id: PipelineId, pipeline_id: PipelineId,
metadata: Vec<LayerMetadata>, properties: Vec<LayerProperties>,
epoch: Epoch); epoch: Epoch);
/// Sends new buffers for the given layers to the compositor. /// Sends new buffers for the given layers to the compositor.
@ -112,14 +93,11 @@ pub trait PaintListener {
replies: Vec<(LayerId, Box<LayerBufferSet>)>, replies: Vec<(LayerId, Box<LayerBufferSet>)>,
frame_tree_id: FrameTreeId); frame_tree_id: FrameTreeId);
fn paint_msg_discarded(&mut self);
fn set_paint_state(&mut self, PipelineId, PaintState);
} }
/// The interface used by the script task to tell the compositor to update its ready state, /// The interface used by the script task to tell the compositor to update its ready state,
/// which is used in displaying the appropriate message in the window's title. /// which is used in displaying the appropriate message in the window's title.
pub trait ScriptListener { pub trait ScriptListener {
fn set_ready_state(&mut self, PipelineId, ReadyState);
fn scroll_fragment_point(&mut self, fn scroll_fragment_point(&mut self,
pipeline_id: PipelineId, pipeline_id: PipelineId,
layer_id: LayerId, layer_id: LayerId,

View file

@ -5,6 +5,7 @@
//! The high-level interface from script to constellation. Using this abstract interface helps //! The high-level interface from script to constellation. Using this abstract interface helps
//! reduce coupling between these two components. //! reduce coupling between these two components.
use compositor_msg::Epoch;
use geom::rect::Rect; use geom::rect::Rect;
use geom::size::TypedSize2D; use geom::size::TypedSize2D;
use geom::scale_factor::ScaleFactor; use geom::scale_factor::ScaleFactor;
@ -14,6 +15,7 @@ use layers::geometry::DevicePixel;
use png; use png;
use util::cursor::Cursor; use util::cursor::Cursor;
use util::geometry::{PagePx, ViewportPx}; use util::geometry::{PagePx, ViewportPx};
use std::collections::HashMap;
use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::mpsc::{channel, Sender, Receiver};
use style::viewport::ViewportConstraints; use style::viewport::ViewportConstraints;
use webdriver_traits::WebDriverScriptCommand; use webdriver_traits::WebDriverScriptCommand;
@ -239,7 +241,9 @@ pub enum Msg {
/// Notifies the constellation that the viewport has been constrained in some manner /// Notifies the constellation that the viewport has been constrained in some manner
ViewportConstrained(PipelineId, ViewportConstraints), ViewportConstrained(PipelineId, ViewportConstraints),
/// Create a PNG of the window contents /// Create a PNG of the window contents
CompositePng(Sender<Option<png::Image>>) CompositePng(Sender<Option<png::Image>>),
/// Query the constellation to see if the current compositor output is stable
IsReadyToSaveImage(HashMap<PipelineId, Epoch>),
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]

View file

@ -12,6 +12,7 @@ use geom::point::Point2D;
use geom::rect::Rect; use geom::rect::Rect;
use libc::uintptr_t; use libc::uintptr_t;
use msg::constellation_msg::{PipelineExitType, WindowSizeData}; use msg::constellation_msg::{PipelineExitType, WindowSizeData};
use msg::compositor_msg::Epoch;
use net_traits::PendingAsyncLoad; use net_traits::PendingAsyncLoad;
use profile_traits::mem::{Reporter, ReportsChan}; use profile_traits::mem::{Reporter, ReportsChan};
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress}; use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress};
@ -63,6 +64,9 @@ pub enum Msg {
/// Requests that the layout task immediately shut down. There must be no more nodes left after /// Requests that the layout task immediately shut down. There must be no more nodes left after
/// this, or layout will crash. /// this, or layout will crash.
ExitNow(PipelineExitType), ExitNow(PipelineExitType),
/// Get the last epoch counter for this layout task.
GetCurrentEpoch(Sender<Epoch>)
} }
/// Synchronous messages that script can send to layout. /// Synchronous messages that script can send to layout.

View file

@ -58,9 +58,8 @@ use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent};
use script_traits::CompositorEvent::{MouseMoveEvent, KeyEvent}; use script_traits::CompositorEvent::{MouseMoveEvent, KeyEvent};
use script_traits::{NewLayoutInfo, OpaqueScriptLayoutChannel}; use script_traits::{NewLayoutInfo, OpaqueScriptLayoutChannel};
use script_traits::{ConstellationControlMsg, ScriptControlChan}; use script_traits::{ConstellationControlMsg, ScriptControlChan};
use script_traits::ScriptTaskFactory; use script_traits::{ScriptState, ScriptTaskFactory};
use webdriver_traits::WebDriverScriptCommand; use webdriver_traits::WebDriverScriptCommand;
use msg::compositor_msg::ReadyState::{FinishedLoading, Loading, PerformingLayout};
use msg::compositor_msg::{LayerId, ScriptListener}; use msg::compositor_msg::{LayerId, ScriptListener};
use msg::constellation_msg::{ConstellationChan, FocusType}; use msg::constellation_msg::{ConstellationChan, FocusType};
use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId}; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId};
@ -735,6 +734,10 @@ impl ScriptTask {
responder.respond(); responder.respond();
self.handle_resource_loaded(id, LoadType::Stylesheet(url)); self.handle_resource_loaded(id, LoadType::Stylesheet(url));
} }
ConstellationControlMsg::GetCurrentState(sender, pipeline_id) => {
let state = self.handle_get_current_state(pipeline_id);
sender.send(state).unwrap();
}
} }
} }
@ -859,6 +862,40 @@ impl ScriptTask {
doc.r().finish_load(load); doc.r().finish_load(load);
} }
/// Get the current state of a given pipeline.
fn handle_get_current_state(&self, pipeline_id: PipelineId) -> ScriptState {
// Check if the main page load is still pending
let loads = self.incomplete_loads.borrow();
if let Some(_) = loads.iter().find(|load| load.pipeline_id == pipeline_id) {
return ScriptState::DocumentLoading;
}
// If not in pending loads, the page should exist by now.
let page = self.root_page();
let page = page.find(pipeline_id).expect("GetCurrentState sent to nonexistent pipeline");
let doc = page.document().root();
// Check if document load event has fired. If the document load
// event has fired, this also guarantees that the first reflow
// has been kicked off. Since the script task does a join with
// layout, this ensures there are no race conditions that can occur
// between load completing and the first layout completing.
let load_pending = doc.r().ReadyState() != DocumentReadyState::Complete;
if load_pending {
return ScriptState::DocumentLoading;
}
// Checks if the html element has reftest-wait attribute present.
// See http://testthewebforward.org/docs/reftests.html
let html_element = doc.r().GetDocumentElement().root();
let reftest_wait = html_element.r().map_or(false, |elem| elem.has_class(&Atom::from_slice("reftest-wait")));
if reftest_wait {
return ScriptState::DocumentLoading;
}
return ScriptState::DocumentLoaded;
}
fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) { fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {
let NewLayoutInfo { let NewLayoutInfo {
containing_pipeline_id, containing_pipeline_id,
@ -993,14 +1030,6 @@ impl ScriptTask {
with this script task. This is a bug."); with this script task. This is a bug.");
let window = page.window().root(); let window = page.window().root();
window.r().handle_reflow_complete_msg(reflow_id); window.r().handle_reflow_complete_msg(reflow_id);
let doc = page.document().root();
let html_element = doc.r().GetDocumentElement().root();
let reftest_wait = html_element.r().map_or(false, |elem| elem.has_class(&Atom::from_slice("reftest-wait")));
if !reftest_wait {
self.compositor.borrow_mut().set_ready_state(pipeline_id, FinishedLoading);
}
} }
/// Window was resized, but this script was not active, so don't reflow yet /// Window was resized, but this script was not active, so don't reflow yet
@ -1102,8 +1131,6 @@ impl ScriptTask {
}) })
}).root(); }).root();
self.compositor.borrow_mut().set_ready_state(incomplete.pipeline_id, Loading);
// Create a new frame tree entry. // Create a new frame tree entry.
let page = Rc::new(Page::new(incomplete.pipeline_id, final_url.clone())); let page = Rc::new(Page::new(incomplete.pipeline_id, final_url.clone()));
if !root_page_exists { if !root_page_exists {
@ -1462,7 +1489,6 @@ impl ScriptTask {
let final_url = document.r().url(); let final_url = document.r().url();
document.r().set_ready_state(DocumentReadyState::Interactive); document.r().set_ready_state(DocumentReadyState::Interactive);
self.compositor.borrow_mut().set_ready_state(id, PerformingLayout);
// Kick off the initial reflow of the page. // Kick off the initial reflow of the page.
debug!("kicking off initial reflow of {:?}", final_url); debug!("kicking off initial reflow of {:?}", final_url);

View file

@ -60,6 +60,13 @@ pub trait StylesheetLoadResponder {
fn respond(self: Box<Self>); fn respond(self: Box<Self>);
} }
/// Used to determine if a script has any pending asynchronous activity.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ScriptState {
DocumentLoaded,
DocumentLoading,
}
/// Messages sent from the constellation to the script task /// Messages sent from the constellation to the script task
pub enum ConstellationControlMsg { pub enum ConstellationControlMsg {
/// Gives a channel and ID to a layout task, as well as the ID of that layout's parent /// Gives a channel and ID to a layout task, as well as the ID of that layout's parent
@ -96,6 +103,8 @@ pub enum ConstellationControlMsg {
TickAllAnimations(PipelineId), TickAllAnimations(PipelineId),
/// Notifies script that a stylesheet has finished loading. /// Notifies script that a stylesheet has finished loading.
StylesheetLoadComplete(PipelineId, Url, Box<StylesheetLoadResponder+Send>), StylesheetLoadComplete(PipelineId, Url, Box<StylesheetLoadResponder+Send>),
/// Get the current state of the script task for a given pipeline.
GetCurrentState(Sender<ScriptState>, PipelineId),
} }
/// The mouse button involved in the event. /// The mouse button involved in the event.

View file

@ -22,7 +22,6 @@ use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata; use layers::platform::surface::NativeGraphicsMetadata;
use libc::{c_char, c_void}; use libc::{c_char, c_void};
use msg::constellation_msg::{Key, KeyModifiers}; use msg::constellation_msg::{Key, KeyModifiers};
use msg::compositor_msg::{ReadyState, PaintState};
use std::ptr; use std::ptr;
use std_url::Url; use std_url::Url;
use util::cursor::Cursor; use util::cursor::Cursor;
@ -212,26 +211,6 @@ impl WindowMethods for Window {
} }
} }
fn set_ready_state(&self, ready_state: ReadyState) {
let browser = self.cef_browser.borrow();
let browser = match *browser {
None => return,
Some(ref browser) => browser,
};
let is_loading = match ready_state {
ReadyState::Blank | ReadyState::FinishedLoading => 0,
ReadyState::Loading | ReadyState::PerformingLayout => 1,
};
browser.get_host()
.get_client()
.get_load_handler()
.on_loading_state_change(browser.clone(), is_loading, 1, 1);
}
fn set_paint_state(&self, _: PaintState) {
// TODO(pcwalton)
}
fn hidpi_factor(&self) -> ScaleFactor<ScreenPx,DevicePixel,f32> { fn hidpi_factor(&self) -> ScaleFactor<ScreenPx,DevicePixel,f32> {
let browser = self.cef_browser.borrow(); let browser = self.cef_browser.borrow();
match *browser { match *browser {

View file

@ -14,7 +14,6 @@ use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata; use layers::platform::surface::NativeGraphicsMetadata;
use msg::constellation_msg; use msg::constellation_msg;
use msg::constellation_msg::Key; use msg::constellation_msg::Key;
use msg::compositor_msg::{PaintState, ReadyState};
use NestedEventLoopListener; use NestedEventLoopListener;
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::{channel, Sender}; use std::sync::mpsc::{channel, Sender};
@ -64,8 +63,6 @@ pub struct Window {
event_queue: RefCell<Vec<WindowEvent>>, event_queue: RefCell<Vec<WindowEvent>>,
mouse_pos: Cell<Point2D<i32>>, mouse_pos: Cell<Point2D<i32>>,
ready_state: Cell<ReadyState>,
paint_state: Cell<PaintState>,
key_modifiers: Cell<KeyModifiers>, key_modifiers: Cell<KeyModifiers>,
} }
@ -93,8 +90,6 @@ impl Window {
mouse_down_point: Cell::new(Point2D(0, 0)), mouse_down_point: Cell::new(Point2D(0, 0)),
mouse_pos: Cell::new(Point2D(0, 0)), mouse_pos: Cell::new(Point2D(0, 0)),
ready_state: Cell::new(ReadyState::Blank),
paint_state: Cell::new(PaintState::Idle),
key_modifiers: Cell::new(KeyModifiers::empty()), key_modifiers: Cell::new(KeyModifiers::empty()),
}; };
@ -476,16 +471,6 @@ impl WindowMethods for Window {
box receiver as Box<CompositorReceiver>) box receiver as Box<CompositorReceiver>)
} }
/// Sets the ready state.
fn set_ready_state(&self, ready_state: ReadyState) {
self.ready_state.set(ready_state);
}
/// Sets the paint state.
fn set_paint_state(&self, paint_state: PaintState) {
self.paint_state.set(paint_state);
}
fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> { fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
ScaleFactor::new(self.window.hidpi_factor()) ScaleFactor::new(self.window.hidpi_factor())
} }
@ -670,14 +655,6 @@ impl WindowMethods for Window {
box receiver as Box<CompositorReceiver>) box receiver as Box<CompositorReceiver>)
} }
/// Sets the ready state.
fn set_ready_state(&self, _: ReadyState) {
}
/// Sets the paint state.
fn set_paint_state(&self, _: PaintState) {
}
fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> { fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
ScaleFactor::new(1.0) ScaleFactor::new(1.0)
} }

View file

@ -11,9 +11,7 @@ use geom::size::TypedSize2D;
use layers::geometry::DevicePixel; use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata; use layers::platform::surface::NativeGraphicsMetadata;
use libc::c_int; use libc::c_int;
use msg::compositor_msg::{ReadyState, PaintState};
use msg::constellation_msg::{Key, KeyModifiers}; use msg::constellation_msg::{Key, KeyModifiers};
use std::cell::Cell;
use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::mpsc::{channel, Sender, Receiver};
use std::rc::Rc; use std::rc::Rc;
use std::mem::transmute; use std::mem::transmute;
@ -622,9 +620,6 @@ pub struct Window {
dpy: EGLDisplay, dpy: EGLDisplay,
ctx: EGLContext, ctx: EGLContext,
surf: EGLSurface, surf: EGLSurface,
ready_state: Cell<ReadyState>,
paint_state: Cell<PaintState>,
} }
impl Window { impl Window {
@ -750,9 +745,6 @@ impl Window {
dpy: dpy, dpy: dpy,
ctx: ctx, ctx: ctx,
surf: eglwindow, surf: eglwindow,
ready_state: Cell::new(ReadyState::Blank),
paint_state: Cell::new(PaintState::Idle),
}; };
Rc::new(window) Rc::new(window)
@ -787,16 +779,6 @@ impl WindowMethods for Window {
let _ = egl::SwapBuffers(self.dpy, self.surf); let _ = egl::SwapBuffers(self.dpy, self.surf);
} }
/// Sets the ready state.
fn set_ready_state(&self, ready_state: ReadyState) {
self.ready_state.set(ready_state);
}
/// Sets the paint state.
fn set_paint_state(&self, paint_state: PaintState) {
self.paint_state.set(paint_state);
}
fn set_page_title(&self, _: Option<String>) { fn set_page_title(&self, _: Option<String>) {
} }