diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index c3a178d9f49..d14e4d921db 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -1071,6 +1071,32 @@ where load_data, replace, ); + } else { + let pipeline_is_top_level_pipeline = self + .browsing_contexts + .get(&BrowsingContextId::from(top_level_browsing_context_id)) + .map(|ctx| ctx.pipeline_id == pipeline_id) + .unwrap_or(false); + // If the navigation is refused, and this concerns an iframe, + // we need to take it out of it's "delaying-load-events-mode". + // https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + if !pipeline_is_top_level_pipeline { + let msg = ConstellationControlMsg::StopDelayingLoadEventsMode( + pipeline_id, + ); + let result = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.event_loop.send(msg), + None => { + return warn!( + "Attempted to navigate {} after closure.", + pipeline_id + ) + }, + }; + if let Err(e) = result { + self.handle_send_error(pipeline_id, e); + } + } } }, None => { @@ -1838,6 +1864,9 @@ where Some(parent_pipeline_id) => parent_pipeline_id, None => return warn!("Subframe {} has no parent.", pipeline_id), }; + // https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded + // When a Document in an iframe is marked as completely loaded, + // the user agent must run the iframe load event steps. let msg = ConstellationControlMsg::DispatchIFrameLoadEvent { target: browsing_context_id, parent: parent_pipeline_id, diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 66b9e379d75..78323d604bd 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -416,6 +416,8 @@ pub struct Document { /// List of tasks to execute as soon as last script/layout blocker is removed. #[ignore_malloc_size_of = "Measuring trait objects is hard"] delayed_tasks: DomRefCell>>, + /// https://html.spec.whatwg.org/multipage/#completely-loaded + completely_loaded: Cell, } #[derive(JSTraceable, MallocSizeOf)] @@ -507,6 +509,10 @@ impl Document { self.https_state.set(https_state); } + pub fn is_completely_loaded(&self) -> bool { + self.completely_loaded.get() + } + pub fn is_fully_active(&self) -> bool { self.activity.get() == DocumentActivity::FullyActive } @@ -1899,7 +1905,21 @@ impl Document { // https://html.spec.whatwg.org/multipage/#the-end pub fn maybe_queue_document_completion(&self) { - if self.loader.borrow().is_blocked() { + // https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + let is_in_delaying_load_events_mode = match self.window.undiscarded_window_proxy() { + Some(window_proxy) => window_proxy.is_delaying_load_events_mode(), + None => false, + }; + + // Note: if the document is not fully active, the layout thread will have exited already, + // and this method will panic. + // The underlying problem might actually be that layout exits while it should be kept alive. + // See https://github.com/servo/servo/issues/22507 + let not_ready_for_load = self.loader.borrow().is_blocked() || + !self.is_fully_active() || + is_in_delaying_load_events_mode; + + if not_ready_for_load { // Step 6. return; } @@ -1952,8 +1972,6 @@ impl Document { window.reflow(ReflowGoal::Full, ReflowReason::DocumentLoaded); - document.notify_constellation_load(); - if let Some(fragment) = document.url().fragment() { document.check_and_scroll_fragment(fragment); } @@ -2008,8 +2026,26 @@ impl Document { // Step 11. // TODO: ready for post-load tasks. - // Step 12. - // TODO: completely loaded. + // Step 12: completely loaded. + // https://html.spec.whatwg.org/multipage/#completely-loaded + // TODO: fully implement "completely loaded". + let document = Trusted::new(self); + if document.root().browsing_context().is_some() { + self.window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(completely_loaded: move || { + let document = document.root(); + document.completely_loaded.set(true); + // Note: this will, among others, result in the "iframe-load-event-steps" being run. + // https://html.spec.whatwg.org/multipage/#iframe-load-event-steps + document.notify_constellation_load(); + }), + self.window.upcast(), + ) + .unwrap(); + } } // https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script @@ -2701,6 +2737,7 @@ impl Document { fired_unload: Cell::new(false), responsive_images: Default::default(), redirect_count: Cell::new(0), + completely_loaded: Cell::new(false), script_and_layout_blockers: Cell::new(0), delayed_tasks: Default::default(), } diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index e471e78aeaf..60a6c53e0a9 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -276,8 +276,13 @@ impl HTMLIFrameElement { ); let pipeline_id = self.pipeline_id(); - // If the initial `about:blank` page is the current page, load with replacement enabled. - let replace = pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get(); + // If the initial `about:blank` page is the current page, load with replacement enabled, + // see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3 + let is_about_blank = + pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get(); + // Replacement enabled also takes into account whether the document is "completely loaded", + // see https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded + let replace = is_about_blank || !document.is_completely_loaded(); self.navigate_or_reload_child_browsing_context( Some(load_data), NavigationType::Regular, diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 639d4677993..f4062cd2e33 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1417,7 +1417,9 @@ impl Window { dom_count: self.Document().dom_count(), }; - self.layout_chan.send(Msg::Reflow(reflow)).unwrap(); + self.layout_chan + .send(Msg::Reflow(reflow)) + .expect("Layout thread disconnected."); debug!("script: layout forked"); @@ -1776,8 +1778,14 @@ impl Window { } } - // Step 7 + // Step 8 if doc.prompt_to_unload(false) { + if self.window_proxy().parent().is_some() { + // Step 10 + // If browsingContext is a nested browsing context, + // then put it in the delaying load events mode. + self.window_proxy().start_delaying_load_events_mode(); + } self.main_thread_script_chan() .send(MainThreadScriptMsg::Navigate( pipeline_id, diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index 65362ee0d17..42b53fc5ead 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -94,6 +94,9 @@ pub struct WindowProxy { /// The parent browsing context's window proxy, if this is a nested browsing context parent: Option>, + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + delaying_load_events_mode: Cell, } impl WindowProxy { @@ -118,6 +121,7 @@ impl WindowProxy { disowned: Cell::new(false), frame_element: frame_element.map(Dom::from_ref), parent: parent.map(Dom::from_ref), + delaying_load_events_mode: Cell::new(false), opener, } } @@ -314,6 +318,26 @@ impl WindowProxy { None } + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + pub fn is_delaying_load_events_mode(&self) -> bool { + self.delaying_load_events_mode.get() + } + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + pub fn start_delaying_load_events_mode(&self) { + self.delaying_load_events_mode.set(true); + } + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + pub fn stop_delaying_load_events_mode(&self) { + self.delaying_load_events_mode.set(false); + if let Some(document) = self.document() { + if !document.loader().events_inhibited() { + ScriptThread::mark_document_with_no_blocked_loads(&document); + } + } + } + // https://html.spec.whatwg.org/multipage/#disowned-its-opener pub fn disown(&self) { self.disowned.set(true); diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 50f6135ff52..23d7adcf012 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1396,6 +1396,7 @@ impl ScriptThread { match *msg { MixedMessage::FromConstellation(ref inner_msg) => { match *inner_msg { + StopDelayingLoadEventsMode(id) => Some(id), NavigationResponse(id, _) => Some(id), AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id), Resize(id, ..) => Some(id), @@ -1533,6 +1534,9 @@ impl ScriptThread { fn handle_msg_from_constellation(&self, msg: ConstellationControlMsg) { match msg { + ConstellationControlMsg::StopDelayingLoadEventsMode(pipeline_id) => { + self.handle_stop_delaying_load_events_mode(pipeline_id) + }, ConstellationControlMsg::NavigationResponse(id, fetch_data) => { match fetch_data { FetchResponseMsg::ProcessResponse(metadata) => { @@ -2080,6 +2084,19 @@ impl ScriptThread { } } + fn handle_stop_delaying_load_events_mode(&self, pipeline_id: PipelineId) { + let window = self.documents.borrow().find_window(pipeline_id); + if let Some(window) = window { + match window.undiscarded_window_proxy() { + Some(window_proxy) => window_proxy.stop_delaying_load_events_mode(), + None => warn!( + "Attempted to take {} of 'delaying-load-events-mode' after having been discarded.", + pipeline_id + ), + }; + } + } + fn handle_unload_document(&self, pipeline_id: PipelineId) { let document = self.documents.borrow().find_document(pipeline_id); if let Some(document) = document { @@ -2166,6 +2183,20 @@ impl ScriptThread { status: Some((204...205, _)), .. }) => { + // If we have an existing window that is being navigated: + if let Some(window) = self.documents.borrow().find_window(id.clone()) { + let window_proxy = window.window_proxy(); + // https://html.spec.whatwg.org/multipage/ + // #navigating-across-documents:delaying-load-events-mode-2 + if window_proxy.parent().is_some() { + // The user agent must take this nested browsing context + // out of the delaying load events mode + // when this navigation algorithm later matures, + // or when it terminates (whether due to having run all the steps, + // or being canceled, or being aborted), whichever happens first. + window_proxy.stop_delaying_load_events_mode(); + } + } self.script_sender .send((id.clone(), ScriptMsg::AbortLoadUrl)) .unwrap(); @@ -2710,6 +2741,13 @@ impl ScriptThread { incomplete.parent_info, incomplete.opener, ); + if window_proxy.parent().is_some() { + // https://html.spec.whatwg.org/multipage/#navigating-across-documents:delaying-load-events-mode-2 + // The user agent must take this nested browsing context + // out of the delaying load events mode + // when this navigation algorithm later matures. + window_proxy.stop_delaying_load_events_mode(); + } window.init_window_proxy(&window_proxy); let last_modified = metadata.headers.as_ref().and_then(|headers| { diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 4443bee3919..4a0e1efd9b1 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -248,6 +248,10 @@ pub enum UpdatePipelineIdReason { /// Messages sent from the constellation or layout to the script thread. #[derive(Deserialize, Serialize)] pub enum ConstellationControlMsg { + /// Takes the associated window proxy out of "delaying-load-events-mode", + /// used if a scheduled navigated was refused by the embedder. + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + StopDelayingLoadEventsMode(PipelineId), /// Sends the final response to script thread for fetching after all redirections /// have been resolved NavigationResponse(PipelineId, FetchResponseMsg), @@ -340,6 +344,7 @@ impl fmt::Debug for ConstellationControlMsg { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { use self::ConstellationControlMsg::*; let variant = match *self { + StopDelayingLoadEventsMode(..) => "StopDelayingLoadsEventMode", NavigationResponse(..) => "NavigationResponse", AttachLayout(..) => "AttachLayout", Resize(..) => "Resize", diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/008.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/008.html.ini deleted file mode 100644 index f06bb4cfea7..00000000000 --- a/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/008.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[008.html] - type: testharness - [Link with onclick form submit to javascript url and href navigation ] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/009.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/009.html.ini deleted file mode 100644 index f2431729df2..00000000000 --- a/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/009.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[009.html] - type: testharness - [Link with onclick form submit to javascript url with document.write and href navigation ] - expected: FAIL - diff --git a/tests/wpt/metadata/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js.ini b/tests/wpt/metadata/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js.ini index 93a9dfe98cf..c03333ff354 100644 --- a/tests/wpt/metadata/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js.ini +++ b/tests/wpt/metadata/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js.ini @@ -1,4 +1,5 @@ [active.window.html] + expected: CRASH [document.open() removes the document's children (non-active document without an associated Window object; createHTMLDocument)] expected: FAIL