diff options
Diffstat (limited to 'components/script/script_task.rs')
-rw-r--r-- | components/script/script_task.rs | 173 |
1 files changed, 115 insertions, 58 deletions
diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 5d26ef23010..7df8bef26aa 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -32,18 +32,21 @@ use dom::bindings::refcounted::{LiveDOMReferences, Trusted, TrustedReference}; use dom::bindings::structuredclone::StructuredCloneData; use dom::bindings::trace::{JSTraceable, trace_collections, RootedVec}; use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap}; -use dom::document::{Document, IsHTMLDocument, DocumentHelpers, DocumentProgressHandler, DocumentProgressTask, DocumentSource, MouseEventType}; +use dom::document::{Document, IsHTMLDocument, DocumentHelpers, DocumentProgressHandler, + DocumentProgressTask, DocumentSource, MouseEventType}; use dom::element::{Element, AttributeHandlers}; use dom::event::{Event, EventHelpers, EventBubbles, EventCancelable}; use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementHelpers}; use dom::uievent::UIEvent; use dom::eventtarget::EventTarget; use dom::node::{Node, NodeHelpers, NodeDamage, window_from_node}; +use dom::servohtmlparser::{ServoHTMLParser, ParserContext}; use dom::window::{Window, WindowHelpers, ScriptHelpers, ReflowReason}; use dom::worker::TrustedWorkerAddress; -use parse::html::{HTMLInput, parse_html}; +use parse::html::{ParseContext, parse_html}; use layout_interface::{ScriptLayoutChan, LayoutChan, ReflowGoal, ReflowQueryType}; use layout_interface; +use network_listener::NetworkListener; use page::{Page, IterablePage, Frame}; use timers::TimerId; use devtools; @@ -65,13 +68,13 @@ use msg::constellation_msg::{ConstellationChan, FocusType}; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId}; use msg::constellation_msg::{Failure, WindowSizeData, PipelineExitType}; use msg::constellation_msg::Msg as ConstellationMsg; -use net_traits::{ResourceTask, LoadResponse, LoadConsumer, ControlMsg}; +use net_traits::{ResourceTask, LoadConsumer, ControlMsg, Metadata}; use net_traits::LoadData as NetLoadData; use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask, ImageCacheResult}; use net_traits::storage_task::StorageTask; use string_cache::Atom; use util::str::DOMString; -use util::task::{spawn_named, spawn_named_with_send_on_failure}; +use util::task::spawn_named_with_send_on_failure; use util::task_state; use geom::Rect; @@ -93,6 +96,7 @@ use std::option::Option; use std::ptr; use std::rc::Rc; use std::result::Result; +use std::sync::{Arc, Mutex}; use std::sync::mpsc::{channel, Sender, Receiver, Select}; use time::Tm; @@ -189,8 +193,6 @@ pub enum ScriptMsg { MainThreadRunnableMsg(Box<MainThreadRunnable+Send>), /// A DOM object's last pinned reference was removed (dispatched to all tasks). RefcountCleanup(TrustedReference), - /// The final network response for a page has arrived. - PageFetchComplete(PipelineId, Option<SubpageId>, LoadResponse), /// Notify a document that all pending loads are complete. DocumentLoadsComplete(PipelineId), } @@ -318,6 +320,9 @@ pub struct ScriptTask { js_runtime: Rc<Runtime>, mouse_over_targets: DOMRefCell<Vec<JS<Node>>>, + + /// List of pipelines that have been owned and closed by this script task. + closed_pipelines: RefCell<HashSet<PipelineId>>, } /// In the event of task failure, all data on the stack runs its destructor. However, there @@ -387,7 +392,7 @@ impl ScriptTaskFactory for ScriptTask { 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 || { + spawn_named_with_send_on_failure(format!("ScriptTask {:?}", id), task_state::SCRIPT, move || { let script_task = ScriptTask::new(box compositor as Box<ScriptListener>, script_port, NonWorkerScriptChan(script_chan), @@ -425,6 +430,21 @@ unsafe extern "C" fn debug_gc_callback(_rt: *mut JSRuntime, status: JSGCStatus) } impl ScriptTask { + pub fn page_fetch_complete(id: PipelineId, subpage: Option<SubpageId>, metadata: Metadata) + -> Option<Temporary<ServoHTMLParser>> { + SCRIPT_TASK_ROOT.with(|root| { + let script_task = unsafe { &*root.borrow().unwrap() }; + script_task.handle_page_fetch_complete(id, subpage, metadata) + }) + } + + pub fn parsing_complete(id: PipelineId) { + SCRIPT_TASK_ROOT.with(|root| { + let script_task = unsafe { &*root.borrow().unwrap() }; + script_task.handle_parsing_complete(id); + }); + } + pub fn process_event(msg: ScriptMsg) { SCRIPT_TASK_ROOT.with(|root| { if let Some(script_task) = *root.borrow() { @@ -495,7 +515,8 @@ impl ScriptTask { devtools_marker_sender: RefCell::new(None), js_runtime: Rc::new(runtime), - mouse_over_targets: DOMRefCell::new(vec!()) + mouse_over_targets: DOMRefCell::new(vec!()), + closed_pipelines: RefCell::new(HashSet::new()), } } @@ -762,8 +783,6 @@ impl ScriptTask { runnable.handler(self), ScriptMsg::RefcountCleanup(addr) => LiveDOMReferences::cleanup(self.get_cx(), addr), - ScriptMsg::PageFetchComplete(id, subpage, response) => - self.handle_page_fetch_complete(id, subpage, response), ScriptMsg::DocumentLoadsComplete(id) => self.handle_loads_complete(id), } @@ -796,7 +815,7 @@ impl ScriptTask { } fn handle_msg_from_image_cache(&self, msg: ImageCacheResult) { - msg.responder.unwrap().respond(msg.image); + msg.responder.unwrap().respond(msg.image_response); } fn handle_webdriver_msg(&self, pipeline_id: PipelineId, msg: WebDriverScriptCommand) { @@ -1028,11 +1047,15 @@ impl ScriptTask { fn handle_reflow_complete_msg(&self, pipeline_id: PipelineId, reflow_id: u32) { debug!("Script: Reflow {:?} complete for {:?}", reflow_id, pipeline_id); let page = self.root_page(); - let page = page.find(pipeline_id).expect( - "ScriptTask: received a load message for a layout channel that is not associated \ - with this script task. This is a bug."); - let window = page.window().root(); - window.r().handle_reflow_complete_msg(reflow_id); + match page.find(pipeline_id) { + Some(page) => { + let window = page.window().root(); + window.r().handle_reflow_complete_msg(reflow_id); + } + None => { + assert!(self.closed_pipelines.borrow().contains(&pipeline_id)); + } + } } /// Window was resized, but this script was not active, so don't reflow yet @@ -1062,13 +1085,22 @@ impl ScriptTask { /// We have received notification that the response associated with a load has completed. /// Kick off the document and frame tree creation process using the result. fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>, - response: LoadResponse) { - // Any notification received should refer to an existing, in-progress load that is tracked. + metadata: Metadata) -> Option<Temporary<ServoHTMLParser>> { let idx = self.incomplete_loads.borrow().iter().position(|load| { load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage - }).unwrap(); - let load = self.incomplete_loads.borrow_mut().remove(idx); - self.load(response, load); + }); + // The matching in progress load structure may not exist if + // the pipeline exited before the page load completed. + match idx { + Some(idx) => { + let load = self.incomplete_loads.borrow_mut().remove(idx); + Some(self.load(metadata, load)) + } + None => { + assert!(self.closed_pipelines.borrow().contains(&id)); + None + } + } } /// Handles a request for the window title. @@ -1081,11 +1113,31 @@ impl ScriptTask { /// 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 self.page.borrow().is_none() { - // The root page doesn't even exist yet, so it - // is safe to exit this script task. - // TODO(gw): This probably leaks resources! - return true; + self.closed_pipelines.borrow_mut().insert(id); + + // Check if the exit message is for an in progress load. + let idx = self.incomplete_loads.borrow().iter().position(|load| { + load.pipeline_id == id + }); + + if let Some(idx) = idx { + let load = self.incomplete_loads.borrow_mut().remove(idx); + + // Tell the layout task to begin shutting down, and wait until it + // processed this message. + let (response_chan, response_port) = channel(); + let LayoutChan(chan) = load.layout_chan; + if chan.send(layout_interface::Msg::PrepareToExit(response_chan)).is_ok() { + debug!("shutting down layout for page {:?}", id); + response_port.recv().unwrap(); + chan.send(layout_interface::Msg::ExitNow(exit_type)).ok(); + } + + let has_pending_loads = self.incomplete_loads.borrow().len() > 0; + let has_root_page = self.page.borrow().is_some(); + + // Exit if no pending loads and no root page + return !has_pending_loads && !has_root_page; } // If root is being exited, shut down all pages @@ -1113,8 +1165,8 @@ 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, response: LoadResponse, incomplete: InProgressLoad) { - let final_url = response.metadata.final_url.clone(); + fn load(&self, metadata: Metadata, incomplete: InProgressLoad) -> Temporary<ServoHTMLParser> { + let final_url = metadata.final_url.clone(); debug!("ScriptTask: loading {} on page {:?}", incomplete.url.serialize(), incomplete.pipeline_id); // We should either be initializing a root page or loading a child page of an @@ -1214,11 +1266,11 @@ impl ScriptTask { incomplete.parent_info, incomplete.window_size).root(); - let last_modified: Option<DOMString> = response.metadata.headers.as_ref().and_then(|headers| { + let last_modified: Option<DOMString> = metadata.headers.as_ref().and_then(|headers| { headers.get().map(|&LastModified(HttpDate(ref tm))| dom_last_modified(tm)) }); - let content_type = match response.metadata.content_type { + let content_type = match metadata.content_type { Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => Some("text/plain".to_owned()), _ => None }; @@ -1229,7 +1281,7 @@ impl ScriptTask { }; let loader = DocumentLoader::new_with_task(self.resource_task.clone(), Some(notifier_data), - Some(final_url.clone())); + Some(incomplete.url.clone())); let document = Document::new(window.r(), Some(final_url.clone()), IsHTMLDocument::HTMLDocument, @@ -1253,14 +1305,17 @@ impl ScriptTask { let jsval = window.r().evaluate_js_on_global_with_result(evalstr); let strval = FromJSValConvertible::from_jsval(self.get_cx(), jsval, StringificationBehavior::Empty); - HTMLInput::InputString(strval.unwrap_or("".to_owned())) + strval.unwrap_or("".to_owned()) } else { - HTMLInput::InputUrl(response) + "".to_owned() }; - parse_html(document.r(), parse_input, &final_url, None); - self.handle_parsing_complete(incomplete.pipeline_id); + parse_html(document.r(), parse_input, &final_url, + ParseContext::Owner(Some(incomplete.pipeline_id))); + page_remover.neuter(); + + document.r().get_current_parser().unwrap() } fn notify_devtools(&self, title: DOMString, url: Url, ids: (PipelineId, Option<WorkerId>)) { @@ -1362,7 +1417,11 @@ impl ScriptTask { } } - fn handle_mouse_event(&self, pipeline_id: PipelineId, mouse_event_type: MouseEventType, button: MouseButton, point: Point2D<f32>) { + fn handle_mouse_event(&self, + pipeline_id: PipelineId, + mouse_event_type: MouseEventType, + button: MouseButton, + point: Point2D<f32>) { let _marker; if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { _marker = AutoDOMEventMarker::new(self); @@ -1446,29 +1505,26 @@ impl ScriptTask { let script_chan = self.chan.clone(); let resource_task = self.resource_task.clone(); - spawn_named(format!("fetch for {:?}", load_data.url.serialize()), move || { - if load_data.url.scheme == "javascript" { - load_data.url = Url::parse("about:blank").unwrap(); - } + let context = Arc::new(Mutex::new(ParserContext::new(id, subpage, script_chan.clone(), + load_data.url.clone()))); + let listener = box NetworkListener { + context: context, + script_chan: script_chan.clone(), + }; - 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, - pipeline_id: Some(id), - }, LoadConsumer::Channel(input_chan))).unwrap(); - - let load_response = input_port.recv().unwrap(); - if script_chan.send(ScriptMsg::PageFetchComplete(id, subpage, load_response)).is_err() { - // TODO(gw): This should be handled by aborting - // the load before the script task exits. - debug!("PageFetchComplete: script channel has exited"); - } - }); + if load_data.url.scheme == "javascript" { + load_data.url = Url::parse("about:blank").unwrap(); + } + + 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, + pipeline_id: Some(id), + }, LoadConsumer::Listener(listener))).unwrap(); self.incomplete_loads.borrow_mut().push(incomplete); } @@ -1507,6 +1563,7 @@ impl ScriptTask { // Kick off the initial reflow of the page. debug!("kicking off initial reflow of {:?}", final_url); + document.r().content_changed(NodeCast::from_ref(document.r()), NodeDamage::OtherNodeDamage); let window = window_from_node(document.r()).root(); |