Index: chrome/browser/ui/unload_controller.cc |
diff --git a/chrome/browser/ui/unload_controller.cc b/chrome/browser/ui/unload_controller.cc |
index 3768f734c747178e8442ccc0834141cbd809edae..9e827de4958d814ffa8f6d98fb7121e4bd795bbd 100644 |
--- a/chrome/browser/ui/unload_controller.cc |
+++ b/chrome/browser/ui/unload_controller.cc |
@@ -4,6 +4,9 @@ |
#include "chrome/browser/ui/unload_controller.h" |
+#include <set> |
+ |
+#include "base/callback.h" |
#include "base/message_loop.h" |
#include "chrome/browser/ui/browser.h" |
#include "chrome/browser/ui/browser_tabstrip.h" |
@@ -15,15 +18,73 @@ |
#include "content/public/browser/notification_types.h" |
#include "content/public/browser/render_view_host.h" |
#include "content/public/browser/web_contents.h" |
+#include "content/public/browser/web_contents_delegate.h" |
namespace chrome { |
+typedef base::Callback<void(void)> DetachedTabsClosedCallback; |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// UnloadDetachedHandler is used to close tabs quickly, http://crbug.com/142458. |
+// - Allows unload handlers to run in the background. |
+// - Comes into play after the beforeunload handlers (if any) have run. |
+// - Does not close the tabs; it holds tabs while they are closed. |
+class UnloadDetachedHandler : public content::WebContentsDelegate { |
+ public: |
+ explicit UnloadDetachedHandler(const DetachedTabsClosedCallback& callback) |
+ : tabs_closed_callback_(callback) { } |
+ virtual ~UnloadDetachedHandler() { } |
+ |
+ // Returns true if it succeeds. |
+ bool DetachWebContents(TabStripModel* tab_strip_model, |
+ content::WebContents* web_contents) { |
+ int index = tab_strip_model->GetIndexOfWebContents(web_contents); |
+ if (index != TabStripModel::kNoTab && |
+ web_contents->NeedToFireBeforeUnload()) { |
+ tab_strip_model->DetachTabContentsAndCreateHistoryAt(index); |
+ detached_web_contents_.insert(web_contents); |
+ web_contents->SetDelegate(this); |
+ web_contents->OnUnloadDetachedStarted(); |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ bool HasTabs() const { |
+ return !detached_web_contents_.empty(); |
+ } |
+ |
+ private: |
+ // WebContentsDelegate implementation. |
+ virtual bool ShouldSuppressDialogs() OVERRIDE { |
+ return true; // Return true so dialogs are suppressed. |
+ } |
+ virtual void CloseContents(content::WebContents* source) OVERRIDE { |
+ detached_web_contents_.erase(source); |
+ TabContents* tab_contents = TabContents::FromWebContents(source); |
+ DCHECK(tab_contents); |
+ delete tab_contents; |
+ if (detached_web_contents_.empty()) |
+ tabs_closed_callback_.Run(); |
+ } |
+ |
+ const DetachedTabsClosedCallback tabs_closed_callback_; |
+ std::set<content::WebContents*> detached_web_contents_; |
+ |
+ DISALLOW_IMPLICIT_CONSTRUCTORS(UnloadDetachedHandler); |
+}; |
+ |
+ |
//////////////////////////////////////////////////////////////////////////////// |
// UnloadController, public: |
UnloadController::UnloadController(Browser* browser) |
: browser_(browser), |
is_attempting_to_close_browser_(false), |
+ ALLOW_THIS_IN_INITIALIZER_LIST( |
+ unload_detached_handler_(new UnloadDetachedHandler( |
+ base::Bind(&UnloadController::ProcessPendingTabs, |
+ base::Unretained(this))))), |
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { |
browser_->tab_strip_model()->AddObserver(this); |
} |
@@ -43,8 +104,14 @@ bool UnloadController::CanCloseContents(content::WebContents* contents) { |
bool UnloadController::BeforeUnloadFired(content::WebContents* contents, |
bool proceed) { |
if (!is_attempting_to_close_browser_) { |
- if (!proceed) |
+ if (proceed) { |
+ // No more dialogs are possible, so remove the tab and finish |
+ // running unload listeners asynchrounously. |
+ unload_detached_handler_->DetachWebContents(browser_->tab_strip_model(), |
+ contents); |
+ } else { |
contents->SetClosedByUserGesture(false); |
+ } |
return proceed; |
} |
@@ -55,8 +122,7 @@ bool UnloadController::BeforeUnloadFired(content::WebContents* contents, |
} |
if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) { |
- // Now that beforeunload has fired, put the tab on the queue to fire |
- // unload. |
+ // Now that beforeunload has fired, queue the tab to fire unload. |
tabs_needing_unload_fired_.insert(contents); |
ProcessPendingTabs(); |
// We want to handle firing the unload event ourselves since we want to |
@@ -69,16 +135,15 @@ bool UnloadController::BeforeUnloadFired(content::WebContents* contents, |
} |
bool UnloadController::ShouldCloseWindow() { |
- if (HasCompletedUnloadProcessing()) |
- return true; |
- |
- is_attempting_to_close_browser_ = true; |
- |
- if (!TabsNeedBeforeUnloadFired()) |
- return true; |
- |
- ProcessPendingTabs(); |
- return false; |
+ if (!is_attempting_to_close_browser_) { |
+ is_attempting_to_close_browser_ = true; |
+ if (TabsNeedBeforeUnloadFired()) { |
+ ProcessPendingTabs(); |
+ } else { |
+ browser_->OnWindowClosing(); |
+ } |
+ } |
+ return HasCompletedUnloadProcessing(); |
} |
bool UnloadController::TabsNeedBeforeUnloadFired() { |
@@ -160,63 +225,69 @@ void UnloadController::TabDetachedImpl(content::WebContents* contents) { |
} |
void UnloadController::ProcessPendingTabs() { |
- if (!is_attempting_to_close_browser_) { |
- // Because we might invoke this after a delay it's possible for the value of |
- // is_attempting_to_close_browser_ to have changed since we scheduled the |
- // task. |
- return; |
- } |
- |
if (HasCompletedUnloadProcessing()) { |
- // We've finished all the unload events and can proceed to close the |
- // browser. |
- browser_->OnWindowClosing(); |
- return; |
- } |
- |
- // Process beforeunload tabs first. When that queue is empty, process |
- // unload tabs. |
- if (!tabs_needing_before_unload_fired_.empty()) { |
+ if (browser_->tab_strip_model()->empty()) { |
+ browser_->OnUnloadProcessingCompleted(); |
+ } else { |
+ // Having tabs means OnWindowClosing was never called. That happens if |
+ // the last tab needing beforeunload crashes (i.e. we observe that the |
+ // web_contents is disconnected). |
+ browser_->OnWindowClosing(); |
+ } |
+ } else if (!tabs_needing_before_unload_fired_.empty()) { |
+ // Process all the beforeunload handlers before the unload handlers. |
content::WebContents* web_contents = |
*(tabs_needing_before_unload_fired_.begin()); |
// Null check render_view_host here as this gets called on a PostTask and |
// the tab's render_view_host may have been nulled out. |
if (web_contents->GetRenderViewHost()) { |
+ web_contents->OnCloseStarted(); |
web_contents->GetRenderViewHost()->FirePageBeforeUnload(false); |
} else { |
ClearUnloadState(web_contents, true); |
} |
} else if (!tabs_needing_unload_fired_.empty()) { |
- // We've finished firing all beforeunload events and can proceed with unload |
- // events. |
- // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting |
- // somewhere around here so that we have accurate measurements of shutdown |
- // time. |
- // TODO(ojan): We can probably fire all the unload events in parallel and |
- // get a perf benefit from that in the cases where the tab hangs in it's |
- // unload handler or takes a long time to page in. |
- content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin()); |
- // Null check render_view_host here as this gets called on a PostTask and |
- // the tab's render_view_host may have been nulled out. |
- if (web_contents->GetRenderViewHost()) { |
- web_contents->GetRenderViewHost()->ClosePage(); |
- } else { |
- ClearUnloadState(web_contents, true); |
+ // All beforeunload handlers have fired. Proceed with unload handlers. |
+ |
+ // Copy unload tabs to avoid iterator issues when detaching tabs. |
+ UnloadListenerSet unload_tabs = tabs_needing_unload_fired_; |
+ |
+ // Run unload handlers detached since no more interaction is possible. |
+ for (UnloadListenerSet::iterator it = unload_tabs.begin(); |
+ it != unload_tabs.end(); it++) { |
+ content::WebContents* web_contents = *it; |
+ // Null check render_view_host here as this gets called on a PostTask |
+ // and the tab's render_view_host may have been nulled out. |
+ if (web_contents->GetRenderViewHost()) { |
+ web_contents->OnUnloadStarted(); |
+ unload_detached_handler_->DetachWebContents( |
+ browser_->tab_strip_model(), web_contents); |
+ web_contents->GetRenderViewHost()->ClosePage(); |
+ } |
} |
- } else { |
- NOTREACHED(); |
+ tabs_needing_unload_fired_.clear(); |
+ |
+ // Close the remaining tabs to make the browser hide the window |
+ // while the unload handlers finish. |
+ browser_->OnWindowClosing(); |
} |
} |
bool UnloadController::HasCompletedUnloadProcessing() const { |
return is_attempting_to_close_browser_ && |
tabs_needing_before_unload_fired_.empty() && |
- tabs_needing_unload_fired_.empty(); |
+ tabs_needing_unload_fired_.empty() && |
+ !unload_detached_handler_->HasTabs(); |
} |
void UnloadController::CancelWindowClose() { |
// Closing of window can be canceled from a beforeunload handler. |
DCHECK(is_attempting_to_close_browser_); |
+ for (UnloadListenerSet::iterator it = tabs_needing_unload_fired_.begin(); |
+ it != tabs_needing_unload_fired_.end(); it++) { |
+ content::WebContents* web_contents = *it; |
+ web_contents->OnCloseCanceled(); |
+ } |
tabs_needing_before_unload_fired_.clear(); |
tabs_needing_unload_fired_.clear(); |
is_attempting_to_close_browser_ = false; |