diff options
author | Dan Fox <iamdanfox@gmail.com> | 2015-03-05 17:34:18 +0000 |
---|---|---|
committer | Dan Fox <iamdanfox@gmail.com> | 2015-03-05 17:34:18 +0000 |
commit | 19686acdec7a8f6e36fe999a2896ed44718f42d6 (patch) | |
tree | adc8d1b7f13790bee5c34239cf679e5062268e1f /components/script/script_task.rs | |
parent | 3441b2c329176fbb3e63b564c6eaef40862be38a (diff) | |
parent | caf5663e0981f36a621dde1dd51be2bd0a15dd24 (diff) | |
download | servo-19686acdec7a8f6e36fe999a2896ed44718f42d6.tar.gz servo-19686acdec7a8f6e36fe999a2896ed44718f42d6.zip |
Merge in servo/master
Diffstat (limited to 'components/script/script_task.rs')
-rw-r--r-- | components/script/script_task.rs | 701 |
1 files changed, 410 insertions, 291 deletions
diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 51361bd1436..673dc3510ef 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -3,7 +3,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing -//! and layout tasks. +//! and layout tasks. It's in charge of processing events for all same-origin pages in a frame +//! tree, and manages the entire lifetime of pages in the frame tree from initial request to +//! teardown. +//! +//! Page loads follow a two-step process. When a request for a new page load is received, the +//! network request is initiated and the relevant data pertaining to the new page is stashed. +//! While the non-blocking request is ongoing, the script task is free to process further events, +//! noting when they pertain to ongoing loads (such as resizes/viewport adjustments). When the +//! initial response is received for an ongoing load, the second phase starts - the frame tree +//! entry is created, along with the Window and Document objects, and the appropriate parser +//! takes over the response body. Once parsing is complete, the document lifecycle for loading +//! a page runs its course and the script task returns to processing events in the main event +//! loop. #![allow(unsafe_blocks)] @@ -23,7 +35,7 @@ use dom::element::{Element, AttributeHandlers}; use dom::event::{Event, EventHelpers}; use dom::uievent::UIEvent; use dom::eventtarget::EventTarget; -use dom::node::{self, Node, NodeHelpers, NodeDamage}; +use dom::node::{self, Node, NodeHelpers, NodeDamage, window_from_node}; use dom::window::{Window, WindowHelpers, ScriptHelpers}; use dom::worker::{Worker, TrustedWorkerAddress}; use parse::html::{HTMLInput, parse_html}; @@ -49,19 +61,20 @@ use msg::constellation_msg::{LoadData, PipelineId, SubpageId}; use msg::constellation_msg::{Failure, Msg, WindowSizeData, PipelineExitType}; use msg::constellation_msg::Msg as ConstellationMsg; use net::image_cache_task::ImageCacheTask; -use net::resource_task::{ResourceTask, ControlMsg}; +use net::resource_task::{ResourceTask, ControlMsg, LoadResponse}; use net::resource_task::LoadData as NetLoadData; use net::storage_task::StorageTask; use string_cache::Atom; use util::geometry::to_frac_px; use util::smallvec::SmallVec; use util::str::DOMString; -use util::task::spawn_named_with_send_on_failure; +use util::task::{spawn_named, spawn_named_with_send_on_failure}; use util::task_state; +use geom::Rect; use geom::point::Point2D; use hyper::header::{LastModified, Headers}; -use js::jsapi::{JS_SetWrapObjectCallbacks, JS_SetGCZeal, JS_DEFAULT_ZEAL_FREQ, JS_GC}; +use js::jsapi::{JS_SetWrapObjectCallbacks, JS_SetGCZeal, JS_DEFAULT_ZEAL_FREQ}; use js::jsapi::{JSContext, JSRuntime, JSObject}; use js::jsapi::{JS_SetGCParameter, JSGC_MAX_BYTES}; use js::jsapi::{JS_SetGCCallback, JSGCStatus, JSGC_BEGIN, JSGC_END}; @@ -73,7 +86,6 @@ use libc; use std::any::Any; use std::borrow::ToOwned; use std::cell::Cell; -use std::mem::replace; use std::num::ToPrimitive; use std::rc::Rc; use std::result::Result; @@ -83,6 +95,43 @@ use time::Tm; thread_local!(pub static STACK_ROOTS: Cell<Option<RootCollectionPtr>> = Cell::new(None)); +/// A document load that is in the process of fetching the requested resource. Contains +/// data that will need to be present when the document and frame tree entry are created, +/// but is only easily available at initiation of the load and on a push basis (so some +/// data will be updated according to future resize events, viewport changes, etc.) +struct InProgressLoad { + /// The pipeline which requested this load. + pipeline_id: PipelineId, + /// The parent pipeline and child subpage associated with this load, if any. + subpage_id: Option<(PipelineId, SubpageId)>, + /// The current window size associated with this pipeline. + window_size: WindowSizeData, + /// Channel to the layout task associated with this pipeline. + layout_chan: LayoutChan, + /// The current viewport clipping rectangle applying to this pipelie, if any. + clip_rect: Option<Rect<f32>>, + /// The requested URL of the load. + url: Url, +} + +impl InProgressLoad { + /// Create a new InProgressLoad object. + fn new(id: PipelineId, + subpage_id: Option<(PipelineId, SubpageId)>, + layout_chan: LayoutChan, + window_size: WindowSizeData, + url: Url) -> InProgressLoad { + InProgressLoad { + pipeline_id: id, + subpage_id: subpage_id, + layout_chan: layout_chan, + window_size: window_size, + clip_rect: None, + url: url, + } + } +} + #[derive(Copy)] pub enum TimerSource { FromWindow(PipelineId), @@ -118,6 +167,8 @@ pub enum ScriptMsg { RunnableMsg(Box<Runnable+Send>), /// A DOM object's last pinned reference was removed (dispatched to all tasks). RefcountCleanup(TrustedReference), + /// The final network response for a page has arrived. + PageFetchComplete(PipelineId, Option<SubpageId>, LoadResponse), } /// A cloneable interface for communicating with an event loop. @@ -175,11 +226,15 @@ impl Drop for StackRootTLS { /// FIXME: Rename to `Page`, following WebKit? pub struct ScriptTask { /// A handle to the information pertaining to page layout - page: DOMRefCell<Rc<Page>>, + page: DOMRefCell<Option<Rc<Page>>>, + /// A list of data pertaining to loads that have not yet received a network response + incomplete_loads: DOMRefCell<Vec<InProgressLoad>>, /// A handle to the image cache task. image_cache_task: ImageCacheTask, /// A handle to the resource task. resource_task: ResourceTask, + /// A handle to the storage task. + storage_task: StorageTask, /// The port on which the script task receives messages (load URL, exit, etc.) port: Receiver<ScriptMsg>, @@ -242,7 +297,8 @@ impl<'a> Drop for ScriptMemoryFailsafe<'a> { unsafe { let page = owner.page.borrow_for_script_deallocation(); for page in page.iter() { - *page.unsafe_mut_js_info() = None; + let window = page.window().root(); + window.r().clear_js_context_for_script_deallocation(); } *owner.js_context.borrow_for_script_deallocation() = None; } @@ -274,15 +330,14 @@ impl ScriptTaskFactory for ScriptTask { storage_task: StorageTask, image_cache_task: ImageCacheTask, devtools_chan: Option<DevtoolsControlChan>, - window_size: WindowSizeData) + window_size: WindowSizeData, + load_data: LoadData) where C: ScriptListener + Send + 'static { let ConstellationChan(const_chan) = constellation_chan.clone(); let (script_chan, script_port) = channel(); let layout_chan = LayoutChan(layout_chan.sender()); spawn_named_with_send_on_failure("ScriptTask", task_state::SCRIPT, move || { - let script_task = ScriptTask::new(id, - box compositor as Box<ScriptListener>, - layout_chan, + let script_task = ScriptTask::new(box compositor as Box<ScriptListener>, script_port, NonWorkerScriptChan(script_chan), control_chan, @@ -291,9 +346,13 @@ impl ScriptTaskFactory for ScriptTask { resource_task, storage_task, image_cache_task, - devtools_chan, - window_size); + devtools_chan); let mut failsafe = ScriptMemoryFailsafe::new(&script_task); + + let new_load = InProgressLoad::new(id, None, layout_chan, window_size, + load_data.url.clone()); + script_task.start_page_load(new_load, load_data); + script_task.start(); // This must always be the very last operation performed before the task completes @@ -312,9 +371,7 @@ unsafe extern "C" fn debug_gc_callback(_rt: *mut JSRuntime, status: JSGCStatus) impl ScriptTask { /// Creates a new script task. - pub fn new(id: PipelineId, - compositor: Box<ScriptListener+'static>, - layout_chan: LayoutChan, + pub fn new(compositor: Box<ScriptListener+'static>, port: Receiver<ScriptMsg>, chan: NonWorkerScriptChan, control_chan: ScriptControlChan, @@ -323,8 +380,7 @@ impl ScriptTask { resource_task: ResourceTask, storage_task: StorageTask, img_cache_task: ImageCacheTask, - devtools_chan: Option<DevtoolsControlChan>, - window_size: WindowSizeData) + devtools_chan: Option<DevtoolsControlChan>) -> ScriptTask { let (js_runtime, js_context) = ScriptTask::new_rt_and_cx(); let wrap_for_same_compartment = wrap_for_same_compartment as @@ -348,19 +404,14 @@ impl ScriptTask { Some(pre_wrap)); } - let page = Page::new(id, None, layout_chan, window_size, - resource_task.clone(), - storage_task, - constellation_chan.clone(), - js_context.clone(), - devtools_chan.clone()); - let (devtools_sender, devtools_receiver) = channel(); ScriptTask { - page: DOMRefCell::new(Rc::new(page)), + page: DOMRefCell::new(None), + incomplete_loads: DOMRefCell::new(vec!()), image_cache_task: img_cache_task, resource_task: resource_task, + storage_task: storage_task, port: port, chan: chan, @@ -417,6 +468,11 @@ impl ScriptTask { (js_runtime, js_context) } + // Return the root page in the frame tree. Panics if it doesn't exist. + fn root_page(&self) -> Rc<Page> { + self.page.borrow().as_ref().unwrap().clone() + } + pub fn get_cx(&self) -> *mut JSContext { (**self.js_context.borrow().as_ref().unwrap()).ptr } @@ -439,17 +495,18 @@ impl ScriptTask { let mut resizes = vec!(); { - let page = self.page.borrow_mut(); - for page in page.iter() { - // Only process a resize if layout is idle. - let layout_join_port = page.layout_join_port.borrow(); - if layout_join_port.is_none() { - let mut resize_event = page.resize_event.get(); - match resize_event.take() { - Some(size) => resizes.push((page.id, size)), - None => () + let page = self.page.borrow(); + if let Some(page) = page.as_ref() { + for page in page.iter() { + // Only process a resize if layout is idle. + let window = page.window().root(); + if window.r().layout_is_idle() { + let resize_event = window.r().steal_resize_event(); + match resize_event { + Some(size) => resizes.push((window.r().pipeline(), size)), + None => () + } } - page.resize_event.set(None); } } } @@ -502,17 +559,10 @@ impl ScriptTask { self.handle_new_layout(new_layout_info); } MixedMessage::FromConstellation(ConstellationControlMsg::Resize(id, size)) => { - let page = self.page.borrow_mut(); - let page = page.find(id).expect("resize sent to nonexistent pipeline"); - page.resize_event.set(Some(size)); + self.handle_resize(id, size); } MixedMessage::FromConstellation(ConstellationControlMsg::Viewport(id, rect)) => { - let page = self.page.borrow_mut(); - let inner_page = page.find(id).expect("Page rect message sent to nonexistent pipeline"); - if inner_page.set_page_clip_rect_with_new_viewport(rect) { - let page = get_page(&*page, id); - self.force_reflow(&*page); - } + self.handle_viewport(id, rect); } _ => { sequential.push(event); @@ -555,8 +605,8 @@ impl ScriptTask { match msg { ConstellationControlMsg::AttachLayout(_) => panic!("should have handled AttachLayout already"), - ConstellationControlMsg::Load(id, parent, load_data) => - self.load(id, parent, load_data), + ConstellationControlMsg::Activate(id) => + self.handle_activate(id), ConstellationControlMsg::SendEvent(id, event) => self.handle_event(id, event), ConstellationControlMsg::ReflowComplete(id, reflow_id) => @@ -598,98 +648,144 @@ impl ScriptTask { runnable.handler(), ScriptMsg::RefcountCleanup(addr) => LiveDOMReferences::cleanup(self.get_cx(), addr), + ScriptMsg::PageFetchComplete(id, subpage, response) => + self.handle_page_fetch_complete(id, subpage, response), } } fn handle_msg_from_devtools(&self, msg: DevtoolScriptControlMsg) { + let page = self.root_page(); match msg { DevtoolScriptControlMsg::EvaluateJS(id, s, reply) => - devtools::handle_evaluate_js(&*self.page.borrow(), id, s, reply), + devtools::handle_evaluate_js(&page, id, s, reply), DevtoolScriptControlMsg::GetRootNode(id, reply) => - devtools::handle_get_root_node(&*self.page.borrow(), id, reply), + devtools::handle_get_root_node(&page, id, reply), DevtoolScriptControlMsg::GetDocumentElement(id, reply) => - devtools::handle_get_document_element(&*self.page.borrow(), id, reply), + devtools::handle_get_document_element(&page, id, reply), DevtoolScriptControlMsg::GetChildren(id, node_id, reply) => - devtools::handle_get_children(&*self.page.borrow(), id, node_id, reply), + devtools::handle_get_children(&page, id, node_id, reply), DevtoolScriptControlMsg::GetLayout(id, node_id, reply) => - devtools::handle_get_layout(&*self.page.borrow(), id, node_id, reply), + devtools::handle_get_layout(&page, id, node_id, reply), DevtoolScriptControlMsg::ModifyAttribute(id, node_id, modifications) => - devtools::handle_modify_attribute(&*self.page.borrow(), id, node_id, modifications), + devtools::handle_modify_attribute(&page, id, node_id, modifications), DevtoolScriptControlMsg::WantsLiveNotifications(pipeline_id, to_send) => - devtools::handle_wants_live_notifications(&*self.page.borrow(), pipeline_id, to_send), + devtools::handle_wants_live_notifications(&page, pipeline_id, to_send), } } + fn handle_resize(&self, id: PipelineId, size: WindowSizeData) { + let page = self.page.borrow(); + if let Some(ref page) = page.as_ref() { + if let Some(ref page) = page.find(id) { + let window = page.window().root(); + window.r().set_resize_event(size); + return; + } + } + let mut loads = self.incomplete_loads.borrow_mut(); + if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) { + load.window_size = size; + return; + } + panic!("resize sent to nonexistent pipeline"); + } + + fn handle_viewport(&self, id: PipelineId, rect: Rect<f32>) { + let page = self.page.borrow(); + if let Some(page) = page.as_ref() { + if let Some(ref inner_page) = page.find(id) { + let window = inner_page.window().root(); + if window.r().set_page_clip_rect_with_new_viewport(rect) { + let page = get_page(page, id); + self.force_reflow(&*page); + } + return; + } + } + let mut loads = self.incomplete_loads.borrow_mut(); + if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) { + load.clip_rect = Some(rect); + return; + } + panic!("Page rect message sent to nonexistent pipeline"); + } + + /// Handle a request to load a page in a new child frame of an existing page. fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) { let NewLayoutInfo { old_pipeline_id, new_pipeline_id, subpage_id, - layout_chan + layout_chan, + load_data, } = new_layout_info; - let page = self.page.borrow_mut(); + let page = self.root_page(); let parent_page = page.find(old_pipeline_id).expect("ScriptTask: received a layout whose parent has a PipelineId which does not correspond to a pipeline in the script task's page tree. This is a bug."); - let new_page = { - let window_size = parent_page.window_size.get(); - Page::new(new_pipeline_id, Some(subpage_id), - LayoutChan(layout_chan.downcast_ref::<Sender<layout_interface::Msg>>().unwrap().clone()), - window_size, - parent_page.resource_task.clone(), - parent_page.storage_task.clone(), - self.constellation_chan.clone(), - self.js_context.borrow().as_ref().unwrap().clone(), - self.devtools_chan.clone()) - }; - parent_page.children.borrow_mut().push(Rc::new(new_page)); + + let parent_window = parent_page.window().root(); + let chan = layout_chan.downcast_ref::<Sender<layout_interface::Msg>>().unwrap(); + let layout_chan = LayoutChan(chan.clone()); + // Kick off the fetch for the new resource. + let new_load = InProgressLoad::new(new_pipeline_id, Some((old_pipeline_id, subpage_id)), + layout_chan, parent_window.r().window_size(), + load_data.url.clone()); + self.start_page_load(new_load, load_data); } /// Handles a timer that fired. fn handle_fire_timer_msg(&self, id: PipelineId, timer_id: TimerId) { - let page = self.page.borrow_mut(); + let page = self.root_page(); let page = page.find(id).expect("ScriptTask: received fire timer msg for a pipeline ID not associated with this script task. This is a bug."); - let frame = page.frame(); - let window = frame.as_ref().unwrap().window.root(); + let window = page.window().root(); window.r().handle_fire_timer(timer_id); } /// Handles freeze message fn handle_freeze_msg(&self, id: PipelineId) { - let page = self.page.borrow_mut(); + let page = self.root_page(); let page = page.find(id).expect("ScriptTask: received freeze msg for a pipeline ID not associated with this script task. This is a bug."); - let frame = page.frame(); - let window = frame.as_ref().unwrap().window.root(); + let window = page.window().root(); window.r().freeze(); } /// Handles thaw message fn handle_thaw_msg(&self, id: PipelineId) { - let page = self.page.borrow_mut(); + let page = self.root_page(); let page = page.find(id).expect("ScriptTask: received thaw msg for a pipeline ID not associated with this script task. This is a bug."); - let frame = page.frame(); - let window = frame.as_ref().unwrap().window.root(); + let window = page.window().root(); window.r().thaw(); } + /// Handle a request to make a previously-created pipeline active. + //TODO: unsuspend JS and timers, etc. when we support such things. + fn handle_activate(&self, pipeline_id: PipelineId) { + // We should only get this message when moving in history, so all pages requested + // should exist. + let page = self.root_page().find(pipeline_id).unwrap(); + + let needed_reflow = page.set_reflow_status(false); + if needed_reflow { + self.force_reflow(&*page); + } + } + /// Handles a notification that reflow completed. fn handle_reflow_complete_msg(&self, pipeline_id: PipelineId, reflow_id: uint) { debug!("Script: Reflow {:?} complete for {:?}", reflow_id, pipeline_id); - let page = self.page.borrow_mut(); + let page = self.root_page(); let page = page.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."); - let last_reflow_id = page.last_reflow_id.get(); - if last_reflow_id == reflow_id { - let mut layout_join_port = page.layout_join_port.borrow_mut(); - *layout_join_port = None; - } + let window = page.window().root(); + window.r().handle_reflow_complete_msg(reflow_id); - let doc = page.frame().as_ref().unwrap().document.root(); + let doc = page.document().root(); let html_element = doc.r().GetDocumentElement().root(); let reftest_wait = html_element.r().map_or(false, |elem| elem.has_class(&Atom::from_slice("reftest-wait"))); @@ -700,14 +796,12 @@ impl ScriptTask { /// Window was resized, but this script was not active, so don't reflow yet fn handle_resize_inactive_msg(&self, id: PipelineId, new_size: WindowSizeData) { - let page = self.page.borrow_mut(); + let page = self.root_page(); let page = page.find(id).expect("Received resize message for PipelineId not associated with a page in the page tree. This is a bug."); - page.window_size.set(new_size); - match &mut *page.mut_url() { - &mut Some((_, ref mut needs_reflow)) => *needs_reflow = true, - &mut None => (), - } + let window = page.window().root(); + window.r().set_window_size(new_size); + page.set_reflow_status(true); } /// We have gotten a window.close from script, which we pass on to the compositor. @@ -724,182 +818,199 @@ impl ScriptTask { self.compositor.borrow_mut().close(); } + /// We have received notification that the response associated with a load has completed. + /// Kick off the document and frame tree creation process using the result. + fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>, + response: LoadResponse) { + // Any notification received should refer to an existing, in-progress load that is tracked. + let idx = self.incomplete_loads.borrow().iter().position(|&:load| { + load.pipeline_id == id && load.subpage_id.map(|sub| sub.1) == subpage + }).unwrap(); + let load = self.incomplete_loads.borrow_mut().remove(idx); + self.load(response, load); + } + /// Handles a request for the window title. fn handle_get_title_msg(&self, pipeline_id: PipelineId) { - get_page(&*self.page.borrow(), pipeline_id).send_title_to_compositor(); + let page = get_page(&self.root_page(), pipeline_id); + let document = page.document().root(); + document.r().send_title_to_compositor(); } /// Handles a request to exit the script task and shut down layout. /// Returns true if the script task should shut down and false otherwise. fn handle_exit_pipeline_msg(&self, id: PipelineId, exit_type: PipelineExitType) -> bool { // If root is being exited, shut down all pages - let page = self.page.borrow_mut(); - if page.id == id { + let page = self.root_page(); + let window = page.window().root(); + if window.r().pipeline() == id { debug!("shutting down layout for root page {:?}", id); + // To ensure the elements of the DOM tree remain usable (such as the window global), + // don't free the JS context until all interactions with it are finished. + shut_down_layout(&page, exit_type); *self.js_context.borrow_mut() = None; - shut_down_layout(&*page, (*self.js_runtime).ptr, exit_type); return true } // otherwise find just the matching page and exit all sub-pages - match page.find(id) { - Some(ref mut page) => { - shut_down_layout(&*page, (*self.js_runtime).ptr, exit_type); - page.remove(id); - false - } - // TODO(tkuehn): pipeline closing is currently duplicated across - // script and constellation, which can cause this to happen. Constellation - // needs to be smarter about exiting pipelines. - None => false, + if let Some(ref mut child_page) = page.remove(id) { + shut_down_layout(&*child_page, exit_type); } - + return false; } /// 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(&self, pipeline_id: PipelineId, - parent: Option<(PipelineId, SubpageId)>, load_data: LoadData) { - let url = load_data.url.clone(); - debug!("ScriptTask: loading {} on page {:?}", url.serialize(), pipeline_id); - - let borrowed_page = self.page.borrow_mut(); - - let frame_element = parent.and_then(|(parent_id, subpage_id)| { - // In the case a parent id exists but the matching page - // cannot be found, this means the page exists in a different - // script task (due to origin) so it shouldn't be returned. - // TODO: window.parent will continue to return self in that - // case, which is wrong. We should be returning an object that - // denies access to most properties (per - // https://github.com/servo/servo/issues/3939#issuecomment-62287025). - borrowed_page.find(parent_id).and_then(|page| { - let doc = page.frame().as_ref().unwrap().document.root(); - let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); - - doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_ref) - .find(|node| node.subpage_id() == Some(subpage_id)) - .map(ElementCast::from_ref) - .map(Temporary::from_rooted) - }) + fn load(&self, response: LoadResponse, incomplete: InProgressLoad) { + let final_url = response.metadata.final_url.clone(); + debug!("ScriptTask: loading {} on page {:?}", incomplete.url.serialize(), incomplete.pipeline_id); + + // We should either be initializing a root page or loading a child page of an + // existing one. + let root_page_exists = self.page.borrow().is_some(); + assert!(incomplete.subpage_id.is_none() || root_page_exists); + + let frame_element = incomplete.subpage_id.and_then(|(parent_id, subpage_id)| { + let borrowed_page = self.root_page(); + // In the case a parent id exists but the matching page + // cannot be found, this means the page exists in a different + // script task (due to origin) so it shouldn't be returned. + // TODO: window.parent will continue to return self in that + // case, which is wrong. We should be returning an object that + // denies access to most properties (per + // https://github.com/servo/servo/issues/3939#issuecomment-62287025). + borrowed_page.find(parent_id).and_then(|page| { + let doc = page.document().root(); + let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); + + doc.traverse_preorder() + .filter_map(HTMLIFrameElementCast::to_ref) + .find(|node| node.subpage_id() == Some(subpage_id)) + .map(ElementCast::from_ref) + .map(Temporary::from_rooted) + }) }).root(); - let page = borrowed_page.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."); + self.compositor.borrow_mut().set_ready_state(incomplete.pipeline_id, Loading); - // Are we reloading? - let reloading = match *page.url() { - Some((ref loaded, _)) => *loaded == url, - _ => false, - }; - if reloading { - // Pull out the `needs_reflow` flag explicitly because `reflow` can ask for the page's - // URL, and we can't be holding a borrow on that URL (#4402). - let needed_reflow = match &mut *page.mut_url() { - &mut Some((_, ref mut needs_reflow)) => replace(needs_reflow, false), - _ => panic!("can't reload a page with no URL!") - }; - if needed_reflow { - self.force_reflow(&*page); - } - return - } - let last_url = replace(&mut *page.mut_url(), None).map(|(last_url, _)| last_url); - - let is_javascript = url.scheme.as_slice() == "javascript"; - - self.compositor.borrow_mut().set_ready_state(pipeline_id, Loading); - - let (mut parser_input, final_url, last_modified) = if !is_javascript { - // Wait for the LoadResponse so that the parser knows the final URL. - let (input_chan, input_port) = channel(); - self.resource_task.send(ControlMsg::Load(NetLoadData { - url: url, - method: load_data.method, - headers: Headers::new(), - preserved_headers: load_data.headers, - data: load_data.data, - cors: None, - consumer: input_chan, - })).unwrap(); + let cx = self.js_context.borrow(); + let cx = cx.as_ref().unwrap(); - let load_response = input_port.recv().unwrap(); + // Create a new frame tree entry. + let page = Rc::new(Page::new(incomplete.pipeline_id, incomplete.subpage_id.map(|p| p.1), + final_url.clone())); + if !root_page_exists { + // We have a new root frame tree. + *self.page.borrow_mut() = Some(page.clone()); + } else if let Some((parent, _)) = incomplete.subpage_id { + // We have a new child frame. + let parent_page = self.root_page(); + parent_page.find(parent).expect("received load for subpage with missing parent"); + parent_page.children.borrow_mut().push(page.clone()); + } - let last_modified = load_response.metadata.headers.as_ref().and_then(|headers| { - headers.get().map(|&LastModified(ref tm)| tm.clone()) - }); + enum PageToRemove { + Root, + Child(PipelineId), + } + struct AutoPageRemover<'a> { + page: PageToRemove, + script_task: &'a ScriptTask, + neutered: bool, + } + impl<'a> AutoPageRemover<'a> { + fn new(script_task: &'a ScriptTask, page: PageToRemove) -> AutoPageRemover<'a> { + AutoPageRemover { + page: page, + script_task: script_task, + neutered: false, + } + } - let final_url = load_response.metadata.final_url.clone(); + fn neuter(&mut self) { + self.neutered = true; + } + } + #[unsafe_destructor] + impl<'a> Drop for AutoPageRemover<'a> { + fn drop(&mut self) { + if !self.neutered { + match self.page { + PageToRemove::Root => *self.script_task.page.borrow_mut() = None, + PageToRemove::Child(id) => { + let _ = self.script_task.root_page().remove(id); + } + } + } + } + } - (Some(HTMLInput::InputUrl(load_response)), final_url, last_modified) + let page_to_remove = if !root_page_exists { + PageToRemove::Root } else { - let doc_url = last_url.unwrap_or_else(|| { - Url::parse("about:blank").unwrap() - }); - (None, doc_url, None) + PageToRemove::Child(incomplete.pipeline_id) }; + let mut page_remover = AutoPageRemover::new(self, page_to_remove); - // Store the final URL before we start parsing, so that DOM routines - // (e.g. HTMLImageElement::update_image) can resolve relative URLs - // correctly. - { - *page.mut_url() = Some((final_url.clone(), true)); - } - - let cx = self.js_context.borrow(); - let cx = cx.as_ref().unwrap(); // Create the window and document objects. - let window = Window::new(cx.ptr, + let window = Window::new(cx.clone(), page.clone(), self.chan.clone(), self.control_chan.clone(), self.compositor.borrow_mut().dup(), - self.image_cache_task.clone()).root(); + self.image_cache_task.clone(), + self.resource_task.clone(), + self.storage_task.clone(), + self.devtools_chan.clone(), + self.constellation_chan.clone(), + incomplete.layout_chan, + incomplete.pipeline_id, + incomplete.subpage_id.map(|s| s.1), + incomplete.window_size).root(); let document = Document::new(window.r(), Some(final_url.clone()), IsHTMLDocument::HTMLDocument, None, DocumentSource::FromParser).root(); + + window.r().init_browser_context(document.r(), frame_element.r()); + + let last_modified = response.metadata.headers.as_ref().and_then(|headers| { + headers.get().map(|&LastModified(ref tm)| tm.clone()) + }); if let Some(tm) = last_modified { document.r().set_last_modified(dom_last_modified(&tm)); } - window.r().init_browser_context(document.r(), frame_element.r()); + // Create the root frame + page.set_frame(Some(Frame { + document: JS::from_rooted(document.r()), + window: JS::from_rooted(window.r()), + })); - { - // Create the root frame. - let mut frame = page.mut_frame(); - *frame = Some(Frame { - document: JS::from_rooted(document.r()), - window: JS::from_rooted(window.r()), - }); - } - - if is_javascript { - let evalstr = load_data.url.non_relative_scheme_data().unwrap(); + let is_javascript = incomplete.url.scheme.as_slice() == "javascript"; + let parse_input = if is_javascript { + let evalstr = incomplete.url.non_relative_scheme_data().unwrap(); let jsval = window.r().evaluate_js_on_global_with_result(evalstr); let strval = FromJSValConvertible::from_jsval(self.get_cx(), jsval, StringificationBehavior::Empty); - parser_input = Some(HTMLInput::InputString(strval.unwrap_or("".to_owned()))); + HTMLInput::InputString(strval.unwrap_or("".to_owned())) + } else { + HTMLInput::InputUrl(response) }; - parse_html(document.r(), parser_input.unwrap(), &final_url); + parse_html(document.r(), parse_input, &final_url); document.r().set_ready_state(DocumentReadyState::Interactive); - self.compositor.borrow_mut().set_ready_state(pipeline_id, PerformingLayout); + self.compositor.borrow_mut().set_ready_state(incomplete.pipeline_id, PerformingLayout); // Kick off the initial reflow of the page. debug!("kicking off initial reflow of {:?}", final_url); document.r().content_changed(NodeCast::from_ref(document.r()), NodeDamage::OtherNodeDamage); - window.r().flush_layout(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); + window.r().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); - { - // No more reflow required - let mut page_url = page.mut_url(); - *page_url = Some((final_url.clone(), false)); - } + // No more reflow required + page.set_reflow_status(false); // https://html.spec.whatwg.org/multipage/#the-end step 4 let addr: Trusted<Document> = Trusted::new(self.get_cx(), document.r(), self.chan.clone()); @@ -914,7 +1025,7 @@ impl ScriptTask { let handler = Box::new(DocumentProgressHandler::new(addr, DocumentProgressTask::Load)); self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); - *page.fragment_name.borrow_mut() = final_url.fragment.clone(); + window.r().set_fragment_name(final_url.fragment.clone()); let ConstellationChan(ref chan) = self.constellation_chan; chan.send(ConstellationMsg::LoadComplete).unwrap(); @@ -927,11 +1038,13 @@ impl ScriptTask { title: document.r().Title(), url: final_url }; - chan.send(DevtoolsControlMsg::NewGlobal(pipeline_id, + chan.send(DevtoolsControlMsg::NewGlobal(incomplete.pipeline_id, self.devtools_sender.clone(), page_info)).unwrap(); } } + + page_remover.neuter(); } fn scroll_fragment_point(&self, pipeline_id: PipelineId, node: JSRef<Element>) { @@ -948,11 +1061,10 @@ impl ScriptTask { /// Reflows non-incrementally. fn force_reflow(&self, page: &Page) { - page.dirty_all_nodes(); - page.reflow(ReflowGoal::ForDisplay, - self.control_chan.clone(), - &mut **self.compositor.borrow_mut(), - ReflowQueryType::NoQuery); + let document = page.document().root(); + document.r().dirty_all_nodes(); + let window = window_from_node(document.r()).root(); + window.r().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); } /// This is the main entry point for receiving and dispatching DOM events. @@ -978,9 +1090,8 @@ impl ScriptTask { for node in nodes.iter() { let node_to_dirty = node::from_untrusted_node_address(self.js_runtime.ptr, *node).root(); - let page = get_page(&*self.page.borrow(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let page = get_page(&self.root_page(), pipeline_id); + let document = page.document().root(); document.r().content_changed(node_to_dirty.r(), NodeDamage::OtherNodeDamage); } @@ -989,18 +1100,16 @@ impl ScriptTask { } ClickEvent(_button, point) => { - let page = get_page(&*self.page.borrow(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let page = get_page(&self.root_page(), pipeline_id); + let document = page.document().root(); document.r().handle_click_event(self.js_runtime.ptr, _button, point); } MouseDownEvent(..) => {} MouseUpEvent(..) => {} MouseMoveEvent(point) => { - let page = get_page(&*self.page.borrow(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let page = get_page(&self.root_page(), pipeline_id); + let document = page.document().root(); let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut(); if document.r().handle_mouse_move_event(self.js_runtime.ptr, point, mouse_over_targets) { @@ -1009,9 +1118,8 @@ impl ScriptTask { } KeyEvent(key, state, modifiers) => { - let page = get_page(&*self.page.borrow(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let page = get_page(&self.root_page(), pipeline_id); + let document = page.document().root(); document.r().dispatch_key_event( key, state, modifiers, &mut *self.compositor.borrow_mut()); } @@ -1028,8 +1136,9 @@ impl ScriptTask { /// The entry point for content to notify that a fragment url has been requested /// for the given pipeline. fn trigger_fragment(&self, pipeline_id: PipelineId, fragment: String) { - let page = get_page(&*self.page.borrow(), pipeline_id); - match page.find_fragment_node(fragment).root() { + let page = get_page(&self.root_page(), pipeline_id); + let document = page.document().root(); + match document.r().find_fragment_node(fragment).root() { Some(node) => { self.scroll_fragment_point(pipeline_id, node.r()); } @@ -1039,87 +1148,97 @@ impl ScriptTask { fn handle_resize_event(&self, pipeline_id: PipelineId, new_size: WindowSizeData) { - let window = { - let page = get_page(&*self.page.borrow(), pipeline_id); - page.window_size.set(new_size); - - let frame = page.frame(); - if frame.is_some() { - self.force_reflow(&*page); - } - - let fragment_node = - page.fragment_name - .borrow_mut() - .take() - .and_then(|name| page.find_fragment_node(name)) - .root(); - match fragment_node { - Some(node) => self.scroll_fragment_point(pipeline_id, node.r()), - None => {} - } + let page = get_page(&self.root_page(), pipeline_id); + let window = page.window().root(); + window.r().set_window_size(new_size); + self.force_reflow(&*page); + + let document = page.document().root(); + let fragment_node = window.r().steal_fragment_name() + .and_then(|name| document.r().find_fragment_node(name)) + .root(); + match fragment_node { + Some(node) => self.scroll_fragment_point(pipeline_id, node.r()), + None => {} + } - frame.as_ref().map(|frame| Temporary::new(frame.window.clone())) - }; + // http://dev.w3.org/csswg/cssom-view/#resizing-viewports + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-resize + let uievent = UIEvent::new(window.r(), + "resize".to_owned(), false, + false, Some(window.r()), + 0i32).root(); + let event: JSRef<Event> = EventCast::from_ref(uievent.r()); - match window.root() { - Some(window) => { - // http://dev.w3.org/csswg/cssom-view/#resizing-viewports - // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-resize - let uievent = UIEvent::new(window.r(), - "resize".to_owned(), false, - false, Some(window.r()), - 0i32).root(); - let event: JSRef<Event> = EventCast::from_ref(uievent.r()); - - let wintarget: JSRef<EventTarget> = EventTargetCast::from_ref(window.r()); - event.fire(wintarget); - } - None => () - } + let wintarget: JSRef<EventTarget> = EventTargetCast::from_ref(window.r()); + event.fire(wintarget); } fn handle_reflow_event(&self, pipeline_id: PipelineId) { debug!("script got reflow event"); - let page = get_page(&*self.page.borrow(), pipeline_id); - let frame = page.frame(); - if frame.is_some() { - self.force_reflow(&*page); - } + let page = get_page(&self.root_page(), pipeline_id); + self.force_reflow(&*page); + } + + /// Initiate a non-blocking fetch for a specified resource. Stores the InProgressLoad + /// argument until a notification is received that the fetch is complete. + fn start_page_load(&self, incomplete: InProgressLoad, mut load_data: LoadData) { + let id = incomplete.pipeline_id.clone(); + let subpage = incomplete.subpage_id.clone().map(|p| p.1); + + let script_chan = self.chan.clone(); + let resource_task = self.resource_task.clone(); + + spawn_named(format!("fetch for {:?}", load_data.url), move || { + if load_data.url.scheme.as_slice() == "javascript" { + load_data.url = Url::parse("about:blank").unwrap(); + } + + let (input_chan, input_port) = channel(); + resource_task.send(ControlMsg::Load(NetLoadData { + url: load_data.url, + method: load_data.method, + headers: Headers::new(), + preserved_headers: load_data.headers, + data: load_data.data, + cors: None, + consumer: input_chan, + })).unwrap(); + + let load_response = input_port.recv().unwrap(); + script_chan.send(ScriptMsg::PageFetchComplete(id, subpage, load_response)).unwrap(); + }); + + self.incomplete_loads.borrow_mut().push(incomplete); } } /// Shuts down layout for the given page tree. -fn shut_down_layout(page_tree: &Rc<Page>, rt: *mut JSRuntime, exit_type: PipelineExitType) { +fn shut_down_layout(page_tree: &Rc<Page>, exit_type: PipelineExitType) { + let mut channels = vec!(); + for page in page_tree.iter() { // Tell the layout task to begin shutting down, and wait until it // processed this message. let (response_chan, response_port) = channel(); - let LayoutChan(ref chan) = page.layout_chan; + let window = page.window().root(); + let LayoutChan(chan) = window.r().layout_chan(); if chan.send(layout_interface::Msg::PrepareToExit(response_chan)).is_ok() { - response_port.recv().unwrap(); + channels.push(chan); + response_port.recv().unwrap(); } } - // Remove our references to the DOM objects in this page tree. - for page in page_tree.iter() { - *page.mut_frame() = None; - } - - // Drop our references to the JSContext, potentially triggering a GC. + // Drop our references to the JSContext and DOM objects. for page in page_tree.iter() { - *page.mut_js_info() = None; - } - - // Force a GC to make sure that our DOM reflectors are released before we tell - // layout to exit. - unsafe { - JS_GC(rt); + let window = page.window().root(); + window.r().clear_js_context(); + // Sever the connection between the global and the DOM tree + page.set_frame(None); } // Destroy the layout task. If there were node leaks, layout will now crash safely. - for page in page_tree.iter() { - let LayoutChan(ref chan) = page.layout_chan; + for chan in channels.into_iter() { chan.send(layout_interface::Msg::ExitNow(exit_type)).ok(); } } |