aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorGregory Terzian <gterzian@users.noreply.github.com>2018-03-18 13:20:24 +0800
committerGregory Terzian <gterzian@users.noreply.github.com>2018-05-05 19:14:36 +0800
commit427eaed535dfdeaf735a40e3cb82ad7077aa86c5 (patch)
treecc723041b13093541e8d5986b74bbd02e1c920a6 /components
parenta1d1b187102b340ba65955f2c4f1131c8276d050 (diff)
downloadservo-427eaed535dfdeaf735a40e3cb82ad7077aa86c5.tar.gz
servo-427eaed535dfdeaf735a40e3cb82ad7077aa86c5.zip
beforeunload and unload infrastructure
Diffstat (limited to 'components')
-rw-r--r--components/atoms/static_atoms.txt2
-rw-r--r--components/constellation/constellation.rs27
-rw-r--r--components/script/dom/document.rs145
-rw-r--r--components/script/dom/event.rs9
-rw-r--r--components/script/dom/eventtarget.rs19
-rw-r--r--components/script/dom/window.rs23
-rw-r--r--components/script/script_thread.rs10
-rw-r--r--components/script/timers.rs2
-rw-r--r--components/script_traits/lib.rs3
-rw-r--r--components/script_traits/script_msg.rs2
10 files changed, 222 insertions, 20 deletions
diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt
index ba159bafdfa..8b168ae2fd5 100644
--- a/components/atoms/static_atoms.txt
+++ b/components/atoms/static_atoms.txt
@@ -48,6 +48,7 @@ none
number
onchange
open
+pagehide
pageshow
password
pause
@@ -77,6 +78,7 @@ time
timeupdate
toggle
transitionend
+unload
url
waiting
webglcontextcreationerror
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs
index 77305b4e153..ca5d4649bc7 100644
--- a/components/constellation/constellation.rs
+++ b/components/constellation/constellation.rs
@@ -1033,6 +1033,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
FromScriptMsg::PipelineExited => {
self.handle_pipeline_exited(source_pipeline_id);
}
+ FromScriptMsg::DiscardDocument => {
+ self.handle_discard_document(source_top_ctx_id, source_pipeline_id);
+ }
FromScriptMsg::InitiateNavigateRequest(req_init, cancel_chan) => {
debug!("constellation got initiate navigate request message");
self.handle_navigate_request(source_pipeline_id, req_init, cancel_chan);
@@ -2547,6 +2550,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
self.notify_history_changed(change.top_level_browsing_context_id);
},
Some(old_pipeline_id) => {
+ // https://html.spec.whatwg.org/multipage/#unload-a-document
+ self.unload_document(old_pipeline_id);
// Deactivate the old pipeline, and activate the new one.
let (pipelines_to_close, states_to_close) = if let Some(replace_reloader) = change.replace {
let session_history = self.joint_session_histories
@@ -2997,6 +3002,28 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
debug!("Closed browsing context children {}.", browsing_context_id);
}
+ // Discard the pipeline for a given document, udpdate the joint session history.
+ fn handle_discard_document(&mut self,
+ top_level_browsing_context_id: TopLevelBrowsingContextId,
+ pipeline_id: PipelineId) {
+ let load_data = match self.pipelines.get(&pipeline_id) {
+ Some(pipeline) => pipeline.load_data.clone(),
+ None => return
+ };
+ self.joint_session_histories
+ .entry(top_level_browsing_context_id).or_insert(JointSessionHistory::new())
+ .replace_reloader(NeedsToReload::No(pipeline_id), NeedsToReload::Yes(pipeline_id, load_data));
+ self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
+ }
+
+ // Send a message to script requesting the document associated with this pipeline runs the 'unload' algorithm.
+ fn unload_document(&self, pipeline_id: PipelineId) {
+ if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
+ let msg = ConstellationControlMsg::UnloadDocument(pipeline_id);
+ let _ = pipeline.event_loop.send(msg);
+ }
+ }
+
// Close all pipelines at and beneath a given browsing context
fn close_pipeline(&mut self, pipeline_id: PipelineId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) {
debug!("Closing pipeline {:?}.", pipeline_id);
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 9a33121b5ae..4fa33056253 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -63,7 +63,8 @@ use dom::keyboardevent::KeyboardEvent;
use dom::location::Location;
use dom::messageevent::MessageEvent;
use dom::mouseevent::MouseEvent;
-use dom::node::{self, CloneChildrenFlag, Node, NodeDamage, window_from_node, NodeFlags, LayoutNodeHelpers};
+use dom::node::{self, CloneChildrenFlag, document_from_node, window_from_node};
+use dom::node::{Node, NodeDamage, NodeFlags, LayoutNodeHelpers};
use dom::node::VecPreOrderInsertionHelper;
use dom::nodeiterator::NodeIterator;
use dom::nodelist::NodeList;
@@ -348,6 +349,8 @@ pub struct Document {
last_click_info: DomRefCell<Option<(Instant, Point2D<f32>)>>,
/// <https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter>
ignore_destructive_writes_counter: Cell<u32>,
+ /// <https://html.spec.whatwg.org/multipage/#ignore-opens-during-unload-counter>
+ ignore_opens_during_unload_counter: Cell<u32>,
/// The number of spurious `requestAnimationFrame()` requests we've received.
///
/// A rAF request is considered spurious if nothing was actually reflowed.
@@ -375,6 +378,10 @@ pub struct Document {
throw_on_dynamic_markup_insertion_counter: Cell<u64>,
/// https://html.spec.whatwg.org/multipage/#page-showing
page_showing: Cell<bool>,
+ /// Whether the document is salvageable.
+ salvageable: Cell<bool>,
+ /// Whether the unload event has already been fired.
+ fired_unload: Cell<bool>,
}
#[derive(JSTraceable, MallocSizeOf)]
@@ -483,18 +490,21 @@ impl Document {
self.dirty_all_nodes();
self.window().reflow(ReflowGoal::Full, ReflowReason::CachedPageNeededReflow);
self.window().resume();
- // html.spec.whatwg.org/multipage/browsing-the-web.html#history-traversal
- // Step 6
+ // html.spec.whatwg.org/multipage/#history-traversal
+ // Step 4.6
if self.ready_state.get() == DocumentReadyState::Complete {
let document = Trusted::new(self);
self.window.dom_manipulation_task_source().queue(
task!(fire_pageshow_event: move || {
let document = document.root();
let window = document.window();
- if document.page_showing.get() || !window.is_alive() {
+ // Step 4.6.1
+ if document.page_showing.get() {
return;
}
+ // Step 4.6.2
document.page_showing.set(true);
+ // Step 4.6.4
let event = PageTransitionEvent::new(
window,
atom!("pageshow"),
@@ -1623,6 +1633,112 @@ impl Document {
ScriptThread::mark_document_with_no_blocked_loads(self);
}
+ // https://html.spec.whatwg.org/multipage/#prompt-to-unload-a-document
+ pub fn prompt_to_unload(&self, recursive_flag: bool) -> bool {
+ // TODO: Step 1, increase the event loop's termination nesting level by 1.
+ // Step 2
+ self.incr_ignore_opens_during_unload_counter();
+ //Step 3-5.
+ let document = Trusted::new(self);
+ let beforeunload_event = BeforeUnloadEvent::new(&self.window,
+ atom!("beforeunload"),
+ EventBubbles::Bubbles,
+ EventCancelable::Cancelable);
+ let event = beforeunload_event.upcast::<Event>();
+ event.set_trusted(true);
+ self.window.upcast::<EventTarget>().dispatch_event_with_target(
+ document.root().upcast(),
+ &event,
+ );
+ // TODO: Step 6, decrease the event loop's termination nesting level by 1.
+ // Step 7
+ if event.get_cancel_state() != EventDefault::Allowed {
+ self.salvageable.set(false);
+ }
+ let mut can_unload = true;
+ // TODO: Step 8 send a message to embedder to prompt user.
+ // Step 9
+ if !recursive_flag {
+ for iframe in self.iter_iframes() {
+ // TODO: handle the case of cross origin iframes.
+ let document = document_from_node(&*iframe);
+ if !document.prompt_to_unload(true) {
+ self.salvageable.set(document.salvageable());
+ can_unload = false;
+ break;
+ }
+ }
+ }
+ // Step 10
+ self.decr_ignore_opens_during_unload_counter();
+ can_unload
+ }
+
+ // https://html.spec.whatwg.org/multipage/#unload-a-document
+ pub fn unload(&self, recursive_flag: bool, recycle: bool) {
+ // TODO: Step 1, increase the event loop's termination nesting level by 1.
+ // Step 2
+ self.incr_ignore_opens_during_unload_counter();
+ let document = Trusted::new(self);
+ // Step 3-6
+ if self.page_showing.get() {
+ self.page_showing.set(false);
+ let event = PageTransitionEvent::new(
+ &self.window,
+ atom!("pagehide"),
+ false, // bubbles
+ false, // cancelable
+ self.salvageable.get(), // persisted
+ );
+ let event = event.upcast::<Event>();
+ event.set_trusted(true);
+ let _ = self.window.upcast::<EventTarget>().dispatch_event_with_target(
+ document.root().upcast(),
+ &event,
+ );
+ // TODO Step 6, document visibility steps.
+ }
+ let mut event_handled = false;
+ // Step 7
+ if !self.fired_unload.get() {
+ let event = Event::new(
+ &self.window.upcast(),
+ atom!("unload"),
+ EventBubbles::Bubbles,
+ EventCancelable::Cancelable,
+ );
+ event.set_trusted(true);
+ let _ = self.window.upcast::<EventTarget>().dispatch_event_with_target(
+ document.root().upcast(),
+ &event,
+ );
+ self.fired_unload.set(true);
+ event_handled = event.get_cancel_state() != EventDefault::Allowed;
+ }
+ // TODO: Step 8, decrease the event loop's termination nesting level by 1.
+ // Step 9
+ self.salvageable.set(!event_handled);
+ // Step 13
+ if !recursive_flag {
+ for iframe in self.iter_iframes() {
+ // TODO: handle the case of cross origin iframes.
+ let document = document_from_node(&*iframe);
+ document.unload(true, recycle);
+ if !document.salvageable() {
+ self.salvageable.set(false);
+ }
+ }
+ }
+ // Step 10, 14
+ if !self.salvageable.get() {
+ // https://html.spec.whatwg.org/multipage/#unloading-document-cleanup-steps
+ let msg = ScriptMsg::DiscardDocument;
+ let _ = self.window.upcast::<GlobalScope>().script_to_constellation_chan().send(msg);
+ }
+ // Step 15, End
+ self.decr_ignore_opens_during_unload_counter();
+ }
+
// https://html.spec.whatwg.org/multipage/#the-end
pub fn maybe_queue_document_completion(&self) {
if self.loader.borrow().is_blocked() {
@@ -2297,6 +2413,7 @@ impl Document {
target_element: MutNullableDom::new(None),
last_click_info: DomRefCell::new(None),
ignore_destructive_writes_counter: Default::default(),
+ ignore_opens_during_unload_counter: Default::default(),
spurious_animation_frames: Cell::new(0),
dom_count: Cell::new(1),
fullscreen_element: MutNullableDom::new(None),
@@ -2306,6 +2423,8 @@ impl Document {
canceller: canceller,
throw_on_dynamic_markup_insertion_counter: Cell::new(0),
page_showing: Cell::new(false),
+ salvageable: Cell::new(true),
+ fired_unload: Cell::new(false)
}
}
@@ -2485,6 +2604,10 @@ impl Document {
self.stylesheets.borrow().len()
}
+ pub fn salvageable(&self) -> bool {
+ self.salvageable.get()
+ }
+
pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> {
let stylesheets = self.stylesheets.borrow();
@@ -2608,6 +2731,20 @@ impl Document {
self.ignore_destructive_writes_counter.get() - 1);
}
+ pub fn is_prompting_or_unloading(&self) -> bool {
+ self.ignore_opens_during_unload_counter.get() > 0
+ }
+
+ fn incr_ignore_opens_during_unload_counter(&self) {
+ self.ignore_opens_during_unload_counter.set(
+ self.ignore_opens_during_unload_counter.get() + 1);
+ }
+
+ fn decr_ignore_opens_during_unload_counter(&self) {
+ self.ignore_opens_during_unload_counter.set(
+ self.ignore_opens_during_unload_counter.get() - 1);
+ }
+
/// Whether we've seen so many spurious animation frames (i.e. animation frames that didn't
/// mutate the DOM) that we've decided to fall back to fake ones.
fn is_faking_animation_frames(&self) -> bool {
diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs
index 607469ca6bd..aab86758e23 100644
--- a/components/script/dom/event.rs
+++ b/components/script/dom/event.rs
@@ -482,7 +482,14 @@ fn invoke(window: Option<&Window>,
event.current_target.set(Some(object));
// Step 5.
- inner_invoke(window, object, event, &listeners);
+ if inner_invoke(window, object, event, &listeners) {
+ // <https://html.spec.whatwg.org/multipage/#unload-a-document>
+ // Step 9.
+ // Required to establish the 'salvageable' state of document as part of unloading.
+ if event.canceled.get() != EventDefault::Prevented {
+ event.mark_as_handled();
+ }
+ }
// TODO: step 6.
}
diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs
index f909d97e253..df15ff1b2bc 100644
--- a/components/script/dom/eventtarget.rs
+++ b/components/script/dom/eventtarget.rs
@@ -187,22 +187,21 @@ impl CompiledEventListener {
CommonEventHandler::BeforeUnloadEventHandler(ref handler) => {
if let Some(event) = event.downcast::<BeforeUnloadEvent>() {
- let rv = event.ReturnValue();
-
+ // Step 5
if let Ok(value) = handler.Call_(object,
event.upcast::<Event>(),
exception_handle) {
- match value {
- Some(value) => {
- if rv.is_empty() {
- event.SetReturnValue(value);
- }
- }
- None => {
- event.upcast::<Event>().PreventDefault();
+ let rv = event.ReturnValue();
+ if let Some(v) = value {
+ if rv.is_empty() {
+ event.SetReturnValue(v);
}
+ event.upcast::<Event>().PreventDefault();
}
}
+ } else {
+ // Step 5, "Otherwise" clause
+ let _ = handler.Call_(object, event.upcast::<Event>(), exception_handle);
}
}
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 3a672291e8d..88156737e11 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -1550,7 +1550,7 @@ impl Window {
// https://html.spec.whatwg.org/multipage/#navigating-across-documents
if !force_reload && url.as_url()[..Position::AfterQuery] ==
doc.url().as_url()[..Position::AfterQuery] {
- // Step 5
+ // Step 6
if let Some(fragment) = url.fragment() {
doc.check_and_scroll_fragment(fragment);
doc.set_url(url.clone());
@@ -1559,9 +1559,24 @@ impl Window {
}
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
- self.main_thread_script_chan().send(
- MainThreadScriptMsg::Navigate(pipeline_id,
- LoadData::new(url, Some(pipeline_id), referrer_policy, Some(doc.url())), replace)).unwrap();
+
+ // Step 4
+ let window_proxy = self.window_proxy();
+ if let Some(active) = window_proxy.currently_active() {
+ if pipeline_id == active {
+ if doc.is_prompting_or_unloading() {
+ return;
+ }
+ }
+ }
+
+ // Step 7
+ if doc.prompt_to_unload(false) {
+ self.main_thread_script_chan().send(
+ MainThreadScriptMsg::Navigate(pipeline_id,
+ LoadData::new(url, Some(pipeline_id), referrer_policy, Some(doc.url())), replace)).unwrap();
+ };
+
}
pub fn handle_fire_timer(&self, timer_id: TimerEventId) {
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 6ee0ec46d33..613918f6a76 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -1157,6 +1157,7 @@ impl ScriptThread {
AttachLayout(ref new_layout_info) => Some(new_layout_info.new_pipeline_id),
Resize(id, ..) => Some(id),
ResizeInactive(id, ..) => Some(id),
+ UnloadDocument(id) => Some(id),
ExitPipeline(id, ..) => Some(id),
ExitScriptThread => None,
SendEvent(id, ..) => Some(id),
@@ -1275,6 +1276,8 @@ impl ScriptThread {
},
ConstellationControlMsg::Navigate(parent_pipeline_id, browsing_context_id, load_data, replace) =>
self.handle_navigate(parent_pipeline_id, Some(browsing_context_id), load_data, replace),
+ ConstellationControlMsg::UnloadDocument(pipeline_id) =>
+ self.handle_unload_document(pipeline_id),
ConstellationControlMsg::SendEvent(id, event) =>
self.handle_event(id, event),
ConstellationControlMsg::ResizeInactive(id, new_size) =>
@@ -1668,6 +1671,13 @@ impl ScriptThread {
}
}
+ fn handle_unload_document(&self, pipeline_id: PipelineId) {
+ let document = self.documents.borrow().find_document(pipeline_id);
+ if let Some(document) = document {
+ document.unload(false, false);
+ }
+ }
+
fn handle_update_pipeline_id(&self,
parent_pipeline_id: PipelineId,
browsing_context_id: BrowsingContextId,
diff --git a/components/script/timers.rs b/components/script/timers.rs
index 50f95d8400a..202a8edc8ae 100644
--- a/components/script/timers.rs
+++ b/components/script/timers.rs
@@ -241,7 +241,7 @@ impl OneshotTimers {
}
pub fn resume(&self) {
- // Suspend is idempotent: do nothing if the timers are already suspended.
+ // Resume is idempotent: do nothing if the timers are already resumed.
let additional_offset = match self.suspended_since.get() {
Some(suspended_since) => precise_time_ms() - suspended_since,
None => return warn!("Resuming an already resumed timer."),
diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs
index 98cf57116a7..5cbdd8456d7 100644
--- a/components/script_traits/lib.rs
+++ b/components/script_traits/lib.rs
@@ -262,6 +262,8 @@ pub enum ConstellationControlMsg {
Resize(PipelineId, WindowSizeData, WindowSizeType),
/// Notifies script that window has been resized but to not take immediate action.
ResizeInactive(PipelineId, WindowSizeData),
+ /// Notifies the script that the document associated with this pipeline should 'unload'.
+ UnloadDocument(PipelineId),
/// Notifies the script that a pipeline should be closed.
ExitPipeline(PipelineId, DiscardBrowsingContext),
/// Notifies the script that the whole thread should be closed.
@@ -335,6 +337,7 @@ impl fmt::Debug for ConstellationControlMsg {
AttachLayout(..) => "AttachLayout",
Resize(..) => "Resize",
ResizeInactive(..) => "ResizeInactive",
+ UnloadDocument(..) => "UnloadDocument",
ExitPipeline(..) => "ExitPipeline",
ExitScriptThread => "ExitScriptThread",
SendEvent(..) => "SendEvent",
diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs
index efc368e0761..379c73b66b2 100644
--- a/components/script_traits/script_msg.rs
+++ b/components/script_traits/script_msg.rs
@@ -148,6 +148,8 @@ pub enum ScriptMsg {
TouchEventProcessed(EventResult),
/// A log entry, with the top-level browsing context id and thread name
LogEntry(Option<String>, LogEntry),
+ /// Discard the document.
+ DiscardDocument,
/// Notifies the constellation that this pipeline has exited.
PipelineExited,
/// Send messages from postMessage calls from serviceworker