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

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

Issue 10749002: Move unload handling off Browser onto its own class, UnloadController. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 5 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
« no previous file with comments | « chrome/browser/ui/unload_controller.h ('k') | chrome/chrome_browser.gypi » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/ui/unload_controller.cc
===================================================================
--- chrome/browser/ui/unload_controller.cc (revision 0)
+++ chrome/browser/ui/unload_controller.cc (revision 0)
@@ -0,0 +1,258 @@
+// 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/unload_controller.h"
+
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/tab_contents/tab_contents.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.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"
+
+namespace chrome {
+
+////////////////////////////////////////////////////////////////////////////////
+// UnloadController, public:
+
+UnloadController::UnloadController(Browser* browser)
+ : browser_(browser),
+ is_attempting_to_close_browser_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+ browser_->tab_strip_model()->AddObserver(this);
+}
+
+UnloadController::~UnloadController() {
+ browser_->tab_strip_model()->RemoveObserver(this);
+}
+
+bool UnloadController::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.
+ if (is_attempting_to_close_browser_)
+ ClearUnloadState(contents, true);
+ return !is_attempting_to_close_browser_;
+}
+
+bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
+ bool proceed) {
+ if (!is_attempting_to_close_browser_) {
+ if (!proceed)
+ contents->SetClosedByUserGesture(false);
+ return proceed;
+ }
+
+ if (!proceed) {
+ CancelWindowClose();
+ contents->SetClosedByUserGesture(false);
+ return false;
+ }
+
+ if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) {
+ // Now that beforeunload has fired, put the tab on the queue to fire
+ // unload.
+ tabs_needing_unload_fired_.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 UnloadController::ShouldCloseWindow() {
+ if (HasCompletedUnloadProcessing())
+ return true;
+
+ is_attempting_to_close_browser_ = true;
+
+ if (!TabsNeedBeforeUnloadFired())
+ return true;
+
+ ProcessPendingTabs();
+ return false;
+}
+
+bool UnloadController::TabsNeedBeforeUnloadFired() {
+ if (tabs_needing_before_unload_fired_.empty()) {
+ for (int i = 0; i < browser_->tab_count(); ++i) {
+ content::WebContents* contents =
+ chrome::GetTabContentsAt(browser_, i)->web_contents();
+ if (contents->NeedToFireBeforeUnload())
+ tabs_needing_before_unload_fired_.insert(contents);
+ }
+ }
+ return !tabs_needing_before_unload_fired_.empty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// UnloadController, content::NotificationObserver implementation:
+
+void UnloadController::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
+ if (is_attempting_to_close_browser_) {
+ ClearUnloadState(content::Source<content::WebContents>(source).ptr(),
+ false); // See comment for ClearUnloadState().
+ }
+ break;
+ default:
+ NOTREACHED() << "Got a notification we didn't register for.";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// UnloadController, TabStripModelObserver implementation:
+
+void UnloadController::TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground) {
+ TabAttachedImpl(contents);
+}
+
+void UnloadController::TabDetachedAt(TabContents* contents, int index) {
+ TabDetachedImpl(contents);
+}
+
+void UnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
+ TabContents* old_contents,
+ TabContents* new_contents,
+ int index) {
+ TabDetachedImpl(old_contents);
+ TabAttachedImpl(new_contents);
+}
+
+void UnloadController::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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// UnloadController, private:
+
+void UnloadController::TabAttachedImpl(TabContents* 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->web_contents()));
+}
+
+void UnloadController::TabDetachedImpl(TabContents* contents) {
+ if (is_attempting_to_close_browser_)
+ ClearUnloadState(contents->web_contents(), false);
+ registrar_.Remove(
+ this,
+ content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ content::Source<content::WebContents>(contents->web_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()) {
+ 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->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);
+ }
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool UnloadController::HasCompletedUnloadProcessing() const {
+ return is_attempting_to_close_browser_ &&
+ tabs_needing_before_unload_fired_.empty() &&
+ tabs_needing_unload_fired_.empty();
+}
+
+void UnloadController::CancelWindowClose() {
+ // Closing of window can be canceled from a beforeunload handler.
+ DCHECK(is_attempting_to_close_browser_);
+ tabs_needing_before_unload_fired_.clear();
+ tabs_needing_unload_fired_.clear();
+ is_attempting_to_close_browser_ = false;
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
+ content::Source<Browser>(browser_),
+ content::NotificationService::NoDetails());
+}
+
+bool UnloadController::RemoveFromSet(UnloadListenerSet* set,
+ content::WebContents* web_contents) {
+ DCHECK(is_attempting_to_close_browser_);
+
+ UnloadListenerSet::iterator iter =
+ std::find(set->begin(), set->end(), web_contents);
+ if (iter != set->end()) {
+ set->erase(iter);
+ return true;
+ }
+ return false;
+}
+
+void UnloadController::ClearUnloadState(content::WebContents* web_contents,
+ bool process_now) {
+ if (is_attempting_to_close_browser_) {
+ RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents);
+ RemoveFromSet(&tabs_needing_unload_fired_, web_contents);
+ if (process_now) {
+ ProcessPendingTabs();
+ } else {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&UnloadController::ProcessPendingTabs,
+ weak_factory_.GetWeakPtr()));
+ }
+ }
+}
+
+} // namespace chrome
Property changes on: chrome\browser\ui\unload_controller.cc
___________________________________________________________________
Added: svn:eol-style
+ LF
« no previous file with comments | « chrome/browser/ui/unload_controller.h ('k') | chrome/chrome_browser.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698