implement windowproxy "delay-load-event-mode", and partially document "completely-loaded"

This commit is contained in:
Gregory Terzian 2018-12-12 21:16:07 +08:00
parent 483bf245df
commit eb82e781a3
10 changed files with 156 additions and 19 deletions

View file

@ -1071,6 +1071,32 @@ where
load_data, load_data,
replace, 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 => { None => {
@ -1838,6 +1864,9 @@ where
Some(parent_pipeline_id) => parent_pipeline_id, Some(parent_pipeline_id) => parent_pipeline_id,
None => return warn!("Subframe {} has no 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 { let msg = ConstellationControlMsg::DispatchIFrameLoadEvent {
target: browsing_context_id, target: browsing_context_id,
parent: parent_pipeline_id, parent: parent_pipeline_id,

View file

@ -416,6 +416,8 @@ pub struct Document {
/// List of tasks to execute as soon as last script/layout blocker is removed. /// List of tasks to execute as soon as last script/layout blocker is removed.
#[ignore_malloc_size_of = "Measuring trait objects is hard"] #[ignore_malloc_size_of = "Measuring trait objects is hard"]
delayed_tasks: DomRefCell<Vec<Box<dyn TaskBox>>>, delayed_tasks: DomRefCell<Vec<Box<dyn TaskBox>>>,
/// https://html.spec.whatwg.org/multipage/#completely-loaded
completely_loaded: Cell<bool>,
} }
#[derive(JSTraceable, MallocSizeOf)] #[derive(JSTraceable, MallocSizeOf)]
@ -507,6 +509,10 @@ impl Document {
self.https_state.set(https_state); self.https_state.set(https_state);
} }
pub fn is_completely_loaded(&self) -> bool {
self.completely_loaded.get()
}
pub fn is_fully_active(&self) -> bool { pub fn is_fully_active(&self) -> bool {
self.activity.get() == DocumentActivity::FullyActive self.activity.get() == DocumentActivity::FullyActive
} }
@ -1899,7 +1905,21 @@ impl Document {
// https://html.spec.whatwg.org/multipage/#the-end // https://html.spec.whatwg.org/multipage/#the-end
pub fn maybe_queue_document_completion(&self) { 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. // Step 6.
return; return;
} }
@ -1952,8 +1972,6 @@ impl Document {
window.reflow(ReflowGoal::Full, ReflowReason::DocumentLoaded); window.reflow(ReflowGoal::Full, ReflowReason::DocumentLoaded);
document.notify_constellation_load();
if let Some(fragment) = document.url().fragment() { if let Some(fragment) = document.url().fragment() {
document.check_and_scroll_fragment(fragment); document.check_and_scroll_fragment(fragment);
} }
@ -2008,8 +2026,26 @@ impl Document {
// Step 11. // Step 11.
// TODO: ready for post-load tasks. // TODO: ready for post-load tasks.
// Step 12. // Step 12: completely loaded.
// TODO: 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 // https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script
@ -2701,6 +2737,7 @@ impl Document {
fired_unload: Cell::new(false), fired_unload: Cell::new(false),
responsive_images: Default::default(), responsive_images: Default::default(),
redirect_count: Cell::new(0), redirect_count: Cell::new(0),
completely_loaded: Cell::new(false),
script_and_layout_blockers: Cell::new(0), script_and_layout_blockers: Cell::new(0),
delayed_tasks: Default::default(), delayed_tasks: Default::default(),
} }

View file

@ -276,8 +276,13 @@ impl HTMLIFrameElement {
); );
let pipeline_id = self.pipeline_id(); let pipeline_id = self.pipeline_id();
// If the initial `about:blank` page is the current page, load with replacement enabled. // 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(); // 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( self.navigate_or_reload_child_browsing_context(
Some(load_data), Some(load_data),
NavigationType::Regular, NavigationType::Regular,

View file

@ -1417,7 +1417,9 @@ impl Window {
dom_count: self.Document().dom_count(), 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"); debug!("script: layout forked");
@ -1776,8 +1778,14 @@ impl Window {
} }
} }
// Step 7 // Step 8
if doc.prompt_to_unload(false) { 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() self.main_thread_script_chan()
.send(MainThreadScriptMsg::Navigate( .send(MainThreadScriptMsg::Navigate(
pipeline_id, pipeline_id,

View file

@ -94,6 +94,9 @@ pub struct WindowProxy {
/// The parent browsing context's window proxy, if this is a nested browsing context /// The parent browsing context's window proxy, if this is a nested browsing context
parent: Option<Dom<WindowProxy>>, parent: Option<Dom<WindowProxy>>,
/// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode
delaying_load_events_mode: Cell<bool>,
} }
impl WindowProxy { impl WindowProxy {
@ -118,6 +121,7 @@ impl WindowProxy {
disowned: Cell::new(false), disowned: Cell::new(false),
frame_element: frame_element.map(Dom::from_ref), frame_element: frame_element.map(Dom::from_ref),
parent: parent.map(Dom::from_ref), parent: parent.map(Dom::from_ref),
delaying_load_events_mode: Cell::new(false),
opener, opener,
} }
} }
@ -314,6 +318,26 @@ impl WindowProxy {
None 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 // https://html.spec.whatwg.org/multipage/#disowned-its-opener
pub fn disown(&self) { pub fn disown(&self) {
self.disowned.set(true); self.disowned.set(true);

View file

@ -1396,6 +1396,7 @@ impl ScriptThread {
match *msg { match *msg {
MixedMessage::FromConstellation(ref inner_msg) => { MixedMessage::FromConstellation(ref inner_msg) => {
match *inner_msg { match *inner_msg {
StopDelayingLoadEventsMode(id) => Some(id),
NavigationResponse(id, _) => Some(id), NavigationResponse(id, _) => Some(id),
AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id), AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id),
Resize(id, ..) => Some(id), Resize(id, ..) => Some(id),
@ -1533,6 +1534,9 @@ impl ScriptThread {
fn handle_msg_from_constellation(&self, msg: ConstellationControlMsg) { fn handle_msg_from_constellation(&self, msg: ConstellationControlMsg) {
match msg { match msg {
ConstellationControlMsg::StopDelayingLoadEventsMode(pipeline_id) => {
self.handle_stop_delaying_load_events_mode(pipeline_id)
},
ConstellationControlMsg::NavigationResponse(id, fetch_data) => { ConstellationControlMsg::NavigationResponse(id, fetch_data) => {
match fetch_data { match fetch_data {
FetchResponseMsg::ProcessResponse(metadata) => { 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) { fn handle_unload_document(&self, pipeline_id: PipelineId) {
let document = self.documents.borrow().find_document(pipeline_id); let document = self.documents.borrow().find_document(pipeline_id);
if let Some(document) = document { if let Some(document) = document {
@ -2166,6 +2183,20 @@ impl ScriptThread {
status: Some((204...205, _)), 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 self.script_sender
.send((id.clone(), ScriptMsg::AbortLoadUrl)) .send((id.clone(), ScriptMsg::AbortLoadUrl))
.unwrap(); .unwrap();
@ -2710,6 +2741,13 @@ impl ScriptThread {
incomplete.parent_info, incomplete.parent_info,
incomplete.opener, 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); window.init_window_proxy(&window_proxy);
let last_modified = metadata.headers.as_ref().and_then(|headers| { let last_modified = metadata.headers.as_ref().and_then(|headers| {

View file

@ -248,6 +248,10 @@ pub enum UpdatePipelineIdReason {
/// Messages sent from the constellation or layout to the script thread. /// Messages sent from the constellation or layout to the script thread.
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub enum ConstellationControlMsg { 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 /// Sends the final response to script thread for fetching after all redirections
/// have been resolved /// have been resolved
NavigationResponse(PipelineId, FetchResponseMsg), NavigationResponse(PipelineId, FetchResponseMsg),
@ -340,6 +344,7 @@ impl fmt::Debug for ConstellationControlMsg {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
use self::ConstellationControlMsg::*; use self::ConstellationControlMsg::*;
let variant = match *self { let variant = match *self {
StopDelayingLoadEventsMode(..) => "StopDelayingLoadsEventMode",
NavigationResponse(..) => "NavigationResponse", NavigationResponse(..) => "NavigationResponse",
AttachLayout(..) => "AttachLayout", AttachLayout(..) => "AttachLayout",
Resize(..) => "Resize", Resize(..) => "Resize",

View file

@ -1,5 +0,0 @@
[008.html]
type: testharness
[Link with onclick form submit to javascript url and href navigation ]
expected: FAIL

View file

@ -1,5 +0,0 @@
[009.html]
type: testharness
[Link with onclick form submit to javascript url with document.write and href navigation ]
expected: FAIL

View file

@ -1,4 +1,5 @@
[active.window.html] [active.window.html]
expected: CRASH
[document.open() removes the document's children (non-active document without an associated Window object; createHTMLDocument)] [document.open() removes the document's children (non-active document without an associated Window object; createHTMLDocument)]
expected: FAIL expected: FAIL