From eec3fad93da44724a661520e569fb7471c1b4f35 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 13 May 2015 09:58:01 +1000 Subject: [PATCH] Fixes a number of race conditions and reliability issues with reftests and compositor. 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. --- components/compositing/compositor.rs | 297 ++++++++++----------- components/compositing/compositor_layer.rs | 35 +-- components/compositing/compositor_task.rs | 83 +----- components/compositing/constellation.rs | 92 ++++++- components/compositing/headless.rs | 9 +- components/compositing/windowing.rs | 5 - components/gfx/paint_task.rs | 49 ++-- components/layout/layout_task.rs | 18 +- components/layout_traits/lib.rs | 2 + components/msg/compositor_msg.rs | 30 +-- components/msg/constellation_msg.rs | 6 +- components/script/layout_interface.rs | 4 + components/script/script_task.rs | 52 +++- components/script_traits/lib.rs | 9 + ports/cef/window.rs | 21 -- ports/glutin/window.rs | 23 -- ports/gonk/src/window.rs | 18 -- 17 files changed, 363 insertions(+), 390 deletions(-) diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index a3771b40878..d5cebea2aaf 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -4,7 +4,7 @@ use compositor_layer::{CompositorData, CompositorLayer, WantsScrollEventsFlag}; use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver}; -use compositor_task::{CompositorTask, LayerProperties, Msg}; +use compositor_task::{CompositorTask, Msg}; use constellation::SendableFrameTree; use pipeline::CompositionPipeline; use scrolling::ScrollingTimerProxy; @@ -14,7 +14,7 @@ use windowing::{MouseWindowEvent, WindowEvent, WindowMethods, WindowNavigateMsg} use geom::point::{Point2D, TypedPoint2D}; use geom::rect::{Rect, TypedRect}; use geom::scale_factor::ScaleFactor; -use geom::size::{Size2D, TypedSize2D}; +use geom::size::TypedSize2D; use gfx::color; use gfx::paint_task::Msg as PaintMsg; use gfx::paint_task::PaintRequest; @@ -26,7 +26,7 @@ use layers::rendergl::RenderContext; use layers::rendergl; use layers::scene::Scene; 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::Msg as ConstellationMsg; use msg::constellation_msg::{ConstellationChan, NavigationDirection}; @@ -36,7 +36,6 @@ use png; use profile_traits::mem; use profile_traits::time::{self, ProfilerCategory, profile}; use script_traits::{ConstellationControlMsg, ScriptControlChan}; -use std::cmp; use std::collections::HashMap; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::mem as std_mem; @@ -49,6 +48,15 @@ use url::Url; use util::geometry::{PagePx, ScreenPx, ViewportPx}; 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. pub struct IOCompositor { /// The application window. @@ -102,9 +110,6 @@ pub struct IOCompositor { /// the compositor. shutdown_state: ShutdownState, - /// Tracks outstanding paint_msg's sent to the paint tasks. - outstanding_paint_msgs: u32, - /// Tracks the last composite time. last_composite_time: u64, @@ -139,6 +144,10 @@ pub struct IOCompositor { /// Has a Quit event been seen? 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 { @@ -169,11 +178,8 @@ struct PipelineDetails { /// The pipeline associated with this PipelineDetails object. pipeline: Option, - /// The status of this pipeline's ScriptTask. - ready_state: ReadyState, - - /// The status of this pipeline's PaintTask. - paint_state: PaintState, + /// The current layout epoch that this pipeline wants to draw. + current_epoch: Epoch, /// Whether animations are running animations_running: bool, @@ -186,15 +192,14 @@ impl PipelineDetails { fn new() -> PipelineDetails { PipelineDetails { pipeline: None, - ready_state: ReadyState::Blank, - paint_state: PaintState::Painting, + current_epoch: Epoch(0), animations_running: false, animation_callbacks_running: false, } } } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Debug)] enum CompositeTarget { /// Normal composition to a window Window, @@ -274,9 +279,9 @@ impl IOCompositor { time_profiler_chan: time_profiler_chan, mem_profiler_chan: mem_profiler_chan, fragment_point: None, - outstanding_paint_msgs: 0, last_composite_time: 0, has_seen_quit_event: false, + ready_to_save_state: ReadyState::Unknown, } } @@ -322,14 +327,6 @@ impl IOCompositor { 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), ShutdownState::NotShuttingDown) => { self.change_running_animations_state(pipeline_id, animation_state); @@ -343,10 +340,6 @@ impl IOCompositor { 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), ShutdownState::NotShuttingDown) => { self.set_frame_tree(&frame_tree, response_chan, new_constellation_chan); @@ -354,13 +347,15 @@ impl IOCompositor { self.get_title_for_main_frame(); } - (Msg::CreateOrUpdateBaseLayer(layer_properties), ShutdownState::NotShuttingDown) => { - self.create_or_update_base_layer(layer_properties); - } - - (Msg::CreateOrUpdateDescendantLayer(layer_properties), - ShutdownState::NotShuttingDown) => { - self.create_or_update_descendant_layer(layer_properties); + (Msg::InitializeLayersForPipeline(pipeline_id, epoch, properties), ShutdownState::NotShuttingDown) => { + 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::GetGraphicsMetadata(chan), ShutdownState::NotShuttingDown) => { @@ -381,7 +376,6 @@ impl IOCompositor { epoch, frame_tree_id); } - self.remove_outstanding_paint_msg(); } (Msg::ScrollFragmentPoint(pipeline_id, layer_id, point), @@ -446,6 +440,16 @@ impl IOCompositor { 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 // such as Paint that may crash because we have begun tearing down // the rest of our resources. @@ -455,31 +459,6 @@ impl IOCompositor { 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 /// recomposite if necessary. fn change_running_animations_state(&mut self, @@ -539,54 +518,6 @@ impl IOCompositor { 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, frame_tree: &SendableFrameTree, response_chan: Sender<()>, @@ -616,15 +547,14 @@ impl IOCompositor { frame_rect: Option>) -> Rc> { let layer_properties = LayerProperties { - pipeline_id: pipeline.id, - epoch: Epoch(0), id: LayerId::null(), rect: Rect::zero(), background_color: color::transparent(), scroll_policy: ScrollPolicy::Scrollable, }; - let root_layer = CompositorData::new_layer(layer_properties, + let root_layer = CompositorData::new_layer(pipeline.id, + layer_properties, WantsScrollEventsFlag::WantsScrollEvents, opts::get().tile_size); @@ -661,8 +591,8 @@ impl IOCompositor { self.find_layer_with_pipeline_and_layer_id(pipeline_id, LayerId::null()) } - fn update_layer_if_exists(&mut self, properties: LayerProperties) -> bool { - match self.find_layer_with_pipeline_and_layer_id(properties.pipeline_id, properties.id) { + fn update_layer_if_exists(&mut self, pipeline_id: PipelineId, properties: LayerProperties) -> bool { + match self.find_layer_with_pipeline_and_layer_id(pipeline_id, properties.id) { Some(existing_layer) => { existing_layer.update_layer(properties); true @@ -671,8 +601,7 @@ impl IOCompositor { } } - fn create_or_update_base_layer(&mut self, layer_properties: LayerProperties) { - let pipeline_id = layer_properties.pipeline_id; + fn create_or_update_base_layer(&mut self, pipeline_id: PipelineId, layer_properties: LayerProperties) { let root_layer = match self.find_pipeline_root_layer(pipeline_id) { Some(root_layer) => root_layer, None => { @@ -683,11 +612,12 @@ impl IOCompositor { } }; - 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 { root_layer.update_layer_except_bounds(layer_properties); let base_layer = CompositorData::new_layer( + pipeline_id, layer_properties, WantsScrollEventsFlag::DoesntWantScrollEvents, opts::get().tile_size); @@ -699,27 +629,28 @@ impl IOCompositor { 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); self.send_buffer_requests_for_all_layers(); } - fn create_or_update_descendant_layer(&mut self, layer_properties: LayerProperties) { - if !self.update_layer_if_exists(layer_properties) { - self.create_descendant_layer(layer_properties); + fn create_or_update_descendant_layer(&mut self, pipeline_id: PipelineId, layer_properties: LayerProperties) { + if !self.update_layer_if_exists(pipeline_id, 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); self.send_buffer_requests_for_all_layers(); } - fn create_descendant_layer(&self, layer_properties: LayerProperties) { - let root_layer = match self.find_pipeline_root_layer(layer_properties.pipeline_id) { + fn create_descendant_layer(&self, pipeline_id: PipelineId, layer_properties: LayerProperties) { + let root_layer = match self.find_pipeline_root_layer(pipeline_id) { Some(root_layer) => root_layer, 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, root_layer.tile_size); root_layer.add_child(new_layer); @@ -806,8 +737,17 @@ impl IOCompositor { // has already drawn the most recently painted buffer, and missing a frame. if frame_tree_id == self.frame_tree_id { if let Some(layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) { - self.assign_painted_buffers_to_layer(layer, new_layer_buffer_set, epoch); - return + 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); + return + } else { + debug!("assign_painted_buffers epoch mismatch {:?} {:?} req={:?} actual={:?}", + pipeline_id, + layer_id, + requested_epoch, + epoch); + } } } @@ -830,7 +770,7 @@ impl IOCompositor { // FIXME(pcwalton): This is going to cause problems with inconsistent frames since // 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); } @@ -1112,9 +1052,11 @@ impl IOCompositor { let mut results: HashMap> = HashMap::new(); for (layer, mut layer_requests) in requests.into_iter() { - let vec = match results.entry(layer.get_pipeline_id()) { - Occupied(mut entry) => { - *entry.get_mut() = Vec::new(); + let pipeline_id = layer.get_pipeline_id(); + let current_epoch = self.pipeline_details.get(&pipeline_id).unwrap().current_epoch; + layer.extra_data.borrow_mut().requested_epoch = current_epoch; + let vec = match results.entry(pipeline_id) { + Occupied(entry) => { entry.into_mut() } Vacant(entry) => { @@ -1132,7 +1074,7 @@ impl IOCompositor { buffer_requests: layer_requests, scale: scale.get(), layer_id: layer.extra_data.borrow().id, - epoch: layer.extra_data.borrow().epoch, + epoch: layer.extra_data.borrow().requested_epoch, }); } @@ -1189,39 +1131,94 @@ impl IOCompositor { let pipeline_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() { - num_paint_msgs_sent += 1; let msg = PaintMsg::Paint(requests, self.frame_tree_id); let _ = self.get_pipeline(pipeline_id).paint_chan.send(msg); } - self.add_outstanding_paint_msg(num_paint_msgs_sent); true } - fn is_ready_to_paint_image_output(&self) -> bool { - if !self.got_load_complete_message { - return false; + /// Check if a layer (or its children) have any outstanding paint + /// results to arrive yet. + fn does_layer_have_outstanding_paint_messages(&self, layer: &Rc>) -> bool { + let layer_data = layer.extra_data.borrow(); + let current_epoch = self.pipeline_details.get(&layer_data.pipeline_id).unwrap().current_epoch; + + // 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; } - if self.get_earliest_pipeline_ready_state() != ReadyState::FinishedLoading { - return false; + for child in layer.children().iter() { + if self.does_layer_have_outstanding_paint_messages(child) { + return true; + } } - if self.has_outstanding_paint_msgs() { - return false; - } + false + } - if !self.all_pipelines_in_idle_paint_state() { - return 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. - if self.frame_tree_id == FrameTreeId(0) { - return false; - } + // 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; + } + } - return true; + // 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) { diff --git a/components/compositing/compositor_layer.rs b/components/compositing/compositor_layer.rs index b5234e9d09e..b13295e496c 100644 --- a/components/compositing/compositor_layer.rs +++ b/components/compositing/compositor_layer.rs @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use compositor_task::LayerProperties; use compositor::IOCompositor; use windowing::{MouseWindowEvent, WindowMethods}; @@ -18,7 +17,7 @@ use layers::geometry::LayerPixel; use layers::layers::{Layer, LayerBufferSet}; use script_traits::CompositorEvent::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent}; 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 std::rc::Rc; @@ -36,9 +35,11 @@ pub struct CompositorData { /// Whether an ancestor layer that receives scroll events moves this layer. pub scroll_policy: ScrollPolicy, - /// A monotonically increasing counter that keeps track of the current epoch. - /// add_buffer() calls that don't match the current epoch will be ignored. - pub epoch: Epoch, + /// The epoch that has been requested for this layer (via send_buffer_requests). + pub requested_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 /// to track their current scroll position even while their content_offset does not change. @@ -46,16 +47,18 @@ pub struct CompositorData { } impl CompositorData { - pub fn new_layer(layer_properties: LayerProperties, + pub fn new_layer(pipeline_id: PipelineId, + layer_properties: LayerProperties, wants_scroll_events: WantsScrollEventsFlag, tile_size: usize) -> Rc> { let new_compositor_data = CompositorData { - pipeline_id: layer_properties.pipeline_id, + pipeline_id: pipeline_id, id: layer_properties.id, wants_scroll_events: wants_scroll_events, scroll_policy: layer_properties.scroll_policy, - epoch: layer_properties.epoch, + requested_epoch: Epoch(0), + painted_epoch: Epoch(0), scroll_offset: TypedPoint2D(0., 0.), }; @@ -76,7 +79,6 @@ pub trait CompositorLayer { compositor: &IOCompositor, new_buffers: Box, epoch: Epoch) - -> bool where Window: WindowMethods; /// 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 { 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.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color); @@ -205,17 +206,9 @@ impl CompositorLayer for Layer { compositor: &IOCompositor, new_buffers: Box, epoch: Epoch) - -> bool where Window: WindowMethods { - if self.extra_data.borrow().epoch != epoch { - debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}", - 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; - } + self.extra_data.borrow_mut().painted_epoch = epoch; + assert!(self.extra_data.borrow().painted_epoch == self.extra_data.borrow().requested_epoch); for buffer in new_buffers.buffers.into_iter().rev() { self.add_buffer(buffer); @@ -226,8 +219,6 @@ impl CompositorLayer for Layer { let pipeline = compositor.get_pipeline(self.get_pipeline_id()); let _ = pipeline.paint_chan.send(PaintMsg::UnusedBuffer(unused_buffers)); } - - true } fn clear(&self, compositor: &IOCompositor) where Window: WindowMethods { diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs index 31b45f90d2b..69cc33ad8de 100644 --- a/components/compositing/compositor_task.rs +++ b/components/compositing/compositor_task.rs @@ -11,14 +11,12 @@ use compositor; use headless; use windowing::{WindowEvent, WindowMethods}; -use azure::azure_hl::{SourceSurfaceMethods, Color}; use geom::point::Point2D; use geom::rect::Rect; -use geom::size::Size2D; use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata}; use layers::layers::LayerBufferSet; -use msg::compositor_msg::{Epoch, LayerId, LayerMetadata, FrameTreeId, ReadyState}; -use msg::compositor_msg::{PaintListener, PaintState, ScriptListener, ScrollPolicy}; +use msg::compositor_msg::{Epoch, LayerId, LayerProperties, FrameTreeId}; +use msg::compositor_msg::{PaintListener, ScriptListener}; use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId}; use msg::constellation_msg::{Key, KeyState, KeyModifiers}; use profile_traits::mem; @@ -65,11 +63,6 @@ impl CompositorReceiver for Receiver { /// Implementation of the abstract `ScriptListener` interface. impl ScriptListener for Box { - 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, pipeline_id: PipelineId, layer_id: LayerId, @@ -96,33 +89,6 @@ impl ScriptListener for Box { } } -/// 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, - 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. impl PaintListener for Box { fn get_graphics_metadata(&mut self) -> Option { @@ -141,29 +107,12 @@ impl PaintListener for Box { fn initialize_layers_for_pipeline(&mut self, pipeline_id: PipelineId, - metadata: Vec, + properties: Vec, epoch: Epoch) { // 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 // `position: fixed` but will not be sufficient to handle `overflow: scroll` or transforms. - let mut first = true; - 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)) + self.send(Msg::InitializeLayersForPipeline(pipeline_id, epoch, properties)); } } @@ -184,30 +133,21 @@ pub enum Msg { /// The headless compositor returns `None`. GetGraphicsMetadata(Sender>), - /// Tells the compositor to create the root layer for a pipeline if necessary (i.e. if no layer - /// with that ID exists). - CreateOrUpdateBaseLayer(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), + /// Tells the compositor to create or update the layers for a pipeline if necessary + /// (i.e. if no layer with that ID exists). + InitializeLayersForPipeline(PipelineId, Epoch, Vec), /// Alerts the compositor that the specified layer's rect has changed. SetLayerRect(PipelineId, LayerId, Rect), /// Scroll a page in a window ScrollFragmentPoint(PipelineId, LayerId, Point2D), /// Requests that the compositor assign the painted buffers to the given layers. AssignPaintedBuffers(PipelineId, Epoch, Vec<(LayerId, Box)>, 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. ChangePageTitle(PipelineId, Option), /// Alerts the compositor that the current page has changed its URL. ChangePageUrl(PipelineId, Url), /// Alerts the compositor that the given pipeline has changed whether it is running animations. ChangeRunningAnimationsState(PipelineId, AnimationState), - /// Alerts the compositor that a `PaintMsg` has been discarded. - PaintMsgDiscarded, /// Replaces the current frame tree, typically called during main frame navigation. SetFrameTree(SendableFrameTree, Sender<()>, ConstellationChan), /// The load of a page has completed. @@ -226,6 +166,8 @@ pub enum Msg { PaintTaskExited(PipelineId), /// Alerts the compositor that the viewport has been constrained in some manner ViewportConstrained(PipelineId, ViewportConstraints), + /// A reply to the compositor asking if the output image is stable. + IsReadyToSaveImageReply(bool), } impl Debug for Msg { @@ -234,17 +176,13 @@ impl Debug for Msg { Msg::Exit(..) => write!(f, "Exit"), Msg::ShutdownComplete(..) => write!(f, "ShutdownComplete"), Msg::GetGraphicsMetadata(..) => write!(f, "GetGraphicsMetadata"), - Msg::CreateOrUpdateBaseLayer(..) => write!(f, "CreateOrUpdateBaseLayer"), - Msg::CreateOrUpdateDescendantLayer(..) => write!(f, "CreateOrUpdateDescendantLayer"), + Msg::InitializeLayersForPipeline(..) => write!(f, "InitializeLayersForPipeline"), Msg::SetLayerRect(..) => write!(f, "SetLayerRect"), Msg::ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"), Msg::AssignPaintedBuffers(..) => write!(f, "AssignPaintedBuffers"), - Msg::ChangeReadyState(..) => write!(f, "ChangeReadyState"), - Msg::ChangePaintState(..) => write!(f, "ChangePaintState"), Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"), Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"), Msg::ChangePageUrl(..) => write!(f, "ChangePageUrl"), - Msg::PaintMsgDiscarded(..) => write!(f, "PaintMsgDiscarded"), Msg::SetFrameTree(..) => write!(f, "SetFrameTree"), Msg::LoadComplete => write!(f, "LoadComplete"), Msg::ScrollTimeout(..) => write!(f, "ScrollTimeout"), @@ -254,6 +192,7 @@ impl Debug for Msg { Msg::CreatePng(..) => write!(f, "CreatePng"), Msg::PaintTaskExited(..) => write!(f, "PaintTaskExited"), Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"), + Msg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"), } } } diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index 24b58199986..4ad7d699073 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -16,11 +16,12 @@ use compositor_task::Msg as CompositorMsg; use devtools_traits::{DevtoolsControlChan, DevtoolsControlMsg}; use geom::point::Point2D; use geom::rect::{Rect, TypedRect}; +use geom::size::Size2D; use geom::scale_factor::ScaleFactor; use gfx::font_cache_task::FontCacheTask; -use layout_traits::{LayoutControlMsg, LayoutTaskFactory}; +use layout_traits::{LayoutControlChan, LayoutControlMsg, LayoutTaskFactory}; use libc; -use msg::compositor_msg::LayerId; +use msg::compositor_msg::{Epoch, LayerId}; use msg::constellation_msg::AnimationState; use msg::constellation_msg::Msg as ConstellationMsg; 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::time; use script_traits::{CompositorEvent, ConstellationControlMsg}; -use script_traits::{ScriptControlChan, ScriptTaskFactory}; +use script_traits::{ScriptControlChan, ScriptState, ScriptTaskFactory}; use std::borrow::ToOwned; use std::collections::HashMap; use std::io::{self, Write}; @@ -438,6 +439,10 @@ impl Constellation { debug!("constellation got viewport-constrained event message"); 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 } @@ -938,6 +943,87 @@ impl Constellation { 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) -> 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) fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) { let frame = self.frames.remove(&frame_id).unwrap(); diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs index 0b2567821ad..37aca5d1883 100644 --- a/components/compositing/headless.rs +++ b/components/compositing/headless.rs @@ -92,16 +92,12 @@ impl CompositorEventListener for NullCompositor { // we'll notice and think about whether it needs a response, like // SetFrameTree. - Msg::CreateOrUpdateBaseLayer(..) | - Msg::CreateOrUpdateDescendantLayer(..) | + Msg::InitializeLayersForPipeline(..) | Msg::SetLayerRect(..) | Msg::AssignPaintedBuffers(..) | - Msg::ChangeReadyState(..) | - Msg::ChangePaintState(..) | Msg::ChangeRunningAnimationsState(..) | Msg::ScrollFragmentPoint(..) | Msg::LoadComplete | - Msg::PaintMsgDiscarded(..) | Msg::ScrollTimeout(..) | Msg::RecompositeAfterScroll | Msg::ChangePageTitle(..) | @@ -110,7 +106,8 @@ impl CompositorEventListener for NullCompositor { Msg::SetCursor(..) | Msg::ViewportConstrained(..) => {} Msg::CreatePng(..) | - Msg::PaintTaskExited(..) => {} + Msg::PaintTaskExited(..) | + Msg::IsReadyToSaveImageReply(..) => {} } true } diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 852b97cc2e1..51b13bfdf81 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -11,7 +11,6 @@ use geom::scale_factor::ScaleFactor; use geom::size::TypedSize2D; use layers::geometry::DevicePixel; use layers::platform::surface::NativeGraphicsMetadata; -use msg::compositor_msg::{PaintState, ReadyState}; use msg::constellation_msg::{Key, KeyState, KeyModifiers}; use script_traits::MouseButton; use url::Url; @@ -100,10 +99,6 @@ pub trait WindowMethods { /// Presents the window to the screen (perhaps by page flipping). 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. fn set_page_title(&self, title: Option); /// Sets the load data for the current page. diff --git a/components/gfx/paint_task.rs b/components/gfx/paint_task.rs index 74c15ad16c2..d02782473d2 100644 --- a/components/gfx/paint_task.rs +++ b/components/gfx/paint_task.rs @@ -20,8 +20,8 @@ use layers::platform::surface::{NativeGraphicsMetadata, NativePaintingGraphicsCo use layers::platform::surface::NativeSurface; use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet}; use layers; -use msg::compositor_msg::{Epoch, FrameTreeId, PaintState, LayerId}; -use msg::compositor_msg::{LayerMetadata, PaintListener, ScrollPolicy}; +use msg::compositor_msg::{Epoch, FrameTreeId, LayerId}; +use msg::compositor_msg::{LayerProperties, PaintListener, ScrollPolicy}; use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::{ConstellationChan, Failure, PipelineId}; use msg::constellation_msg::PipelineExitType; @@ -67,7 +67,7 @@ pub struct PaintRequest { } pub enum Msg { - PaintInit(Arc), + PaintInit(Epoch, Arc), Paint(Vec, FrameTreeId), UnusedBuffer(Vec>), PaintPermissionGranted, @@ -112,8 +112,8 @@ pub struct PaintTask { /// Permission to send paint messages to the compositor paint_permission: bool, - /// A counter for epoch messages - epoch: Epoch, + /// The current epoch counter is passed by the layout task + current_epoch: Option, /// A data structure to store unused LayerBuffers buffer_map: BufferMap, @@ -165,7 +165,7 @@ impl PaintTask where C: PaintListener + Send + 'static { native_graphics_context: native_graphics_context, root_stacking_context: None, paint_permission: false, - epoch: Epoch(0), + current_epoch: None, buffer_map: BufferMap::new(10000000), worker_threads: worker_threads, used_buffer_count: 0, @@ -197,7 +197,8 @@ impl PaintTask where C: PaintListener + Send + 'static { let mut waiting_for_compositor_buffers_to_exit = false; loop { 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()); if !self.paint_permission { @@ -207,7 +208,6 @@ impl PaintTask where C: PaintListener + Send + 'static { continue; } - self.epoch.next(); self.initialize_layers(); } Msg::Paint(requests, frame_tree_id) => { @@ -215,30 +215,26 @@ impl PaintTask where C: PaintListener + Send + 'static { debug!("PaintTask: paint ready msg"); let ConstellationChan(ref mut c) = self.constellation_chan; c.send(ConstellationMsg::PainterReady(self.id)).unwrap(); - self.compositor.paint_msg_discarded(); continue; } let mut replies = Vec::new(); - self.compositor.set_paint_state(self.id, PaintState::Painting); for PaintRequest { buffer_requests, scale, layer_id, epoch } in requests.into_iter() { - if self.epoch == epoch { + if self.current_epoch == Some(epoch) { self.paint(&mut replies, buffer_requests, scale, layer_id); } 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() { let &(_, ref buffer_set) = reply; self.used_buffer_count += (*buffer_set).buffers.len(); } 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) => { debug!("PaintTask: Received {} unused buffers", unused_buffers.len()); @@ -258,7 +254,6 @@ impl PaintTask where C: PaintListener + Send + 'static { self.paint_permission = true; if self.root_stacking_context.is_some() { - self.epoch.next(); self.initialize_layers(); } } @@ -379,11 +374,11 @@ impl PaintTask where C: PaintListener + Send + 'static { Some(ref root_stacking_context) => root_stacking_context, }; - let mut metadata = Vec::new(); - build(&mut metadata, &**root_stacking_context, &ZERO_POINT); - self.compositor.initialize_layers_for_pipeline(self.id, metadata, self.epoch); + let mut properties = Vec::new(); + build(&mut properties, &**root_stacking_context, &ZERO_POINT); + self.compositor.initialize_layers_for_pipeline(self.id, properties, self.current_epoch.unwrap()); - fn build(metadata: &mut Vec, + fn build(properties: &mut Vec, stacking_context: &StackingContext, page_position: &Point2D) { let page_position = stacking_context.bounds.origin + *page_position; @@ -392,20 +387,20 @@ impl PaintTask where C: PaintListener + Send + 'static { // the compositor is concerned. let overflow_relative_page_position = page_position + stacking_context.overflow.origin; let layer_position = - Rect(Point2D(overflow_relative_page_position.x.to_nearest_px() as i32, - overflow_relative_page_position.y.to_nearest_px() as i32), - Size2D(stacking_context.overflow.size.width.to_nearest_px() as i32, - stacking_context.overflow.size.height.to_nearest_px() as i32)); - metadata.push(LayerMetadata { + Rect(Point2D(overflow_relative_page_position.x.to_nearest_px() as f32, + overflow_relative_page_position.y.to_nearest_px() as f32), + Size2D(stacking_context.overflow.size.width.to_nearest_px() as f32, + stacking_context.overflow.size.height.to_nearest_px() as f32)); + properties.push(LayerProperties { id: paint_layer.id, - position: layer_position, + rect: layer_position, background_color: paint_layer.background_color, scroll_policy: paint_layer.scroll_policy, }) } for kid in stacking_context.display_list.children.iter() { - build(metadata, &**kid, &page_position) + build(properties, &**kid, &page_position) } } } diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 8c52975b515..b5d2b6ff417 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -39,7 +39,7 @@ use gfx::paint_task::Msg as PaintMsg; use gfx::paint_task::{PaintChan, PaintLayer}; use layout_traits::{LayoutControlMsg, LayoutTaskFactory}; use log; -use msg::compositor_msg::ScrollPolicy; +use msg::compositor_msg::{Epoch, ScrollPolicy}; use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, PipelineId}; 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 /// sent. pub new_animations_sender: Sender, + + /// A counter for epoch messages + epoch: Epoch, } /// Information needed by the layout task. @@ -329,6 +332,7 @@ impl LayoutTask { running_animations: Vec::new(), new_animations_receiver: new_animations_receiver, new_animations_sender: new_animations_sender, + epoch: Epoch(0), })), } } @@ -405,6 +409,10 @@ impl LayoutTask { LayoutControlMsg::TickAnimations => { 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) => { self.handle_request_helper(Msg::ExitNow(exit_type), possibly_locked_rw_data) @@ -509,8 +517,11 @@ impl LayoutTask { Msg::CollectReports(reports_chan) => { 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) => { - debug!("layout: PrepareToExitMsg received"); self.prepare_to_exit(response_chan, possibly_locked_rw_data); return false }, @@ -825,7 +836,8 @@ impl LayoutTask { 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)); } }); } diff --git a/components/layout_traits/lib.rs b/components/layout_traits/lib.rs index 47732f36285..7f374138079 100644 --- a/components/layout_traits/lib.rs +++ b/components/layout_traits/lib.rs @@ -17,6 +17,7 @@ extern crate util; use gfx::font_cache_task::FontCacheTask; use gfx::paint_task::PaintChan; +use msg::compositor_msg::Epoch; use msg::constellation_msg::{ConstellationChan, Failure, PipelineId, PipelineExitType}; use profile_traits::mem; use profile_traits::time; @@ -28,6 +29,7 @@ use std::sync::mpsc::{Sender, Receiver}; /// Messages sent to the layout task from the constellation pub enum LayoutControlMsg { ExitNow(PipelineExitType), + GetCurrentEpoch(Sender), TickAnimations, } diff --git a/components/msg/compositor_msg.rs b/components/msg/compositor_msg.rs index 9d6c8fc7069..efb84a0402d 100644 --- a/components/msg/compositor_msg.rs +++ b/components/msg/compositor_msg.rs @@ -13,27 +13,8 @@ use std::fmt; 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. -#[derive(PartialEq, Eq, Debug, Copy, Clone)] +#[derive(PartialEq, Eq, Debug, Copy, Clone, PartialOrd, Ord)] pub struct Epoch(pub u32); impl Epoch { @@ -82,11 +63,11 @@ pub enum ScrollPolicy { /// All layer-specific information that the painting task sends to the compositor other than the /// buffer contents of the layer itself. #[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. pub id: LayerId, /// The position and size of the layer in pixels. - pub position: Rect, + pub rect: Rect, /// The background color of the layer. pub background_color: Color, /// The scrolling policy of this layer. @@ -102,7 +83,7 @@ pub trait PaintListener { /// creating and/or destroying paint layers as necessary. fn initialize_layers_for_pipeline(&mut self, pipeline_id: PipelineId, - metadata: Vec, + properties: Vec, epoch: Epoch); /// Sends new buffers for the given layers to the compositor. @@ -112,14 +93,11 @@ pub trait PaintListener { replies: Vec<(LayerId, Box)>, 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, /// which is used in displaying the appropriate message in the window's title. pub trait ScriptListener { - fn set_ready_state(&mut self, PipelineId, ReadyState); fn scroll_fragment_point(&mut self, pipeline_id: PipelineId, layer_id: LayerId, diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index c819ee14944..ed8de8c3a97 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -5,6 +5,7 @@ //! The high-level interface from script to constellation. Using this abstract interface helps //! reduce coupling between these two components. +use compositor_msg::Epoch; use geom::rect::Rect; use geom::size::TypedSize2D; use geom::scale_factor::ScaleFactor; @@ -14,6 +15,7 @@ use layers::geometry::DevicePixel; use png; use util::cursor::Cursor; use util::geometry::{PagePx, ViewportPx}; +use std::collections::HashMap; use std::sync::mpsc::{channel, Sender, Receiver}; use style::viewport::ViewportConstraints; use webdriver_traits::WebDriverScriptCommand; @@ -239,7 +241,9 @@ pub enum Msg { /// Notifies the constellation that the viewport has been constrained in some manner ViewportConstrained(PipelineId, ViewportConstraints), /// Create a PNG of the window contents - CompositePng(Sender>) + CompositePng(Sender>), + /// Query the constellation to see if the current compositor output is stable + IsReadyToSaveImage(HashMap), } #[derive(Clone, Eq, PartialEq)] diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index 6fde5c4312f..ad474a0daed 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -12,6 +12,7 @@ use geom::point::Point2D; use geom::rect::Rect; use libc::uintptr_t; use msg::constellation_msg::{PipelineExitType, WindowSizeData}; +use msg::compositor_msg::Epoch; use net_traits::PendingAsyncLoad; use profile_traits::mem::{Reporter, ReportsChan}; 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 /// this, or layout will crash. ExitNow(PipelineExitType), + + /// Get the last epoch counter for this layout task. + GetCurrentEpoch(Sender) } /// Synchronous messages that script can send to layout. diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 504a4f169b3..c88dbb8537a 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -58,9 +58,8 @@ use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent}; use script_traits::CompositorEvent::{MouseMoveEvent, KeyEvent}; use script_traits::{NewLayoutInfo, OpaqueScriptLayoutChannel}; use script_traits::{ConstellationControlMsg, ScriptControlChan}; -use script_traits::ScriptTaskFactory; +use script_traits::{ScriptState, ScriptTaskFactory}; use webdriver_traits::WebDriverScriptCommand; -use msg::compositor_msg::ReadyState::{FinishedLoading, Loading, PerformingLayout}; use msg::compositor_msg::{LayerId, ScriptListener}; use msg::constellation_msg::{ConstellationChan, FocusType}; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId}; @@ -735,6 +734,10 @@ impl ScriptTask { responder.respond(); 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); } + /// 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) { let NewLayoutInfo { containing_pipeline_id, @@ -993,14 +1030,6 @@ impl ScriptTask { with this script task. This is a bug."); let window = page.window().root(); 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 @@ -1102,8 +1131,6 @@ impl ScriptTask { }) }).root(); - self.compositor.borrow_mut().set_ready_state(incomplete.pipeline_id, Loading); - // Create a new frame tree entry. let page = Rc::new(Page::new(incomplete.pipeline_id, final_url.clone())); if !root_page_exists { @@ -1462,7 +1489,6 @@ impl ScriptTask { let final_url = document.r().url(); document.r().set_ready_state(DocumentReadyState::Interactive); - self.compositor.borrow_mut().set_ready_state(id, PerformingLayout); // Kick off the initial reflow of the page. debug!("kicking off initial reflow of {:?}", final_url); diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 2ed5685a00d..4969c2bf9cb 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -60,6 +60,13 @@ pub trait StylesheetLoadResponder { fn respond(self: Box); } +/// 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 pub enum ConstellationControlMsg { /// 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), /// Notifies script that a stylesheet has finished loading. StylesheetLoadComplete(PipelineId, Url, Box), + /// Get the current state of the script task for a given pipeline. + GetCurrentState(Sender, PipelineId), } /// The mouse button involved in the event. diff --git a/ports/cef/window.rs b/ports/cef/window.rs index 6b032e26d24..47ec13e03da 100644 --- a/ports/cef/window.rs +++ b/ports/cef/window.rs @@ -22,7 +22,6 @@ use layers::geometry::DevicePixel; use layers::platform::surface::NativeGraphicsMetadata; use libc::{c_char, c_void}; use msg::constellation_msg::{Key, KeyModifiers}; -use msg::compositor_msg::{ReadyState, PaintState}; use std::ptr; use std_url::Url; 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 { let browser = self.cef_browser.borrow(); match *browser { diff --git a/ports/glutin/window.rs b/ports/glutin/window.rs index 48a1c9b61c9..e1fc64202e0 100644 --- a/ports/glutin/window.rs +++ b/ports/glutin/window.rs @@ -14,7 +14,6 @@ use layers::geometry::DevicePixel; use layers::platform::surface::NativeGraphicsMetadata; use msg::constellation_msg; use msg::constellation_msg::Key; -use msg::compositor_msg::{PaintState, ReadyState}; use NestedEventLoopListener; use std::rc::Rc; use std::sync::mpsc::{channel, Sender}; @@ -64,8 +63,6 @@ pub struct Window { event_queue: RefCell>, mouse_pos: Cell>, - ready_state: Cell, - paint_state: Cell, key_modifiers: Cell, } @@ -92,8 +89,6 @@ impl Window { mouse_down_point: 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()), }; @@ -475,16 +470,6 @@ impl WindowMethods for Window { box receiver as Box) } - /// 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 { ScaleFactor::new(self.window.hidpi_factor()) } @@ -669,14 +654,6 @@ impl WindowMethods for Window { box receiver as Box) } - /// 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 { ScaleFactor::new(1.0) } diff --git a/ports/gonk/src/window.rs b/ports/gonk/src/window.rs index 621804640bc..281b3cbc462 100644 --- a/ports/gonk/src/window.rs +++ b/ports/gonk/src/window.rs @@ -11,9 +11,7 @@ use geom::size::TypedSize2D; use layers::geometry::DevicePixel; use layers::platform::surface::NativeGraphicsMetadata; use libc::c_int; -use msg::compositor_msg::{ReadyState, PaintState}; use msg::constellation_msg::{Key, KeyModifiers}; -use std::cell::Cell; use std::sync::mpsc::{channel, Sender, Receiver}; use std::rc::Rc; use std::mem::transmute; @@ -622,9 +620,6 @@ pub struct Window { dpy: EGLDisplay, ctx: EGLContext, surf: EGLSurface, - - ready_state: Cell, - paint_state: Cell, } impl Window { @@ -750,9 +745,6 @@ impl Window { dpy: dpy, ctx: ctx, surf: eglwindow, - - ready_state: Cell::new(ReadyState::Blank), - paint_state: Cell::new(PaintState::Idle), }; Rc::new(window) @@ -787,16 +779,6 @@ impl WindowMethods for Window { 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) { }