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

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

Issue 17571018: Reland fast tab closure behind a flag (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebased, 2nd attempt to land. Created 7 years, 6 months 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/fast_unload_controller.cc
diff --git a/chrome/browser/ui/fast_unload_controller.cc b/chrome/browser/ui/fast_unload_controller.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ab66e7a9e024c9e8127d845436be1250c8cc7f27
--- /dev/null
+++ b/chrome/browser/ui/fast_unload_controller.cc
@@ -0,0 +1,380 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/fast_unload_controller.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#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 {
+
+
+////////////////////////////////////////////////////////////////////////////////
+// DetachedWebContentsDelegate will delete web contents when they close.
+class FastUnloadController::DetachedWebContentsDelegate
+ : public content::WebContentsDelegate {
+ public:
+ DetachedWebContentsDelegate() { }
+ virtual ~DetachedWebContentsDelegate() { }
+
+ private:
+ // WebContentsDelegate implementation.
+ virtual bool ShouldSuppressDialogs() OVERRIDE {
+ return true; // Return true so dialogs are suppressed.
+ }
+
+ virtual void CloseContents(content::WebContents* source) OVERRIDE {
+ // Finished detached close.
+ // FastUnloadController will observe
+ // |NOTIFICATION_WEB_CONTENTS_DISCONNECTED|.
+ delete source;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(DetachedWebContentsDelegate);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// FastUnloadController, public:
+
+FastUnloadController::FastUnloadController(Browser* browser)
+ : browser_(browser),
+ tab_needing_before_unload_ack_(NULL),
+ is_attempting_to_close_browser_(false),
+ detached_delegate_(new DetachedWebContentsDelegate()),
+ weak_factory_(this) {
+ browser_->tab_strip_model()->AddObserver(this);
+}
+
+FastUnloadController::~FastUnloadController() {
+ browser_->tab_strip_model()->RemoveObserver(this);
+}
+
+bool FastUnloadController::CanCloseContents(content::WebContents* contents) {
+ // Don't try to close the tab when the whole browser is being closed, since
+ // that avoids the fast shutdown path where we just kill all the renderers.
+ return !is_attempting_to_close_browser_;
+}
+
+bool FastUnloadController::BeforeUnloadFired(content::WebContents* contents,
+ bool proceed) {
+ if (!is_attempting_to_close_browser_) {
+ if (!proceed) {
+ contents->SetClosedByUserGesture(false);
+ } else {
+ // No more dialogs are possible, so remove the tab and finish
+ // running unload listeners asynchrounously.
+ browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents);
+ DetachWebContents(contents);
+ }
+ return proceed;
+ }
+
+ if (!proceed) {
+ CancelWindowClose();
+ contents->SetClosedByUserGesture(false);
+ return false;
+ }
+
+ if (tab_needing_before_unload_ack_ == contents) {
+ // Now that beforeunload has fired, queue the tab to fire unload.
+ tab_needing_before_unload_ack_ = NULL;
+ tabs_needing_unload_.insert(contents);
+ ProcessPendingTabs();
+ // We want to handle firing the unload event ourselves since we want to
+ // fire all the beforeunload events before attempting to fire the unload
+ // events should the user cancel closing the browser.
+ return false;
+ }
+
+ return true;
+}
+
+bool FastUnloadController::ShouldCloseWindow() {
+ if (HasCompletedUnloadProcessing())
+ return true;
+
+ is_attempting_to_close_browser_ = true;
+
+ if (!TabsNeedBeforeUnloadFired())
+ return true;
+
+ ProcessPendingTabs();
+ return false;
+}
+
+bool FastUnloadController::TabsNeedBeforeUnloadFired() {
+ if (!tabs_needing_before_unload_.empty() ||
+ tab_needing_before_unload_ack_ != NULL)
+ return true;
+
+ if (!tabs_needing_unload_.empty())
+ return false;
+
+ for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
+ content::WebContents* contents =
+ browser_->tab_strip_model()->GetWebContentsAt(i);
+ if (contents->NeedToFireBeforeUnload())
+ tabs_needing_before_unload_.insert(contents);
+ }
+ return !tabs_needing_before_unload_.empty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FastUnloadController, content::NotificationObserver implementation:
+
+void FastUnloadController::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: {
+ registrar_.Remove(this,
+ content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ source);
+ content::WebContents* contents =
+ content::Source<content::WebContents>(source).ptr();
+ ClearUnloadState(contents);
+ break;
+ }
+ default:
+ NOTREACHED() << "Got a notification we didn't register for.";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FastUnloadController, TabStripModelObserver implementation:
+
+void FastUnloadController::TabInsertedAt(content::WebContents* contents,
+ int index,
+ bool foreground) {
+ TabAttachedImpl(contents);
+}
+
+void FastUnloadController::TabDetachedAt(content::WebContents* contents,
+ int index) {
+ TabDetachedImpl(contents);
+}
+
+void FastUnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
+ content::WebContents* old_contents,
+ content::WebContents* new_contents,
+ int index) {
+ TabDetachedImpl(old_contents);
+ TabAttachedImpl(new_contents);
+}
+
+void FastUnloadController::TabStripEmpty() {
+ // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
+ // attempt to add tabs to the browser before it closes.
+ is_attempting_to_close_browser_ = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FastUnloadController, private:
+
+void FastUnloadController::TabAttachedImpl(content::WebContents* contents) {
+ // If the tab crashes in the beforeunload or unload handler, it won't be
+ // able to ack. But we know we can close it.
+ registrar_.Add(
+ this,
+ content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ content::Source<content::WebContents>(contents));
+}
+
+void FastUnloadController::TabDetachedImpl(content::WebContents* contents) {
+ if (tabs_needing_unload_ack_.find(contents) !=
+ tabs_needing_unload_ack_.end()) {
+ // Tab needs unload to complete.
+ // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done.
+ return;
+ }
+
+ // If WEB_CONTENTS_DISCONNECTED was received then the notification may have
+ // already been unregistered.
+ const content::NotificationSource& source =
+ content::Source<content::WebContents>(contents);
+ if (registrar_.IsRegistered(this,
+ content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ source)) {
+ registrar_.Remove(this,
+ content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ source);
+ }
+
+ if (is_attempting_to_close_browser_)
+ ClearUnloadState(contents);
+}
+
+bool FastUnloadController::DetachWebContents(content::WebContents* contents) {
+ int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents);
+ if (index != TabStripModel::kNoTab &&
+ contents->NeedToFireBeforeUnload()) {
+ tabs_needing_unload_ack_.insert(contents);
+ browser_->tab_strip_model()->DetachWebContentsAt(index);
+ contents->SetDelegate(detached_delegate_.get());
+ CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
+ core_tab_helper->OnUnloadDetachedStarted();
+ return true;
+ }
+ return false;
+}
+
+void FastUnloadController::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 (tab_needing_before_unload_ack_ != NULL) {
+ // Wait for |BeforeUnloadFired| before proceeding.
+ return;
+ }
+
+ // Process a beforeunload handler.
+ if (!tabs_needing_before_unload_.empty()) {
+ WebContentsSet::iterator it = tabs_needing_before_unload_.begin();
+ content::WebContents* contents = *it;
+ tabs_needing_before_unload_.erase(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 (contents->GetRenderViewHost()) {
+ tab_needing_before_unload_ack_ = contents;
+
+ CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
+ core_tab_helper->OnCloseStarted();
+
+ contents->GetRenderViewHost()->FirePageBeforeUnload(false);
+ } else {
+ ProcessPendingTabs();
+ }
+ return;
+ }
+
+ // Process all the unload handlers. (The beforeunload handlers have finished.)
+ if (!tabs_needing_unload_.empty()) {
+ browser_->OnWindowClosing();
+
+ // Run unload handlers detached since no more interaction is possible.
+ WebContentsSet::iterator it = tabs_needing_unload_.begin();
+ while (it != tabs_needing_unload_.end()) {
+ WebContentsSet::iterator current = it++;
+ content::WebContents* contents = *current;
+ tabs_needing_unload_.erase(current);
+ // 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 (contents->GetRenderViewHost()) {
+ CoreTabHelper* core_tab_helper =
+ CoreTabHelper::FromWebContents(contents);
+ core_tab_helper->OnUnloadStarted();
+ DetachWebContents(contents);
+ contents->GetRenderViewHost()->ClosePage();
+ }
+ }
+
+ // Get the browser hidden.
+ if (browser_->tab_strip_model()->empty()) {
+ browser_->TabStripEmpty();
+ } else {
+ browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload
+ }
+ return;
+ }
+
+ if (HasCompletedUnloadProcessing()) {
+ browser_->OnWindowClosing();
+
+ // Get the browser closed.
+ if (browser_->tab_strip_model()->empty()) {
+ browser_->TabStripEmpty();
+ } else {
+ // There may be tabs if the last tab needing beforeunload crashed.
+ browser_->tab_strip_model()->CloseAllTabs();
+ }
+ return;
+ }
+}
+
+bool FastUnloadController::HasCompletedUnloadProcessing() const {
+ return is_attempting_to_close_browser_ &&
+ tabs_needing_before_unload_.empty() &&
+ tab_needing_before_unload_ack_ == NULL &&
+ tabs_needing_unload_.empty() &&
+ tabs_needing_unload_ack_.empty();
+}
+
+void FastUnloadController::CancelWindowClose() {
+ // Closing of window can be canceled from a beforeunload handler.
+ DCHECK(is_attempting_to_close_browser_);
+ tabs_needing_before_unload_.clear();
+ if (tab_needing_before_unload_ack_ != NULL) {
+
+ CoreTabHelper* core_tab_helper =
+ CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_);
+ core_tab_helper->OnCloseCanceled();
+ tab_needing_before_unload_ack_ = NULL;
+ }
+ for (WebContentsSet::iterator it = tabs_needing_unload_.begin();
+ it != tabs_needing_unload_.end(); it++) {
+ content::WebContents* contents = *it;
+
+ CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
+ core_tab_helper->OnCloseCanceled();
+ }
+ tabs_needing_unload_.clear();
+
+ // No need to clear tabs_needing_unload_ack_. Those tabs are already detached.
+
+ is_attempting_to_close_browser_ = false;
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
+ content::Source<Browser>(browser_),
+ content::NotificationService::NoDetails());
+}
+
+void FastUnloadController::ClearUnloadState(content::WebContents* contents) {
+ if (tabs_needing_unload_ack_.erase(contents) > 0) {
+ if (HasCompletedUnloadProcessing())
+ PostTaskForProcessPendingTabs();
+ return;
+ }
+
+ if (!is_attempting_to_close_browser_)
+ return;
+
+ if (tab_needing_before_unload_ack_ == contents) {
+ tab_needing_before_unload_ack_ = NULL;
+ PostTaskForProcessPendingTabs();
+ return;
+ }
+
+ if (tabs_needing_before_unload_.erase(contents) > 0 ||
+ tabs_needing_unload_.erase(contents) > 0) {
+ if (tab_needing_before_unload_ack_ == NULL)
+ PostTaskForProcessPendingTabs();
+ }
+}
+
+void FastUnloadController::PostTaskForProcessPendingTabs() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FastUnloadController::ProcessPendingTabs,
+ weak_factory_.GetWeakPtr()));
+}
+
+} // namespace chrome

Powered by Google App Engine
This is Rietveld 408576698