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 |