| 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
|
|
|