diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index f5a5983fa9b..485c828cc9d 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -95,7 +95,7 @@ use profile_traits::mem; use profile_traits::time; use rand::{Rng, SeedableRng, StdRng, random}; use script_traits::{AnimationState, AnimationTickType, CompositorEvent}; -use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg}; +use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext}; use script_traits::{DocumentState, LayoutControlMsg, LoadData}; use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerEventRequest}; use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory}; @@ -975,13 +975,11 @@ impl Constellation debug!("constellation got set visibility change complete message"); self.handle_visibility_change_complete(pipeline_id, visible); } - FromScriptMsg::RemoveIFrame(pipeline_id, sender) => { + FromScriptMsg::RemoveIFrame(frame_id, sender) => { debug!("constellation got remove iframe message"); - self.handle_remove_iframe_msg(pipeline_id); - if let Some(sender) = sender { - if let Err(e) = sender.send(()) { - warn!("Error replying to remove iframe ({})", e); - } + let removed_pipeline_ids = self.handle_remove_iframe_msg(frame_id); + if let Err(e) = sender.send(removed_pipeline_ids) { + warn!("Error replying to remove iframe ({})", e); } } FromScriptMsg::NewFavicon(url) => { @@ -1126,7 +1124,7 @@ impl Constellation debug!("Removing pending frame {}.", pending.frame_id); self.close_frame(pending.frame_id, ExitPipelineMode::Normal); debug!("Removing pending pipeline {}.", pending.new_pipeline_id); - self.close_pipeline(pending.new_pipeline_id, ExitPipelineMode::Normal); + self.close_pipeline(pending.new_pipeline_id, DiscardBrowsingContext::Yes, ExitPipelineMode::Normal); } // In case there are frames which weren't attached to the frame tree, we close them. @@ -1140,7 +1138,7 @@ impl Constellation let pipeline_ids: Vec = self.pipelines.keys().cloned().collect(); for pipeline_id in pipeline_ids { debug!("Removing detached pipeline {}.", pipeline_id); - self.close_pipeline(pipeline_id, ExitPipelineMode::Normal); + self.close_pipeline(pipeline_id, DiscardBrowsingContext::Yes, ExitPipelineMode::Normal); } } @@ -1234,7 +1232,7 @@ impl Constellation let parent_info = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.parent_info)); let window_size = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.size)); - self.close_frame_children(top_level_frame_id, ExitPipelineMode::Force); + self.close_frame_children(top_level_frame_id, DiscardBrowsingContext::No, ExitPipelineMode::Force); let failure_url = ServoUrl::parse("about:failure").expect("infallible"); @@ -1805,20 +1803,14 @@ impl Constellation self.focus_parent_pipeline(pipeline_id); } - fn handle_remove_iframe_msg(&mut self, pipeline_id: PipelineId) { - let frame_id = self.pipelines.get(&pipeline_id).map(|pipeline| pipeline.frame_id); - match frame_id { - Some(frame_id) => { - // This iframe has already loaded and been added to the frame tree. - self.close_frame(frame_id, ExitPipelineMode::Normal); - } - None => { - // This iframe is currently loading / painting for the first time. - // In this case, it doesn't exist in the frame tree, but the pipeline - // still needs to be shut down. - self.close_pipeline(pipeline_id, ExitPipelineMode::Normal); - } - } + fn handle_remove_iframe_msg(&mut self, frame_id: FrameId) -> Vec { + let result = self.full_frame_tree_iter(frame_id) + .flat_map(|frame| frame.next.iter().chain(frame.prev.iter()) + .filter_map(|entry| entry.pipeline_id) + .chain(once(frame.pipeline_id))) + .collect(); + self.close_frame(frame_id, ExitPipelineMode::Normal); + result } fn handle_set_visible_msg(&mut self, pipeline_id: PipelineId, visible: bool) { @@ -2118,7 +2110,7 @@ impl Constellation }; if let Some(evicted_id) = evicted_id { - self.close_pipeline(evicted_id, ExitPipelineMode::Normal); + self.close_pipeline(evicted_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); } if new_frame { @@ -2344,7 +2336,7 @@ impl Constellation }; for entry in evicted { if let Some(pipeline_id) = entry.pipeline_id { - self.close_pipeline(pipeline_id, ExitPipelineMode::Normal); + self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); } } } @@ -2357,7 +2349,7 @@ impl Constellation .and_then(|frame| self.pipelines.get(&frame.pipeline_id)) .and_then(|pipeline| pipeline.parent_info); - self.close_frame_children(frame_id, exit_mode); + self.close_frame_children(frame_id, DiscardBrowsingContext::Yes, exit_mode); self.event_loops.remove(&frame_id); if self.frames.remove(&frame_id).is_none() { @@ -2375,7 +2367,7 @@ impl Constellation } // Close the children of a frame - fn close_frame_children(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) { + fn close_frame_children(&mut self, frame_id: FrameId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) { debug!("Closing frame children {}.", frame_id); // Store information about the pipelines to be closed. Then close the // pipelines, before removing ourself from the frames hash map. This @@ -2393,18 +2385,18 @@ impl Constellation } for pipeline_id in pipelines_to_close { - self.close_pipeline(pipeline_id, exit_mode); + self.close_pipeline(pipeline_id, dbc, exit_mode); } debug!("Closed frame children {}.", frame_id); } // Close all pipelines at and beneath a given frame - fn close_pipeline(&mut self, pipeline_id: PipelineId, exit_mode: ExitPipelineMode) { + fn close_pipeline(&mut self, pipeline_id: PipelineId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) { debug!("Closing pipeline {:?}.", pipeline_id); // Store information about the frames to be closed. Then close the // frames, before removing ourself from the pipelines hash map. This - // ordering is vital - so that if close_frames() ends up closing + // ordering is vital - so that if close_frame() ends up closing // any child pipelines, they can be removed from the parent pipeline correctly. let frames_to_close = { let mut frames_to_close = vec!(); @@ -2438,8 +2430,8 @@ impl Constellation // Inform script, compositor that this pipeline has exited. match exit_mode { - ExitPipelineMode::Normal => pipeline.exit(), - ExitPipelineMode::Force => pipeline.force_exit(), + ExitPipelineMode::Normal => pipeline.exit(dbc), + ExitPipelineMode::Force => pipeline.force_exit(dbc), } debug!("Closed pipeline {:?}.", pipeline_id); } @@ -2463,7 +2455,7 @@ impl Constellation // Note that we deliberately do not do any of the tidying up // associated with closing a pipeline. The constellation should cope! warn!("Randomly closing pipeline {}.", pipeline_id); - pipeline.force_exit(); + pipeline.force_exit(DiscardBrowsingContext::No); } } } diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 10aed362427..aa7d3461615 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -20,7 +20,7 @@ use net_traits::{IpcSend, ResourceThreads}; use net_traits::image_cache_thread::ImageCacheThread; use profile_traits::mem as profile_mem; use profile_traits::time; -use script_traits::{ConstellationControlMsg, InitialScriptState}; +use script_traits::{ConstellationControlMsg, DiscardBrowsingContext, InitialScriptState}; use script_traits::{LayoutControlMsg, LayoutMsg, LoadData, MozBrowserEvent}; use script_traits::{NewLayoutInfo, SWManagerMsg, SWManagerSenders, ScriptMsg}; use script_traits::{ScriptThreadFactory, TimerEventRequest, WindowSizeData}; @@ -328,7 +328,7 @@ impl Pipeline { /// A normal exit of the pipeline, which waits for the compositor, /// and delegates layout shutdown to the script thread. - pub fn exit(&self) { + pub fn exit(&self, discard_bc: DiscardBrowsingContext) { debug!("pipeline {:?} exiting", self.id); // The compositor wants to know when pipelines shut down too. @@ -345,15 +345,17 @@ impl Pipeline { // Script thread handles shutting down layout, and layout handles shutting down the painter. // For now, if the script thread has failed, we give up on clean shutdown. - if let Err(e) = self.event_loop.send(ConstellationControlMsg::ExitPipeline(self.id)) { + let msg = ConstellationControlMsg::ExitPipeline(self.id, discard_bc); + if let Err(e) = self.event_loop.send(msg) { warn!("Sending script exit message failed ({}).", e); } } /// A forced exit of the shutdown, which does not wait for the compositor, /// or for the script thread to shut down layout. - pub fn force_exit(&self) { - if let Err(e) = self.event_loop.send(ConstellationControlMsg::ExitPipeline(self.id)) { + pub fn force_exit(&self, discard_bc: DiscardBrowsingContext) { + let msg = ConstellationControlMsg::ExitPipeline(self.id, discard_bc); + if let Err(e) = self.event_loop.send(msg) { warn!("Sending script exit message failed ({}).", e); } if let Err(e) = self.layout_chan.send(LayoutControlMsg::ExitNow) { diff --git a/components/script/dom/browsingcontext.rs b/components/script/dom/browsingcontext.rs index 1a7f26253a0..9652445d96f 100644 --- a/components/script/dom/browsingcontext.rs +++ b/components/script/dom/browsingcontext.rs @@ -3,16 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; +use dom::bindings::js::{JS, Root, RootedReference}; use dom::bindings::proxyhandler::{fill_property_descriptor, get_property_descriptor}; use dom::bindings::reflector::{DomObject, MutDomObject, Reflector}; use dom::bindings::trace::JSTraceable; use dom::bindings::utils::WindowProxyHandler; use dom::bindings::utils::get_array_index_from_id; -use dom::document::Document; use dom::element::Element; -use dom::globalscope::GlobalScope; use dom::window::Window; use js::JSCLASS_IS_GLOBAL; use js::glue::{CreateWrapperProxyHandler, ProxyTraps, NewWindowProxy}; @@ -26,7 +23,6 @@ use js::jsapi::{MutableHandle, MutableHandleObject, MutableHandleValue}; use js::jsapi::{ObjectOpResult, PropertyDescriptor}; use js::jsval::{UndefinedValue, PrivateValue}; use js::rust::get_object_class; -use msg::constellation_msg::PipelineId; use std::cell::Cell; #[dom_struct] @@ -40,10 +36,8 @@ pub struct BrowsingContext { /// Indicates if reflow is required when reloading. needs_reflow: Cell, - /// The current active document. - /// Note that the session history is stored in the constellation, - /// in the script thread we just track the current active document. - active_document: MutNullableJS, + /// Has this browsing context been discarded? + discarded: Cell, /// The containing iframe element, if this is a same-origin iframe frame_element: Option>, @@ -54,7 +48,7 @@ impl BrowsingContext { BrowsingContext { reflector: Reflector::new(), needs_reflow: Cell::new(true), - active_document: Default::default(), + discarded: Cell::new(false), frame_element: frame_element.map(JS::from_ref), } } @@ -84,20 +78,12 @@ impl BrowsingContext { } } - pub fn set_active_document(&self, document: &Document) { - self.active_document.set(Some(document)) + pub fn discard(&self) { + self.discarded.set(true); } - pub fn active_document(&self) -> Root { - self.active_document.get().expect("No active document.") - } - - pub fn maybe_active_document(&self) -> Option> { - self.active_document.get() - } - - pub fn active_window(&self) -> Root { - Root::from_ref(self.active_document().window()) + pub fn is_discarded(&self) -> bool { + self.discarded.get() } pub fn frame_element(&self) -> Option<&Element> { @@ -115,15 +101,6 @@ impl BrowsingContext { self.needs_reflow.set(status); old } - - pub fn active_pipeline_id(&self) -> Option { - self.active_document.get() - .map(|doc| doc.window().upcast::().pipeline_id()) - } - - pub fn unset_active_document(&self) { - self.active_document.set(None) - } } #[allow(unsafe_code)] diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index d6d967db6c4..bfc98d61b0f 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -190,6 +190,7 @@ pub struct Document { last_modified: Option, encoding: Cell, is_html_document: bool, + is_fully_active: Cell, url: DOMRefCell, quirks_mode: Cell, /// Caches for the getElement methods @@ -385,20 +386,17 @@ impl Document { self.trigger_mozbrowser_event(MozBrowserEvent::SecurityChange(https_state)); } - // https://html.spec.whatwg.org/multipage/#active-document - pub fn is_active(&self) -> bool { - self.browsing_context().map_or(false, |context| { - self == &*context.active_document() - }) - } - // https://html.spec.whatwg.org/multipage/#fully-active pub fn is_fully_active(&self) -> bool { - if !self.is_active() { - return false; - } - // FIXME: It should also check whether the browser context is top-level or not - true + self.is_fully_active.get() + } + + pub fn fully_activate(&self) { + self.is_fully_active.set(true) + } + + pub fn fully_deactivate(&self) { + self.is_fully_active.set(false) } pub fn origin(&self) -> &Origin { @@ -1693,11 +1691,16 @@ impl Document { self.current_parser.get() } - /// Find an iframe element in the document. - pub fn find_iframe(&self, frame_id: FrameId) -> Option> { + /// Iterate over all iframes in the document. + pub fn iter_iframes(&self) -> impl Iterator> { self.upcast::() .traverse_preorder() .filter_map(Root::downcast::) + } + + /// Find an iframe element in the document. + pub fn find_iframe(&self, frame_id: FrameId) -> Option> { + self.iter_iframes() .find(|node| node.frame_id() == frame_id) } @@ -1864,6 +1867,7 @@ impl Document { // https://dom.spec.whatwg.org/#concept-document-encoding encoding: Cell::new(UTF_8), is_html_document: is_html_document == IsHTMLDocument::HTMLDocument, + is_fully_active: Cell::new(false), id_map: DOMRefCell::new(HashMap::new()), tag_map: DOMRefCell::new(HashMap::new()), tagns_map: DOMRefCell::new(HashMap::new()), @@ -2261,19 +2265,12 @@ impl DocumentMethods for Document { // https://html.spec.whatwg.org/multipage/#dom-document-hasfocus fn HasFocus(&self) -> bool { - match self.browsing_context() { - Some(browsing_context) => { - // Step 2. - let candidate = browsing_context.active_document(); - // Step 3. - if &*candidate == self { - true - } else { - false //TODO Step 4. - } - } - None => false, + // Step 1-2. + if self.window().parent_info().is_none() && self.is_fully_active() { + return true; } + // TODO Step 3. + false } // https://html.spec.whatwg.org/multipage/#relaxing-the-same-origin-restriction @@ -3190,8 +3187,8 @@ impl DocumentMethods for Document { // Step 2. // TODO: handle throw-on-dynamic-markup-insertion counter. - - if !self.is_active() { + // FIXME: this should check for being active rather than fully active + if !self.is_fully_active() { // Step 3. return Ok(()); } diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 692598d5339..3a1c9daf1c9 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -33,7 +33,6 @@ use dom::eventtarget::EventTarget; use dom::globalscope::GlobalScope; use dom::htmlelement::HTMLElement; use dom::node::{Node, NodeDamage, UnbindContext, document_from_node, window_from_node}; -use dom::urlhelper::UrlHelper; use dom::virtualmethods::VirtualMethods; use dom::window::{ReflowReason, Window}; use html5ever_atoms::LocalName; @@ -709,42 +708,34 @@ impl VirtualMethods for HTMLIFrameElement { LoadBlocker::terminate(&mut blocker); // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded - if let Some(pipeline_id) = self.pipeline_id.get() { - debug!("Unbinding pipeline {} from frame {}.", pipeline_id, self.frame_id); - let window = window_from_node(self); + debug!("Unbinding frame {}.", self.frame_id); + let window = window_from_node(self); + let (sender, receiver) = ipc::channel().unwrap(); - // The only reason we're waiting for the iframe to be totally - // removed is to ensure the script thread can't add iframes faster - // than the compositor can remove them. - // - // Since most of this cleanup doesn't happen on same-origin - // iframes, and since that would cause a deadlock, don't do it. - let same_origin = { - // FIXME(#10968): this should probably match the origin check in - // HTMLIFrameElement::contentDocument. - let self_url = self.get_url(); - let win_url = window_from_node(self).get_url(); - UrlHelper::SameOrigin(&self_url, &win_url) || self_url.as_str() == "about:blank" - }; - let (sender, receiver) = if same_origin { - (None, None) - } else { - let (sender, receiver) = ipc::channel().unwrap(); - (Some(sender), Some(receiver)) - }; - let msg = ConstellationMsg::RemoveIFrame(pipeline_id, sender); - window.upcast::().constellation_chan().send(msg).unwrap(); - if let Some(receiver) = receiver { - receiver.recv().unwrap() + // Ask the constellation to remove the iframe, and tell us the + // pipeline ids of the closed pipelines. + let msg = ConstellationMsg::RemoveIFrame(self.frame_id, sender); + window.upcast::().constellation_chan().send(msg).unwrap(); + let exited_pipeline_ids = receiver.recv().unwrap(); + + // The spec for discarding is synchronous, + // so we need to discard the browsing contexts now, rather than + // when the `PipelineExit` message arrives. + for exited_pipeline_id in exited_pipeline_ids { + if let Some(exited_document) = ScriptThread::find_document(exited_pipeline_id) { + exited_document.window().browsing_context().discard(); + for exited_iframe in exited_document.iter_iframes() { + exited_iframe.pipeline_id.set(None); + } } - - // Resetting the pipeline_id to None is required here so that - // if this iframe is subsequently re-added to the document - // the load doesn't think that it's a navigation, but instead - // a new iframe. Without this, the constellation gets very - // confused. - self.pipeline_id.set(None); } + + // Resetting the pipeline_id to None is required here so that + // if this iframe is subsequently re-added to the document + // the load doesn't think that it's a navigation, but instead + // a new iframe. Without this, the constellation gets very + // confused. + self.pipeline_id.set(None); } } diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index dbc4b76591e..47c753f43b1 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -31,9 +31,13 @@ // other browsing contexts [Replaceable] readonly attribute WindowProxy frames; //[Replaceable] readonly attribute unsigned long length; - [Unforgeable] readonly attribute WindowProxy top; + // Note that this can return null in the case that the browsing context has been discarded. + // https://github.com/whatwg/html/issues/2115 + [Unforgeable] readonly attribute WindowProxy? top; // attribute any opener; - readonly attribute WindowProxy parent; + // Note that this can return null in the case that the browsing context has been discarded. + // https://github.com/whatwg/html/issues/2115 + readonly attribute WindowProxy? parent; readonly attribute Element? frameElement; //WindowProxy open(optional DOMString url = "about:blank", optional DOMString target = "_blank", // optional DOMString features = "", optional boolean replace = false); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 37bf817ed7a..87d15ab1616 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -163,6 +163,7 @@ pub struct Window { #[ignore_heap_size_of = "channels are hard"] image_cache_chan: ImageCacheChan, browsing_context: MutNullableJS, + document: MutNullableJS, history: MutNullableJS, performance: MutNullableJS, navigation_start: u64, @@ -443,7 +444,7 @@ impl WindowMethods for Window { // https://html.spec.whatwg.org/multipage/#dom-document-2 fn Document(&self) -> Root { - self.browsing_context().active_document() + self.document.get().expect("Document accessed before initialization.") } // https://html.spec.whatwg.org/multipage/#dom-history @@ -551,21 +552,32 @@ impl WindowMethods for Window { } // https://html.spec.whatwg.org/multipage/#dom-parent - fn Parent(&self) -> Root { - match self.parent() { - Some(window) => window.browsing_context(), - None => self.Window() + fn GetParent(&self) -> Option> { + // Steps 1. and 2. + if self.browsing_context().is_discarded() { + return None; } - } + match self.parent() { + // Step 4. + Some(parent) => Some(parent.Window()), + // Step 5. + None => Some(self.Window()) + } + } // https://html.spec.whatwg.org/multipage/#dom-top - fn Top(&self) -> Root { + fn GetTop(&self) -> Option> { + // Steps 1. and 2. + if self.browsing_context().is_discarded() { + return None; + } + // Step 5. let mut window = Root::from_ref(self); while let Some(parent) = window.parent() { window = parent; } - window.browsing_context() - } + Some(window.Window()) + } // https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/ // NavigationTiming/Overview.html#sec-window.performance-attribute @@ -1351,6 +1363,13 @@ impl Window { unsafe { SetWindowProxy(cx, window, browsing_context.reflector().get_jsobject()); } } + #[allow(unsafe_code)] + pub fn init_document(&self, document: &Document) { + assert!(self.document.get().is_none()); + assert!(document.window() == self); + self.document.set(Some(&document)); + } + /// Commence a new URL load which will either replace this window or scroll to a fragment. pub fn load_url(&self, url: ServoUrl, replace: bool, force_reload: bool, referrer_policy: Option) { @@ -1518,13 +1537,8 @@ impl Window { return None; } - let browsing_context = self.browsing_context(); - - browsing_context.frame_element().map(|frame_element| { - let window = window_from_node(frame_element); - let context = window.browsing_context(); - context.active_window() - }) + self.browsing_context().frame_element() + .map(|frame_element| window_from_node(frame_element)) } /// Returns whether this window is mozbrowser. @@ -1610,6 +1624,7 @@ impl Window { image_cache_thread: image_cache_thread, history: Default::default(), browsing_context: Default::default(), + document: Default::default(), performance: Default::default(), navigation_start: (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64, navigation_start_precise: time::precise_time_ns() as f64, diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 563e4c52529..407a32cb1de 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -83,7 +83,7 @@ use profile_traits::time::{self, ProfilerCategory, profile}; use script_layout_interface::message::{self, NewLayoutThreadInfo, ReflowQueryType}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory, EnqueuedPromiseCallback}; use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx, PromiseJobQueue}; -use script_traits::{CompositorEvent, ConstellationControlMsg, EventResult}; +use script_traits::{CompositorEvent, ConstellationControlMsg, DiscardBrowsingContext, EventResult}; use script_traits::{InitialScriptState, LayoutMsg, LoadData, MouseButton, MouseEventType, MozBrowserEvent}; use script_traits::{NewLayoutInfo, ScriptMsg as ConstellationMsg}; use script_traits::{ScriptThreadFactory, TimerEvent, TimerEventRequest, TimerSource}; @@ -1007,8 +1007,8 @@ impl ScriptThread { self.handle_css_error_reporting(pipeline_id, filename, line, column, msg), ConstellationControlMsg::Reload(pipeline_id) => self.handle_reload(pipeline_id), - ConstellationControlMsg::ExitPipeline(pipeline_id) => - self.handle_exit_pipeline_msg(pipeline_id), + ConstellationControlMsg::ExitPipeline(pipeline_id, discard_browsing_context) => + self.handle_exit_pipeline_msg(pipeline_id, discard_browsing_context), msg @ ConstellationControlMsg::AttachLayout(..) | msg @ ConstellationControlMsg::Viewport(..) | msg @ ConstellationControlMsg::SetScrollState(..) | @@ -1368,6 +1368,7 @@ impl ScriptThread { } } document.window().thaw(); + document.fully_activate(); return; } let mut loads = self.incomplete_loads.borrow_mut(); @@ -1565,7 +1566,7 @@ impl ScriptThread { } /// Handles a request to exit a pipeline and shut down layout. - fn handle_exit_pipeline_msg(&self, id: PipelineId) { + fn handle_exit_pipeline_msg(&self, id: PipelineId, discard_bc: DiscardBrowsingContext) { debug!("Exiting pipeline {}.", id); self.closed_pipelines.borrow_mut().insert(id); @@ -1591,6 +1592,11 @@ impl ScriptThread { if let Some(document) = self.documents.borrow_mut().remove(id) { shut_down_layout(document.window()); + if discard_bc == DiscardBrowsingContext::Yes { + if let Some(context) = document.browsing_context() { + context.discard(); + } + } let _ = self.constellation_chan.send(ConstellationMsg::PipelineExited(id)); } @@ -1606,7 +1612,7 @@ impl ScriptThread { pipeline_ids.extend(self.documents.borrow().iter().next().map(|(pipeline_id, _)| pipeline_id)); for pipeline_id in pipeline_ids { - self.handle_exit_pipeline_msg(pipeline_id); + self.handle_exit_pipeline_msg(pipeline_id, DiscardBrowsingContext::Yes); } debug!("Exited script thread."); @@ -1819,9 +1825,13 @@ impl ScriptThread { referrer_policy); document.set_ready_state(DocumentReadyState::Loading); + if !incomplete.is_frozen { + document.fully_activate(); + } + self.documents.borrow_mut().insert(incomplete.pipeline_id, &*document); - browsing_context.set_active_document(&document); + window.init_document(&document); self.constellation_chan .send(ConstellationMsg::ActivateDocument(incomplete.pipeline_id)) @@ -2253,8 +2263,8 @@ fn shut_down_layout(window: &Window) { // Drop our references to the JSContext and DOM objects. window.clear_js_runtime(); - // Sever the connection between the global and the DOM tree - browsing_context.unset_active_document(); + // Discard the browsing context. + browsing_context.discard(); // Destroy the layout thread. If there were node leaks, layout will now crash safely. chan.send(message::Msg::ExitNow).ok(); diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index d87fdbad5d3..534a488baff 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -184,6 +184,15 @@ pub struct NewLayoutInfo { pub layout_threads: usize, } +/// When a pipeline is closed, should its browsing context be discarded too? +#[derive(Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum DiscardBrowsingContext { + /// Discard the browsing context + Yes, + /// Don't discard the browsing context + No, +} + /// Messages sent from the constellation or layout to the script thread. #[derive(Deserialize, Serialize)] pub enum ConstellationControlMsg { @@ -194,7 +203,7 @@ pub enum ConstellationControlMsg { /// Notifies script that window has been resized but to not take immediate action. ResizeInactive(PipelineId, WindowSizeData), /// Notifies the script that a pipeline should be closed. - ExitPipeline(PipelineId), + ExitPipeline(PipelineId, DiscardBrowsingContext), /// Notifies the script that the whole thread should be closed. ExitScriptThread, /// Sends a DOM event. diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index e268d67bc1d..fc29563cbbd 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -102,7 +102,8 @@ pub enum ScriptMsg { /// Status message to be displayed in the chrome, eg. a link URL on mouseover. NodeStatus(Option), /// Notification that this iframe should be removed. - RemoveIFrame(PipelineId, Option>), + /// Returns a list of pipelines which were closed. + RemoveIFrame(FrameId, IpcSender>), /// Change pipeline visibility SetVisible(PipelineId, bool), /// Notifies constellation that an iframe's visibility has been changed. diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index 93c28136f07..f7d01004cf1 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -45852,6 +45852,12 @@ "path": "cssom/overflow-serialization.html", "url": "/cssom/overflow-serialization.html" } + ], + "html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html": [ + { + "path": "html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html", + "url": "/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html" + } ] } }, diff --git a/tests/wpt/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html b/tests/wpt/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html new file mode 100644 index 00000000000..c339525ebd1 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html @@ -0,0 +1,32 @@ + + +IFrame discards are processed synchronously + + + +