diff options
Diffstat (limited to 'components/script/script_task.rs')
-rw-r--r-- | components/script/script_task.rs | 199 |
1 files changed, 147 insertions, 52 deletions
diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 3b17c49e45c..2db2ef24a80 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -17,7 +17,7 @@ //! a page runs its course and the script task returns to processing events in the main event //! loop. -#![allow(unsafe_blocks)] +#![allow(unsafe_code)] use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; @@ -25,10 +25,10 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLIFr use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::conversions::StringificationBehavior; use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, RootedReference}; -use dom::bindings::js::{RootCollection, RootCollectionPtr}; +use dom::bindings::js::{RootCollection, RootCollectionPtr, Unrooted}; use dom::bindings::refcounted::{LiveDOMReferences, Trusted, TrustedReference}; use dom::bindings::structuredclone::StructuredCloneData; -use dom::bindings::trace::JSTraceable; +use dom::bindings::trace::{JSTraceable, trace_collections}; use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap}; use dom::document::{Document, IsHTMLDocument, DocumentHelpers, DocumentProgressHandler, DocumentProgressTask, DocumentSource}; use dom::element::{Element, AttributeHandlers}; @@ -57,8 +57,8 @@ use script_traits::ScriptTaskFactory; use msg::compositor_msg::ReadyState::{FinishedLoading, Loading, PerformingLayout}; use msg::compositor_msg::{LayerId, ScriptListener}; use msg::constellation_msg::{ConstellationChan}; -use msg::constellation_msg::{LoadData, PipelineId, SubpageId}; -use msg::constellation_msg::{Failure, Msg, WindowSizeData, PipelineExitType}; +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::image_cache_task::ImageCacheTask; use net::resource_task::{ResourceTask, ControlMsg, LoadResponse}; @@ -116,7 +116,7 @@ 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)>, + parent_info: Option<(PipelineId, SubpageId)>, /// The current window size associated with this pipeline. window_size: Option<WindowSizeData>, /// Channel to the layout task associated with this pipeline. @@ -130,13 +130,13 @@ struct InProgressLoad { impl InProgressLoad { /// Create a new InProgressLoad object. fn new(id: PipelineId, - subpage_id: Option<(PipelineId, SubpageId)>, + parent_info: Option<(PipelineId, SubpageId)>, layout_chan: LayoutChan, window_size: Option<WindowSizeData>, url: Url) -> InProgressLoad { InProgressLoad { pipeline_id: id, - subpage_id: subpage_id, + parent_info: parent_info, layout_chan: layout_chan, window_size: window_size, clip_rect: None, @@ -155,6 +155,10 @@ pub trait Runnable { fn handler(self: Box<Self>); } +pub trait MainThreadRunnable { + fn handler(self: Box<Self>, script_task: &ScriptTask); +} + /// Messages used to control script event loops, such as ScriptTask and /// DedicatedWorkerGlobalScope. pub enum ScriptMsg { @@ -176,6 +180,8 @@ pub enum ScriptMsg { DOMMessage(StructuredCloneData), /// Generic message that encapsulates event handling. RunnableMsg(Box<Runnable+Send>), + /// Generic message for running tasks in the ScriptTask + 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. @@ -303,14 +309,15 @@ impl<'a> ScriptMemoryFailsafe<'a> { #[unsafe_destructor] impl<'a> Drop for ScriptMemoryFailsafe<'a> { + #[allow(unrooted_must_root)] fn drop(&mut self) { match self.owner { Some(owner) => { unsafe { let page = owner.page.borrow_for_script_deallocation(); for page in page.iter() { - let window = page.window().root(); - window.r().clear_js_context_for_script_deallocation(); + let window = Unrooted::from_temporary(page.window()); + (*window.unsafe_get()).clear_js_context_for_script_deallocation(); } *owner.js_context.borrow_for_script_deallocation() = None; } @@ -332,6 +339,7 @@ impl ScriptTaskFactory for ScriptTask { fn create<C>(_phantom: Option<&mut ScriptTask>, id: PipelineId, + parent_info: Option<(PipelineId, SubpageId)>, compositor: C, layout_chan: &OpaqueScriptLayoutChannel, control_chan: ScriptControlChan, @@ -365,7 +373,7 @@ impl ScriptTaskFactory for ScriptTask { }); let mut failsafe = ScriptMemoryFailsafe::new(&script_task); - let new_load = InProgressLoad::new(id, None, layout_chan, window_size, + let new_load = InProgressLoad::new(id, parent_info, layout_chan, window_size, load_data.url.clone()); script_task.start_page_load(new_load, load_data); @@ -453,6 +461,10 @@ impl ScriptTask { !ptr.is_null() }); + + unsafe { + JS_SetExtraGCRootsTracer((*js_runtime).ptr, Some(trace_collections), ptr::null_mut()); + } // Unconstrain the runtime's threshold on nominal heap size, to avoid // triggering GC too often if operating continuously near an arbitrary // finite threshold. This leaves the maximum-JS_malloc-bytes threshold @@ -566,13 +578,15 @@ impl ScriptTask { } }; - // Squash any pending resize and reflow events in the queue. + // Squash any pending resize, reflow, and mouse-move events in the queue. + let mut mouse_move_event_index = None; loop { match event { // This has to be handled before the ResizeMsg below, // otherwise the page may not have been added to the // child list yet, causing the find() to fail. - MixedMessage::FromConstellation(ConstellationControlMsg::AttachLayout(new_layout_info)) => { + MixedMessage::FromConstellation(ConstellationControlMsg::AttachLayout( + new_layout_info)) => { self.handle_new_layout(new_layout_info); } MixedMessage::FromConstellation(ConstellationControlMsg::Resize(id, size)) => { @@ -581,6 +595,19 @@ impl ScriptTask { MixedMessage::FromConstellation(ConstellationControlMsg::Viewport(id, rect)) => { self.handle_viewport(id, rect); } + MixedMessage::FromConstellation(ConstellationControlMsg::SendEvent( + _, + MouseMoveEvent(_))) => { + match mouse_move_event_index { + None => { + mouse_move_event_index = Some(sequential.len()); + sequential.push(event); + } + Some(index) => { + sequential[index] = event + } + } + } _ => { sequential.push(event); } @@ -641,7 +668,17 @@ impl ScriptTask { ConstellationControlMsg::Freeze(pipeline_id) => self.handle_freeze_msg(pipeline_id), ConstellationControlMsg::Thaw(pipeline_id) => - self.handle_thaw_msg(pipeline_id) + self.handle_thaw_msg(pipeline_id), + ConstellationControlMsg::MozBrowserEventMsg(parent_pipeline_id, + subpage_id, + event) => + self.handle_mozbrowser_event_msg(parent_pipeline_id, + subpage_id, + event), + ConstellationControlMsg::UpdateSubpageId(containing_pipeline_id, + old_subpage_id, + new_subpage_id) => + self.handle_update_subpage_id(containing_pipeline_id, old_subpage_id, new_subpage_id), } } @@ -661,6 +698,8 @@ impl ScriptTask { panic!("unexpected message"), ScriptMsg::RunnableMsg(runnable) => runnable.handler(), + ScriptMsg::MainThreadRunnableMsg(runnable) => + runnable.handler(self), ScriptMsg::RefcountCleanup(addr) => LiveDOMReferences::cleanup(self.get_cx(), addr), ScriptMsg::PageFetchComplete(id, subpage, response) => @@ -783,8 +822,50 @@ impl ScriptTask { window.r().thaw(); } + /// Handles a mozbrowser event, for example see: + /// https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart + fn handle_mozbrowser_event_msg(&self, + parent_pipeline_id: PipelineId, + subpage_id: SubpageId, + event: MozBrowserEvent) { + let borrowed_page = self.root_page(); + + let frame_element = borrowed_page.find(parent_pipeline_id).and_then(|page| { + let doc = page.document().root(); + let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); + + doc.traverse_preorder() + .filter_map(HTMLIFrameElementCast::to_ref) + .find(|node| node.subpage_id() == Some(subpage_id)) + .map(Temporary::from_rooted) + }).root(); + + if let Some(frame_element) = frame_element { + frame_element.r().dispatch_mozbrowser_event(event); + } + } + + fn handle_update_subpage_id(&self, + containing_pipeline_id: PipelineId, + old_subpage_id: SubpageId, + new_subpage_id: SubpageId) { + let borrowed_page = self.root_page(); + + let frame_element = borrowed_page.find(containing_pipeline_id).and_then(|page| { + let doc = page.document().root(); + let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); + + doc.traverse_preorder() + .filter_map(HTMLIFrameElementCast::to_ref) + .find(|node| node.subpage_id() == Some(old_subpage_id)) + .map(Temporary::from_rooted) + }).root(); + + frame_element.unwrap().r().update_subpage_id(new_subpage_id); + } + /// Handles a notification that reflow completed. - fn handle_reflow_complete_msg(&self, pipeline_id: PipelineId, reflow_id: uint) { + 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( @@ -831,8 +912,8 @@ impl ScriptTask { fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>, response: LoadResponse) { // Any notification received should refer to an existing, in-progress load that is tracked. - let idx = self.incomplete_loads.borrow().iter().position(|&:load| { - load.pipeline_id == id && load.subpage_id.map(|sub| sub.1) == subpage + 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); @@ -876,27 +957,31 @@ impl ScriptTask { // We should either be initializing a root page or loading a child page of an // existing one. let root_page_exists = self.page.borrow().is_some(); - assert!(incomplete.subpage_id.is_none() || root_page_exists); - - let frame_element = incomplete.subpage_id.and_then(|(parent_id, subpage_id)| { - let borrowed_page = self.root_page(); - // In the case a parent id exists but the matching page - // cannot be found, this means the page exists in a different - // script task (due to origin) so it shouldn't be returned. - // TODO: window.parent will continue to return self in that - // case, which is wrong. We should be returning an object that - // denies access to most properties (per - // https://github.com/servo/servo/issues/3939#issuecomment-62287025). - borrowed_page.find(parent_id).and_then(|page| { - let doc = page.document().root(); - let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); - doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_ref) - .find(|node| node.subpage_id() == Some(subpage_id)) - .map(ElementCast::from_ref) - .map(Temporary::from_rooted) - }) + let frame_element = incomplete.parent_info.and_then(|(parent_id, subpage_id)| { + // The root page may not exist yet, if the parent of this frame + // exists in a different script task. + let borrowed_page = self.page.borrow(); + + // 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.as_ref().and_then(|borrowed_page| { + borrowed_page.find(parent_id).and_then(|page| { + let doc = page.document().root(); + let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); + + doc.traverse_preorder() + .filter_map(HTMLIFrameElementCast::to_ref) + .find(|node| node.subpage_id() == Some(subpage_id)) + .map(ElementCast::from_ref) + .map(Temporary::from_rooted) + }) + }) }).root(); self.compositor.borrow_mut().set_ready_state(incomplete.pipeline_id, Loading); @@ -909,9 +994,11 @@ impl ScriptTask { 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 { + } else if let Some((parent, _)) = incomplete.parent_info { // We have a new child frame. let parent_page = self.root_page(); + // TODO(gw): This find will fail when we are sharing script tasks + // between cross origin iframes in the same TLD. parent_page.find(parent).expect("received load for subpage with missing parent"); parent_page.children.borrow_mut().push(page.clone()); } @@ -972,7 +1059,7 @@ impl ScriptTask { self.constellation_chan.clone(), incomplete.layout_chan, incomplete.pipeline_id, - incomplete.subpage_id.map(|s| s.1), + incomplete.parent_info, incomplete.window_size).root(); let last_modified: Option<DOMString> = response.metadata.headers.as_ref().and_then(|headers| { @@ -1002,7 +1089,7 @@ impl ScriptTask { window: JS::from_rooted(window.r()), })); - let is_javascript = incomplete.url.scheme.as_slice() == "javascript"; + let is_javascript = incomplete.url.scheme == "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); @@ -1013,7 +1100,7 @@ impl ScriptTask { HTMLInput::InputUrl(response) }; - parse_html(document.r(), parse_input, &final_url); + parse_html(document.r(), parse_input, &final_url, None); document.r().set_ready_state(DocumentReadyState::Interactive); self.compositor.borrow_mut().set_ready_state(incomplete.pipeline_id, PerformingLayout); @@ -1029,7 +1116,7 @@ impl ScriptTask { // https://html.spec.whatwg.org/multipage/#the-end step 4 let addr: Trusted<Document> = Trusted::new(self.get_cx(), document.r(), self.chan.clone()); - let handler = Box::new(DocumentProgressHandler::new(addr.clone(), DocumentProgressTask::DOMContentLoaded)); + let handler = box DocumentProgressHandler::new(addr.clone(), DocumentProgressTask::DOMContentLoaded); self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); // We have no concept of a document loader right now, so just dispatch the @@ -1037,7 +1124,7 @@ impl ScriptTask { // the initial load. // https://html.spec.whatwg.org/multipage/#the-end step 7 - let handler = Box::new(DocumentProgressHandler::new(addr, DocumentProgressTask::Load)); + let handler = box DocumentProgressHandler::new(addr, DocumentProgressTask::Load); self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); window.r().set_fragment_name(final_url.fragment.clone()); @@ -1046,20 +1133,24 @@ impl ScriptTask { chan.send(ConstellationMsg::LoadComplete).unwrap(); // Notify devtools that a new script global exists. + self.notify_devtools(document.r().Title(), final_url, (incomplete.pipeline_id, None)); + + page_remover.neuter(); + } + + fn notify_devtools(&self, title: DOMString, url: Url, ids: (PipelineId, Option<WorkerId>)) { match self.devtools_chan { None => {} Some(ref chan) => { let page_info = DevtoolsPageInfo { - title: document.r().Title(), - url: final_url + title: title, + url: url, }; - chan.send(DevtoolsControlMsg::NewGlobal(incomplete.pipeline_id, + chan.send(DevtoolsControlMsg::NewGlobal(ids, self.devtools_sender.clone(), page_info)).unwrap(); } } - - page_remover.neuter(); } fn scroll_fragment_point(&self, pipeline_id: PipelineId, node: JSRef<Element>) { @@ -1212,20 +1303,24 @@ impl ScriptTask { fn handle_reflow_event(&self, pipeline_id: PipelineId) { debug!("script got reflow event"); let page = get_page(&self.root_page(), pipeline_id); - self.force_reflow(&*page, ReflowReason::ReceivedReflowEvent); + let document = page.document().root(); + let window = window_from_node(document.r()).root(); + window.r().reflow(ReflowGoal::ForDisplay, + ReflowQueryType::NoQuery, + ReflowReason::ReceivedReflowEvent); } /// Initiate a non-blocking fetch for a specified resource. Stores the InProgressLoad /// argument until a notification is received that the fetch is complete. fn start_page_load(&self, incomplete: InProgressLoad, mut load_data: LoadData) { let id = incomplete.pipeline_id.clone(); - let subpage = incomplete.subpage_id.clone().map(|p| p.1); + let subpage = incomplete.parent_info.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.serialize()), move || { - if load_data.url.scheme.as_slice() == "javascript" { + if load_data.url.scheme == "javascript" { load_data.url = Url::parse("about:blank").unwrap(); } @@ -1294,5 +1389,5 @@ pub fn get_page(page: &Rc<Page>, pipeline_id: PipelineId) -> Rc<Page> { } fn dom_last_modified(tm: &Tm) -> String { - format!("{}", tm.to_local().strftime("%m/%d/%Y %H:%M:%S").unwrap()) + tm.to_local().strftime("%m/%d/%Y %H:%M:%S").unwrap().to_string() } |