auto merge of #748 : eschweic/servo/iframes, r=pcwalton

Fixes tests and replaces #702.
This commit is contained in:
bors-servo 2013-08-20 10:54:58 -07:00
commit e4bfad144e
16 changed files with 804 additions and 450 deletions

View file

@ -8,7 +8,7 @@ use azure::{AzFloat, AzGLContext};
use azure::azure_hl::{B8G8R8A8, DrawTarget};
use display_list::DisplayList;
use servo_msg::compositor_msg::{RenderListener, IdleRenderState, RenderingRenderState, LayerBuffer};
use servo_msg::compositor_msg::{LayerBufferSet};
use servo_msg::compositor_msg::{LayerBufferSet, Epoch};
use servo_msg::constellation_msg::PipelineId;
use font_context::FontContext;
use geom::matrix2d::Matrix2D;
@ -32,7 +32,7 @@ pub struct RenderLayer {
pub enum Msg {
RenderMsg(RenderLayer),
ReRenderMsg(~[BufferRequest], f32, PipelineId),
ReRenderMsg(~[BufferRequest], f32, PipelineId, Epoch),
PaintPermissionGranted,
PaintPermissionRevoked,
ExitMsg(Chan<()>),
@ -89,6 +89,8 @@ struct RenderTask<C> {
paint_permission: bool,
/// Cached copy of last layers rendered
last_paint_msg: Option<(arc::Arc<LayerBufferSet>, Size2D<uint>)>,
/// A counter for epoch messages
epoch: Epoch,
}
impl<C: RenderListener + Send> RenderTask<C> {
@ -123,6 +125,7 @@ impl<C: RenderListener + Send> RenderTask<C> {
paint_permission: false,
last_paint_msg: None,
epoch: Epoch(0),
};
render_task.start();
@ -136,18 +139,24 @@ impl<C: RenderListener + Send> RenderTask<C> {
match self.port.recv() {
RenderMsg(render_layer) => {
if self.paint_permission {
self.compositor.new_layer(self.id, render_layer.size);
self.epoch.next();
self.compositor.set_layer_page_size(self.id, render_layer.size, self.epoch);
}
self.render_layer = Some(render_layer);
}
ReRenderMsg(tiles, scale, id) => {
self.render(tiles, scale, id);
ReRenderMsg(tiles, scale, id, epoch) => {
if self.epoch == epoch {
self.render(tiles, scale, id);
} else {
debug!("renderer epoch mismatch: %? != %?", self.epoch, epoch);
}
}
PaintPermissionGranted => {
self.paint_permission = true;
match self.render_layer {
Some(ref render_layer) => {
self.compositor.new_layer(self.id, render_layer.size);
self.epoch.next();
self.compositor.set_layer_page_size(self.id, render_layer.size, self.epoch);
}
None => {}
}
@ -235,7 +244,7 @@ impl<C: RenderListener + Send> RenderTask<C> {
debug!("render_task: returning surface");
if self.paint_permission {
self.compositor.paint(id, layer_buffer_set.clone());
self.compositor.paint(id, layer_buffer_set.clone(), self.epoch);
}
debug!("caching paint msg");
self.last_paint_msg = Some((layer_buffer_set, render_layer.size));

View file

@ -7,7 +7,7 @@ use geom::size::Size2D;
use geom::rect::Rect;
use geom::matrix::identity;
use gfx::render_task::ReRenderMsg;
use servo_msg::compositor_msg::{LayerBuffer, LayerBufferSet};
use servo_msg::compositor_msg::{LayerBuffer, LayerBufferSet, Epoch};
use servo_msg::constellation_msg::PipelineId;
use script::dom::event::{ClickEvent, MouseDownEvent, MouseUpEvent};
use script::script_task::SendEventMsg;
@ -15,6 +15,7 @@ use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEve
use compositing::quadtree::{Quadtree, Invalid};
use layers::layers::{ContainerLayerKind, ContainerLayer, TextureLayerKind, TextureLayer, TextureManager};
use pipeline::Pipeline;
use constellation::{SendableChildFrameTree, SendableFrameTree};
/// The CompositorLayer represents an element on a page that has a unique scroll
/// or animation behavior. This can include absolute positioned elements, iframes, etc.
@ -42,6 +43,9 @@ pub struct CompositorLayer {
/// When set to true, this layer is ignored by its parents. This is useful for
/// soft deletion or when waiting on a page size.
hidden: bool,
/// A monotonically increasing counter that keeps track of the current epoch.
/// add_buffer() calls that don't match the current epoch will be ignored.
epoch: Epoch,
}
/// Helper struct for keeping CompositorLayer children organized.
@ -61,7 +65,8 @@ enum MaybeQuadtree {
}
impl CompositorLayer {
/// Creates a new CompositorLayer without a page size that is initially hidden.
/// Creates a new CompositorLayer with an optional page size. If no page size is given,
/// the layer is initially hidden and initialized without a quadtree.
pub fn new(pipeline: Pipeline, page_size: Option<Size2D<f32>>, tile_size: uint, max_mem: Option<uint>)
-> CompositorLayer {
CompositorLayer {
@ -78,9 +83,41 @@ impl CompositorLayer {
},
root_layer: @mut ContainerLayer(),
hidden: true,
epoch: Epoch(0),
}
}
/// Constructs a CompositorLayer tree from a frame tree.
pub fn from_frame_tree(frame_tree: SendableFrameTree,
tile_size: uint,
max_mem: Option<uint>) -> CompositorLayer {
let SendableFrameTree { pipeline, children } = frame_tree;
let mut layer = CompositorLayer::new(pipeline, None, tile_size, max_mem);
layer.children = (do children.move_iter().map |child| {
let SendableChildFrameTree { frame_tree, rect } = child;
let container = @mut ContainerLayer();
match rect {
Some(rect) => {
container.scissor = Some(rect);
container.common.transform = identity().translate(rect.origin.x,
rect.origin.y,
0f32);
}
None => {}
}
let child_layer = ~CompositorLayer::from_frame_tree(frame_tree, tile_size, max_mem);
container.add_child(ContainerLayerKind(child_layer.root_layer));
CompositorLayerChild {
child: child_layer,
container: container,
}
}).collect();
layer
}
// Move the layer by as relative specified amount in page coordinates. Does not change
// the position of the layer relative to its parent. This also takes in a cursor position
// to see if the mouse is over child layers first. If a layer successfully scrolled, returns
@ -166,13 +203,14 @@ impl CompositorLayer {
let mut redisplay: bool;
{ // block here to prevent double mutable borrow of self
let quadtree = match self.quadtree {
NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request, no quadtree initialized"),
NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request for %?,
no quadtree initialized", self.pipeline.id),
Tree(ref mut quadtree) => quadtree,
};
let (request, r) = quadtree.get_tile_rects_page(rect, scale);
redisplay = r; // workaround to make redisplay visible outside block
if !request.is_empty() {
self.pipeline.render_chan.send(ReRenderMsg(request, scale, self.pipeline.id.clone()));
self.pipeline.render_chan.send(ReRenderMsg(request, scale, self.pipeline.id.clone(), self.epoch));
}
}
if redisplay {
@ -204,9 +242,10 @@ impl CompositorLayer {
// Move the sublayer to an absolute position in page coordinates relative to its parent,
// and clip the layer to the specified size in page coordinates.
// If the layer is hidden and has a defined page size, unhide it.
// This method returns false if the specified layer is not found.
pub fn set_clipping_rect(&mut self, pipeline_id: PipelineId, new_rect: Rect<f32>) -> bool {
for child_node in self.children.iter() {
for child_node in self.children.mut_iter() {
if pipeline_id != child_node.child.pipeline.id {
loop;
}
@ -215,6 +254,10 @@ impl CompositorLayer {
new_rect.origin.y,
0.0));
con.scissor = Some(new_rect);
// If possible, unhide child
if child_node.child.hidden && child_node.child.page_size.is_some() {
child_node.child.hidden = false;
}
return true;
}
@ -223,10 +266,12 @@ impl CompositorLayer {
}
// Called when the layer changes size (NOT as a result of a zoom event).
// Set the layer's page size. This signals that the renderer is ready for BufferRequests.
// If the layer is hidden and has a defined clipping rect, unhide it.
// This method returns false if the specified layer is not found.
pub fn resize(&mut self, pipeline_id: PipelineId, new_size: Size2D<f32>, window_size: Size2D<f32>) -> bool {
pub fn resize(&mut self, pipeline_id: PipelineId, new_size: Size2D<f32>, window_size: Size2D<f32>, epoch: Epoch) -> bool {
if self.pipeline.id == pipeline_id {
self.epoch = epoch;
self.page_size = Some(new_size);
// TODO: might get buffers back here
match self.quadtree {
@ -236,27 +281,48 @@ impl CompositorLayer {
tile_size,
max_mem)),
}
// Call scroll for bounds checking of the page shrunk. Use (-1, -1) as the cursor position
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position
// to make sure the scroll isn't propagated downwards.
self.scroll(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), window_size);
self.hidden = false;
return true;
}
self.resize_helper(pipeline_id, new_size, epoch)
}
// A helper method to resize sublayers.
fn resize_helper(&mut self, pipeline_id: PipelineId, new_size: Size2D<f32>, epoch: Epoch) -> bool {
for child_node in self.children.mut_iter() {
if pipeline_id != child_node.child.pipeline.id {
loop;
}
let child = &mut child_node.child;
child.epoch = epoch;
child.page_size = Some(new_size);
// TODO: might get buffers back here
match child.quadtree {
Tree(ref mut quadtree) => quadtree.resize(new_size.width as uint, new_size.height as uint),
NoTree(tile_size, max_mem) => child.quadtree = Tree(Quadtree::new(new_size.width as uint,
new_size.height as uint,
tile_size,
max_mem)),
}
match child_node.container.scissor {
Some(scissor) => {
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position
// to make sure the scroll isn't propagated downwards.
child.scroll(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), scissor.size);
child.hidden = false;
}
None => {} // Nothing to do
}
return true;
}
// ID does not match ours, so recurse on descendents (including hidden children)
let transform = |x: &mut CompositorLayerChild| -> bool {
match x.container.scissor {
Some(scissor) => {
x.child.resize(pipeline_id, new_size, scissor.size)
}
None => {
fail!("CompositorLayer: Child layer not clipped");
}
}
};
self.children.mut_iter().any(transform)
self.children.mut_iter().map(|x| &mut x.child).any(|x| x.resize_helper(pipeline_id, new_size, epoch))
}
// Collect buffers from the quadtree. This method IS NOT recursive, so child CompositorLayers
// are not rebuilt directly from this method.
pub fn build_layer_tree(&mut self) {
@ -287,10 +353,11 @@ impl CompositorLayer {
}
};
}
// Add new tiles.
let quadtree = match self.quadtree {
NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request, no quadtree initialized"),
NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request for %?,
no quadtree initialized", self.pipeline.id),
Tree(ref mut quadtree) => quadtree,
};
@ -331,11 +398,18 @@ impl CompositorLayer {
}
// Add LayerBuffers to the specified layer. Returns false if the layer is not found.
pub fn add_buffers(&mut self, pipeline_id: PipelineId, new_buffers: &LayerBufferSet) -> bool {
// If the epoch of the message does not match the layer's epoch, the message is ignored.
pub fn add_buffers(&mut self, pipeline_id: PipelineId, new_buffers: &LayerBufferSet, epoch: Epoch) -> bool {
if self.pipeline.id == pipeline_id {
if self.epoch != epoch {
debug!("compositor epoch mismatch: %? != %?, id: %?", self.epoch, epoch, self.pipeline.id);
// TODO: send buffers back
return true;
}
{ // block here to prevent double mutable borrow of self
let quadtree = match self.quadtree {
NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request, no quadtree initialized"),
NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request for %?,
no quadtree initialized", self.pipeline.id),
Tree(ref mut quadtree) => quadtree,
};
@ -349,7 +423,7 @@ impl CompositorLayer {
return true;
}
// ID does not match ours, so recurse on descendents (including hidden children).
self.children.mut_iter().map(|x| &mut x.child).any(|x| x.add_buffers(pipeline_id, new_buffers))
self.children.mut_iter().map(|x| &mut x.child).any(|x| x.add_buffers(pipeline_id, new_buffers, epoch))
}
// Deletes a specified sublayer, including hidden children. Returns false if the layer is not found.

View file

@ -3,8 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use platform::{Application, Window};
use script::dom::event::ResizeEvent;
use script::script_task::{LoadMsg, NavigateMsg, SendEventMsg};
pub use windowing;
use windowing::{ApplicationMethods, WindowEvent, WindowMethods};
@ -13,8 +11,8 @@ use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, Finis
use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState};
use servo_msg::compositor_msg::{ReadyState, ScriptListener};
use servo_msg::constellation_msg::PipelineId;
use servo_msg::compositor_msg::{ReadyState, ScriptListener, Epoch};
use servo_msg::constellation_msg::{ConstellationChan, NavigateMsg, PipelineId, ResizedWindowMsg, LoadUrlMsg};
use servo_msg::constellation_msg;
use gfx::opts::Opts;
@ -40,11 +38,11 @@ use servo_util::{time, url};
use servo_util::time::profile;
use servo_util::time::ProfilerChan;
use extra::future::from_value;
use extra::time::precise_time_s;
use extra::arc;
use constellation::SendableFrameTree;
use pipeline::Pipeline;
use compositing::compositor_layer::CompositorLayer;
mod quadtree;
@ -81,16 +79,26 @@ impl RenderListener for CompositorChan {
port.recv()
}
fn paint(&self, id: PipelineId, layer_buffer_set: arc::Arc<LayerBufferSet>) {
self.chan.send(Paint(id, layer_buffer_set))
fn paint(&self, id: PipelineId, layer_buffer_set: arc::Arc<LayerBufferSet>, epoch: Epoch) {
self.chan.send(Paint(id, layer_buffer_set, epoch))
}
fn new_layer(&self, id: PipelineId, page_size: Size2D<uint>) {
self.chan.send(NewLayer(id, page_size))
let Size2D { width, height } = page_size;
self.chan.send(NewLayer(id, Size2D(width as f32, height as f32)))
}
fn resize_layer(&self, id: PipelineId, page_size: Size2D<uint>) {
self.chan.send(ResizeLayer(id, page_size))
fn set_layer_page_size(&self, id: PipelineId, page_size: Size2D<uint>, epoch: Epoch) {
let Size2D { width, height } = page_size;
self.chan.send(SetLayerPageSize(id, Size2D(width as f32, height as f32), epoch))
}
fn set_layer_clip_rect(&self, id: PipelineId, new_rect: Rect<uint>) {
let new_rect = Rect(Point2D(new_rect.origin.x as f32,
new_rect.origin.y as f32),
Size2D(new_rect.size.width as f32,
new_rect.size.height as f32));
self.chan.send(SetLayerClipRect(id, new_rect))
}
fn delete_layer(&self, id: PipelineId) {
self.chan.send(DeleteLayer(id))
}
@ -128,24 +136,25 @@ pub enum Msg {
/// Requests the compositors GL context.
GetGLContext(Chan<AzGLContext>),
// TODO: Attach epochs to these messages
/// Alerts the compositor that there is a new layer to be rendered.
NewLayer(PipelineId, Size2D<uint>),
/// Alerts the compositor that the specified layer has changed size.
ResizeLayer(PipelineId, Size2D<uint>),
NewLayer(PipelineId, Size2D<f32>),
/// Alerts the compositor that the specified layer's page has changed size.
SetLayerPageSize(PipelineId, Size2D<f32>, Epoch),
/// Alerts the compositor that the specified layer's clipping rect has changed.
SetLayerClipRect(PipelineId, Rect<f32>),
/// Alerts the compositor that the specified layer has been deleted.
DeleteLayer(PipelineId),
/// Invalidate a rect for a given layer
InvalidateRect(PipelineId, Rect<uint>),
/// Requests that the compositor paint the given layer buffer set for the given page size.
Paint(PipelineId, arc::Arc<LayerBufferSet>),
Paint(PipelineId, arc::Arc<LayerBufferSet>, Epoch),
/// Alerts the compositor to the current status of page loading.
ChangeReadyState(ReadyState),
/// Alerts the compositor to the current status of rendering.
ChangeRenderState(RenderState),
/// Sets the channel to the current layout and render tasks, along with their id
SetIds(SendableFrameTree, Chan<()>),
SetIds(SendableFrameTree, Chan<()>, ConstellationChan),
}
/// Azure surface wrapping to work with the layers infrastructure.
@ -207,7 +216,7 @@ impl CompositorTask {
let root_layer = @mut ContainerLayer();
let window_size = window.size();
let mut scene = Scene(ContainerLayerKind(root_layer), window_size, identity());
let mut window_size = Size2D(window_size.width as int, window_size.height as int);
let mut window_size = Size2D(window_size.width as uint, window_size.height as uint);
let mut done = false;
let mut recomposite = false;
@ -216,21 +225,21 @@ impl CompositorTask {
let mut zoom_action = false;
let mut zoom_time = 0f;
// Channel to the outermost frame's pipeline.
// FIXME: Events are only forwarded to this pipeline, but they should be
// routed to the appropriate pipeline via the constellation.
let mut pipeline: Option<Pipeline> = None;
// The root CompositorLayer
let mut compositor_layer: Option<CompositorLayer> = None;
let mut constellation_chan: Option<ConstellationChan> = None;
// Get BufferRequests from each layer.
let ask_for_tiles = || {
let window_size_page = Size2D(window_size.width as f32 / world_zoom,
window_size.height as f32 / world_zoom);
for layer in compositor_layer.mut_iter() {
recomposite = layer.get_buffer_request(Rect(Point2D(0f32, 0f32), window_size_page),
world_zoom) || recomposite;
if !layer.hidden {
recomposite = layer.get_buffer_request(Rect(Point2D(0f32, 0f32), window_size_page),
world_zoom) || recomposite;
} else {
debug!("Compositor: root layer is hidden!");
}
}
};
@ -243,9 +252,22 @@ impl CompositorTask {
ChangeReadyState(ready_state) => window.set_ready_state(ready_state),
ChangeRenderState(render_state) => window.set_render_state(render_state),
SetIds(frame_tree, response_chan) => {
pipeline = Some(frame_tree.pipeline);
SetIds(frame_tree, response_chan, new_constellation_chan) => {
response_chan.send(());
// This assumes there is at most one child, which should be the case.
match root_layer.first_child {
Some(old_layer) => root_layer.remove_child(old_layer),
None => {}
}
let layer = CompositorLayer::from_frame_tree(frame_tree,
self.opts.tile_size,
Some(10000000u));
root_layer.add_child(ContainerLayerKind(layer.root_layer));
compositor_layer = Some(layer);
constellation_chan = Some(new_constellation_chan);
}
GetSize(chan) => {
@ -259,12 +281,12 @@ impl CompositorTask {
// FIXME: This should create an additional layer instead of replacing the current one.
// Once ResizeLayer messages are set up, we can switch to the new functionality.
let p = match pipeline {
Some(ref pipeline) => pipeline,
let p = match compositor_layer {
Some(ref compositor_layer) => compositor_layer.pipeline.clone(),
None => fail!("Compositor: Received new layer without initialized pipeline"),
};
let page_size = Size2D(new_size.width as f32, new_size.height as f32);
let new_layer = CompositorLayer::new(p.clone(), Some(page_size),
let new_layer = CompositorLayer::new(p, Some(page_size),
self.opts.tile_size, Some(10000000u));
let current_child = root_layer.first_child;
@ -279,14 +301,22 @@ impl CompositorTask {
ask_for_tiles();
}
ResizeLayer(id, new_size) => {
SetLayerPageSize(id, new_size, epoch) => {
match compositor_layer {
Some(ref mut layer) => {
let page_window = Size2D(window_size.width as f32 / world_zoom,
window_size.height as f32 / world_zoom);
assert!(layer.resize(id, Size2D(new_size.width as f32,
new_size.height as f32),
page_window));
assert!(layer.resize(id, new_size, page_window, epoch));
ask_for_tiles();
}
None => {}
}
}
SetLayerClipRect(id, new_rect) => {
match compositor_layer {
Some(ref mut layer) => {
assert!(layer.set_clipping_rect(id, new_rect));
ask_for_tiles();
}
None => {}
@ -303,12 +333,12 @@ impl CompositorTask {
}
}
Paint(id, new_layer_buffer_set) => {
Paint(id, new_layer_buffer_set, epoch) => {
debug!("osmain: received new frame");
match compositor_layer {
Some(ref mut layer) => {
assert!(layer.add_buffers(id, new_layer_buffer_set.get()));
assert!(layer.add_buffers(id, new_layer_buffer_set.get(), epoch));
recomposite = true;
}
None => {
@ -340,12 +370,12 @@ impl CompositorTask {
IdleWindowEvent => {}
ResizeWindowEvent(width, height) => {
let new_size = Size2D(width as int, height as int);
let new_size = Size2D(width, height);
if window_size != new_size {
debug!("osmain: window resized to %ux%u", width, height);
window_size = new_size;
match pipeline {
Some(ref pipeline) => pipeline.script_chan.send(SendEventMsg(pipeline.id.clone(), ResizeEvent(width, height))),
match constellation_chan {
Some(ref chan) => chan.send(ResizedWindowMsg(new_size)),
None => error!("Compositor: Recieved resize event without initialized layout chan"),
}
} else {
@ -355,8 +385,14 @@ impl CompositorTask {
LoadUrlWindowEvent(url_string) => {
debug!("osmain: loading URL `%s`", url_string);
match pipeline {
Some(ref pipeline) => pipeline.script_chan.send(LoadMsg(pipeline.id.clone(), url::make_url(url_string.to_str(), None))),
let root_pipeline_id = match compositor_layer {
Some(ref layer) => layer.pipeline.id.clone(),
None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor layers"),
};
match constellation_chan {
Some(ref chan) => chan.send(LoadUrlMsg(root_pipeline_id,
url::make_url(url_string.to_str(), None),
from_value(window_size))),
None => error!("Compositor: Recieved loadurl event without initialized layout chan"),
}
}
@ -413,8 +449,8 @@ impl CompositorTask {
windowing::Forward => constellation_msg::Forward,
windowing::Back => constellation_msg::Back,
};
match pipeline {
Some(ref pipeline) => pipeline.script_chan.send(NavigateMsg(direction)),
match constellation_chan {
Some(ref chan) => chan.send(NavigateMsg(direction)),
None => error!("Compositor: Recieved navigation event without initialized layout chan"),
}
}

View file

@ -211,11 +211,30 @@ impl<T: Tile> Quadtree<T> {
(ret, redisplay)
}
/// Resize the quadtree. This can add more space, changing the root node, or it can shrink, making
/// an internal node the new root.
/// TODO: return tiles after shrinking
/// Creates a new quadtree at the specified size. This should be called when the window changes size.
/// TODO: return old tiles.
pub fn resize(&mut self, width: uint, height: uint) {
// Spaces must be squares and powers of 2, so expand the space until it is
let longer = width.max(&height);
let num_tiles = div_ceil(longer, self.max_tile_size);
let power_of_two = next_power_of_two(num_tiles);
let size = power_of_two * self.max_tile_size;
self.root = ~QuadtreeNode {
tile: None,
origin: Point2D(0f32, 0f32),
size: size as f32,
quadrants: [None, None, None, None],
status: Normal,
tile_mem: 0,
};
self.clip_size = Size2D(width, height);
}
/// Resize the underlying quadtree without removing tiles already in place.
/// Might be useful later on, but resize() should be used for now.
/// TODO: return tiles after shrinking
pub fn bad_resize(&mut self, width: uint, height: uint) {
self.clip_size = Size2D(width, height);
let longer = width.max(&height);
let new_num_tiles = div_ceil(longer, self.max_tile_size);
@ -731,13 +750,13 @@ pub fn test_resize() {
let mut q = Quadtree::new(6, 6, 1, None);
q.add_tile_pixel(0, 0, 1f32, T{a: 0});
q.add_tile_pixel(5, 5, 1f32, T{a: 1});
q.resize(8, 1);
q.bad_resize(8, 1);
assert!(q.root.size == 8.0);
q.resize(18, 1);
q.bad_resize(18, 1);
assert!(q.root.size == 32.0);
q.resize(8, 1);
q.bad_resize(8, 1);
assert!(q.root.size == 8.0);
q.resize(3, 1);
q.bad_resize(3, 1);
assert!(q.root.size == 4.0);
assert!(q.get_all_tiles().len() == 1);
}

View file

@ -2,28 +2,31 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use compositing::{CompositorChan, SetIds};
use compositing::{CompositorChan, SetIds, SetLayerClipRect};
use script::dom::event::ResizeEvent;
use std::cell::Cell;
use std::comm;
use std::comm::Port;
use std::task;
use geom::size::Size2D;
use geom::rect::Rect;
use gfx::opts::Opts;
use pipeline::Pipeline;
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg};
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, FrameRectMsg};
use servo_msg::constellation_msg::{InitLoadUrlMsg, LoadIframeUrlMsg, LoadUrlMsg};
use servo_msg::constellation_msg::{Msg, NavigateMsg};
use servo_msg::constellation_msg::{PipelineId, RendererReadyMsg, ResizedWindowBroadcast};
use servo_msg::constellation_msg::{Msg, NavigateMsg, NavigationType};
use servo_msg::constellation_msg::{PipelineId, RendererReadyMsg, ResizedWindowMsg, SubpageId};
use servo_msg::constellation_msg;
use script::script_task::{ResizeInactiveMsg, ExecuteMsg};
use script::script_task::{SendEventMsg, ResizeInactiveMsg, ExecuteMsg};
use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
use servo_net::resource_task::ResourceTask;
use servo_net::resource_task;
use servo_util::time::ProfilerChan;
use std::hashmap::HashMap;
use std::hashmap::{HashMap, HashSet};
use std::util::replace;
use extra::future::from_value;
use extra::url::Url;
use extra::future::{Future, from_value};
/// Maintains the pipelines and navigation context and grants permission to composite
pub struct Constellation {
@ -36,6 +39,7 @@ pub struct Constellation {
navigation_context: NavigationContext,
priv next_pipeline_id: PipelineId,
pending_frames: ~[FrameChange],
pending_sizes: HashMap<(PipelineId, SubpageId), Rect<f32>>,
profiler_chan: ProfilerChan,
opts: Opts,
}
@ -44,13 +48,14 @@ pub struct Constellation {
struct FrameTree {
pipeline: @mut Pipeline,
parent: Option<@mut Pipeline>,
children: ~[@mut FrameTree],
children: ~[ChildFrameTree],
}
// Need to clone the FrameTrees, but _not_ the Pipelines
impl Clone for FrameTree {
fn clone(&self) -> FrameTree {
let mut children = do self.children.iter().map |&frame_tree| {
@mut (*frame_tree).clone()
let mut children = do self.children.iter().map |child_frame_tree| {
child_frame_tree.clone()
};
FrameTree {
pipeline: self.pipeline,
@ -60,54 +65,67 @@ impl Clone for FrameTree {
}
}
struct ChildFrameTree {
frame_tree: @mut FrameTree,
/// Clipping rect representing the size and position, in page coordinates, of the visible
/// region of the child frame relative to the parent.
rect: Option<Rect<f32>>,
}
impl Clone for ChildFrameTree {
fn clone(&self) -> ChildFrameTree {
ChildFrameTree {
frame_tree: @mut (*self.frame_tree).clone(),
rect: self.rect.clone(),
}
}
}
pub struct SendableFrameTree {
pipeline: Pipeline,
children: ~[SendableFrameTree],
children: ~[SendableChildFrameTree],
}
pub struct SendableChildFrameTree {
frame_tree: SendableFrameTree,
rect: Option<Rect<f32>>,
}
impl SendableFrameTree {
fn contains(&self, id: PipelineId) -> bool {
self.pipeline.id == id ||
do self.children.iter().any |frame_tree| {
do self.children.iter().any |&SendableChildFrameTree { frame_tree: ref frame_tree, _ }| {
frame_tree.contains(id)
}
}
}
impl FrameTree {
fn contains(&self, id: PipelineId) -> bool {
self.pipeline.id == id ||
do self.children.iter().any |frame_tree| {
frame_tree.contains(id)
fn contains(@mut self, id: PipelineId) -> bool {
do self.iter().any |frame_tree| {
id == frame_tree.pipeline.id
}
}
/// Returns the frame tree whose key is id
fn find_mut(@mut self, id: PipelineId) -> Option<@mut FrameTree> {
if self.pipeline.id == id { return Some(self); }
let mut finder = do self.children.iter().filter_map |frame_tree| {
frame_tree.find_mut(id)
};
finder.next()
do self.iter().find |frame_tree| {
id == frame_tree.pipeline.id
}
}
/// Replaces a node of the frame tree in place. Returns the node that was removed or the original node
/// if the node to replace could not be found.
fn replace_child(&mut self, id: PipelineId, new_child: @mut FrameTree) -> Either<@mut FrameTree, @mut FrameTree> {
let new_child_cell = Cell::new(new_child);
for child in self.children.mut_iter() {
let new_child = new_child_cell.take();
if child.pipeline.id == id {
new_child.parent = child.parent;
return Left(replace(child, new_child));
}
let replaced = child.replace_child(id, new_child);
if replaced.is_left() {
return replaced;
fn replace_child(@mut self, id: PipelineId, new_child: @mut FrameTree) -> Either<@mut FrameTree, @mut FrameTree> {
for frame_tree in self.iter() {
let mut child = frame_tree.children.mut_iter()
.find(|child| child.frame_tree.pipeline.id == id);
for child in child.mut_iter() {
new_child.parent = child.frame_tree.parent;
return Left(replace(&mut child.frame_tree, new_child));
}
new_child_cell.put_back(replaced.unwrap_right());
}
Right(new_child_cell.take())
Right(new_child)
}
fn to_sendable(&self) -> SendableFrameTree {
@ -125,6 +143,18 @@ impl FrameTree {
}
}
impl ChildFrameTree {
fn to_sendable(&self) -> SendableChildFrameTree {
SendableChildFrameTree {
frame_tree: self.frame_tree.to_sendable(),
rect: self.rect,
}
}
}
/// An iterator over a frame tree, returning nodes in depth-first order.
/// Note that this iterator should _not_ be used to mutate nodes _during_
/// iteration. Mutating nodes once the iterator is out of scope is OK.
pub struct FrameTreeIterator {
priv stack: ~[@mut FrameTree],
}
@ -133,7 +163,9 @@ impl Iterator<@mut FrameTree> for FrameTreeIterator {
fn next(&mut self) -> Option<@mut FrameTree> {
if !self.stack.is_empty() {
let next = self.stack.pop();
self.stack.push_all(next.children);
for &ChildFrameTree { frame_tree, _ } in next.children.rev_iter() {
self.stack.push(frame_tree);
}
Some(next)
} else {
None
@ -145,6 +177,7 @@ impl Iterator<@mut FrameTree> for FrameTreeIterator {
struct FrameChange {
before: Option<PipelineId>,
after: @mut FrameTree,
navigation_type: NavigationType,
}
/// Stores the Id's of the pipelines previous and next in the browser's history
@ -248,6 +281,7 @@ impl Constellation {
navigation_context: NavigationContext::new(),
next_pipeline_id: PipelineId(0),
pending_frames: ~[],
pending_sizes: HashMap::new(),
profiler_chan: profiler_chan.take(),
opts: opts.take(),
};
@ -281,330 +315,433 @@ impl Constellation {
/// Handles loading pages, navigation, and granting access to the compositor
fn handle_request(&mut self, request: Msg) -> bool {
match request {
ExitMsg(sender) => {
for (_id, ref pipeline) in self.pipelines.iter() {
pipeline.exit();
}
self.image_cache_task.exit();
self.resource_task.send(resource_task::Exit);
sender.send(());
return false
self.handle_exit(sender);
return false;
}
// This should only be called once per constellation, and only by the browser
InitLoadUrlMsg(url) => {
let pipeline = @mut Pipeline::create(self.get_next_pipeline_id(),
None,
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.resource_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
{
let size = self.compositor_chan.get_size();
from_value(Size2D(size.width as uint, size.height as uint))
});
if url.path.ends_with(".js") {
pipeline.script_chan.send(ExecuteMsg(pipeline.id, url));
} else {
pipeline.load(url, Some(constellation_msg::Load));
self.pending_frames.push(FrameChange{
before: None,
after: @mut FrameTree {
pipeline: pipeline,
parent: None,
children: ~[],
},
});
}
self.pipelines.insert(pipeline.id, pipeline);
self.handle_init_load(url);
}
// A layout assigned a size and position to a subframe. This needs to be reflected by all
// frame trees in the navigation context containing the subframe.
FrameRectMsg(pipeline_id, subpage_id, rect) => {
self.handle_frame_rect_msg(pipeline_id, subpage_id, rect);
}
LoadIframeUrlMsg(url, source_pipeline_id, subpage_id, size_future) => {
// A message from the script associated with pipeline_id that it has
// parsed an iframe during html parsing. This iframe will result in a
// new pipeline being spawned and a frame tree being added to pipeline_id's
// frame tree's children. This message is never the result of a link clicked
// or a new url entered.
// Start by finding the frame trees matching the pipeline id,
// and add the new pipeline to their sub frames.
let frame_trees: ~[@mut FrameTree] = {
let matching_navi_frames = self.navigation_context.find_all(source_pipeline_id);
let matching_pending_frames = do self.pending_frames.iter().filter_map |frame_change| {
frame_change.after.find_mut(source_pipeline_id)
};
matching_navi_frames.move_iter().chain(matching_pending_frames).collect()
};
if frame_trees.is_empty() {
fail!("Constellation: source pipeline id of LoadIframeUrlMsg is not in
navigation context, nor is it in a pending frame. This should be
impossible.");
}
let next_pipeline_id = self.get_next_pipeline_id();
// Compare the pipeline's url to the new url. If the origin is the same,
// then reuse the script task in creating the new pipeline
let source_pipeline = *self.pipelines.find(&source_pipeline_id).expect("Constellation:
source Id of LoadIframeUrlMsg does have an associated pipeline in
constellation. This should be impossible.");
let source_url = source_pipeline.url.clone().expect("Constellation: LoadUrlIframeMsg's
source's Url is None. There should never be a LoadUrlIframeMsg from a pipeline
that was never given a url to load.");
// FIXME(tkuehn): Need to follow the standardized spec for checking same-origin
let pipeline = @mut if (source_url.host == url.host &&
source_url.port == url.port) {
// Reuse the script task if same-origin url's
Pipeline::with_script(next_pipeline_id,
Some(subpage_id),
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
source_pipeline,
size_future)
} else {
// Create a new script task if not same-origin url's
Pipeline::create(next_pipeline_id,
Some(subpage_id),
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.resource_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
size_future)
};
if url.path.ends_with(".js") {
pipeline.execute(url);
} else {
pipeline.load(url, None);
}
for frame_tree in frame_trees.iter() {
frame_tree.children.push(@mut FrameTree {
pipeline: pipeline,
parent: Some(source_pipeline),
children: ~[],
});
}
self.pipelines.insert(pipeline.id, pipeline);
self.handle_load_iframe_url_msg(url, source_pipeline_id, subpage_id, size_future);
}
// Load a new page, usually -- but not always -- from a mouse click or typed url
// If there is already a pending page (self.pending_frames), it will not be overridden;
// However, if the id is not encompassed by another change, it will be.
LoadUrlMsg(source_id, url, size_future) => {
debug!("received message to load %s", url.to_str());
// Make sure no pending page would be overridden.
let source_frame = self.current_frame().get_ref().find_mut(source_id).expect(
"Constellation: received a LoadUrlMsg from a pipeline_id associated
with a pipeline not in the active frame tree. This should be
impossible.");
for frame_change in self.pending_frames.iter() {
let old_id = frame_change.before.expect("Constellation: Received load msg
from pipeline, but there is no currently active page. This should
be impossible.");
let changing_frame = self.current_frame().get_ref().find_mut(old_id).expect("Constellation:
Pending change has non-active source pipeline. This should be
impossible.");
if changing_frame.contains(source_id) || source_frame.contains(old_id) {
// id that sent load msg is being changed already; abort
return true;
}
}
// Being here means either there are no pending frames, or none of the pending
// changes would be overriden by changing the subframe associated with source_id.
let parent = source_frame.parent.clone();
let subpage_id = source_frame.pipeline.subpage_id.clone();
let next_pipeline_id = self.get_next_pipeline_id();
let pipeline = @mut Pipeline::create(next_pipeline_id,
subpage_id,
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.resource_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
size_future);
if url.path.ends_with(".js") {
pipeline.script_chan.send(ExecuteMsg(pipeline.id, url));
} else {
pipeline.load(url, Some(constellation_msg::Load));
self.pending_frames.push(FrameChange{
before: Some(source_id),
after: @mut FrameTree {
pipeline: pipeline,
parent: parent,
children: ~[],
},
});
}
self.pipelines.insert(pipeline.id, pipeline);
self.handle_load_url_msg(source_id, url, size_future);
}
// Handle a forward or back request
NavigateMsg(direction) => {
debug!("received message to navigate %?", direction);
// TODO(tkuehn): what is the "critical point" beyond which pending frames
// should not be cleared? Currently, the behavior is that forward/back
// navigation always has navigation priority, and after that new page loading is
// first come, first served.
let destination_frame = match direction {
constellation_msg::Forward => {
if self.navigation_context.next.is_empty() {
debug!("no next page to navigate to");
return true
} else {
let old = self.current_frame().get_ref();
for frame in old.iter() {
frame.pipeline.revoke_paint_permission();
}
}
self.navigation_context.forward()
}
constellation_msg::Back => {
if self.navigation_context.previous.is_empty() {
debug!("no previous page to navigate to");
return true
} else {
let old = self.current_frame().get_ref();
for frame in old.iter() {
frame.pipeline.revoke_paint_permission();
}
}
self.navigation_context.back()
}
};
for frame in destination_frame.iter() {
let pipeline = &frame.pipeline;
pipeline.reload(Some(constellation_msg::Navigate));
}
self.grant_paint_permission(destination_frame);
self.handle_navigate_msg(direction);
}
// Notification that rendering has finished and is requesting permission to paint.
RendererReadyMsg(pipeline_id) => {
// This message could originate from a pipeline in the navigation context or
// from a pending frame. The only time that we will grant paint permission is
// when the message originates from a pending frame or the current frame.
for &current_frame in self.current_frame().iter() {
// Messages originating in the current frame are not navigations;
// TODO(tkuehn): In fact, this kind of message might be provably
// impossible to occur.
if current_frame.contains(pipeline_id) {
self.set_ids(current_frame);
return true;
}
}
// Find the pending frame change whose new pipeline id is pipeline_id.
// If it is not found, it simply means that this pipeline will not receive
// permission to paint.
let pending_index = do self.pending_frames.rposition |frame_change| {
frame_change.after.pipeline.id == pipeline_id
};
for &pending_index in pending_index.iter() {
let frame_change = self.pending_frames.swap_remove(pending_index);
let to_add = frame_change.after;
// Create the next frame tree that will be given to the compositor
let next_frame_tree = match to_add.parent {
None => to_add, // to_add is the root
Some(_parent) => @mut (*self.current_frame().unwrap()).clone(),
};
// If there are frames to revoke permission from, do so now.
match frame_change.before {
Some(revoke_id) => {
let current_frame = self.current_frame().unwrap();
let to_revoke = current_frame.find_mut(revoke_id).expect(
"Constellation: pending frame change refers to an old
frame not contained in the current frame. This is a bug");
for frame in to_revoke.iter() {
frame.pipeline.revoke_paint_permission();
}
// If to_add is not the root frame, then replace revoked_frame with it
if to_add.parent.is_some() {
next_frame_tree.replace_child(revoke_id, to_add);
}
}
None => {
// Add to_add to parent's children, if it is not the root
let parent = &to_add.parent;
let to_add = Cell::new(to_add);
for parent in parent.iter() {
let parent = next_frame_tree.find_mut(parent.id).expect(
"Constellation: pending frame has a parent frame that is not
active. This is a bug.");
parent.children.push(to_add.take());
}
}
}
self.grant_paint_permission(next_frame_tree);
}
self.handle_renderer_ready_msg(pipeline_id);
}
ResizedWindowBroadcast(new_size) => match *self.current_frame() {
Some(ref current_frame) => {
let current_frame_id = current_frame.pipeline.id.clone();
for frame_tree in self.navigation_context.previous.iter() {
let pipeline = &frame_tree.pipeline;
if current_frame_id != pipeline.id {
pipeline.script_chan.send(ResizeInactiveMsg(new_size));
}
}
for frame_tree in self.navigation_context.next.iter() {
let pipeline = &frame_tree.pipeline;
if current_frame_id != pipeline.id {
pipeline.script_chan.send(ResizeInactiveMsg(new_size));
}
}
}
None => {
for frame_tree in self.navigation_context.previous.iter() {
frame_tree.pipeline.script_chan.send(ResizeInactiveMsg(new_size));
}
for frame_tree in self.navigation_context.next.iter() {
frame_tree.pipeline.script_chan.send(ResizeInactiveMsg(new_size));
}
}
ResizedWindowMsg(new_size) => {
self.handle_resized_window_msg(new_size);
}
}
true
}
fn handle_exit(&self, sender: Chan<()>) {
for (_id, ref pipeline) in self.pipelines.iter() {
pipeline.exit();
}
self.image_cache_task.exit();
self.resource_task.send(resource_task::Exit);
sender.send(());
}
fn handle_init_load(&mut self, url: Url) {
let pipeline = @mut Pipeline::create(self.get_next_pipeline_id(),
None,
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.resource_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
{
let size = self.compositor_chan.get_size();
from_value(Size2D(size.width as uint, size.height as uint))
});
if url.path.ends_with(".js") {
pipeline.script_chan.send(ExecuteMsg(pipeline.id, url));
} else {
pipeline.load(url);
self.pending_frames.push(FrameChange{
before: None,
after: @mut FrameTree {
pipeline: pipeline,
parent: None,
children: ~[],
},
navigation_type: constellation_msg::Load,
});
}
self.pipelines.insert(pipeline.id, pipeline);
}
fn handle_frame_rect_msg(&mut self, pipeline_id: PipelineId, subpage_id: SubpageId, rect: Rect<f32>) {
debug!("Received frame rect %? from %?, %?", rect, pipeline_id, subpage_id);
let mut already_sent = HashSet::new();
// If the subframe is in the current frame tree, the compositor needs the new size
for current_frame in self.current_frame().iter() {
debug!("Constellation: Sending size for frame in current frame tree.");
let source_frame = current_frame.find_mut(pipeline_id);
for source_frame in source_frame.iter() {
for child_frame_tree in source_frame.children.mut_iter() {
let pipeline = &child_frame_tree.frame_tree.pipeline;
if pipeline.subpage_id.expect("Constellation: child frame does not have a
subpage id. This should not be possible.") == subpage_id {
child_frame_tree.rect = Some(rect.clone());
let Rect { size: Size2D { width, height }, _ } = rect;
pipeline.script_chan.send(SendEventMsg(pipeline.id.clone(),
ResizeEvent(width as uint,
height as uint)));
self.compositor_chan.send(SetLayerClipRect(pipeline.id, rect));
already_sent.insert(pipeline.id.clone());
break;
}
}
}
}
// Traverse the navigation context and pending frames and tell each associated pipeline to resize.
let frame_trees: ~[@mut FrameTree] = {
let matching_navi_frames = self.navigation_context.find_all(pipeline_id);
let matching_pending_frames = do self.pending_frames.iter().filter_map |frame_change| {
frame_change.after.find_mut(pipeline_id)
};
matching_navi_frames.move_iter().chain(matching_pending_frames).collect()
};
for frame_tree in frame_trees.iter() {
for child_frame_tree in frame_tree.children.mut_iter() {
let pipeline = &child_frame_tree.frame_tree.pipeline;
if pipeline.subpage_id.expect("Constellation: child frame does not have a
subpage id. This should not be possible.") == subpage_id {
child_frame_tree.rect = Some(rect.clone());
if !already_sent.contains(&pipeline.id) {
let Size2D { width, height } = rect.size;
pipeline.script_chan.send(ResizeInactiveMsg(pipeline.id.clone(),
Size2D(width as uint, height as uint)));
already_sent.insert(pipeline.id.clone());
}
break;
}
}
}
// At this point, if no pipelines were sent a resize msg, then this subpage id
// should be added to pending sizes
if already_sent.len() == 0 {
self.pending_sizes.insert((pipeline_id, subpage_id), rect);
}
}
fn handle_load_iframe_url_msg(&mut self,
url: Url,
source_pipeline_id: PipelineId,
subpage_id: SubpageId,
size_future: Future<Size2D<uint>>) {
// A message from the script associated with pipeline_id that it has
// parsed an iframe during html parsing. This iframe will result in a
// new pipeline being spawned and a frame tree being added to pipeline_id's
// frame tree's children. This message is never the result of a link clicked
// or a new url entered.
// Start by finding the frame trees matching the pipeline id,
// and add the new pipeline to their sub frames.
let frame_trees: ~[@mut FrameTree] = {
let matching_navi_frames = self.navigation_context.find_all(source_pipeline_id);
let matching_pending_frames = do self.pending_frames.iter().filter_map |frame_change| {
frame_change.after.find_mut(source_pipeline_id)
};
matching_navi_frames.move_iter().chain(matching_pending_frames).collect()
};
if frame_trees.is_empty() {
fail!("Constellation: source pipeline id of LoadIframeUrlMsg is not in
navigation context, nor is it in a pending frame. This should be
impossible.");
}
let next_pipeline_id = self.get_next_pipeline_id();
// Compare the pipeline's url to the new url. If the origin is the same,
// then reuse the script task in creating the new pipeline
let source_pipeline = *self.pipelines.find(&source_pipeline_id).expect("Constellation:
source Id of LoadIframeUrlMsg does have an associated pipeline in
constellation. This should be impossible.");
let source_url = source_pipeline.url.clone().expect("Constellation: LoadUrlIframeMsg's
source's Url is None. There should never be a LoadUrlIframeMsg from a pipeline
that was never given a url to load.");
// FIXME(tkuehn): Need to follow the standardized spec for checking same-origin
let pipeline = @mut if (source_url.host == url.host &&
source_url.port == url.port) {
debug!("Constellation: loading same-origin iframe at %?", url);
// Reuse the script task if same-origin url's
Pipeline::with_script(next_pipeline_id,
Some(subpage_id),
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
source_pipeline,
size_future)
} else {
debug!("Constellation: loading cross-origin iframe at %?", url);
// Create a new script task if not same-origin url's
Pipeline::create(next_pipeline_id,
Some(subpage_id),
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.resource_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
size_future)
};
if url.path.ends_with(".js") {
pipeline.execute(url);
} else {
debug!("Constellation: sending load msg to %?", pipeline);
pipeline.load(url);
}
let rect = self.pending_sizes.pop(&(source_pipeline_id, subpage_id));
for frame_tree in frame_trees.iter() {
frame_tree.children.push(ChildFrameTree {
frame_tree: @mut FrameTree {
pipeline: pipeline,
parent: Some(source_pipeline),
children: ~[],
},
rect: rect,
});
}
self.pipelines.insert(pipeline.id, pipeline);
}
fn handle_load_url_msg(&mut self, source_id: PipelineId, url: Url, size_future: Future<Size2D<uint>>) {
debug!("Constellation: received message to load %s", url.to_str());
// Make sure no pending page would be overridden.
let source_frame = self.current_frame().get_ref().find_mut(source_id).expect(
"Constellation: received a LoadUrlMsg from a pipeline_id associated
with a pipeline not in the active frame tree. This should be
impossible.");
for frame_change in self.pending_frames.iter() {
let old_id = frame_change.before.expect("Constellation: Received load msg
from pipeline, but there is no currently active page. This should
be impossible.");
let changing_frame = self.current_frame().get_ref().find_mut(old_id).expect("Constellation:
Pending change has non-active source pipeline. This should be
impossible.");
if changing_frame.contains(source_id) || source_frame.contains(old_id) {
// id that sent load msg is being changed already; abort
return;
}
}
// Being here means either there are no pending frames, or none of the pending
// changes would be overriden by changing the subframe associated with source_id.
let parent = source_frame.parent.clone();
let subpage_id = source_frame.pipeline.subpage_id.clone();
let next_pipeline_id = self.get_next_pipeline_id();
let pipeline = @mut Pipeline::create(next_pipeline_id,
subpage_id,
self.chan.clone(),
self.compositor_chan.clone(),
self.image_cache_task.clone(),
self.resource_task.clone(),
self.profiler_chan.clone(),
self.opts.clone(),
size_future);
if url.path.ends_with(".js") {
pipeline.script_chan.send(ExecuteMsg(pipeline.id, url));
} else {
pipeline.load(url);
self.pending_frames.push(FrameChange{
before: Some(source_id),
after: @mut FrameTree {
pipeline: pipeline,
parent: parent,
children: ~[],
},
navigation_type: constellation_msg::Load,
});
}
self.pipelines.insert(pipeline.id, pipeline);
}
fn handle_navigate_msg(&mut self, direction: constellation_msg::NavigationDirection) {
debug!("received message to navigate %?", direction);
// TODO(tkuehn): what is the "critical point" beyond which pending frames
// should not be cleared? Currently, the behavior is that forward/back
// navigation always has navigation priority, and after that new page loading is
// first come, first served.
let destination_frame = match direction {
constellation_msg::Forward => {
if self.navigation_context.next.is_empty() {
debug!("no next page to navigate to");
return;
} else {
let old = self.current_frame().get_ref();
for frame in old.iter() {
frame.pipeline.revoke_paint_permission();
}
}
self.navigation_context.forward()
}
constellation_msg::Back => {
if self.navigation_context.previous.is_empty() {
debug!("no previous page to navigate to");
return;
} else {
let old = self.current_frame().get_ref();
for frame in old.iter() {
frame.pipeline.revoke_paint_permission();
}
}
self.navigation_context.back()
}
};
for frame in destination_frame.iter() {
let pipeline = &frame.pipeline;
pipeline.reload();
}
self.grant_paint_permission(destination_frame, constellation_msg::Navigate);
}
fn handle_renderer_ready_msg(&mut self, pipeline_id: PipelineId) {
debug!("Renderer %? ready to send paint msg", pipeline_id);
// This message could originate from a pipeline in the navigation context or
// from a pending frame. The only time that we will grant paint permission is
// when the message originates from a pending frame or the current frame.
for &current_frame in self.current_frame().iter() {
// Messages originating in the current frame are not navigations;
// TODO(tkuehn): In fact, this kind of message might be provably
// impossible to occur.
if current_frame.contains(pipeline_id) {
for frame in current_frame.iter() {
frame.pipeline.grant_paint_permission();
}
return;
}
}
// Find the pending frame change whose new pipeline id is pipeline_id.
// If it is not found, it simply means that this pipeline will not receive
// permission to paint.
let pending_index = do self.pending_frames.rposition |frame_change| {
frame_change.after.pipeline.id == pipeline_id
};
for &pending_index in pending_index.iter() {
let frame_change = self.pending_frames.swap_remove(pending_index);
let to_add = frame_change.after;
// Create the next frame tree that will be given to the compositor
let next_frame_tree = match to_add.parent {
None => to_add, // to_add is the root
Some(_parent) => @mut (*self.current_frame().unwrap()).clone(),
};
// If there are frames to revoke permission from, do so now.
match frame_change.before {
Some(revoke_id) => {
debug!("Constellation: revoking permission from %?", revoke_id);
let current_frame = self.current_frame().unwrap();
let to_revoke = current_frame.find_mut(revoke_id).expect(
"Constellation: pending frame change refers to an old
frame not contained in the current frame. This is a bug");
for frame in to_revoke.iter() {
frame.pipeline.revoke_paint_permission();
}
// If to_add is not the root frame, then replace revoked_frame with it.
// This conveniently keeps scissor rect size intact.
debug!("Constellation: replacing %? with %? in %?", revoke_id, to_add, next_frame_tree);
if to_add.parent.is_some() {
let replaced = next_frame_tree.replace_child(revoke_id, to_add);
debug!("Replaced child: %?", replaced);
}
debug!("Constellation: frame tree after replacing: %?", next_frame_tree);
}
None => {
// Add to_add to parent's children, if it is not the root
let parent = &to_add.parent;
let to_add = Cell::new(to_add);
for parent in parent.iter() {
let to_add = to_add.take();
let subpage_id = to_add.pipeline.subpage_id.expect("Constellation:
Child frame's subpage id is None. This should be impossible.");
let rect = self.pending_sizes.pop(&(parent.id, subpage_id));
let parent = next_frame_tree.find_mut(parent.id).expect(
"Constellation: pending frame has a parent frame that is not
active. This is a bug.");
parent.children.push(ChildFrameTree {
frame_tree: to_add,
rect: rect,
});
}
}
}
self.grant_paint_permission(next_frame_tree, frame_change.navigation_type);
}
}
fn handle_resized_window_msg(&mut self, new_size: Size2D<uint>) {
let mut already_seen = HashSet::new();
for &@FrameTree { pipeline: ref pipeline, _ } in self.current_frame().iter() {
let Size2D { width, height } = new_size;
pipeline.script_chan.send(SendEventMsg(pipeline.id.clone(),
ResizeEvent(width, height)));
already_seen.insert(pipeline.id.clone());
}
for frame_tree in self.navigation_context.previous.iter()
.chain(self.navigation_context.next.iter()) {
let pipeline = &frame_tree.pipeline;
if !already_seen.contains(&pipeline.id) {
pipeline.script_chan.send(ResizeInactiveMsg(pipeline.id.clone(), new_size));
already_seen.insert(pipeline.id.clone());
}
}
}
// Grants a frame tree permission to paint; optionally updates navigation to reflect a new page
fn grant_paint_permission(&mut self, frame_tree: @mut FrameTree) {
fn grant_paint_permission(&mut self, frame_tree: @mut FrameTree, navigation_type: NavigationType) {
// Give permission to paint to the new frame and all child frames
self.set_ids(frame_tree);
// Don't call navigation_context.load() on a Navigate type (or None, as in the case of
// parsed iframes that finish loading)
match frame_tree.pipeline.navigation_type {
Some(constellation_msg::Load) => {
match navigation_type {
constellation_msg::Load => {
let evicted = self.navigation_context.load(frame_tree);
for frame_tree in evicted.iter() {
// exit any pipelines that don't exist outside the evicted frame trees
@ -622,7 +759,7 @@ impl Constellation {
fn set_ids(&self, frame_tree: @mut FrameTree) {
let (port, chan) = comm::stream();
self.compositor_chan.send(SetIds(frame_tree.to_sendable(), chan));
self.compositor_chan.send(SetIds(frame_tree.to_sendable(), chan, self.chan.clone()));
port.recv();
for frame in frame_tree.iter() {
frame.pipeline.grant_paint_permission();

View file

@ -14,9 +14,10 @@ use layout::float_context::{FloatContext, Invalid};
use std::cell::Cell;
use geom::point::Point2D;
use geom::size::Size2D;
use geom::rect::Rect;
use gfx::display_list::DisplayList;
use gfx::geometry::Au;
use gfx::geometry::{Au, to_frac_px};
use gfx::geometry;
use servo_util::tree::TreeNodeRef;
@ -364,6 +365,27 @@ impl BlockFlowData {
list: &Cell<DisplayList<E>>)
-> bool {
if self.common.node.is_iframe_element() {
let x = self.common.abs_position.x + do self.box.map_default(Au(0)) |box| {
box.with_model(|model| model.margin.left + model.border.left + model.padding.left)
};
let y = self.common.abs_position.y + do self.box.map_default(Au(0)) |box| {
box.with_model(|model| model.margin.top + model.border.top + model.padding.top)
};
let w = self.common.position.size.width - do self.box.map_default(Au(0)) |box| {
box.with_model(|model| model.noncontent_width())
};
let h = self.common.position.size.height - do self.box.map_default(Au(0)) |box| {
box.with_model(|model| model.noncontent_height())
};
do self.common.node.with_mut_iframe_element |iframe_element| {
iframe_element.size.get_mut_ref().set_rect(Rect(Point2D(to_frac_px(x) as f32,
to_frac_px(y) as f32),
Size2D(to_frac_px(w) as f32,
to_frac_px(h) as f32)));
}
}
let abs_rect = Rect(self.common.abs_position, self.common.position.size);
if !abs_rect.intersects(dirty) {
return false;

View file

@ -295,6 +295,10 @@ impl FloatFlowData {
list: &Cell<DisplayList<E>>)
-> bool {
//TODO: implement iframe size messaging
if self.common.node.is_iframe_element() {
error!("float iframe size messaging not implemented yet");
}
let abs_rect = Rect(self.common.abs_position, self.common.position.size);
if !abs_rect.intersects(dirty) {
return false;

View file

@ -753,6 +753,11 @@ impl InlineFlowData {
list: &Cell<DisplayList<E>>)
-> bool {
//TODO: implement inline iframe size messaging
if self.common.node.is_iframe_element() {
error!("inline iframe size messaging not implemented yet");
}
let abs_rect = Rect(self.common.abs_position, self.common.position.size);
if !abs_rect.intersects(dirty) {
return false;

View file

@ -117,6 +117,12 @@ impl BoxModel {
left + right
}
pub fn noncontent_height(&self) -> Au {
let top = self.margin.top + self.border.top + self.padding.top;
let bottom = self.margin.bottom + self.border.bottom + self.padding.bottom;
top + bottom
}
pub fn offset(&self) -> Au {
self.margin.left + self.border.left + self.padding.left
}

View file

@ -11,7 +11,7 @@ use gfx::opts::Opts;
use layout::layout_task::LayoutTask;
use script::layout_interface::LayoutChan;
use script::script_task::{ExecuteMsg, LoadMsg};
use servo_msg::constellation_msg::{ConstellationChan, NavigationType, PipelineId, SubpageId};
use servo_msg::constellation_msg::{ConstellationChan, PipelineId, SubpageId};
use script::script_task::{AttachLayoutMsg, NewLayoutInfo, ScriptTask, ScriptChan};
use script::script_task;
use servo_net::image_cache_task::ImageCacheTask;
@ -31,7 +31,6 @@ pub struct Pipeline {
render_chan: RenderChan,
/// The most recently loaded url
url: Option<Url>,
navigation_type: Option<NavigationType>,
}
impl Pipeline {
@ -140,13 +139,11 @@ impl Pipeline {
layout_chan: layout_chan,
render_chan: render_chan,
url: None,
navigation_type: None,
}
}
pub fn load(&mut self, url: Url, navigation_type: Option<NavigationType>) {
pub fn load(&mut self, url: Url) {
self.url = Some(url.clone());
self.navigation_type = navigation_type;
self.script_chan.send(LoadMsg(self.id, url));
}
@ -163,11 +160,10 @@ impl Pipeline {
self.render_chan.send(PaintPermissionRevoked);
}
pub fn reload(&mut self, navigation_type: Option<NavigationType>) {
if self.url.is_some() {
let url = self.url.get_ref().clone();
self.load(url, navigation_type);
}
pub fn reload(&mut self) {
do self.url.clone().map_move() |url| {
self.load(url);
};
}
pub fn exit(&self) {

View file

@ -54,14 +54,25 @@ pub enum ReadyState {
FinishedLoading,
}
/// A newtype struct for denoting the age of messages; prevents race conditions.
#[deriving(Eq)]
pub struct Epoch(uint);
impl Epoch {
pub fn next(&mut self) {
**self += 1;
}
}
/// The interface used by the renderer to acquire draw targets for each render frame and
/// submit them to be drawn to the display.
pub trait RenderListener {
fn get_gl_context(&self) -> AzGLContext;
fn new_layer(&self, PipelineId, Size2D<uint>);
fn resize_layer(&self, PipelineId, Size2D<uint>);
fn set_layer_page_size(&self, PipelineId, Size2D<uint>, Epoch);
fn set_layer_clip_rect(&self, PipelineId, Rect<uint>);
fn delete_layer(&self, PipelineId);
fn paint(&self, id: PipelineId, layer_buffer_set: arc::Arc<LayerBufferSet>);
fn paint(&self, id: PipelineId, layer_buffer_set: arc::Arc<LayerBufferSet>, Epoch);
fn set_render_state(&self, render_state: RenderState);
}

View file

@ -9,6 +9,7 @@ use std::comm::{Chan, SharedChan};
use extra::url::Url;
use extra::future::Future;
use geom::size::Size2D;
use geom::rect::Rect;
#[deriving(Clone)]
pub struct ConstellationChan {
@ -29,11 +30,12 @@ impl ConstellationChan {
pub enum Msg {
ExitMsg(Chan<()>),
InitLoadUrlMsg(Url),
FrameRectMsg(PipelineId, SubpageId, Rect<f32>),
LoadUrlMsg(PipelineId, Url, Future<Size2D<uint>>),
LoadIframeUrlMsg(Url, PipelineId, SubpageId, Future<Size2D<uint>>),
NavigateMsg(NavigationDirection),
RendererReadyMsg(PipelineId),
ResizedWindowBroadcast(Size2D<uint>),
ResizedWindowMsg(Size2D<uint>),
}
/// Represents the two different ways to which a page can be navigated

View file

@ -385,8 +385,6 @@ impl Element {
assert!(node.is_element());
let page = win.page;
let (port, chan) = comm::stream();
// TODO(tkuehn): currently just queries top-level page layout. Needs to query
// subframe layout if this element is in a subframe. Probably need an ID field.
match unsafe {(*page).query_layout(ContentBoxesQuery(node, chan), port)} {
Ok(ContentBoxesResponse(rects)) => {
let cx = unsafe {(*page).js_info.get_ref().js_compartment.cx.ptr};

View file

@ -7,19 +7,39 @@ use dom::document::AbstractDocument;
use dom::htmlelement::HTMLElement;
use dom::windowproxy::WindowProxy;
use geom::size::Size2D;
use geom::rect::Rect;
use servo_msg::constellation_msg::SubpageId;
use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId};
use std::comm::ChanOne;
use extra::url::Url;
use std::util::replace;
pub struct HTMLIFrameElement {
parent: HTMLElement,
frame: Option<Url>,
subpage_id: Option<SubpageId>,
size_future_chan: Option<ChanOne<Size2D<uint>>>,
size: Option<IFrameSize>,
}
struct IFrameSize {
pipeline_id: PipelineId,
subpage_id: SubpageId,
future_chan: Option<ChanOne<Size2D<uint>>>,
constellation_chan: ConstellationChan,
}
impl IFrameSize {
pub fn set_rect(&mut self, rect: Rect<f32>) {
let future_chan = replace(&mut self.future_chan, None);
do future_chan.map_move |future_chan| {
let Size2D { width, height } = rect.size;
future_chan.send(Size2D(width as uint, height as uint));
};
self.constellation_chan.send(FrameRectMsg(self.pipeline_id, self.subpage_id, rect));
}
}
impl HTMLIFrameElement {
pub fn Src(&self) -> DOMString {
null_string
@ -123,4 +143,4 @@ impl HTMLIFrameElement {
pub fn GetSVGDocument(&self) -> Option<AbstractDocument> {
None
}
}
}

View file

@ -30,7 +30,7 @@ use dom::htmlanchorelement::HTMLAnchorElement;
use dom::htmlbodyelement::HTMLBodyElement;
use dom::htmlcanvaselement::HTMLCanvasElement;
use dom::htmlhrelement::HTMLHRElement;
use dom::htmliframeelement::HTMLIFrameElement;
use dom::htmliframeelement::{IFrameSize, HTMLIFrameElement};
use dom::htmlimageelement::HTMLImageElement;
use dom::htmlmetaelement::HTMLMetaElement;
use dom::htmlolistelement::HTMLOListElement;
@ -50,6 +50,7 @@ use dom::bindings::utils::str;
use html::cssparse::{InlineProvenance, StylesheetProvenance, UrlProvenance, spawn_css_parser};
use js::jsapi::JSContext;
use newcss::stylesheet::Stylesheet;
use script_task::page_from_context;
use std::cast;
use std::cell::Cell;
@ -59,7 +60,7 @@ use std::str::eq_slice;
use std::task;
use std::from_str::FromStr;
use hubbub::hubbub;
use servo_msg::constellation_msg::SubpageId;
use servo_msg::constellation_msg::{ConstellationChan, SubpageId};
use servo_net::image_cache_task::ImageCacheTask;
use servo_net::image_cache_task;
use servo_net::resource_task::{Done, Load, Payload, ResourceTask};
@ -252,7 +253,7 @@ fn build_element_from_tag(cx: *JSContext, tag: &str) -> AbstractNode<ScriptView>
handle_element!(cx, tag, "ul", HTMLUListElementTypeId, HTMLUListElement, []);
handle_element!(cx, tag, "img", HTMLImageElementTypeId, HTMLImageElement, [(image: None)]);
handle_element!(cx, tag, "iframe", HTMLIframeElementTypeId, HTMLIFrameElement, [(frame: None), (size_future_chan: None), (subpage_id: None)]);
handle_element!(cx, tag, "iframe", HTMLIframeElementTypeId, HTMLIFrameElement, [(frame: None), (size: None)]);
handle_element!(cx, tag, "h1", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading1)]);
handle_element!(cx, tag, "h2", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading2)]);
@ -276,7 +277,8 @@ pub fn parse_html(cx: *JSContext,
url: Url,
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
next_subpage_id: SubpageId) -> HtmlParserResult {
next_subpage_id: SubpageId,
constellation_chan: ConstellationChan) -> HtmlParserResult {
debug!("Hubbub: parsing %?", url);
// Spawn a CSS parser to receive links to CSS style sheets.
let resource_task2 = resource_task.clone();
@ -381,14 +383,24 @@ pub fn parse_html(cx: *JSContext,
// Size future
let (port, chan) = comm::oneshot();
iframe_element.size_future_chan = Some(chan);
let size_future = from_port(port);
// Subpage Id
let subpage_id = next_subpage_id.take();
iframe_element.subpage_id = Some(subpage_id);
next_subpage_id.put_back(SubpageId(*subpage_id + 1));
// Pipeline Id
let pipeline_id = {
let page = page_from_context(cx);
unsafe { (*page).id }
};
iframe_element.size = Some(IFrameSize {
pipeline_id: pipeline_id,
subpage_id: subpage_id,
future_chan: Some(chan),
constellation_chan: constellation_chan.clone(),
});
iframe_chan.send(HtmlDiscoveredIFrame((iframe_url, subpage_id, size_future)));
}
}

View file

@ -21,7 +21,7 @@ use layout_interface::{ReflowDocumentDamage, ReflowForDisplay, ReflowGoal};
use layout_interface::ReflowMsg;
use layout_interface;
use servo_msg::constellation_msg::{ConstellationChan, LoadUrlMsg, NavigationDirection};
use servo_msg::constellation_msg::{PipelineId, SubpageId, RendererReadyMsg, ResizedWindowBroadcast};
use servo_msg::constellation_msg::{PipelineId, SubpageId, RendererReadyMsg};
use servo_msg::constellation_msg::{LoadIframeUrlMsg};
use servo_msg::constellation_msg;
@ -68,7 +68,7 @@ pub enum ScriptMsg {
/// Notifies script that reflow is finished.
ReflowCompleteMsg(PipelineId),
/// Notifies script that window has been resized but to not take immediate action.
ResizeInactiveMsg(Size2D<uint>),
ResizeInactiveMsg(PipelineId, Size2D<uint>),
/// Exits the constellation.
ExitMsg,
}
@ -454,7 +454,7 @@ impl ScriptTask {
FireTimerMsg(id, timer_data) => self.handle_fire_timer_msg(id, timer_data),
NavigateMsg(direction) => self.handle_navigate_msg(direction),
ReflowCompleteMsg(id) => self.handle_reflow_complete_msg(id),
ResizeInactiveMsg(new_size) => self.handle_resize_inactive_msg(new_size),
ResizeInactiveMsg(id, new_size) => self.handle_resize_inactive_msg(id, new_size),
ExitMsg => {
self.handle_exit_msg();
return false
@ -464,6 +464,7 @@ impl ScriptTask {
}
fn handle_new_layout(&mut self, new_layout_info: NewLayoutInfo) {
debug!("Script: new layout: %?", new_layout_info);
let NewLayoutInfo {
old_id,
new_id,
@ -529,6 +530,7 @@ impl ScriptTask {
/// Handles a notification that reflow completed.
fn handle_reflow_complete_msg(&mut self, pipeline_id: PipelineId) {
debug!("Script: Reflow complete for %?", pipeline_id);
self.page_tree.find(pipeline_id).expect("ScriptTask: received a load
message for a layout channel that is not associated with this script task. This
is a bug.").page.layout_join_port = None;
@ -543,11 +545,13 @@ impl ScriptTask {
}
/// Window was resized, but this script was not active, so don't reflow yet
fn handle_resize_inactive_msg(&mut self, new_size: Size2D<uint>) {
self.page_tree.page.window_size = from_value(new_size);
let last_loaded_url = replace(&mut self.page_tree.page.url, None);
fn handle_resize_inactive_msg(&mut self, id: PipelineId, new_size: Size2D<uint>) {
let page = self.page_tree.find(id).expect("Received resize message for PipelineId not associated
with a page in the page tree. This is a bug.").page;
page.window_size = from_value(new_size);
let last_loaded_url = replace(&mut page.url, None);
for url in last_loaded_url.iter() {
self.page_tree.page.url = Some((url.first(), true));
page.url = Some((url.first(), true));
}
}
@ -565,7 +569,7 @@ impl ScriptTask {
/// The entry point to document loading. Defines bindings, sets up the window and document
/// objects, parses HTML and CSS, and kicks off initial layout.
fn load(&mut self, pipeline_id: PipelineId, url: Url) {
debug!("ScriptTask: loading %?", url);
debug!("ScriptTask: loading %? on page %?", url, pipeline_id);
let page = self.page_tree.find(pipeline_id).expect("ScriptTask: received a load
message for a layout channel that is not associated with this script task. This
@ -601,7 +605,8 @@ impl ScriptTask {
url.clone(),
self.resource_task.clone(),
self.image_cache_task.clone(),
page.next_subpage_id.clone());
page.next_subpage_id.clone(),
self.constellation_chan.clone());
let HtmlParserResult {root, discovery_port} = html_parsing_result;
@ -700,8 +705,6 @@ impl ScriptTask {
page.damage(ReflowDocumentDamage);
page.reflow(ReflowForDisplay, self.chan.clone(), self.compositor)
}
self.constellation_chan.send(ResizedWindowBroadcast(page.window_size.get().clone()));
}
// FIXME(pcwalton): This reflows the entire document and is not incremental-y.