diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 6ddbb19df64..8b3d275dbb1 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -136,7 +136,7 @@ impl RenderTask { 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 RenderTask { 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 => {} } diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 1931037b793..fe430853567 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -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) { - 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) { - 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), + NewLayer(PipelineId, Size2D), /// Alerts the compositor that the specified layer has changed size. - ResizeLayer(PipelineId, Size2D), + ResizeLayer(PipelineId, Size2D), /// 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 = None; - // The root CompositorLayer let mut compositor_layer: Option = None; + let mut constellation_chan: Option = 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"), } } diff --git a/src/components/main/constellation.rs b/src/components/main/constellation.rs index 8fb698f8132..4b70eaf04ed 100644 --- a/src/components/main/constellation.rs +++ b/src/components/main/constellation.rs @@ -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 ¤t_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) { + 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>) { + // 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>) { + 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 ¤t_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) { + 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(); diff --git a/src/components/msg/constellation_msg.rs b/src/components/msg/constellation_msg.rs index eb7b196c25d..51bcbb08c46 100644 --- a/src/components/msg/constellation_msg.rs +++ b/src/components/msg/constellation_msg.rs @@ -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), LoadUrlMsg(PipelineId, Url, Future>), LoadIframeUrlMsg(Url, PipelineId, SubpageId, Future>), NavigateMsg(NavigationDirection), RendererReadyMsg(PipelineId), - ResizedWindowBroadcast(Size2D), + ResizedWindowMsg(Size2D), } /// Represents the two different ways to which a page can be navigated diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 28249044e7d..edec2571e2e 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -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), + ResizeInactiveMsg(PipelineId, Size2D), /// 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) { - 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) { + 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.