reorganized constellation.

compositor routes dom events via constellation.
constellation handles iframe sizing and resizing.
This commit is contained in:
Tim Kuehn 2013-08-06 19:05:24 -07:00
parent e4d44fb8d2
commit 86f0aacb3d
5 changed files with 445 additions and 348 deletions

View file

@ -136,7 +136,7 @@ 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.compositor.resize_layer(self.id, render_layer.size);
}
self.render_layer = Some(render_layer);
}
@ -147,7 +147,7 @@ impl<C: RenderListener + Send> RenderTask<C> {
self.paint_permission = true;
match self.render_layer {
Some(ref render_layer) => {
self.compositor.new_layer(self.id, render_layer.size);
self.compositor.resize_layer(self.id, render_layer.size);
}
None => {}
}

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};
@ -14,7 +12,7 @@ use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEven
use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState};
use servo_msg::compositor_msg::{ReadyState, ScriptListener};
use servo_msg::constellation_msg::PipelineId;
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;
@ -86,10 +84,12 @@ impl RenderListener for CompositorChan {
}
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))
let Size2D { width, height } = page_size;
self.chan.send(ResizeLayer(id, Size2D(width as f32, height as f32)))
}
fn delete_layer(&self, id: PipelineId) {
self.chan.send(DeleteLayer(id))
@ -130,9 +130,9 @@ pub enum Msg {
// TODO: Attach epochs to these messages
/// Alerts the compositor that there is a new layer to be rendered.
NewLayer(PipelineId, Size2D<uint>),
NewLayer(PipelineId, Size2D<f32>),
/// Alerts the compositor that the specified layer has changed size.
ResizeLayer(PipelineId, Size2D<uint>),
ResizeLayer(PipelineId, Size2D<f32>),
/// Alerts the compositor that the specified layer has been deleted.
DeleteLayer(PipelineId),
/// Invalidate a rect for a given layer
@ -145,7 +145,7 @@ pub enum Msg {
/// 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 +207,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,13 +216,9 @@ 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 = || {
@ -243,9 +239,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 +268,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;
@ -284,8 +293,9 @@ impl CompositorTask {
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),
assert!(layer.resize(id,
Size2D(new_size.width as f32,
new_size.height as f32),
page_window));
ask_for_tiles();
}
@ -340,12 +350,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 +365,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 +429,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

@ -2,7 +2,8 @@
* 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, ResizeLayer};
use script::dom::event::ResizeEvent;
use std::cell::Cell;
use std::comm;
@ -12,19 +13,19 @@ 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, SubpageId};
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::future::{Future, from_value};
/// Maintains the pipelines and navigation context and grants permission to composite
pub struct Constellation {
@ -167,7 +168,7 @@ impl Iterator<@mut FrameTree> for FrameTreeIterator {
fn next(&mut self) -> Option<@mut FrameTree> {
if !self.stack.is_empty() {
let next = self.stack.pop();
for next.children.iter().advance |&ChildFrameTree { frame_tree, _ }| {
for &ChildFrameTree { frame_tree, _ } in next.children.iter() {
self.stack.push(frame_tree);
}
Some(next)
@ -318,328 +319,407 @@ 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);
}
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);
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(ChildFrameTree {
frame_tree: to_add.take(),
rect: None,
});
}
}
}
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, 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);
}
fn handle_frame_rect_msg(&mut self, pipeline_id: PipelineId, subpage_id: SubpageId, rect: Rect<f32>) {
let mut already_sent = HashSet::new();
// If the subframe is in the current frame tree, the compositor needs the new size
let source_frame = self.current_frame().unwrap().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(ResizeLayer(pipeline.id, rect.size));
already_sent.insert(pipeline.id.clone());
break;
}
}
}
// Go through the navigation context 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) {
// 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);
}
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!("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, 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);
}
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(Some(constellation_msg::Navigate));
}
self.grant_paint_permission(destination_frame);
}
fn handle_renderer_ready_msg(&mut self, pipeline_id: PipelineId) {
// 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;
}
}
// 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.
// This conveniently keeps scissor rect size intact.
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 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);
}
}
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 &@FrameTree { pipeline: ref pipeline, _ } in self.navigation_context.previous.iter()
.chain(self.navigation_context.next.iter()) {
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) {
// Give permission to paint to the new frame and all child frames
@ -666,7 +746,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

@ -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,12 +30,12 @@ impl ConstellationChan {
pub enum Msg {
ExitMsg(Chan<()>),
InitLoadUrlMsg(Url),
//FrameRectMsg(PipelineId, SubpageId, Rect<?>),
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

@ -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
@ -543,11 +543,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));
}
}
@ -700,8 +702,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.