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_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<Window: WindowMethods> {
/// The application window.
@ -102,9 +110,6 @@ pub struct IOCompositor<Window: WindowMethods> {
/// 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<Window: WindowMethods> {
/// 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<CompositionPipeline>,
/// 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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
epoch,
frame_tree_id);
}
self.remove_outstanding_paint_msg();
}
(Msg::ScrollFragmentPoint(pipeline_id, layer_id, point),
@ -446,6 +440,16 @@ impl<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
frame_rect: Option<TypedRect<PagePx, f32>>)
-> Rc<Layer<CompositorData>> {
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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
}
}
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<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 {
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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
// 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<Window: WindowMethods> IOCompositor<Window> {
// 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<Window: WindowMethods> IOCompositor<Window> {
let mut results: HashMap<PipelineId, Vec<PaintRequest>> = 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<Window: WindowMethods> IOCompositor<Window> {
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<Window: WindowMethods> IOCompositor<Window> {
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<Layer<CompositorData>>) -> 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) {

View file

@ -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<Layer<CompositorData>> {
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<Window>,
new_buffers: Box<LayerBufferSet>,
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<CompositorData> {
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<CompositorData> {
compositor: &IOCompositor<Window>,
new_buffers: Box<LayerBufferSet>,
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<CompositorData> {
let pipeline = compositor.get_pipeline(self.get_pipeline_id());
let _ = pipeline.paint_chan.send(PaintMsg::UnusedBuffer(unused_buffers));
}
true
}
fn clear<Window>(&self, compositor: &IOCompositor<Window>) where Window: WindowMethods {

View file

@ -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<Msg> {
/// Implementation of the abstract `ScriptListener` interface.
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,
pipeline_id: PipelineId,
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.
impl PaintListener for Box<CompositorProxy+'static+Send> {
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,
pipeline_id: PipelineId,
metadata: Vec<LayerMetadata>,
properties: Vec<LayerProperties>,
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<Option<NativeGraphicsMetadata>>),
/// 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<LayerProperties>),
/// Alerts the compositor that the specified layer's rect has changed.
SetLayerRect(PipelineId, LayerId, Rect<f32>),
/// Scroll a page in a window
ScrollFragmentPoint(PipelineId, LayerId, Point2D<f32>),
/// Requests that the compositor assign the painted buffers to the given layers.
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.
ChangePageTitle(PipelineId, Option<String>),
/// 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"),
}
}
}

View file

@ -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<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
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<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
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)
fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) {
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
// 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
}

View file

@ -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<String>);
/// 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::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<StackingContext>),
PaintInit(Epoch, Arc<StackingContext>),
Paint(Vec<PaintRequest>, FrameTreeId),
UnusedBuffer(Vec<Box<LayerBuffer>>),
PaintPermissionGranted,
@ -112,8 +112,8 @@ pub struct PaintTask<C> {
/// 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<Epoch>,
/// A data structure to store unused LayerBuffers
buffer_map: BufferMap,
@ -165,7 +165,7 @@ impl<C> PaintTask<C> 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<C> PaintTask<C> 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<C> PaintTask<C> where C: PaintListener + Send + 'static {
continue;
}
self.epoch.next();
self.initialize_layers();
}
Msg::Paint(requests, frame_tree_id) => {
@ -215,30 +215,26 @@ impl<C> PaintTask<C> 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<C> PaintTask<C> 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<C> PaintTask<C> 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<LayerMetadata>,
fn build(properties: &mut Vec<LayerProperties>,
stacking_context: &StackingContext,
page_position: &Point2D<Au>) {
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.
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)
}
}
}

View file

@ -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<Animation>,
/// 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));
}
});
}

View file

@ -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<Epoch>),
TickAnimations,
}

View file

@ -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<i32>,
pub rect: Rect<f32>,
/// 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<LayerMetadata>,
properties: Vec<LayerProperties>,
epoch: Epoch);
/// Sends new buffers for the given layers to the compositor.
@ -112,14 +93,11 @@ pub trait PaintListener {
replies: Vec<(LayerId, Box<LayerBufferSet>)>,
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,

View file

@ -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<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)]

View file

@ -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<Epoch>)
}
/// 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::{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);

View file

@ -60,6 +60,13 @@ pub trait StylesheetLoadResponder {
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
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<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.

View file

@ -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<ScreenPx,DevicePixel,f32> {
let browser = self.cef_browser.borrow();
match *browser {

View file

@ -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<Vec<WindowEvent>>,
mouse_pos: Cell<Point2D<i32>>,
ready_state: Cell<ReadyState>,
paint_state: Cell<PaintState>,
key_modifiers: Cell<KeyModifiers>,
}
@ -93,8 +90,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()),
};
@ -476,16 +471,6 @@ impl WindowMethods for Window {
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> {
ScaleFactor::new(self.window.hidpi_factor())
}
@ -670,14 +655,6 @@ impl WindowMethods for Window {
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> {
ScaleFactor::new(1.0)
}

View file

@ -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<ReadyState>,
paint_state: Cell<PaintState>,
}
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<String>) {
}