From d9f04180a5d9146f4486ede6fabb9da638cccd41 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 19 Feb 2015 10:00:02 -0500 Subject: [PATCH 1/8] Split page load into separate network and parsing stages. Delay Page creation until the load is finished. Make session history traversal simply activate existing pipelines, rather than potentially loading them from the network. --- components/compositing/constellation.rs | 5 +- components/compositing/pipeline.rs | 10 +- components/script/page.rs | 15 +- components/script/script_task.rs | 418 ++++++++++++++---------- components/script_traits/lib.rs | 8 +- 5 files changed, 267 insertions(+), 189 deletions(-) diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index 2d370eca4e5..5ef72e185ec 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -407,8 +407,7 @@ impl Constellation { self.time_profiler_chan.clone(), self.window_size, script_pipeline, - load_data.clone()); - pipe.load(); + load_data); Rc::new(pipe) } @@ -876,7 +875,7 @@ impl Constellation { }; for frame in destination_frame.iter() { - frame.pipeline.borrow().load(); + frame.pipeline.borrow().activate(); frame.pipeline.borrow().thaw(); } self.send_frame_tree_and_grant_paint_permission(destination_frame); diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs index 27ee5eba150..eabf93752b3 100644 --- a/components/compositing/pipeline.rs +++ b/components/compositing/pipeline.rs @@ -88,7 +88,8 @@ impl Pipeline { storage_task.clone(), image_cache_task.clone(), devtools_chan, - window_size); + window_size, + load_data.clone()); ScriptControlChan(script_chan) } Some(spipe) => { @@ -97,6 +98,7 @@ impl Pipeline { new_pipeline_id: id, subpage_id: parent.expect("script_pipeline != None but subpage_id == None").1, layout_chan: ScriptTaskFactory::clone_layout_channel(None::<&mut STF>, &layout_pair), + load_data: load_data.clone(), }; let ScriptControlChan(ref chan) = spipe.script_chan; @@ -160,11 +162,9 @@ impl Pipeline { } } - pub fn load(&self) { + pub fn activate(&self) { let ScriptControlChan(ref chan) = self.script_chan; - chan.send(ConstellationControlMsg::Load(self.id, - self.parent, - self.load_data.clone())).unwrap(); + chan.send(ConstellationControlMsg::Activate(self.id)).unwrap(); } pub fn grant_paint_permission(&self) { diff --git a/components/script/page.rs b/components/script/page.rs index c7ea2e44bec..3800b5b3950 100644 --- a/components/script/page.rs +++ b/components/script/page.rs @@ -68,11 +68,11 @@ pub struct Page { js_info: DOMRefCell>, - /// Cached copy of the most recent url loaded by the script + /// Cached copy of the most recent url loaded by the script, after all redirections. /// TODO(tkuehn): this currently does not follow any particular caching policy /// and simply caches pages forever (!). The bool indicates if reflow is required /// when reloading. - url: DOMRefCell>, + url: DOMRefCell<(Url, bool)>, next_subpage_id: Cell, @@ -140,7 +140,8 @@ impl Page { storage_task: StorageTask, constellation_chan: ConstellationChan, js_context: Rc, - devtools_chan: Option) -> Page { + devtools_chan: Option, + url: Url) -> Page { let js_info = JSPageInfo { dom_static: GlobalStaticData::new(), js_context: js_context, @@ -160,7 +161,7 @@ impl Page { layout_join_port: DOMRefCell::new(None), window_size: Cell::new(window_size), js_info: DOMRefCell::new(Some(js_info)), - url: DOMRefCell::new(None), + url: DOMRefCell::new((url, true)), next_subpage_id: Cell::new(SubpageId(0)), resize_event: Cell::new(None), fragment_name: DOMRefCell::new(None), @@ -292,11 +293,11 @@ impl Page { self.js_info.borrow() } - pub fn url<'a>(&'a self) -> Ref<'a, Option<(Url, bool)>> { + pub fn url<'a>(&'a self) -> Ref<'a, (Url, bool)> { self.url.borrow() } - pub fn mut_url<'a>(&'a self) -> RefMut<'a, Option<(Url, bool)>> { + pub fn mut_url<'a>(&'a self) -> RefMut<'a, (Url, bool)> { self.url.borrow_mut() } @@ -316,7 +317,7 @@ impl Page { } pub fn get_url(&self) -> Url { - self.url().as_ref().unwrap().0.clone() + self.url().0.clone() } // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 51361bd1436..28a2e6437c0 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -49,16 +49,17 @@ 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}; @@ -83,6 +84,32 @@ use time::Tm; thread_local!(pub static STACK_ROOTS: Cell> = Cell::new(None)); +struct InProgressLoad { + pipeline_id: PipelineId, + subpage_id: Option<(PipelineId, SubpageId)>, + window_size: WindowSizeData, + layout_chan: LayoutChan, + clip_rect: Option>, + url: Url, +} + +impl InProgressLoad { + 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 +145,8 @@ pub enum ScriptMsg { RunnableMsg(Box), /// 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, LoadResponse), } /// A cloneable interface for communicating with an event loop. @@ -175,11 +204,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>, + page: DOMRefCell>>, + /// A list of data pertaining to loads that have not yet received a network response + incomplete_loads: DOMRefCell>, /// 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, @@ -274,15 +307,14 @@ impl ScriptTaskFactory for ScriptTask { storage_task: StorageTask, image_cache_task: ImageCacheTask, devtools_chan: Option, - 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, - layout_chan, + let script_task = ScriptTask::new(box compositor as Box, script_port, NonWorkerScriptChan(script_chan), control_chan, @@ -291,9 +323,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 +348,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, - layout_chan: LayoutChan, + pub fn new(compositor: Box, port: Receiver, chan: NonWorkerScriptChan, control_chan: ScriptControlChan, @@ -323,8 +357,7 @@ impl ScriptTask { resource_task: ResourceTask, storage_task: StorageTask, img_cache_task: ImageCacheTask, - devtools_chan: Option, - window_size: WindowSizeData) + devtools_chan: Option) -> ScriptTask { let (js_runtime, js_context) = ScriptTask::new_rt_and_cx(); let wrap_for_same_compartment = wrap_for_same_compartment as @@ -348,19 +381,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 +445,10 @@ impl ScriptTask { (js_runtime, js_context) } + fn root_page(&self) -> Rc { + self.page.borrow().as_ref().unwrap().clone() + } + pub fn get_cx(&self) -> *mut JSContext { (**self.js_context.borrow().as_ref().unwrap()).ptr } @@ -439,17 +471,19 @@ 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(ref page) = page.as_ref() { + 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 => () + } + page.resize_event.set(None); } - page.resize_event.set(None); } } } @@ -502,17 +536,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 +582,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,57 +625,91 @@ 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) { + page.resize_event.set(Some(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) { + let page = self.page.borrow(); + if let Some(page) = page.as_ref() { + if let Some(ref inner_page) = page.find(id) { + if inner_page.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"); + } + 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::>().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 chan = layout_chan.downcast_ref::>().unwrap(); + let layout_chan = LayoutChan(chan.clone()); + let new_load = InProgressLoad::new(new_pipeline_id, Some((old_pipeline_id, subpage_id)), + layout_chan, parent_page.window_size.get(), + 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(); @@ -658,7 +719,7 @@ impl ScriptTask { /// 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(); @@ -668,7 +729,7 @@ impl ScriptTask { /// 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(); @@ -676,10 +737,26 @@ impl ScriptTask { window.r().thaw(); } + 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(); + + // 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 = { + let &mut (_, ref mut needs_reflow) = &mut *page.mut_url(); + replace(needs_reflow, 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."); @@ -700,13 +777,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 => (), + &mut (_, ref mut needs_reflow) => *needs_reflow = true, } } @@ -724,20 +800,29 @@ impl ScriptTask { self.compositor.borrow_mut().close(); } + fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option, + response: LoadResponse) { + 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(); + get_page(&self.root_page(), pipeline_id).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(); + let page = self.root_page(); if page.id == id { debug!("shutting down layout for root page {:?}", id); *self.js_context.borrow_mut() = None; - shut_down_layout(&*page, (*self.js_runtime).ptr, exit_type); + shut_down_layout(&page, (*self.js_runtime).ptr, exit_type); return true } @@ -758,98 +843,56 @@ impl ScriptTask { /// The entry point to document loading. Defines bindings, sets up the window and document /// objects, parses HTML and CSS, and kicks off initial layout. - fn load(&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); + 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); - let borrowed_page = self.page.borrow_mut(); + let root_page_exists = self.page.borrow().is_none(); + assert!(incomplete.subpage_id.is_none() || root_page_exists); - 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 = NodeCast::from_ref(doc.r()); + 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.frame().as_ref().unwrap().document.root(); + let doc: JSRef = 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) - }) + 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 load_response = input_port.recv().unwrap(); - - let last_modified = load_response.metadata.headers.as_ref().and_then(|headers| { - headers.get().map(|&LastModified(ref tm)| tm.clone()) - }); - - let final_url = load_response.metadata.final_url.clone(); - - (Some(HTMLInput::InputUrl(load_response)), final_url, last_modified) - } else { - let doc_url = last_url.unwrap_or_else(|| { - Url::parse("about:blank").unwrap() - }); - (None, doc_url, None) - }; - - // 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 last_modified = response.metadata.headers.as_ref().and_then(|headers| { + headers.get().map(|&LastModified(ref tm)| tm.clone()) + }); let cx = self.js_context.borrow(); let cx = cx.as_ref().unwrap(); + + let page = Rc::new(Page::new(incomplete.pipeline_id, incomplete.subpage_id.map(|p| p.1), + incomplete.layout_chan, incomplete.window_size, + self.resource_task.clone(), self.storage_task.clone(), + self.constellation_chan.clone(), cx.clone(), + self.devtools_chan.clone(), final_url.clone())); + if root_page_exists { + *self.page.borrow_mut() = Some(page.clone()); + } else if let Some((parent, _)) = incomplete.subpage_id { + 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()); + } + // Create the window and document objects. let window = Window::new(cx.ptr, page.clone(), @@ -876,18 +919,21 @@ impl ScriptTask { }); } - 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); @@ -898,7 +944,7 @@ impl ScriptTask { { // No more reflow required let mut page_url = page.mut_url(); - *page_url = Some((final_url.clone(), false)); + (*page_url).1 = false; } // https://html.spec.whatwg.org/multipage/#the-end step 4 @@ -927,7 +973,7 @@ 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(); } @@ -978,7 +1024,7 @@ 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 page = get_page(&self.root_page(), pipeline_id); let frame = page.frame(); let document = frame.as_ref().unwrap().document.root(); document.r().content_changed(node_to_dirty.r(), @@ -989,7 +1035,7 @@ impl ScriptTask { } ClickEvent(_button, point) => { - let page = get_page(&*self.page.borrow(), pipeline_id); + let page = get_page(&self.root_page(), pipeline_id); let frame = page.frame(); let document = frame.as_ref().unwrap().document.root(); document.r().handle_click_event(self.js_runtime.ptr, _button, point); @@ -998,7 +1044,7 @@ impl ScriptTask { MouseDownEvent(..) => {} MouseUpEvent(..) => {} MouseMoveEvent(point) => { - let page = get_page(&*self.page.borrow(), pipeline_id); + let page = get_page(&self.root_page(), pipeline_id); let frame = page.frame(); let document = frame.as_ref().unwrap().document.root(); let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut(); @@ -1009,7 +1055,7 @@ impl ScriptTask { } KeyEvent(key, state, modifiers) => { - let page = get_page(&*self.page.borrow(), pipeline_id); + let page = get_page(&self.root_page(), pipeline_id); let frame = page.frame(); let document = frame.as_ref().unwrap().document.root(); document.r().dispatch_key_event( @@ -1028,7 +1074,7 @@ 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); + let page = get_page(&self.root_page(), pipeline_id); match page.find_fragment_node(fragment).root() { Some(node) => { self.scroll_fragment_point(pipeline_id, node.r()); @@ -1040,7 +1086,7 @@ impl ScriptTask { fn handle_resize_event(&self, pipeline_id: PipelineId, new_size: WindowSizeData) { let window = { - let page = get_page(&*self.page.borrow(), pipeline_id); + let page = get_page(&self.root_page(), pipeline_id); page.window_size.set(new_size); let frame = page.frame(); @@ -1081,12 +1127,42 @@ impl ScriptTask { fn handle_reflow_event(&self, pipeline_id: PipelineId) { debug!("script got reflow event"); - let page = get_page(&*self.page.borrow(), pipeline_id); + let page = get_page(&self.root_page(), pipeline_id); let frame = page.frame(); if frame.is_some() { self.force_reflow(&*page); } } + + 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. diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 7df28d29108..deb5ba30770 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -48,12 +48,13 @@ pub struct NewLayoutInfo { pub new_pipeline_id: PipelineId, pub subpage_id: SubpageId, pub layout_chan: Box, // opaque reference to a LayoutChannel + pub load_data: LoadData, } /// Messages sent from the constellation to the script task pub enum ConstellationControlMsg { - /// Loads a new URL on the specified pipeline. - Load(PipelineId, Option<(PipelineId, SubpageId)>, LoadData), + /// Reactivate an existing pipeline. + Activate(PipelineId), /// Gives a channel and ID to a layout task, as well as the ID of that layout's parent AttachLayout(NewLayoutInfo), /// Window resized. Sends a DOM event eventually, but first we combine events. @@ -111,7 +112,8 @@ pub trait ScriptTaskFactory { storage_task: StorageTask, image_cache_task: ImageCacheTask, devtools_chan: Option, - window_size: WindowSizeData) + window_size: WindowSizeData, + load_data: LoadData) where C: ScriptListener + Send; fn create_layout_channel(_phantom: Option<&mut Self>) -> OpaqueScriptLayoutChannel; fn clone_layout_channel(_phantom: Option<&mut Self>, pair: &OpaqueScriptLayoutChannel) From e2c4f5ed6726ed7434197180b301f74a967d3ffc Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 19 Feb 2015 13:08:50 -0500 Subject: [PATCH 2/8] Move everything unrelated to the frame tree out of Page and into Document or Window. Reduce the API surface of Page to a bare minimum to allow for easier future removal. --- components/script/devtools.rs | 17 +- .../dom/bindings/codegen/CodegenRust.py | 1 - components/script/dom/bindings/global.rs | 4 +- components/script/dom/bindings/utils.rs | 2 + components/script/dom/browsercontext.rs | 6 +- components/script/dom/console.rs | 5 +- components/script/dom/cssstyledeclaration.rs | 8 +- components/script/dom/document.rs | 153 ++++--- components/script/dom/domparser.rs | 2 +- components/script/dom/htmlbodyelement.rs | 1 + components/script/dom/htmlelement.rs | 1 + components/script/dom/htmlformelement.rs | 2 +- components/script/dom/htmliframeelement.rs | 36 +- components/script/dom/htmlimageelement.rs | 1 + components/script/dom/htmllinkelement.rs | 5 +- components/script/dom/htmlscriptelement.rs | 7 +- components/script/dom/htmlstyleelement.rs | 5 +- components/script/dom/location.rs | 34 +- components/script/dom/macros.rs | 1 + components/script/dom/node.rs | 6 +- components/script/dom/window.rs | 421 ++++++++++++++++-- components/script/page.rs | 389 +--------------- components/script/script_task.rs | 229 ++++------ 23 files changed, 664 insertions(+), 672 deletions(-) diff --git a/components/script/devtools.rs b/components/script/devtools.rs index 5333b9a4cee..051002c7478 100644 --- a/components/script/devtools.rs +++ b/components/script/devtools.rs @@ -11,7 +11,7 @@ use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::DOMRectBinding::{DOMRectMethods}; use dom::bindings::codegen::Bindings::ElementBinding::{ElementMethods}; use dom::node::{Node, NodeHelpers}; -use dom::window::{ScriptHelpers}; +use dom::window::{WindowHelpers, ScriptHelpers}; use dom::element::Element; use dom::document::DocumentHelpers; use page::Page; @@ -24,8 +24,7 @@ use std::rc::Rc; pub fn handle_evaluate_js(page: &Rc, pipeline: PipelineId, eval: String, reply: Sender){ let page = get_page(&*page, pipeline); - let frame = page.frame(); - let window = frame.as_ref().unwrap().window.root(); + let window = page.window().root(); let cx = window.r().get_cx(); let rval = window.r().evaluate_js_on_global_with_result(eval.as_slice()); @@ -49,8 +48,7 @@ pub fn handle_evaluate_js(page: &Rc, pipeline: PipelineId, eval: String, r pub fn handle_get_root_node(page: &Rc, pipeline: PipelineId, reply: Sender) { let page = get_page(&*page, pipeline); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let document = page.document().root(); let node: JSRef = NodeCast::from_ref(document.r()); reply.send(node.summarize()).unwrap(); @@ -58,8 +56,7 @@ pub fn handle_get_root_node(page: &Rc, pipeline: PipelineId, reply: Sender pub fn handle_get_document_element(page: &Rc, pipeline: PipelineId, reply: Sender) { let page = get_page(&*page, pipeline); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let document = page.document().root(); let document_element = document.r().GetDocumentElement().root().unwrap(); let node: JSRef = NodeCast::from_ref(document_element.r()); @@ -68,8 +65,7 @@ pub fn handle_get_document_element(page: &Rc, pipeline: PipelineId, reply: fn find_node_by_unique_id(page: &Rc, pipeline: PipelineId, node_id: String) -> Temporary { let page = get_page(&*page, pipeline); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let document = page.document().root(); let node: JSRef = NodeCast::from_ref(document.r()); for candidate in node.traverse_preorder() { @@ -110,5 +106,6 @@ pub fn handle_modify_attribute(page: &Rc, pipeline: PipelineId, node_id: S pub fn handle_wants_live_notifications(page: &Rc, pipeline_id: PipelineId, send_notifications: bool) { let page = get_page(&*page, pipeline_id); - page.devtools_wants_updates.set(send_notifications); + let window = page.window().root(); + window.r().set_devtools_wants_updates(send_notifications); } diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 203bf60f340..9274c6cafb7 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -4643,7 +4643,6 @@ class CGBindingRoot(CGThing): 'dom::bindings::proxyhandler::{fill_property_descriptor, get_expando_object}', 'dom::bindings::proxyhandler::{get_property_descriptor}', 'dom::bindings::str::ByteString', - 'page::JSPageInfo', 'libc', 'util::str::DOMString', 'std::borrow::ToOwned', diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index dc42d0da6e0..3d17778c6b1 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -11,7 +11,7 @@ use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::js::{JS, JSRef, Root, Unrooted}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers}; -use dom::window; +use dom::window::{self, WindowHelpers}; use script_task::ScriptChan; use net::resource_task::ResourceTask; @@ -84,7 +84,7 @@ impl<'a> GlobalRef<'a> { /// Get the `ResourceTask` for this global scope. pub fn resource_task(&self) -> ResourceTask { match *self { - GlobalRef::Window(ref window) => window.page().resource_task.clone(), + GlobalRef::Window(ref window) => window.resource_task().clone(), GlobalRef::Worker(ref worker) => worker.resource_task().clone(), } } diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 56c9b58288d..706c228c0b2 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -45,6 +45,8 @@ use js::JSFUN_CONSTRUCTOR; use js; /// Proxy handler for a WindowProxy. +#[allow(raw_pointer_derive)] +#[derive(Copy)] pub struct WindowProxyHandler(pub *const libc::c_void); #[allow(raw_pointer_derive)] diff --git a/components/script/dom/browsercontext.rs b/components/script/dom/browsercontext.rs index e21533bbfe0..a3af0a8b02a 100644 --- a/components/script/dom/browsercontext.rs +++ b/components/script/dom/browsercontext.rs @@ -71,14 +71,12 @@ impl BrowserContext { fn create_window_proxy(&mut self) { let win = self.active_window().root(); let win = win.r(); - let page = win.page(); - let js_info = page.js_info(); - let WindowProxyHandler(handler) = js_info.as_ref().unwrap().dom_static.windowproxy_handler; + let WindowProxyHandler(handler) = win.windowproxy_handler(); assert!(!handler.is_null()); let parent = win.reflector().get_jsobject(); - let cx = js_info.as_ref().unwrap().js_context.ptr; + let cx = win.get_cx(); let wrapper = with_compartment(cx, parent, || unsafe { WrapperNew(cx, parent, handler) }); diff --git a/components/script/dom/console.rs b/components/script/dom/console.rs index 4dec1c3bd45..6308556ef2e 100644 --- a/components/script/dom/console.rs +++ b/components/script/dom/console.rs @@ -7,6 +7,7 @@ use dom::bindings::codegen::Bindings::ConsoleBinding::ConsoleMethods; use dom::bindings::global::{GlobalRef, GlobalField}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflector, reflect_dom_object}; +use dom::window::WindowHelpers; use devtools_traits::{DevtoolsControlMsg, ConsoleMessage}; use util::str::DOMString; @@ -66,8 +67,8 @@ fn propagate_console_msg(console: &JSRef, console_message: ConsoleMessa let global = console.global.root(); match global.r() { GlobalRef::Window(window_ref) => { - let pipelineId = window_ref.page().id; - console.global.root().r().as_window().page().devtools_chan.as_ref().map(|chan| { + let pipelineId = window_ref.pipeline(); + console.global.root().r().as_window().devtools_chan().as_ref().map(|chan| { chan.send(DevtoolsControlMsg::SendConsoleMessage( pipelineId, console_message.clone())).unwrap(); }); diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs index 2337e6c061a..e1be98f99af 100644 --- a/components/script/dom/cssstyledeclaration.rs +++ b/components/script/dom/cssstyledeclaration.rs @@ -14,7 +14,7 @@ use dom::document::DocumentHelpers; use dom::element::{Element, ElementHelpers, StylePriority}; use dom::htmlelement::HTMLElement; use dom::node::{window_from_node, document_from_node, NodeDamage, Node}; -use dom::window::Window; +use dom::window::{Window, WindowHelpers}; use util::str::DOMString; use string_cache::Atom; use style::properties::{is_supported_property, longhands_from_shorthand, parse_style_attribute}; @@ -222,9 +222,8 @@ impl<'a> CSSStyleDeclarationMethods for JSRef<'a, CSSStyleDeclaration> { let owner = self.owner.root(); let window = window_from_node(owner.r()).root(); let window = window.r(); - let page = window.page(); let decl_block = parse_style_attribute(synthesized_declaration.as_slice(), - &page.get_url()); + &window.get_url()); // Step 7 if decl_block.normal.len() == 0 { @@ -271,9 +270,8 @@ impl<'a> CSSStyleDeclarationMethods for JSRef<'a, CSSStyleDeclaration> { let owner = self.owner.root(); let window = window_from_node(owner.r()).root(); let window = window.r(); - let page = window.page(); let decl_block = parse_style_attribute(property.as_slice(), - &page.get_url()); + &window.get_url()); let element: JSRef = ElementCast::from_ref(owner.r()); // Step 5 diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 6e5413ebdc8..1d611a6a643 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -11,7 +11,6 @@ use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::codegen::InheritTypes::{DocumentDerived, EventCast, HTMLElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLHeadElementCast, TextCast, ElementCast}; use dom::bindings::codegen::InheritTypes::{DocumentTypeCast, HTMLHtmlElementCast, NodeCast}; @@ -59,12 +58,14 @@ use dom::treewalker::TreeWalker; use dom::uievent::UIEvent; use dom::window::{Window, WindowHelpers}; +use layout_interface::{HitTestResponse, MouseOverResponse}; use msg::compositor_msg::ScriptListener; use msg::constellation_msg::{Key, KeyState, KeyModifiers}; use msg::constellation_msg::{SUPER, ALT, SHIFT, CONTROL}; use net::resource_task::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl}; use net::cookie_storage::CookieSource::NonHTTP; use script_task::Runnable; +use script_traits::UntrustedNodeAddress; use util::namespace; use util::str::{DOMString, split_html_space_chars}; use layout_interface::{ReflowGoal, ReflowQueryType}; @@ -201,6 +202,8 @@ pub trait DocumentHelpers<'a> { fn register_named_element(self, element: JSRef, id: Atom); fn load_anchor_href(self, href: DOMString); fn find_fragment_node(self, fragid: DOMString) -> Option>; + fn hit_test(self, point: &Point2D) -> Option; + fn get_nodes_under_mouse(self, point: &Point2D) -> Vec; fn set_ready_state(self, state: DocumentReadyState); fn get_focused_element(self) -> Option>; fn begin_focus_transaction(self); @@ -264,7 +267,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { Quirks => { let window = self.window.root(); let window = window.r(); - let LayoutChan(ref layout_chan) = window.page().layout_chan; + let LayoutChan(ref layout_chan) = window.layout_chan(); layout_chan.send(Msg::SetQuirksMode).unwrap(); } NoQuirks | LimitedQuirks => {} @@ -377,6 +380,44 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { }) } + fn hit_test(self, point: &Point2D) -> Option { + let root = self.GetDocumentElement().root(); + let root = match root.r() { + Some(root) => root, + None => return None, + }; + let root: JSRef = NodeCast::from_ref(root); + let win = self.window.root(); + let address = match win.r().layout().hit_test(root.to_trusted_node_address(), *point) { + Ok(HitTestResponse(node_address)) => { + Some(node_address) + } + Err(()) => { + debug!("layout query error"); + None + } + }; + address + } + + fn get_nodes_under_mouse(self, point: &Point2D) -> Vec { + let root = self.GetDocumentElement().root(); + let root = match root.r() { + Some(root) => root, + None => return vec!(), + }; + let root: JSRef = NodeCast::from_ref(root); + let win = self.window.root(); + match win.r().layout().mouse_over(root.to_trusted_node_address(), *point) { + Ok(MouseOverResponse(node_address)) => { + node_address + } + Err(()) => { + vec!() + } + } + } + // https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness fn set_ready_state(self, state: DocumentReadyState) { self.ready_state.set(state); @@ -416,7 +457,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { /// Sends this document's title to the compositor. fn send_title_to_compositor(self) { let window = self.window().root(); - window.r().page().send_title_to_compositor(); + window.r().compositor().set_title(window.r().pipeline(), Some(self.Title())); } fn dirty_all_nodes(self) { @@ -428,10 +469,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { fn handle_click_event(self, js_runtime: *mut JSRuntime, _button: uint, point: Point2D) { debug!("ClickEvent: clicked at {:?}", point); - let window = self.window.root(); - let window = window.r(); - let page = window.page(); - let node = match page.hit_test(&point) { + let node = match self.hit_test(&point) { Some(node_address) => { debug!("node address is {:?}", node_address.0); node::from_untrusted_node_address(js_runtime, node_address) @@ -460,48 +498,40 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { return; } - match *page.frame() { - Some(ref frame) => { - let window = frame.window.root(); - let doc = window.r().Document().root(); - doc.r().begin_focus_transaction(); + self.begin_focus_transaction(); - // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-click - let x = point.x as i32; - let y = point.y as i32; - let event = MouseEvent::new(window.r(), - "click".to_owned(), - true, - true, - Some(window.r()), - 0i32, - x, y, x, y, - false, false, false, false, - 0i16, - None).root(); - let event: JSRef = EventCast::from_ref(event.r()); + let window = self.window.root(); - // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#trusted-events - event.set_trusted(true); - // https://html.spec.whatwg.org/multipage/interaction.html#run-authentic-click-activation-steps - el.authentic_click_activation(event); + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-click + let x = point.x as i32; + let y = point.y as i32; + let event = MouseEvent::new(window.r(), + "click".to_owned(), + true, + true, + Some(window.r()), + 0i32, + x, y, x, y, + false, false, false, false, + 0i16, + None).root(); + let event: JSRef = EventCast::from_ref(event.r()); - doc.r().commit_focus_transaction(); - window.r().flush_layout(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); - } - None => {} - } + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#trusted-events + event.set_trusted(true); + // https://html.spec.whatwg.org/multipage/interaction.html#run-authentic-click-activation-steps + el.authentic_click_activation(event); + + self.commit_focus_transaction(); + window.r().flush_layout(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); } /// Return need force reflow or not fn handle_mouse_move_event(self, js_runtime: *mut JSRuntime, point: Point2D, prev_mouse_over_targets: &mut Vec>) -> bool { - let window = self.window.root(); - let window = window.r(); - let page = window.page(); let mut needs_reflow = false; // Build a list of elements that are currently under the mouse. - let mouse_over_addresses = page.get_nodes_under_mouse(&point); + let mouse_over_addresses = self.get_nodes_under_mouse(&point); let mouse_over_targets: Vec> = mouse_over_addresses.iter() .filter_map(|node_address| { let node = node::from_untrusted_node_address(js_runtime, *node_address); @@ -534,27 +564,24 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { let top_most_node = node::from_untrusted_node_address(js_runtime, mouse_over_addresses[0]).root(); - if let Some(ref frame) = *page.frame() { - let window = frame.window.root(); + let x = point.x.to_i32().unwrap_or(0); + let y = point.y.to_i32().unwrap_or(0); - let x = point.x.to_i32().unwrap_or(0); - let y = point.y.to_i32().unwrap_or(0); + let window = self.window.root(); + let mouse_event = MouseEvent::new(window.r(), + "mousemove".to_owned(), + true, + true, + Some(window.r()), + 0i32, + x, y, x, y, + false, false, false, false, + 0i16, + None).root(); - let mouse_event = MouseEvent::new(window.r(), - "mousemove".to_owned(), - true, - true, - Some(window.r()), - 0i32, - x, y, x, y, - false, false, false, false, - 0i16, - None).root(); - - let event: JSRef = EventCast::from_ref(mouse_event.r()); - let target: JSRef = EventTargetCast::from_ref(top_most_node.r()); - event.fire(target); - } + let event: JSRef = EventCast::from_ref(mouse_event.r()); + let target: JSRef = EventTargetCast::from_ref(top_most_node.r()); + event.fire(target); } // Store the current mouse over targets for next frame @@ -1259,7 +1286,7 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { fn Location(self) -> Temporary { let window = self.window.root(); let window = window.r(); - self.location.or_init(|| Location::new(window, window.page_clone())) + self.location.or_init(|| Location::new(window)) } // http://dom.spec.whatwg.org/#dom-parentnode-children @@ -1298,10 +1325,8 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { return Err(Security); } let window = self.window.root(); - let window = window.r(); - let page = window.page(); let (tx, rx) = channel(); - let _ = page.resource_task.send(GetCookiesForUrl(url, tx, NonHTTP)); + let _ = window.r().resource_task().send(GetCookiesForUrl(url, tx, NonHTTP)); let cookies = rx.recv().unwrap(); Ok(cookies.unwrap_or("".to_owned())) } @@ -1314,9 +1339,7 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { return Err(Security); } let window = self.window.root(); - let window = window.r(); - let page = window.page(); - let _ = page.resource_task.send(SetCookiesForUrl(url, cookie, NonHTTP)); + let _ = window.r().resource_task().send(SetCookiesForUrl(url, cookie, NonHTTP)); Ok(()) } diff --git a/components/script/dom/domparser.rs b/components/script/dom/domparser.rs index 91d5d96a615..88429277f19 100644 --- a/components/script/dom/domparser.rs +++ b/components/script/dom/domparser.rs @@ -12,7 +12,7 @@ use dom::bindings::js::{JS, JSRef, Temporary}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::document::{Document, DocumentHelpers, IsHTMLDocument}; use dom::document::DocumentSource; -use dom::window::Window; +use dom::window::{Window, WindowHelpers}; use parse::html::{HTMLInput, parse_html}; use util::str::DOMString; diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs index e609169ff05..eaf683ce3bd 100644 --- a/components/script/dom/htmlbodyelement.rs +++ b/components/script/dom/htmlbodyelement.rs @@ -16,6 +16,7 @@ use dom::eventtarget::{EventTarget, EventTargetTypeId, EventTargetHelpers}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::node::{Node, NodeTypeId, window_from_node}; use dom::virtualmethods::VirtualMethods; +use dom::window::WindowHelpers; use cssparser::RGBA; use util::str::{self, DOMString}; diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index dd5cd1cb7e0..f3528185211 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -26,6 +26,7 @@ use dom::htmlmediaelement::HTMLMediaElementTypeId; use dom::htmltablecellelement::HTMLTableCellElementTypeId; use dom::node::{Node, NodeTypeId, window_from_node}; use dom::virtualmethods::VirtualMethods; +use dom::window::WindowHelpers; use util::str::DOMString; diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index ddc8d42b661..9c2148334e2 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -215,7 +215,7 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { } // This is wrong. https://html.spec.whatwg.org/multipage/forms.html#planned-navigation - win.r().script_chan().send(ScriptMsg::TriggerLoad(win.r().page().id, load_data)).unwrap(); + win.r().script_chan().send(ScriptMsg::TriggerLoad(win.r().pipeline(), load_data)).unwrap(); } fn get_form_dataset<'b>(self, submitter: Option>) -> Vec { diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index b4f9b577b7b..3afcb2f4af2 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -20,8 +20,8 @@ use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::node::{Node, NodeHelpers, NodeTypeId, window_from_node}; use dom::urlhelper::UrlHelper; use dom::virtualmethods::VirtualMethods; -use dom::window::Window; -use page::{IterablePage, Page}; +use dom::window::{Window, WindowHelpers}; +use page::IterablePage; use msg::constellation_msg::{PipelineId, SubpageId, ConstellationChan}; use msg::constellation_msg::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed}; @@ -62,7 +62,7 @@ pub trait HTMLIFrameElementHelpers { fn get_url(self) -> Option; /// http://www.whatwg.org/html/#process-the-iframe-attributes fn process_the_iframe_attributes(self); - fn generate_new_subpage_id(self, page: &Page) -> (SubpageId, Option); + fn generate_new_subpage_id(self) -> (SubpageId, Option); } impl<'a> HTMLIFrameElementHelpers for JSRef<'a, HTMLIFrameElement> { @@ -78,15 +78,16 @@ impl<'a> HTMLIFrameElementHelpers for JSRef<'a, HTMLIFrameElement> { None } else { let window = window_from_node(self).root(); - UrlParser::new().base_url(&window.r().page().get_url()) + UrlParser::new().base_url(&window.r().get_url()) .parse(url.as_slice()).ok() } }) } - fn generate_new_subpage_id(self, page: &Page) -> (SubpageId, Option) { + fn generate_new_subpage_id(self) -> (SubpageId, Option) { let old_subpage_id = self.subpage_id.get(); - let subpage_id = page.get_next_subpage_id(); + let win = window_from_node(self).root(); + let subpage_id = win.r().get_next_subpage_id(); self.subpage_id.set(Some(subpage_id)); (subpage_id, old_subpage_id) } @@ -105,14 +106,13 @@ impl<'a> HTMLIFrameElementHelpers for JSRef<'a, HTMLIFrameElement> { let window = window_from_node(self).root(); let window = window.r(); - let page = window.page(); - let (new_subpage_id, old_subpage_id) = self.generate_new_subpage_id(page); + let (new_subpage_id, old_subpage_id) = self.generate_new_subpage_id(); - self.containing_page_pipeline_id.set(Some(page.id)); + self.containing_page_pipeline_id.set(Some(window.pipeline())); - let ConstellationChan(ref chan) = page.constellation_chan; + let ConstellationChan(ref chan) = window.constellation_chan(); chan.send(ConstellationMsg::ScriptLoadedURLInIFrame(url, - page.id, + window.pipeline(), new_subpage_id, old_subpage_id, sandboxed)).unwrap(); @@ -172,14 +172,10 @@ impl<'a> HTMLIFrameElementMethods for JSRef<'a, HTMLIFrameElement> { let window = window_from_node(self).root(); let window = window.r(); let children = window.page().children.borrow(); - let child = children.iter().find(|child| { - child.subpage_id.unwrap() == subpage_id - }); - child.and_then(|page| { - page.frame.borrow().as_ref().map(|frame| { - Temporary::new(frame.window.clone()) - }) - }) + children.iter().find(|child| { + let window = child.window().root(); + window.r().subpage() == Some(subpage_id) + }).map(|page| page.window()) }) } @@ -189,7 +185,7 @@ impl<'a> HTMLIFrameElementMethods for JSRef<'a, HTMLIFrameElement> { Some(self_url) => self_url, None => return None, }; - let win_url = window_from_node(self).root().r().page().get_url(); + let win_url = window_from_node(self).root().r().get_url(); if UrlHelper::SameOrigin(&self_url, &win_url) { Some(window.r().Document()) diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 2f8d09bb759..6b2f0b3dc2f 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -17,6 +17,7 @@ use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::node::{Node, NodeTypeId, NodeHelpers, NodeDamage, window_from_node}; use dom::virtualmethods::VirtualMethods; +use dom::window::WindowHelpers; use net::image_cache_task; use util::geometry::to_px; use util::str::DOMString; diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 4f8f2aa14b8..ab0c45a22d5 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -17,6 +17,7 @@ use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::node::{Node, NodeHelpers, NodeTypeId, window_from_node}; use dom::virtualmethods::VirtualMethods; +use dom::window::WindowHelpers; use layout_interface::{LayoutChan, Msg}; use util::str::{DOMString, HTML_SPACE_CHARACTERS}; @@ -130,9 +131,9 @@ impl<'a> PrivateHTMLLinkElementHelpers for JSRef<'a, HTMLLinkElement> { fn handle_stylesheet_url(self, href: &str) { let window = window_from_node(self).root(); let window = window.r(); - match UrlParser::new().base_url(&window.page().get_url()).parse(href) { + match UrlParser::new().base_url(&window.get_url()).parse(href) { Ok(url) => { - let LayoutChan(ref layout_chan) = window.page().layout_chan; + let LayoutChan(ref layout_chan) = window.layout_chan(); layout_chan.send(Msg::LoadStylesheet(url)).unwrap(); } Err(e) => debug!("Parsing url {} failed: {}", href, e) diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 853d6709fcb..e9809a6e2cb 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -25,7 +25,7 @@ use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node, CloneChildrenFlag}; use dom::virtualmethods::VirtualMethods; -use dom::window::ScriptHelpers; +use dom::window::{WindowHelpers, ScriptHelpers}; use script_task::{ScriptMsg, Runnable}; use encoding::all::UTF_8; @@ -214,8 +214,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> { // Step 14. let window = window_from_node(self).root(); let window = window.r(); - let page = window.page(); - let base_url = page.get_url(); + let base_url = window.get_url(); let load = match element.get_attribute(ns!(""), &atom!("src")).root() { // Step 14. @@ -243,7 +242,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> { // state of the element's `crossorigin` content attribute, the origin being // the origin of the script element's node document, and the default origin // behaviour set to taint. - ScriptOrigin::External(load_whole_resource(&page.resource_task, url)) + ScriptOrigin::External(load_whole_resource(&window.resource_task(), url)) } } }, diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 6fc3e1a2039..945831a35b1 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -12,6 +12,7 @@ use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::node::{Node, NodeHelpers, NodeTypeId, window_from_node}; use dom::virtualmethods::VirtualMethods; +use dom::window::WindowHelpers; use layout_interface::{LayoutChan, Msg}; use util::str::DOMString; use style::stylesheets::{Origin, Stylesheet}; @@ -52,11 +53,11 @@ impl<'a> StyleElementHelpers for JSRef<'a, HTMLStyleElement> { let win = window_from_node(node).root(); let win = win.r(); - let url = win.page().get_url(); + let url = win.get_url(); let data = node.GetTextContent().expect("Element.textContent must be a string"); let sheet = Stylesheet::from_str(data.as_slice(), url, Origin::Author); - let LayoutChan(ref layout_chan) = win.page().layout_chan; + let LayoutChan(ref layout_chan) = win.layout_chan(); layout_chan.send(Msg::AddStylesheet(sheet)).unwrap(); } } diff --git a/components/script/dom/location.rs b/components/script/dom/location.rs index 614b623e5b9..13cc1a05c65 100644 --- a/components/script/dom/location.rs +++ b/components/script/dom/location.rs @@ -5,33 +5,31 @@ use dom::bindings::codegen::Bindings::LocationBinding; use dom::bindings::codegen::Bindings::LocationBinding::LocationMethods; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::js::{JS, JSRef, Temporary}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::urlhelper::UrlHelper; use dom::window::Window; use dom::window::WindowHelpers; -use page::Page; use util::str::DOMString; - -use std::rc::Rc; +use url::Url; #[dom_struct] pub struct Location { reflector_: Reflector, - page: Rc, + window: JS, } impl Location { - fn new_inherited(page: Rc) -> Location { + fn new_inherited(window: JSRef) -> Location { Location { reflector_: Reflector::new(), - page: page + window: JS::from_rooted(window) } } - pub fn new(window: JSRef, page: Rc) -> Temporary { - reflect_dom_object(box Location::new_inherited(page), + pub fn new(window: JSRef) -> Temporary { + reflect_dom_object(box Location::new_inherited(window), GlobalRef::Window(window), LocationBinding::Wrap) } @@ -40,11 +38,11 @@ impl Location { impl<'a> LocationMethods for JSRef<'a, Location> { // https://html.spec.whatwg.org/multipage/browsers.html#dom-location-assign fn Assign(self, url: DOMString) { - self.page.frame().as_ref().unwrap().window.root().r().load_url(url); + self.window.root().r().load_url(url); } fn Href(self) -> DOMString { - UrlHelper::Href(&self.page.get_url()) + UrlHelper::Href(&self.get_url()) } fn Stringify(self) -> DOMString { @@ -52,11 +50,21 @@ impl<'a> LocationMethods for JSRef<'a, Location> { } fn Search(self) -> DOMString { - UrlHelper::Search(&self.page.get_url()) + UrlHelper::Search(&self.get_url()) } fn Hash(self) -> DOMString { - UrlHelper::Hash(&self.page.get_url()) + UrlHelper::Hash(&self.get_url()) } } +trait PrivateLocationHelpers { + fn get_url(self) -> Url; +} + +impl<'a> PrivateLocationHelpers for JSRef<'a, Location> { + fn get_url(self) -> Url { + let window = self.window.root(); + window.r().get_url() + } +} diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index d00aa9961b1..b1cba9ccd3e 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -80,6 +80,7 @@ macro_rules! make_url_or_base_getter( fn $attr(self) -> DOMString { use dom::element::{Element, AttributeHandlers}; use dom::bindings::codegen::InheritTypes::ElementCast; + use dom::window::WindowHelpers; #[allow(unused_imports)] use std::ascii::AsciiExt; let element: JSRef = ElementCast::from_ref(self); diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index a7b5e5484d6..7526c2490d2 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -41,7 +41,7 @@ use dom::nodelist::NodeList; use dom::processinginstruction::ProcessingInstruction; use dom::text::Text; use dom::virtualmethods::{VirtualMethods, vtable_for}; -use dom::window::Window; +use dom::window::{Window, WindowHelpers}; use geom::rect::Rect; use layout_interface::{LayoutChan, Msg}; use devtools_traits::NodeInfo; @@ -737,11 +737,11 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { } fn get_bounding_content_box(self) -> Rect { - window_from_node(self).root().r().page().content_box_query(self.to_trusted_node_address()) + window_from_node(self).root().r().content_box_query(self.to_trusted_node_address()) } fn get_content_boxes(self) -> Vec> { - window_from_node(self).root().r().page().content_boxes_query(self.to_trusted_node_address()) + window_from_node(self).root().r().content_boxes_query(self.to_trusted_node_address()) } // http://dom.spec.whatwg.org/#dom-parentnode-queryselector diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 27b297e21f2..30b1e6baaa0 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -8,50 +8,59 @@ use dom::bindings::codegen::Bindings::FunctionBinding::Function; use dom::bindings::codegen::Bindings::WindowBinding; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::InheritTypes::EventTargetCast; +use dom::bindings::codegen::InheritTypes::{NodeCast, EventTargetCast}; use dom::bindings::global::global_object_for_js_object; use dom::bindings::error::{report_pending_exception, Fallible}; use dom::bindings::error::Error::InvalidCharacter; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{MutNullableJS, JSRef, Temporary}; -use dom::bindings::utils::Reflectable; +use dom::bindings::js::{MutNullableJS, JSRef, Temporary, OptionalRootable, RootedReference}; +use dom::bindings::utils::{GlobalStaticData, Reflectable, WindowProxyHandler}; use dom::browsercontext::BrowserContext; use dom::console::Console; -use dom::document::Document; +use dom::document::{Document, DocumentHelpers}; use dom::element::Element; use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId}; use dom::location::Location; use dom::navigator::Navigator; -use dom::node::window_from_node; +use dom::node::{window_from_node, Node, TrustedNodeAddress, NodeHelpers}; use dom::performance::Performance; use dom::screen::Screen; use dom::storage::Storage; -use layout_interface::{ReflowGoal, ReflowQueryType}; +use layout_interface::{ReflowGoal, ReflowQueryType, LayoutRPC, LayoutChan, Reflow, Msg}; +use layout_interface::{ContentBoxResponse, ContentBoxesResponse}; use page::Page; use script_task::{TimerSource, ScriptChan}; use script_task::ScriptMsg; use script_traits::ScriptControlChan; use timers::{IsInterval, TimerId, TimerManager, TimerCallback}; +use devtools_traits::DevtoolsControlChan; use msg::compositor_msg::ScriptListener; -use msg::constellation_msg::LoadData; +use msg::constellation_msg::{LoadData, PipelineId, SubpageId, ConstellationChan, WindowSizeData}; use net::image_cache_task::ImageCacheTask; +use net::resource_task::ResourceTask; use net::storage_task::StorageTask; +use util::geometry::{self, Au, MAX_RECT}; use util::str::{DOMString,HTML_SPACE_CHARACTERS}; +use geom::{Point2D, Rect, Size2D}; use js::jsapi::JS_EvaluateUCScript; use js::jsapi::JSContext; use js::jsapi::{JS_GC, JS_GetRuntime}; use js::jsval::{JSVal, UndefinedValue}; -use js::rust::with_compartment; +use js::rust::{Cx, with_compartment}; use url::{Url, UrlParser}; use libc; use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD}; -use std::cell::{Ref, RefMut}; +use std::cell::{Cell, Ref, RefMut}; use std::default::Default; use std::ffi::CString; +use std::mem::replace; +use std::num::Float; use std::rc::Rc; +use std::sync::mpsc::{channel, Receiver}; +use std::sync::mpsc::TryRecvError::{Empty, Disconnected}; use time; #[dom_struct] @@ -71,18 +80,80 @@ pub struct Window { screen: MutNullableJS, session_storage: MutNullableJS, timers: TimerManager, + + /// For providing instructions to an optional devtools server. + devtools_chan: Option, + + /// A flag to indicate whether the developer tools have requested live updates of + /// page changes. + devtools_wants_updates: Cell, + + next_subpage_id: Cell, + + /// Pending resize event, if any. + resize_event: Cell>, + + /// Pipeline id associated with this page. + id: PipelineId, + + /// Subpage id associated with this page, if any. + subpage_id: Option, + + /// Unique id for last reflow request; used for confirming completion reply. + last_reflow_id: Cell, + + /// Global static data related to the DOM. + dom_static: GlobalStaticData, + + /// The JavaScript context. + js_context: DOMRefCell>>, + + /// A handle for communicating messages to the layout task. + layout_chan: LayoutChan, + + /// A handle to perform RPC calls into the layout, quickly. + layout_rpc: Box, + + /// The port that we will use to join layout. If this is `None`, then layout is not running. + layout_join_port: DOMRefCell>>, + + /// The current size of the window, in pixels. + window_size: Cell, + + /// Associated resource task for use by DOM objects like XMLHttpRequest + resource_task: ResourceTask, + + /// A handle for communicating messages to the storage task. + storage_task: StorageTask, + + /// A handle for communicating messages to the constellation task. + constellation_chan: ConstellationChan, + + /// Pending scroll to fragment event, if any + fragment_name: DOMRefCell>, + + /// An enlarged rectangle around the page contents visible in the viewport, used + /// to prevent creating display list items for content that is far away from the viewport. + page_clip_rect: Cell>, } impl Window { pub fn get_cx(&self) -> *mut JSContext { - let js_info = self.page().js_info(); - (*js_info.as_ref().unwrap().js_context).ptr + self.js_context.borrow().as_ref().unwrap().ptr } pub fn script_chan(&self) -> Box { self.script_chan.clone() } + pub fn pipeline(&self) -> PipelineId { + self.id.clone() + } + + pub fn subpage(&self) -> Option { + self.subpage_id.clone() + } + pub fn control_chan<'a>(&'a self) -> &'a ScriptControlChan { &self.control_chan } @@ -103,16 +174,8 @@ impl Window { &*self.page } - pub fn page_clone(&self) -> Rc { - self.page.clone() - } - - pub fn get_url(&self) -> Url { - self.page().get_url() - } - pub fn storage_task(&self) -> StorageTask { - self.page().storage_task.clone() + self.storage_task.clone() } } @@ -197,12 +260,11 @@ impl<'a> WindowMethods for JSRef<'a, Window> { } fn Close(self) { - self.script_chan.send(ScriptMsg::ExitWindow(self.page.id.clone())).unwrap(); + self.script_chan.send(ScriptMsg::ExitWindow(self.id.clone())).unwrap(); } fn Document(self) -> Temporary { - let frame = self.page().frame(); - Temporary::new(frame.as_ref().unwrap().document.clone()) + self.browser_context().as_ref().unwrap().active_document() } fn Location(self) -> Temporary { @@ -230,7 +292,7 @@ impl<'a> WindowMethods for JSRef<'a, Window> { args, timeout, IsInterval::NonInterval, - TimerSource::FromWindow(self.page.id.clone()), + TimerSource::FromWindow(self.id.clone()), self.script_chan.clone()) } @@ -239,7 +301,7 @@ impl<'a> WindowMethods for JSRef<'a, Window> { args, timeout, IsInterval::NonInterval, - TimerSource::FromWindow(self.page.id.clone()), + TimerSource::FromWindow(self.id.clone()), self.script_chan.clone()) } @@ -252,7 +314,7 @@ impl<'a> WindowMethods for JSRef<'a, Window> { args, timeout, IsInterval::Interval, - TimerSource::FromWindow(self.page.id.clone()), + TimerSource::FromWindow(self.id.clone()), self.script_chan.clone()) } @@ -261,7 +323,7 @@ impl<'a> WindowMethods for JSRef<'a, Window> { args, timeout, IsInterval::Interval, - TimerSource::FromWindow(self.page.id.clone()), + TimerSource::FromWindow(self.id.clone()), self.script_chan.clone()) } @@ -330,10 +392,35 @@ impl<'a> WindowMethods for JSRef<'a, Window> { } pub trait WindowHelpers { + fn clear_js_context(self); + fn clear_js_context_for_script_deallocation(self); fn flush_layout(self, goal: ReflowGoal, query: ReflowQueryType); fn init_browser_context(self, doc: JSRef, frame_element: Option>); fn load_url(self, href: DOMString); fn handle_fire_timer(self, timer_id: TimerId); + fn reflow(self, goal: ReflowGoal, query_type: ReflowQueryType); + fn join_layout(self); + fn layout(&self) -> &LayoutRPC; + fn content_box_query(self, content_box_request: TrustedNodeAddress) -> Rect; + fn content_boxes_query(self, content_boxes_request: TrustedNodeAddress) -> Vec>; + fn handle_reflow_complete_msg(self, reflow_id: uint); + fn handle_resize_inactive_msg(self, new_size: WindowSizeData); + fn set_fragment_name(self, fragment: Option); + fn steal_fragment_name(self) -> Option; + fn set_window_size(self, size: WindowSizeData); + fn window_size(self) -> WindowSizeData; + fn get_url(self) -> Url; + fn resource_task(self) -> ResourceTask; + fn devtools_chan(self) -> Option; + fn layout_chan(self) -> LayoutChan; + fn constellation_chan(self) -> ConstellationChan; + fn windowproxy_handler(self) -> WindowProxyHandler; + fn get_next_subpage_id(self) -> SubpageId; + fn layout_is_idle(self) -> bool; + fn set_resize_event(self, event: WindowSizeData); + fn steal_resize_event(self) -> Option; + fn set_page_clip_rect_with_new_viewport(self, viewport: Rect) -> bool; + fn set_devtools_wants_updates(self, value: bool); fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option>; fn thaw(self); fn freeze(self); @@ -373,8 +460,132 @@ impl<'a, T: Reflectable> ScriptHelpers for JSRef<'a, T> { } impl<'a> WindowHelpers for JSRef<'a, Window> { + fn clear_js_context(self) { + *self.js_context.borrow_mut() = None; + *self.browser_context.borrow_mut() = None; + } + + #[allow(unsafe_blocks)] + fn clear_js_context_for_script_deallocation(self) { + unsafe { + *self.js_context.borrow_for_script_deallocation() = None; + *self.browser_context.borrow_for_script_deallocation() = None; + } + } + fn flush_layout(self, goal: ReflowGoal, query: ReflowQueryType) { - self.page().flush_layout(goal, query); + self.reflow(goal, query); + } + + /// Reflows the page if it's possible to do so and the page is dirty. This method will wait + /// for the layout thread to complete (but see the `TODO` below). If there is no window size + /// yet, the page is presumed invisible and no reflow is performed. + /// + /// TODO(pcwalton): Only wait for style recalc, since we have off-main-thread layout. + fn reflow(self, goal: ReflowGoal, query_type: ReflowQueryType) { + let document = self.Document().root(); + let root = document.r().GetDocumentElement().root(); + let root = match root.r() { + Some(root) => root, + None => return, + }; + + debug!("script: performing reflow for goal {:?}", goal); + + let root: JSRef = NodeCast::from_ref(root); + if !root.get_has_dirty_descendants() { + debug!("root has no dirty descendants; avoiding reflow"); + return + } + + debug!("script: performing reflow for goal {:?}", goal); + + // Layout will let us know when it's done. + let (join_chan, join_port) = channel(); + + { + let mut layout_join_port = self.layout_join_port.borrow_mut(); + *layout_join_port = Some(join_port); + } + + let last_reflow_id = &self.last_reflow_id; + last_reflow_id.set(last_reflow_id.get() + 1); + + let window_size = self.window_size.get(); + + // Send new document and relevant styles to layout. + let reflow = box Reflow { + document_root: root.to_trusted_node_address(), + url: self.get_url(), + iframe: self.subpage_id.is_some(), + goal: goal, + window_size: window_size, + script_chan: self.control_chan.clone(), + script_join_chan: join_chan, + id: last_reflow_id.get(), + query_type: query_type, + page_clip_rect: self.page_clip_rect.get(), + }; + + let LayoutChan(ref chan) = self.layout_chan; + chan.send(Msg::Reflow(reflow)).unwrap(); + + debug!("script: layout forked"); + + self.join_layout(); + } + + // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a + // reflow between the "join complete" message and returning from this + // function? + + /// Sends a ping to layout and waits for the response. The response will arrive when the + /// layout task has finished any pending request messages. + fn join_layout(self) { + let mut layout_join_port = self.layout_join_port.borrow_mut(); + if let Some(join_port) = replace(&mut *layout_join_port, None) { + match join_port.try_recv() { + Err(Empty) => { + info!("script: waiting on layout"); + join_port.recv().unwrap(); + } + Ok(_) => {} + Err(Disconnected) => { + panic!("Layout task failed while script was waiting for a result."); + } + } + + debug!("script: layout joined") + } + } + + fn layout(&self) -> &LayoutRPC { + &*self.layout_rpc + } + + fn content_box_query(self, content_box_request: TrustedNodeAddress) -> Rect { + self.flush_layout(ReflowGoal::ForScriptQuery, ReflowQueryType::ContentBoxQuery(content_box_request)); + self.join_layout(); //FIXME: is this necessary, or is layout_rpc's mutex good enough? + let ContentBoxResponse(rect) = self.layout_rpc.content_box(); + rect + } + + fn content_boxes_query(self, content_boxes_request: TrustedNodeAddress) -> Vec> { + self.flush_layout(ReflowGoal::ForScriptQuery, ReflowQueryType::ContentBoxesQuery(content_boxes_request)); + self.join_layout(); //FIXME: is this necessary, or is layout_rpc's mutex good enough? + let ContentBoxesResponse(rects) = self.layout_rpc.content_boxes(); + rects + } + + fn handle_reflow_complete_msg(self, reflow_id: uint) { + let last_reflow_id = self.last_reflow_id.get(); + if last_reflow_id == reflow_id { + *self.layout_join_port.borrow_mut() = None; + } + } + + fn handle_resize_inactive_msg(self, new_size: WindowSizeData) { + self.window_size.set(new_size); } fn init_browser_context(self, doc: JSRef, frame_element: Option>) { @@ -383,17 +594,17 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { /// Commence a new URL load which will either replace this window or scroll to a fragment. fn load_url(self, href: DOMString) { - let base_url = self.page().get_url(); + let base_url = self.get_url(); debug!("current page url is {}", base_url); let url = UrlParser::new().base_url(&base_url).parse(href.as_slice()); // FIXME: handle URL parse errors more gracefully. let url = url.unwrap(); match url.fragment { Some(fragment) => { - self.script_chan.send(ScriptMsg::TriggerFragment(self.page.id, fragment)).unwrap(); + self.script_chan.send(ScriptMsg::TriggerFragment(self.id, fragment)).unwrap(); }, None => { - self.script_chan.send(ScriptMsg::TriggerLoad(self.page.id, LoadData::new(url))).unwrap(); + self.script_chan.send(ScriptMsg::TriggerLoad(self.id, LoadData::new(url))).unwrap(); } } } @@ -403,6 +614,97 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { self.flush_layout(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); } + fn set_fragment_name(self, fragment: Option) { + *self.fragment_name.borrow_mut() = fragment; + } + + fn steal_fragment_name(self) -> Option { + self.fragment_name.borrow_mut().take() + } + + fn set_window_size(self, size: WindowSizeData) { + self.window_size.set(size); + } + + fn window_size(self) -> WindowSizeData { + self.window_size.get() + } + + fn get_url(self) -> Url { + let doc = self.Document().root(); + doc.r().url() + } + + fn resource_task(self) -> ResourceTask { + self.resource_task.clone() + } + + fn devtools_chan(self) -> Option { + self.devtools_chan.clone() + } + + fn layout_chan(self) -> LayoutChan { + self.layout_chan.clone() + } + + fn constellation_chan(self) -> ConstellationChan { + self.constellation_chan.clone() + } + + fn windowproxy_handler(self) -> WindowProxyHandler { + self.dom_static.windowproxy_handler + } + + fn get_next_subpage_id(self) -> SubpageId { + let subpage_id = self.next_subpage_id.get(); + let SubpageId(id_num) = subpage_id; + self.next_subpage_id.set(SubpageId(id_num + 1)); + subpage_id + } + + fn layout_is_idle(self) -> bool { + self.layout_join_port.borrow().is_none() + } + + fn set_resize_event(self, event: WindowSizeData) { + self.resize_event.set(Some(event)); + } + + fn steal_resize_event(self) -> Option { + let event = self.resize_event.get(); + self.resize_event.set(None); + event + } + + fn set_page_clip_rect_with_new_viewport(self, viewport: Rect) -> bool { + // We use a clipping rectangle that is five times the size of the of the viewport, + // so that we don't collect display list items for areas too far outside the viewport, + // but also don't trigger reflows every time the viewport changes. + static VIEWPORT_EXPANSION: f32 = 2.0; // 2 lengths on each side plus original length is 5 total. + let proposed_clip_rect = geometry::f32_rect_to_au_rect( + viewport.inflate(viewport.size.width * VIEWPORT_EXPANSION, + viewport.size.height * VIEWPORT_EXPANSION)); + let clip_rect = self.page_clip_rect.get(); + if proposed_clip_rect == clip_rect { + return false; + } + + let had_clip_rect = clip_rect != MAX_RECT; + if had_clip_rect && !should_move_clip_rect(clip_rect, viewport) { + return false; + } + + self.page_clip_rect.set(proposed_clip_rect); + + // If we didn't have a clip rect, the previous display doesn't need rebuilding + // because it was built for infinite clip (MAX_RECT). + had_clip_rect + } + + fn set_devtools_wants_updates(self, value: bool) { + self.devtools_wants_updates.set(value); + } + // https://html.spec.whatwg.org/multipage/browsers.html#accessing-other-browsing-contexts fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option> { None @@ -419,13 +721,28 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { } impl Window { - pub fn new(cx: *mut JSContext, + pub fn new(js_context: Rc, page: Rc, script_chan: Box, control_chan: ScriptControlChan, compositor: Box, - image_cache_task: ImageCacheTask) + image_cache_task: ImageCacheTask, + resource_task: ResourceTask, + storage_task: StorageTask, + devtools_chan: Option, + constellation_chan: ConstellationChan, + layout_chan: LayoutChan, + id: PipelineId, + subpage_id: Option, + window_size: WindowSizeData) -> Temporary { + let layout_rpc: Box = { + let (rpc_send, rpc_recv) = channel(); + let LayoutChan(ref lchan) = layout_chan; + lchan.send(Msg::GetRPC(rpc_send)).unwrap(); + rpc_recv.recv().unwrap() + }; + let win = box Window { eventtarget: EventTarget::new_inherited(EventTargetTypeId::Window), script_chan: script_chan, @@ -435,6 +752,7 @@ impl Window { page: page, navigator: Default::default(), image_cache_task: image_cache_task, + devtools_chan: devtools_chan, browser_context: DOMRefCell::new(None), performance: Default::default(), navigation_start: time::get_time().sec as u64, @@ -442,8 +760,43 @@ impl Window { screen: Default::default(), session_storage: Default::default(), timers: TimerManager::new(), + id: id, + subpage_id: subpage_id, + dom_static: GlobalStaticData::new(), + js_context: DOMRefCell::new(Some(js_context.clone())), + resource_task: resource_task, + storage_task: storage_task, + constellation_chan: constellation_chan, + page_clip_rect: Cell::new(MAX_RECT), + fragment_name: DOMRefCell::new(None), + last_reflow_id: Cell::new(0), + resize_event: Cell::new(None), + next_subpage_id: Cell::new(SubpageId(0)), + layout_chan: layout_chan, + layout_rpc: layout_rpc, + layout_join_port: DOMRefCell::new(None), + window_size: Cell::new(window_size), + devtools_wants_updates: Cell::new(false), }; - WindowBinding::Wrap(cx, win) + WindowBinding::Wrap(js_context.ptr, win) } } + +fn should_move_clip_rect(clip_rect: Rect, new_viewport: Rect) -> bool{ + let clip_rect = Rect(Point2D(geometry::to_frac_px(clip_rect.origin.x) as f32, + geometry::to_frac_px(clip_rect.origin.y) as f32), + Size2D(geometry::to_frac_px(clip_rect.size.width) as f32, + geometry::to_frac_px(clip_rect.size.height) as f32)); + + // We only need to move the clip rect if the viewport is getting near the edge of + // our preexisting clip rect. We use half of the size of the viewport as a heuristic + // for "close." + static VIEWPORT_SCROLL_MARGIN_SIZE: f32 = 0.5; + let viewport_scroll_margin = new_viewport.size * VIEWPORT_SCROLL_MARGIN_SIZE; + + (clip_rect.origin.x - new_viewport.origin.x).abs() <= viewport_scroll_margin.width || + (clip_rect.max_x() - new_viewport.max_x()).abs() <= viewport_scroll_margin.width || + (clip_rect.origin.y - new_viewport.origin.y).abs() <= viewport_scroll_margin.height || + (clip_rect.max_y() - new_viewport.max_y()).abs() <= viewport_scroll_margin.height +} diff --git a/components/script/page.rs b/components/script/page.rs index 3800b5b3950..c080be4d16c 100644 --- a/components/script/page.rs +++ b/components/script/page.rs @@ -3,39 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::InheritTypes::NodeCast; -use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable}; -use dom::bindings::utils::GlobalStaticData; +use dom::bindings::js::{JS, Temporary, Unrooted}; use dom::document::{Document, DocumentHelpers}; -use dom::element::Element; -use dom::node::{Node, NodeHelpers}; +use dom::node::NodeHelpers; use dom::window::Window; -use devtools_traits::DevtoolsControlChan; -use layout_interface::{ - ContentBoxResponse, ContentBoxesResponse, - HitTestResponse, LayoutChan, LayoutRPC, MouseOverResponse, Msg, Reflow, - ReflowGoal, ReflowQueryType, - TrustedNodeAddress -}; -use script_traits::{UntrustedNodeAddress, ScriptControlChan}; -use geom::{Point2D, Rect, Size2D}; -use js::rust::Cx; -use msg::compositor_msg::ScriptListener; -use msg::constellation_msg::{ConstellationChan, WindowSizeData}; use msg::constellation_msg::{PipelineId, SubpageId}; -use net::resource_task::ResourceTask; -use net::storage_task::StorageTask; -use util::geometry::{Au, MAX_RECT}; -use util::geometry; -use util::str::DOMString; use util::smallvec::SmallVec; -use std::cell::{Cell, Ref, RefMut}; -use std::sync::mpsc::{channel, Receiver}; -use std::sync::mpsc::TryRecvError::{Empty, Disconnected}; -use std::mem::replace; -use std::num::Float; use std::rc::Rc; use url::Url; @@ -43,30 +17,13 @@ use url::Url; #[jstraceable] pub struct Page { /// Pipeline id associated with this page. - pub id: PipelineId, + id: PipelineId, /// Subpage id associated with this page, if any. - pub subpage_id: Option, + subpage_id: Option, - /// Unique id for last reflow request; used for confirming completion reply. - pub last_reflow_id: Cell, - - /// The outermost frame containing the document, window, and page URL. - pub frame: DOMRefCell>, - - /// A handle for communicating messages to the layout task. - pub layout_chan: LayoutChan, - - /// A handle to perform RPC calls into the layout, quickly. - layout_rpc: Box, - - /// The port that we will use to join layout. If this is `None`, then layout is not running. - pub layout_join_port: DOMRefCell>>, - - /// The current size of the window, in pixels. - pub window_size: Cell, - - js_info: DOMRefCell>, + /// The outermost frame containing the document and window. + frame: DOMRefCell>, /// Cached copy of the most recent url loaded by the script, after all redirections. /// TODO(tkuehn): this currently does not follow any particular caching policy @@ -74,36 +31,8 @@ pub struct Page { /// when reloading. url: DOMRefCell<(Url, bool)>, - next_subpage_id: Cell, - - /// Pending resize event, if any. - pub resize_event: Cell>, - - /// Pending scroll to fragment event, if any - pub fragment_name: DOMRefCell>, - - /// Associated resource task for use by DOM objects like XMLHttpRequest - pub resource_task: ResourceTask, - - /// A handle for communicating messages to the storage task. - pub storage_task: StorageTask, - - /// A handle for communicating messages to the constellation task. - pub constellation_chan: ConstellationChan, - // Child Pages. pub children: DOMRefCell>>, - - /// An enlarged rectangle around the page contents visible in the viewport, used - /// to prevent creating display list items for content that is far away from the viewport. - pub page_clip_rect: Cell>, - - /// A flag to indicate whether the developer tools have requested live updates of - /// page changes. - pub devtools_wants_updates: Cell, - - /// For providing instructions to an optional devtools server. - pub devtools_chan: Option, } pub struct PageIterator { @@ -133,71 +62,26 @@ impl IterablePage for Rc { } impl Page { - pub fn new(id: PipelineId, subpage_id: Option, - layout_chan: LayoutChan, - window_size: WindowSizeData, - resource_task: ResourceTask, - storage_task: StorageTask, - constellation_chan: ConstellationChan, - js_context: Rc, - devtools_chan: Option, - url: Url) -> Page { - let js_info = JSPageInfo { - dom_static: GlobalStaticData::new(), - js_context: js_context, - }; - let layout_rpc: Box = { - let (rpc_send, rpc_recv) = channel(); - let LayoutChan(ref lchan) = layout_chan; - lchan.send(Msg::GetRPC(rpc_send)).unwrap(); - rpc_recv.recv().unwrap() - }; + pub fn new(id: PipelineId, subpage_id: Option, url: Url) -> Page { Page { id: id, subpage_id: subpage_id, frame: DOMRefCell::new(None), - layout_chan: layout_chan, - layout_rpc: layout_rpc, - layout_join_port: DOMRefCell::new(None), - window_size: Cell::new(window_size), - js_info: DOMRefCell::new(Some(js_info)), url: DOMRefCell::new((url, true)), - next_subpage_id: Cell::new(SubpageId(0)), - resize_event: Cell::new(None), - fragment_name: DOMRefCell::new(None), - last_reflow_id: Cell::new(0), - resource_task: resource_task, - storage_task: storage_task, - constellation_chan: constellation_chan, children: DOMRefCell::new(vec!()), - page_clip_rect: Cell::new(MAX_RECT), - devtools_wants_updates: Cell::new(false), - devtools_chan: devtools_chan, } } - pub fn flush_layout(&self, goal: ReflowGoal, query: ReflowQueryType) { - let frame = self.frame(); - let window = frame.as_ref().unwrap().window.root(); - self.reflow(goal, window.r().control_chan().clone(), &mut **window.r().compositor(), query); + pub fn window(&self) -> Temporary { + Temporary::new(self.frame.borrow().as_ref().unwrap().window.clone()) } - pub fn layout(&self) -> &LayoutRPC { - &*self.layout_rpc + pub fn window_for_script_dealloation(&self) -> Unrooted { + Unrooted::from_js(self.frame.borrow().as_ref().unwrap().window) } - pub fn content_box_query(&self, content_box_request: TrustedNodeAddress) -> Rect { - self.flush_layout(ReflowGoal::ForScriptQuery, ReflowQueryType::ContentBoxQuery(content_box_request)); - self.join_layout(); //FIXME: is this necessary, or is layout_rpc's mutex good enough? - let ContentBoxResponse(rect) = self.layout_rpc.content_box(); - rect - } - - pub fn content_boxes_query(&self, content_boxes_request: TrustedNodeAddress) -> Vec> { - self.flush_layout(ReflowGoal::ForScriptQuery, ReflowQueryType::ContentBoxesQuery(content_boxes_request)); - self.join_layout(); //FIXME: is this necessary, or is layout_rpc's mutex good enough? - let ContentBoxesResponse(rects) = self.layout_rpc.content_boxes(); - rects + pub fn document(&self) -> Temporary { + Temporary::new(self.frame.borrow().as_ref().unwrap().document.clone()) } // must handle root case separately @@ -219,49 +103,6 @@ impl Page { } } } - - pub fn set_page_clip_rect_with_new_viewport(&self, viewport: Rect) -> bool { - // We use a clipping rectangle that is five times the size of the of the viewport, - // so that we don't collect display list items for areas too far outside the viewport, - // but also don't trigger reflows every time the viewport changes. - static VIEWPORT_EXPANSION: f32 = 2.0; // 2 lengths on each side plus original length is 5 total. - let proposed_clip_rect = geometry::f32_rect_to_au_rect( - viewport.inflate(viewport.size.width * VIEWPORT_EXPANSION, - viewport.size.height * VIEWPORT_EXPANSION)); - let clip_rect = self.page_clip_rect.get(); - if proposed_clip_rect == clip_rect { - return false; - } - - let had_clip_rect = clip_rect != MAX_RECT; - if had_clip_rect && !should_move_clip_rect(clip_rect, viewport) { - return false; - } - - self.page_clip_rect.set(proposed_clip_rect); - - // If we didn't have a clip rect, the previous display doesn't need rebuilding - // because it was built for infinite clip (MAX_RECT). - had_clip_rect - } - - pub fn send_title_to_compositor(&self) { - match *self.frame() { - None => {} - Some(ref frame) => { - let window = frame.window.root(); - let document = frame.document.root(); - window.r().compositor().set_title(self.id, Some(document.r().Title())); - } - } - } - - pub fn dirty_all_nodes(&self) { - match *self.frame.borrow() { - None => {} - Some(ref frame) => frame.document.root().r().dirty_all_nodes(), - } - } } impl Iterator for PageIterator { @@ -281,198 +122,15 @@ impl Iterator for PageIterator { } impl Page { - pub fn mut_js_info<'a>(&'a self) -> RefMut<'a, Option> { - self.js_info.borrow_mut() + pub fn set_reflow_status(&self, status: bool) -> bool { + let old = (*self.url.borrow()).1; + (*self.url.borrow_mut()).1 = status; + old } - pub unsafe fn unsafe_mut_js_info<'a>(&'a self) -> &'a mut Option { - self.js_info.borrow_for_script_deallocation() + pub fn set_frame(&self, frame: Option) { + *self.frame.borrow_mut() = frame; } - - pub fn js_info<'a>(&'a self) -> Ref<'a, Option> { - self.js_info.borrow() - } - - pub fn url<'a>(&'a self) -> Ref<'a, (Url, bool)> { - self.url.borrow() - } - - pub fn mut_url<'a>(&'a self) -> RefMut<'a, (Url, bool)> { - self.url.borrow_mut() - } - - pub fn frame<'a>(&'a self) -> Ref<'a, Option> { - self.frame.borrow() - } - - pub fn mut_frame<'a>(&'a self) -> RefMut<'a, Option> { - self.frame.borrow_mut() - } - - pub fn get_next_subpage_id(&self) -> SubpageId { - let subpage_id = self.next_subpage_id.get(); - let SubpageId(id_num) = subpage_id; - self.next_subpage_id.set(SubpageId(id_num + 1)); - subpage_id - } - - pub fn get_url(&self) -> Url { - self.url().0.clone() - } - - // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a - // reflow between the "join complete" message and returning from this - // function? - - /// Sends a ping to layout and waits for the response. The response will arrive when the - /// layout task has finished any pending request messages. - fn join_layout(&self) { - let mut layout_join_port = self.layout_join_port.borrow_mut(); - if let Some(join_port) = replace(&mut *layout_join_port, None) { - match join_port.try_recv() { - Err(Empty) => { - info!("script: waiting on layout"); - join_port.recv().unwrap(); - } - Ok(_) => {} - Err(Disconnected) => { - panic!("Layout task failed while script was waiting for a result."); - } - } - - debug!("script: layout joined") - } - } - - /// Reflows the page if it's possible to do so and the page is dirty. This method will wait - /// for the layout thread to complete (but see the `TODO` below). If there is no window size - /// yet, the page is presumed invisible and no reflow is performed. - /// - /// TODO(pcwalton): Only wait for style recalc, since we have off-main-thread layout. - pub fn reflow(&self, - goal: ReflowGoal, - script_chan: ScriptControlChan, - _: &mut ScriptListener, - query_type: ReflowQueryType) { - let root = match *self.frame() { - None => return, - Some(ref frame) => { - frame.document.root().r().GetDocumentElement() - } - }; - - let root = match root.root() { - None => return, - Some(root) => root, - }; - - debug!("script: performing reflow for goal {:?}", goal); - - let root: JSRef = NodeCast::from_ref(root.r()); - if !root.get_has_dirty_descendants() { - debug!("root has no dirty descendants; avoiding reflow"); - return - } - - debug!("script: performing reflow for goal {:?}", goal); - - // Layout will let us know when it's done. - let (join_chan, join_port) = channel(); - - { - let mut layout_join_port = self.layout_join_port.borrow_mut(); - *layout_join_port = Some(join_port); - } - - let last_reflow_id = &self.last_reflow_id; - last_reflow_id.set(last_reflow_id.get() + 1); - - let window_size = self.window_size.get(); - - // Send new document and relevant styles to layout. - let reflow = box Reflow { - document_root: root.to_trusted_node_address(), - url: self.get_url(), - iframe: self.subpage_id.is_some(), - goal: goal, - window_size: window_size, - script_chan: script_chan, - script_join_chan: join_chan, - id: last_reflow_id.get(), - query_type: query_type, - page_clip_rect: self.page_clip_rect.get(), - }; - - let LayoutChan(ref chan) = self.layout_chan; - chan.send(Msg::Reflow(reflow)).unwrap(); - - debug!("script: layout forked"); - - self.join_layout(); - } - - /// Attempt to find a named element in this page's document. - pub fn find_fragment_node(&self, fragid: DOMString) -> Option> { - let document = self.frame().as_ref().unwrap().document.root(); - document.r().find_fragment_node(fragid) - } - - pub fn hit_test(&self, point: &Point2D) -> Option { - let frame = self.frame(); - let document = frame.as_ref().unwrap().document.root(); - let root = match document.r().GetDocumentElement().root() { - None => return None, - Some(root) => root, - }; - let root: JSRef = NodeCast::from_ref(root.r()); - let address = match self.layout().hit_test(root.to_trusted_node_address(), *point) { - Ok(HitTestResponse(node_address)) => { - Some(node_address) - } - Err(()) => { - debug!("layout query error"); - None - } - }; - address - } - - pub fn get_nodes_under_mouse(&self, point: &Point2D) -> Vec { - let mut results = vec!(); - let frame = self.frame(); - let document = frame.as_ref().unwrap().document.root(); - match document.r().GetDocumentElement().root() { - Some(root) => { - let root: JSRef = NodeCast::from_ref(root.r()); - match self.layout().mouse_over(root.to_trusted_node_address(), *point) { - Ok(MouseOverResponse(node_addresses)) => { - results = node_addresses; - } - Err(()) => {} - }; - } - None => {} - } - results - } -} - -fn should_move_clip_rect(clip_rect: Rect, new_viewport: Rect) -> bool{ - let clip_rect = Rect(Point2D(geometry::to_frac_px(clip_rect.origin.x) as f32, - geometry::to_frac_px(clip_rect.origin.y) as f32), - Size2D(geometry::to_frac_px(clip_rect.size.width) as f32, - geometry::to_frac_px(clip_rect.size.height) as f32)); - - // We only need to move the clip rect if the viewport is getting near the edge of - // our preexisting clip rect. We use half of the size of the viewport as a heuristic - // for "close." - static VIEWPORT_SCROLL_MARGIN_SIZE: f32 = 0.5; - let viewport_scroll_margin = new_viewport.size * VIEWPORT_SCROLL_MARGIN_SIZE; - - (clip_rect.origin.x - new_viewport.origin.x).abs() <= viewport_scroll_margin.width || - (clip_rect.max_x() - new_viewport.max_x()).abs() <= viewport_scroll_margin.width || - (clip_rect.origin.y - new_viewport.origin.y).abs() <= viewport_scroll_margin.height || - (clip_rect.max_y() - new_viewport.max_y()).abs() <= viewport_scroll_margin.height } /// Information for one frame in the browsing context. @@ -484,12 +142,3 @@ pub struct Frame { /// The window object for this frame. pub window: JS, } - -/// Encapsulation of the javascript information associated with each frame. -#[jstraceable] -pub struct JSPageInfo { - /// Global static data related to the DOM. - pub dom_static: GlobalStaticData, - /// The JavaScript context. - pub js_context: Rc, -} diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 28a2e6437c0..de9f36fab24 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -23,7 +23,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}; @@ -74,7 +74,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; @@ -275,7 +274,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; } @@ -475,14 +475,13 @@ impl ScriptTask { if let Some(ref page) = page.as_ref() { 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)), + 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); } } } @@ -654,7 +653,8 @@ impl ScriptTask { let page = self.page.borrow(); if let Some(ref page) = page.as_ref() { if let Some(ref page) = page.find(id) { - page.resize_event.set(Some(size)); + let window = page.window().root(); + window.r().set_resize_event(size); return; } } @@ -670,7 +670,8 @@ impl ScriptTask { let page = self.page.borrow(); if let Some(page) = page.as_ref() { if let Some(ref inner_page) = page.find(id) { - if inner_page.set_page_clip_rect_with_new_viewport(rect) { + 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); } @@ -699,10 +700,11 @@ impl ScriptTask { whose parent has a PipelineId which does not correspond to a pipeline in the script task's page tree. This is a bug."); + let parent_window = parent_page.window().root(); let chan = layout_chan.downcast_ref::>().unwrap(); let layout_chan = LayoutChan(chan.clone()); let new_load = InProgressLoad::new(new_pipeline_id, Some((old_pipeline_id, subpage_id)), - layout_chan, parent_page.window_size.get(), + layout_chan, parent_window.r().window_size(), load_data.url.clone()); self.start_page_load(new_load, load_data); } @@ -712,8 +714,7 @@ impl ScriptTask { 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); } @@ -722,8 +723,7 @@ impl ScriptTask { 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(); } @@ -732,8 +732,7 @@ impl ScriptTask { 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(); } @@ -742,12 +741,7 @@ impl ScriptTask { // should exist. let page = self.root_page().find(pipeline_id).unwrap(); - // 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 = { - let &mut (_, ref mut needs_reflow) = &mut *page.mut_url(); - replace(needs_reflow, false) - }; + let needed_reflow = page.set_reflow_status(false); if needed_reflow { self.force_reflow(&*page); } @@ -760,13 +754,10 @@ impl ScriptTask { 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"))); @@ -780,10 +771,9 @@ impl ScriptTask { 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 (_, ref mut needs_reflow) => *needs_reflow = true, - } + 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. @@ -811,7 +801,9 @@ impl ScriptTask { /// Handles a request for the window title. fn handle_get_title_msg(&self, pipeline_id: PipelineId) { - get_page(&self.root_page(), 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. @@ -819,7 +811,8 @@ impl ScriptTask { fn handle_exit_pipeline_msg(&self, id: PipelineId, exit_type: PipelineExitType) -> bool { // If root is being exited, shut down all pages let page = self.root_page(); - if page.id == id { + let window = page.window().root(); + if window.r().pipeline() == id { debug!("shutting down layout for root page {:?}", id); *self.js_context.borrow_mut() = None; shut_down_layout(&page, (*self.js_runtime).ptr, exit_type); @@ -827,18 +820,10 @@ impl ScriptTask { } // 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, (*self.js_runtime).ptr, exit_type); } - + return false; } /// The entry point to document loading. Defines bindings, sets up the window and document @@ -860,7 +845,7 @@ impl ScriptTask { // 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 = page.document().root(); let doc: JSRef = NodeCast::from_ref(doc.r()); doc.traverse_preorder() @@ -881,10 +866,7 @@ impl ScriptTask { let cx = cx.as_ref().unwrap(); let page = Rc::new(Page::new(incomplete.pipeline_id, incomplete.subpage_id.map(|p| p.1), - incomplete.layout_chan, incomplete.window_size, - self.resource_task.clone(), self.storage_task.clone(), - self.constellation_chan.clone(), cx.clone(), - self.devtools_chan.clone(), final_url.clone())); + final_url.clone())); if root_page_exists { *self.page.borrow_mut() = Some(page.clone()); } else if let Some((parent, _)) = incomplete.subpage_id { @@ -894,12 +876,20 @@ impl ScriptTask { } // 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, @@ -910,14 +900,11 @@ impl ScriptTask { window.r().init_browser_context(document.r(), frame_element.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()), - }); - } + // Create the root frame + page.set_frame(Some(Frame { + document: JS::from_rooted(document.r()), + window: JS::from_rooted(window.r()), + })); let is_javascript = incomplete.url.scheme.as_slice() == "javascript"; let parse_input = if is_javascript { @@ -941,11 +928,8 @@ impl ScriptTask { NodeDamage::OtherNodeDamage); window.r().flush_layout(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); - { - // No more reflow required - let mut page_url = page.mut_url(); - (*page_url).1 = false; - } + // No more reflow required + page.set_reflow_status(false); // https://html.spec.whatwg.org/multipage/#the-end step 4 let addr: Trusted = Trusted::new(self.get_cx(), document.r(), self.chan.clone()); @@ -960,7 +944,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(); @@ -994,11 +978,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. @@ -1025,8 +1008,7 @@ impl ScriptTask { let node_to_dirty = node::from_untrusted_node_address(self.js_runtime.ptr, *node).root(); let page = get_page(&self.root_page(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let document = page.document().root(); document.r().content_changed(node_to_dirty.r(), NodeDamage::OtherNodeDamage); } @@ -1036,8 +1018,7 @@ impl ScriptTask { ClickEvent(_button, point) => { let page = get_page(&self.root_page(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let document = page.document().root(); document.r().handle_click_event(self.js_runtime.ptr, _button, point); } @@ -1045,8 +1026,7 @@ impl ScriptTask { MouseUpEvent(..) => {} MouseMoveEvent(point) => { let page = get_page(&self.root_page(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + 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) { @@ -1056,8 +1036,7 @@ impl ScriptTask { KeyEvent(key, state, modifiers) => { let page = get_page(&self.root_page(), pipeline_id); - let frame = page.frame(); - let document = frame.as_ref().unwrap().document.root(); + let document = page.document().root(); document.r().dispatch_key_event( key, state, modifiers, &mut *self.compositor.borrow_mut()); } @@ -1075,7 +1054,8 @@ impl ScriptTask { /// for the given pipeline. fn trigger_fragment(&self, pipeline_id: PipelineId, fragment: String) { let page = get_page(&self.root_page(), pipeline_id); - match page.find_fragment_node(fragment).root() { + let document = page.document().root(); + match document.r().find_fragment_node(fragment).root() { Some(node) => { self.scroll_fragment_point(pipeline_id, node.r()); } @@ -1085,53 +1065,36 @@ impl ScriptTask { fn handle_resize_event(&self, pipeline_id: PipelineId, new_size: WindowSizeData) { - let window = { - let page = get_page(&self.root_page(), pipeline_id); - page.window_size.set(new_size); + 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 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 => {} - } - - frame.as_ref().map(|frame| Temporary::new(frame.window.clone())) - }; - - 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 = EventCast::from_ref(uievent.r()); - - let wintarget: JSRef = EventTargetCast::from_ref(window.r()); - event.fire(wintarget); - } - None => () + 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 => {} } + + // 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 = EventCast::from_ref(uievent.r()); + + let wintarget: JSRef = 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.root_page(), pipeline_id); - let frame = page.frame(); - if frame.is_some() { - self.force_reflow(&*page); - } + self.force_reflow(&*page); } fn start_page_load(&self, incomplete: InProgressLoad, mut load_data: LoadData) { @@ -1167,24 +1130,25 @@ impl ScriptTask { /// Shuts down layout for the given page tree. fn shut_down_layout(page_tree: &Rc, rt: *mut JSRuntime, 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. + // Drop our references to the JSContext and DOM objects, potentially triggering a GC. for page in page_tree.iter() { - *page.mut_frame() = None; - } - - // Drop our references to the JSContext, potentially triggering a GC. - for page in page_tree.iter() { - *page.mut_js_info() = None; + let window = page.window().root(); + window.r().clear_js_context(); + page.set_frame(None); } // Force a GC to make sure that our DOM reflectors are released before we tell @@ -1194,8 +1158,7 @@ fn shut_down_layout(page_tree: &Rc, rt: *mut JSRuntime, exit_type: Pipelin } // 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(); } } From 6351fc75fd3ce5d2d2136bfc18cde8370dce2646 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 19 Feb 2015 13:27:21 -0500 Subject: [PATCH 3/8] Only store the url inside a pipeline instead of the rest of the LoadData. --- components/compositing/compositor.rs | 8 ++++---- components/compositing/compositor_task.rs | 9 +++++---- components/compositing/constellation.rs | 6 +++--- components/compositing/headless.rs | 2 +- components/compositing/pipeline.rs | 11 ++++++----- components/compositing/windowing.rs | 5 +++-- components/servo/Cargo.lock | 1 + ports/cef/Cargo.lock | 1 + ports/cef/window.rs | 6 +++--- ports/glutin/Cargo.toml | 1 + ports/glutin/lib.rs | 1 + ports/glutin/window.rs | 6 +++--- ports/gonk/src/window.rs | 4 ++-- 13 files changed, 34 insertions(+), 27 deletions(-) diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index cf303102ba3..02461adb15d 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -274,8 +274,8 @@ impl IOCompositor { self.change_page_title(pipeline_id, title); } - (Msg::ChangePageLoadData(frame_id, load_data), ShutdownState::NotShuttingDown) => { - self.change_page_load_data(frame_id, load_data); + (Msg::ChangePageUrl(frame_id, url), ShutdownState::NotShuttingDown) => { + self.change_page_url(frame_id, url); } (Msg::PaintMsgDiscarded, ShutdownState::NotShuttingDown) => { @@ -441,8 +441,8 @@ impl IOCompositor { } } - fn change_page_load_data(&mut self, _: FrameId, load_data: LoadData) { - self.window.set_page_load_data(load_data); + fn change_page_url(&mut self, _: FrameId, url: Url) { + self.window.set_page_url(url); } fn all_pipelines_in_idle_paint_state(&self) -> bool { diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs index e3c1a5102d1..9458c7fa150 100644 --- a/components/compositing/compositor_task.rs +++ b/components/compositing/compositor_task.rs @@ -20,8 +20,9 @@ use layers::layers::LayerBufferSet; use pipeline::CompositionPipeline; use msg::compositor_msg::{Epoch, LayerId, LayerMetadata, ReadyState}; use msg::compositor_msg::{PaintListener, PaintState, ScriptListener, ScrollPolicy}; -use msg::constellation_msg::{ConstellationChan, LoadData, PipelineId}; +use msg::constellation_msg::{ConstellationChan, PipelineId}; use msg::constellation_msg::{Key, KeyState, KeyModifiers}; +use url::Url; use util::cursor::Cursor; use util::geometry::PagePx; use util::memory::MemoryProfilerChan; @@ -200,8 +201,8 @@ pub enum Msg { ChangePaintState(PipelineId, PaintState), /// Alerts the compositor that the current page has changed its title. ChangePageTitle(PipelineId, Option), - /// Alerts the compositor that the current page has changed its load data (including URL). - ChangePageLoadData(FrameId, LoadData), + /// Alerts the compositor that the current page has changed its URL. + ChangePageUrl(FrameId, Url), /// Alerts the compositor that a `PaintMsg` has been discarded. PaintMsgDiscarded, /// Replaces the current frame tree, typically called during main frame navigation. @@ -237,7 +238,7 @@ impl Debug for Msg { Msg::ChangeReadyState(..) => write!(f, "ChangeReadyState"), Msg::ChangePaintState(..) => write!(f, "ChangePaintState"), Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"), - Msg::ChangePageLoadData(..) => write!(f, "ChangePageLoadData"), + Msg::ChangePageUrl(..) => write!(f, "ChangePageUrl"), Msg::PaintMsgDiscarded(..) => write!(f, "PaintMsgDiscarded"), Msg::SetFrameTree(..) => write!(f, "SetFrameTree"), Msg::CreateRootLayerForPipeline(..) => write!(f, "CreateRootLayerForPipeline"), diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index 5ef72e185ec..aff40db788d 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -334,9 +334,9 @@ impl NavigationContext { /// compositor of the new URLs. fn set_current(&mut self, new_frame: Rc, compositor_proxy: &mut CompositorProxy) { self.current = Some(new_frame.clone()); - compositor_proxy.send(CompositorMsg::ChangePageLoadData( + compositor_proxy.send(CompositorMsg::ChangePageUrl( new_frame.id, - new_frame.pipeline.borrow().load_data.clone())); + new_frame.pipeline.borrow().url.clone())); } } @@ -762,7 +762,7 @@ impl Constellation { source Id of ScriptLoadedURLInIFrameMsg does have an associated pipeline in constellation. This should be impossible.").clone(); - let source_url = source_pipeline.load_data.url.clone(); + let source_url = source_pipeline.url.clone(); let same_script = (source_url.host() == url.host() && source_url.port() == url.port()) && diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs index 0c38846d1d0..9957cb067cc 100644 --- a/components/compositing/headless.rs +++ b/components/compositing/headless.rs @@ -113,7 +113,7 @@ impl CompositorEventListener for NullCompositor { Msg::PaintMsgDiscarded(..) | Msg::ScrollTimeout(..) | Msg::ChangePageTitle(..) | - Msg::ChangePageLoadData(..) | + Msg::ChangePageUrl(..) | Msg::KeyEvent(..) | Msg::SetCursor(..) => {} Msg::PaintTaskExited(..) => {} diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs index eabf93752b3..c09f4ea06d5 100644 --- a/components/compositing/pipeline.rs +++ b/components/compositing/pipeline.rs @@ -16,6 +16,7 @@ use msg::constellation_msg::{LoadData, WindowSizeData, PipelineExitType}; use net::image_cache_task::ImageCacheTask; use net::resource_task::ResourceTask; use net::storage_task::StorageTask; +use url::Url; use util::time::TimeProfilerChan; use std::rc::Rc; use std::sync::mpsc::{Receiver, channel}; @@ -29,8 +30,8 @@ pub struct Pipeline { pub paint_chan: PaintChan, pub layout_shutdown_port: Receiver<()>, pub paint_shutdown_port: Receiver<()>, - /// Load data corresponding to the most recently-loaded page. - pub load_data: LoadData, + /// URL corresponding to the most recently-loaded page. + pub url: Url, /// The title of the most recently-loaded page. pub title: Option, } @@ -137,7 +138,7 @@ impl Pipeline { paint_chan, layout_shutdown_port, paint_shutdown_port, - load_data) + load_data.url) } pub fn new(id: PipelineId, @@ -147,7 +148,7 @@ impl Pipeline { paint_chan: PaintChan, layout_shutdown_port: Receiver<()>, paint_shutdown_port: Receiver<()>, - load_data: LoadData) + url: Url) -> Pipeline { Pipeline { id: id, @@ -157,7 +158,7 @@ impl Pipeline { paint_chan: paint_chan, layout_shutdown_port: layout_shutdown_port, paint_shutdown_port: paint_shutdown_port, - load_data: load_data, + url: url, title: None, } } diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 8c20aedd8e5..1990523dd2f 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -12,7 +12,8 @@ use geom::size::TypedSize2D; use layers::geometry::DevicePixel; use layers::platform::surface::NativeGraphicsMetadata; use msg::compositor_msg::{PaintState, ReadyState}; -use msg::constellation_msg::{Key, KeyState, KeyModifiers, LoadData}; +use msg::constellation_msg::{Key, KeyState, KeyModifiers}; +use url::Url; use util::cursor::Cursor; use util::geometry::ScreenPx; use std::fmt::{Error, Formatter, Debug}; @@ -105,7 +106,7 @@ pub trait WindowMethods { /// Sets the page title for the current page. fn set_page_title(&self, title: Option); /// Sets the load data for the current page. - fn set_page_load_data(&self, load_data: LoadData); + fn set_page_url(&self, url: Url); /// Called when the browser is done loading a frame. fn load_end(&self); diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index a52a5ec3191..d94dfcade90 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -389,6 +389,7 @@ dependencies = [ "libc 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "msg 0.0.1", "time 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index c67a240d535..423e2be2246 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -392,6 +392,7 @@ dependencies = [ "libc 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "msg 0.0.1", "time 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/ports/cef/window.rs b/ports/cef/window.rs index 1b133e14d27..c485792005b 100644 --- a/ports/cef/window.rs +++ b/ports/cef/window.rs @@ -23,7 +23,7 @@ use layers::platform::surface::NativeGraphicsMetadata; use libc::{c_char, c_void}; use msg::constellation_msg::{Key, KeyModifiers}; use msg::compositor_msg::{ReadyState, PaintState}; -use msg::constellation_msg::LoadData; +use std_url::Url; use util::cursor::Cursor; use util::geometry::ScreenPx; use std::cell::RefCell; @@ -286,7 +286,7 @@ impl WindowMethods for Window { } } - fn set_page_load_data(&self, load_data: LoadData) { + fn set_page_url(&self, url: Url) { let browser = self.cef_browser.borrow(); let browser = match *browser { None => return, @@ -294,7 +294,7 @@ impl WindowMethods for Window { }; let frame = browser.get_main_frame(); let frame = frame.downcast(); - *frame.url.borrow_mut() = load_data.url.to_string() + *frame.url.borrow_mut() = url.to_string() } fn handle_key(&self, _: Key, _: KeyModifiers) { diff --git a/ports/glutin/Cargo.toml b/ports/glutin/Cargo.toml index d58ed4a32bd..7c3cb6f13d5 100644 --- a/ports/glutin/Cargo.toml +++ b/ports/glutin/Cargo.toml @@ -43,3 +43,4 @@ git = "https://github.com/servo/rust-egl" time = "0.1.12" bitflags = "*" libc = "*" +url = "*" \ No newline at end of file diff --git a/ports/glutin/lib.rs b/ports/glutin/lib.rs index b644483adb2..476ed457dea 100644 --- a/ports/glutin/lib.rs +++ b/ports/glutin/lib.rs @@ -22,6 +22,7 @@ extern crate msg; extern crate time; extern crate util; extern crate egl; +extern crate url; use compositing::windowing::WindowEvent; use geom::scale_factor::ScaleFactor; diff --git a/ports/glutin/window.rs b/ports/glutin/window.rs index 2256d34b998..bf83b445e6a 100644 --- a/ports/glutin/window.rs +++ b/ports/glutin/window.rs @@ -15,10 +15,10 @@ use layers::platform::surface::NativeGraphicsMetadata; use msg::constellation_msg; use msg::constellation_msg::Key; use msg::compositor_msg::{PaintState, ReadyState}; -use msg::constellation_msg::LoadData; use NestedEventLoopListener; use std::rc::Rc; use std::sync::mpsc::{channel, Sender}; +use url::Url; use util::cursor::Cursor; use util::geometry::ScreenPx; @@ -462,7 +462,7 @@ impl WindowMethods for Window { self.window.set_title(&title); } - fn set_page_load_data(&self, _: LoadData) { + fn set_page_url(&self, _: Url) { } fn load_end(&self) { @@ -657,7 +657,7 @@ impl WindowMethods for Window { fn set_page_title(&self, _: Option) { } - fn set_page_load_data(&self, _: LoadData) { + fn set_page_url(&self, _: Url) { } fn load_end(&self) { diff --git a/ports/gonk/src/window.rs b/ports/gonk/src/window.rs index 818e62c6835..0e8bf8451a1 100644 --- a/ports/gonk/src/window.rs +++ b/ports/gonk/src/window.rs @@ -13,7 +13,6 @@ use layers::platform::surface::NativeGraphicsMetadata; use libc::c_int; use msg::compositor_msg::{ReadyState, PaintState}; use msg::constellation_msg::{Key, KeyModifiers}; -use msg::constellation_msg::LoadData; use std::cell::Cell; use std::sync::mpsc::{channel, Sender, Receiver}; use std::rc::Rc; @@ -22,6 +21,7 @@ use std::mem::size_of; use std::mem::zeroed; use std::ptr; use std::ffi::CString; +use url::Url; use util::cursor::Cursor; use util::geometry::ScreenPx; use gleam::gl; @@ -804,7 +804,7 @@ impl WindowMethods for Window { fn set_page_title(&self, _: Option) { } - fn set_page_load_data(&self, _: LoadData) { + fn set_page_url(&self, _: Url) { } fn load_end(&self) { From c816975750c44ea9217e10f1f13ed1e94068a9e8 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Fri, 20 Feb 2015 15:49:43 -0500 Subject: [PATCH 4/8] Documentation and cleanup. --- components/script/dom/bindings/utils.rs | 2 - components/script/dom/cssstyledeclaration.rs | 6 +- components/script/dom/document.rs | 2 +- components/script/dom/htmliframeelement.rs | 4 +- components/script/dom/window.rs | 6 +- components/script/page.rs | 18 +++--- components/script/script_task.rs | 58 ++++++++++++++++---- 7 files changed, 67 insertions(+), 29 deletions(-) diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 706c228c0b2..56c9b58288d 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -45,8 +45,6 @@ use js::JSFUN_CONSTRUCTOR; use js; /// Proxy handler for a WindowProxy. -#[allow(raw_pointer_derive)] -#[derive(Copy)] pub struct WindowProxyHandler(pub *const libc::c_void); #[allow(raw_pointer_derive)] diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs index e1be98f99af..b41adcb8b06 100644 --- a/components/script/dom/cssstyledeclaration.rs +++ b/components/script/dom/cssstyledeclaration.rs @@ -221,9 +221,8 @@ impl<'a> CSSStyleDeclarationMethods for JSRef<'a, CSSStyleDeclaration> { let owner = self.owner.root(); let window = window_from_node(owner.r()).root(); - let window = window.r(); let decl_block = parse_style_attribute(synthesized_declaration.as_slice(), - &window.get_url()); + &window.r().get_url()); // Step 7 if decl_block.normal.len() == 0 { @@ -269,9 +268,8 @@ impl<'a> CSSStyleDeclarationMethods for JSRef<'a, CSSStyleDeclaration> { let owner = self.owner.root(); let window = window_from_node(owner.r()).root(); - let window = window.r(); let decl_block = parse_style_attribute(property.as_slice(), - &window.get_url()); + &window.r().get_url()); let element: JSRef = ElementCast::from_ref(owner.r()); // Step 5 diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 1d611a6a643..6d559483e1e 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -386,7 +386,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { Some(root) => root, None => return None, }; - let root: JSRef = NodeCast::from_ref(root); + let root = NodeCast::from_ref(root); let win = self.window.root(); let address = match win.r().layout().hit_test(root.to_trusted_node_address(), *point) { Ok(HitTestResponse(node_address)) => { diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 3afcb2f4af2..35cc68c3da5 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -172,8 +172,8 @@ impl<'a> HTMLIFrameElementMethods for JSRef<'a, HTMLIFrameElement> { let window = window_from_node(self).root(); let window = window.r(); let children = window.page().children.borrow(); - children.iter().find(|child| { - let window = child.window().root(); + children.iter().find(|page| { + let window = page.window().root(); window.r().subpage() == Some(subpage_id) }).map(|page| page.window()) }) diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 30b1e6baaa0..28d7ac8b92d 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -56,7 +56,7 @@ use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD}; use std::cell::{Cell, Ref, RefMut}; use std::default::Default; use std::ffi::CString; -use std::mem::replace; +use std::mem; use std::num::Float; use std::rc::Rc; use std::sync::mpsc::{channel, Receiver}; @@ -543,7 +543,7 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { /// layout task has finished any pending request messages. fn join_layout(self) { let mut layout_join_port = self.layout_join_port.borrow_mut(); - if let Some(join_port) = replace(&mut *layout_join_port, None) { + if let Some(join_port) = mem::replace(&mut *layout_join_port, None) { match join_port.try_recv() { Err(Empty) => { info!("script: waiting on layout"); @@ -652,7 +652,7 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { } fn windowproxy_handler(self) -> WindowProxyHandler { - self.dom_static.windowproxy_handler + WindowProxyHandler(self.dom_static.windowproxy_handler.0) } fn get_next_subpage_id(self) -> SubpageId { diff --git a/components/script/page.rs b/components/script/page.rs index c080be4d16c..ffaa25c9e14 100644 --- a/components/script/page.rs +++ b/components/script/page.rs @@ -10,10 +10,11 @@ use dom::window::Window; use msg::constellation_msg::{PipelineId, SubpageId}; use util::smallvec::SmallVec; +use std::cell::Cell; use std::rc::Rc; use url::Url; -/// Encapsulates a handle to a frame and its associated layout information. +/// Encapsulates a handle to a frame in a frame tree. #[jstraceable] pub struct Page { /// Pipeline id associated with this page. @@ -27,9 +28,11 @@ pub struct Page { /// Cached copy of the most recent url loaded by the script, after all redirections. /// TODO(tkuehn): this currently does not follow any particular caching policy - /// and simply caches pages forever (!). The bool indicates if reflow is required - /// when reloading. - url: DOMRefCell<(Url, bool)>, + /// and simply caches pages forever (!). + url: Url, + + /// Indicates if reflow is required when reloading. + needs_reflow: Cell, // Child Pages. pub children: DOMRefCell>>, @@ -67,7 +70,8 @@ impl Page { id: id, subpage_id: subpage_id, frame: DOMRefCell::new(None), - url: DOMRefCell::new((url, true)), + url: url, + needs_reflow: Cell::new(true), children: DOMRefCell::new(vec!()), } } @@ -123,8 +127,8 @@ impl Iterator for PageIterator { impl Page { pub fn set_reflow_status(&self, status: bool) -> bool { - let old = (*self.url.borrow()).1; - (*self.url.borrow_mut()).1 = status; + let old = self.needs_reflow.get(); + self.needs_reflow.set(status); old } diff --git a/components/script/script_task.rs b/components/script/script_task.rs index de9f36fab24..61174b97e92 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)] @@ -83,16 +95,27 @@ use time::Tm; thread_local!(pub static STACK_ROOTS: Cell> = 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>, + /// 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, @@ -445,6 +468,7 @@ 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 { self.page.borrow().as_ref().unwrap().clone() } @@ -472,7 +496,7 @@ impl ScriptTask { { let page = self.page.borrow(); - if let Some(ref page) = page.as_ref() { + 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(); @@ -686,6 +710,7 @@ impl ScriptTask { 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, @@ -703,6 +728,7 @@ impl ScriptTask { let parent_window = parent_page.window().root(); let chan = layout_chan.downcast_ref::>().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()); @@ -736,6 +762,8 @@ impl ScriptTask { 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. @@ -790,8 +818,11 @@ 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, 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(); @@ -832,7 +863,9 @@ impl ScriptTask { let final_url = response.metadata.final_url.clone(); debug!("ScriptTask: loading {} on page {:?}", incomplete.url.serialize(), incomplete.pipeline_id); - let root_page_exists = self.page.borrow().is_none(); + // 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)| { @@ -858,18 +891,17 @@ impl ScriptTask { self.compositor.borrow_mut().set_ready_state(incomplete.pipeline_id, Loading); - let last_modified = response.metadata.headers.as_ref().and_then(|headers| { - headers.get().map(|&LastModified(ref tm)| tm.clone()) - }); - let cx = self.js_context.borrow(); let cx = cx.as_ref().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 { + 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()); @@ -894,11 +926,15 @@ impl ScriptTask { 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 { @@ -1097,6 +1133,8 @@ impl ScriptTask { 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); From 4972b623e18d2bdad62a8c617e6885eb9b8158b8 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 26 Feb 2015 00:19:27 -0500 Subject: [PATCH 5/8] Separate disposing of layout data from the GCing of the DOM object reflectors. Change the order of operations when shutting down the script task to ensure that Window globals aren't used after they've been GCed. --- components/script/dom/node.rs | 9 +++++++++ components/script/dom/window.rs | 3 +++ components/script/script_task.rs | 19 ++++++++----------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 7526c2490d2..a0486e6ee11 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -490,9 +490,18 @@ pub trait NodeHelpers<'a> { fn get_unique_id(self) -> String; fn summarize(self) -> NodeInfo; + + fn teardown(self); } impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { + fn teardown(self) { + self.layout_data.dispose(); + for kid in self.children() { + kid.teardown(); + } + } + /// Dumps the subtree rooted at this node, for debugging. fn dump(self) { self.dump_indent(0); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 28d7ac8b92d..097d8ed4ea0 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -461,6 +461,9 @@ impl<'a, T: Reflectable> ScriptHelpers for JSRef<'a, T> { impl<'a> WindowHelpers for JSRef<'a, Window> { fn clear_js_context(self) { + let document = self.Document().root(); + NodeCast::from_ref(document.r()).teardown(); + *self.js_context.borrow_mut() = None; *self.browser_context.borrow_mut() = None; } diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 61174b97e92..0538b2c0b72 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -74,7 +74,7 @@ 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}; @@ -845,14 +845,16 @@ impl ScriptTask { 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 if let Some(ref mut child_page) = page.remove(id) { - shut_down_layout(&*child_page, (*self.js_runtime).ptr, exit_type); + shut_down_layout(&*child_page, exit_type); } return false; } @@ -1167,7 +1169,7 @@ impl ScriptTask { } /// Shuts down layout for the given page tree. -fn shut_down_layout(page_tree: &Rc, rt: *mut JSRuntime, exit_type: PipelineExitType) { +fn shut_down_layout(page_tree: &Rc, exit_type: PipelineExitType) { let mut channels = vec!(); for page in page_tree.iter() { @@ -1182,19 +1184,14 @@ fn shut_down_layout(page_tree: &Rc, rt: *mut JSRuntime, exit_type: Pipelin } } - // Drop our references to the JSContext and DOM objects, potentially triggering a GC. + // Drop our references to the JSContext and DOM objects. for page in page_tree.iter() { let window = page.window().root(); window.r().clear_js_context(); + // Sever the connection between the global and the DOM tree page.set_frame(None); } - // Force a GC to make sure that our DOM reflectors are released before we tell - // layout to exit. - unsafe { - JS_GC(rt); - } - // Destroy the layout task. If there were node leaks, layout will now crash safely. for chan in channels.into_iter() { chan.send(layout_interface::Msg::ExitNow(exit_type)).ok(); From 1fb12b11f4beb2a544ba9ba1a4624aff857ad649 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Fri, 27 Feb 2015 19:17:55 -0500 Subject: [PATCH 6/8] Remove the newly-created Page from the tree if loading fails. --- components/script/script_task.rs | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 0538b2c0b72..59b6278a67d 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -909,6 +909,49 @@ impl ScriptTask { parent_page.children.borrow_mut().push(page.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, + } + } + + 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); + } + } + } + } + } + + let page_to_remove = if !root_page_exists { + PageToRemove::Root + } else { + PageToRemove::Child(incomplete.pipeline_id) + }; + let mut page_remover = AutoPageRemover::new(self, page_to_remove); + // Create the window and document objects. let window = Window::new(cx.clone(), page.clone(), @@ -1000,6 +1043,8 @@ impl ScriptTask { page_info)).unwrap(); } } + + page_remover.neuter(); } fn scroll_fragment_point(&self, pipeline_id: PipelineId, node: JSRef) { From 9a3d58032d53dce1627b2e443c131d45e2d3687d Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Mon, 2 Mar 2015 13:18:00 -0500 Subject: [PATCH 7/8] Update WPT expectations. --- .../document-metadata/the-base-element/base_multiple.html.ini | 2 +- .../the-iframe-element/move_iframe_in_dom_03.html.ini | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html.ini diff --git a/tests/wpt/metadata/html/semantics/document-metadata/the-base-element/base_multiple.html.ini b/tests/wpt/metadata/html/semantics/document-metadata/the-base-element/base_multiple.html.ini index 956ccc14150..df76225f1fe 100644 --- a/tests/wpt/metadata/html/semantics/document-metadata/the-base-element/base_multiple.html.ini +++ b/tests/wpt/metadata/html/semantics/document-metadata/the-base-element/base_multiple.html.ini @@ -1,5 +1,5 @@ [base_multiple.html] type: testharness + expected: ERROR [The attributes of the a element must be affected by the first base element] expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html.ini new file mode 100644 index 00000000000..0ec075cd574 --- /dev/null +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html.ini @@ -0,0 +1,3 @@ +[move_iframe_in_dom_03.html] + type: testharness + expected: TIMEOUT From b89339d05584e3bc27ba3a2cc58078e6e58a08d3 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Tue, 3 Mar 2015 17:56:55 -0500 Subject: [PATCH 8/8] Add missing crate to gonk. --- ports/gonk/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/gonk/src/main.rs b/ports/gonk/src/main.rs index c074ce632cd..fc53e3b7900 100644 --- a/ports/gonk/src/main.rs +++ b/ports/gonk/src/main.rs @@ -22,6 +22,7 @@ extern crate msg; extern crate gleam; extern crate layers; extern crate egl; +extern crate url; use util::opts; use servo::Browser;