Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4273)

Unified Diff: chrome/browser/ui/unload_controller.cc

Issue 11016023: Quickly close tabs/window with long-running unload handlers. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Keep original TabsNeedBeforeUnloadFired implementation. Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;

Powered by Google App Engine
This is Rietveld 408576698