unload_controller.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/unload_controller.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
79ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch#include "base/message_loop/message_loop.h"
87dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "chrome/browser/chrome_notification_types.h"
94e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/devtools/devtools_window.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/browser.h"
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/browser_tabstrip.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/tabs/tab_strip_model.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/notification_service.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/notification_source.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/notification_types.h"
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "content/public/browser/render_frame_host.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/render_view_host.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/web_contents.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace chrome {
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, public:
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)UnloadController::UnloadController(Browser* browser)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : browser_(browser),
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      is_attempting_to_close_browser_(false),
28c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      weak_factory_(this) {
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  browser_->tab_strip_model()->AddObserver(this);
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)UnloadController::~UnloadController() {
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  browser_->tab_strip_model()->RemoveObserver(this);
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::CanCloseContents(content::WebContents* contents) {
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Don't try to close the tab when the whole browser is being closed, since
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // that avoids the fast shutdown path where we just kill all the renderers.
39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (is_attempting_to_close_browser_)
40eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ClearUnloadState(contents, true);
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  return !is_attempting_to_close_browser_ ||
423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      is_calling_before_unload_handlers();
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// static
46f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)bool UnloadController::ShouldRunUnloadEventsHelper(
47f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    content::WebContents* contents) {
48f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // If |contents| is being inspected, devtools needs to intercept beforeunload
49f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // events.
50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  return DevToolsWindow::GetInstanceForInspectedRenderViewHost(
51f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      contents->GetRenderViewHost()) != NULL;
52f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// static
554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)bool UnloadController::RunUnloadEventsHelper(content::WebContents* contents) {
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // If there's a devtools window attached to |contents|,
57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // we would like devtools to call its own beforeunload handlers first,
58f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // and then call beforeunload handlers for |contents|.
59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // See DevToolsWindow::InterceptPageBeforeUnload for details.
60f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (DevToolsWindow::InterceptPageBeforeUnload(contents)) {
61f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return true;
62f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // If the WebContents is not connected yet, then there's no unload
644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // handler we can fire even if the WebContents has an unload listener.
654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // One case where we hit this is in a tab that has an infinite loop
664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // before load.
674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (contents->NeedToFireBeforeUnload()) {
684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // If the page has unload listeners, then we tell the renderer to fire
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // them. Once they have fired, we'll get a message back saying whether
704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // to proceed closing the page or not, which sends us back to this method
714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // with the NeedToFireBeforeUnload bit cleared.
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    contents->GetMainFrame()->DispatchBeforeUnload(false);
734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return true;
744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  return false;
764e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
774e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         bool proceed) {
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (!proceed)
81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    DevToolsWindow::OnPageCloseCanceled(contents);
82f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!is_attempting_to_close_browser_) {
84eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (!proceed)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      contents->SetClosedByUserGesture(false);
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return proceed;
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!proceed) {
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CancelWindowClose();
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    contents->SetClosedByUserGesture(false);
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) {
96eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // Now that beforeunload has fired, put the tab on the queue to fire
97eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // unload.
98eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    tabs_needing_unload_fired_.insert(contents);
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ProcessPendingTabs();
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // We want to handle firing the unload event ourselves since we want to
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // fire all the beforeunload events before attempting to fire the unload
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // events should the user cancel closing the browser.
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::ShouldCloseWindow() {
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (HasCompletedUnloadProcessing())
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
113f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Special case for when we quit an application. The devtools window can
114f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // close if it's beforeunload event has already fired which will happen due
115f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // to the interception of it's content's beforeunload.
116f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (browser_->is_devtools() &&
117f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) {
118f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return true;
119f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1213551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // The behavior followed here varies based on the current phase of the
1223551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // operation and whether a batched shutdown is in progress.
1233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //
1243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // If there are tabs with outstanding beforeunload handlers:
1253551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 1. If a batched shutdown is in progress: return false.
1263551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //    This is to prevent interference with batched shutdown already in
1273551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //    progress.
1283551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 2. Otherwise: start sending beforeunload events and return false.
1293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //
1303551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // Otherwise, If there are no tabs with outstanding beforeunload handlers:
1313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 3. If a batched shutdown is in progress: start sending unload events and
1323551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //    return false.
1333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 4. Otherwise: return true.
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_attempting_to_close_browser_ = true;
1353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // Cases 1 and 4.
1363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
1373551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (need_beforeunload_fired == is_calling_before_unload_handlers())
1383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return !need_beforeunload_fired;
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // Cases 2 and 3.
1413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  on_close_confirmed_.Reset();
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ProcessPendingTabs();
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)bool UnloadController::CallBeforeUnloadHandlers(
1473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    const base::Callback<void(bool)>& on_close_confirmed) {
148f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // The devtools browser gets its beforeunload events as the results of
149f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // intercepting events from the inspected tab, so don't send them here as
150f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // well.
151f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (browser_->is_devtools() || HasCompletedUnloadProcessing() ||
152f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      !TabsNeedBeforeUnloadFired())
1533551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return false;
1543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  is_attempting_to_close_browser_ = true;
1563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  on_close_confirmed_ = on_close_confirmed;
1573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  ProcessPendingTabs();
1593551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  return true;
1603551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)}
1613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void UnloadController::ResetBeforeUnloadHandlers() {
1633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (!is_calling_before_unload_handlers())
1643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return;
1653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  CancelWindowClose();
1663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)}
1673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::TabsNeedBeforeUnloadFired() {
169eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (tabs_needing_before_unload_fired_.empty()) {
170eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
171eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      content::WebContents* contents =
172eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch          browser_->tab_strip_model()->GetWebContentsAt(i);
173f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() ||
174f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          DevToolsWindow::NeedsToInterceptBeforeUnload(contents);
1753551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      if (!ContainsKey(tabs_needing_unload_fired_, contents) &&
176f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          should_fire_beforeunload) {
177eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        tabs_needing_before_unload_fired_.insert(contents);
1783551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      }
179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
181eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return !tabs_needing_before_unload_fired_.empty();
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
184a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)void UnloadController::CancelWindowClose() {
185a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Closing of window can be canceled from a beforeunload handler.
186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK(is_attempting_to_close_browser_);
187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  tabs_needing_before_unload_fired_.clear();
188a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  for (UnloadListenerSet::iterator it = tabs_needing_unload_fired_.begin();
189a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      it != tabs_needing_unload_fired_.end(); ++it) {
190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    DevToolsWindow::OnPageCloseCanceled(*it);
191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  tabs_needing_unload_fired_.clear();
193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (is_calling_before_unload_handlers()) {
194a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    on_close_confirmed_.Reset();
196a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    on_close_confirmed.Run(false);
197a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
198a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  is_attempting_to_close_browser_ = false;
199a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  content::NotificationService::current()->Notify(
201a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
202a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      content::Source<Browser>(browser_),
203a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      content::NotificationService::NoDetails());
204a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
205a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, content::NotificationObserver implementation:
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::Observe(int type,
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               const content::NotificationSource& source,
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               const content::NotificationDetails& details) {
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  switch (type) {
213eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
214eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if (is_attempting_to_close_browser_) {
215eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        ClearUnloadState(content::Source<content::WebContents>(source).ptr(),
216eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                         false);  // See comment for ClearUnloadState().
217eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      }
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    default:
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      NOTREACHED() << "Got a notification we didn't register for.";
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, TabStripModelObserver implementation:
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabInsertedAt(content::WebContents* contents,
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     int index,
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     bool foreground) {
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TabAttachedImpl(contents);
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabDetachedAt(content::WebContents* contents,
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     int index) {
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TabDetachedImpl(contents);
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
2392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                     content::WebContents* old_contents,
2402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                     content::WebContents* new_contents,
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     int index) {
2422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  TabDetachedImpl(old_contents);
2432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  TabAttachedImpl(new_contents);
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabStripEmpty() {
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // attempt to add tabs to the browser before it closes.
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_attempting_to_close_browser_ = true;
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, private:
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabAttachedImpl(content::WebContents* contents) {
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the tab crashes in the beforeunload or unload handler, it won't be
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // able to ack. But we know we can close it.
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  registrar_.Add(
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this,
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      content::Source<content::WebContents>(contents));
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabDetachedImpl(content::WebContents* contents) {
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (is_attempting_to_close_browser_)
266eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ClearUnloadState(contents, false);
267eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  registrar_.Remove(this,
268eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                    content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
269eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                    content::Source<content::WebContents>(contents));
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::ProcessPendingTabs() {
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!is_attempting_to_close_browser_) {
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Because we might invoke this after a delay it's possible for the value of
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // is_attempting_to_close_browser_ to have changed since we scheduled the
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // task.
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
280eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (HasCompletedUnloadProcessing()) {
281eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // We've finished all the unload events and can proceed to close the
282eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // browser.
283eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    browser_->OnWindowClosing();
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
287eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // Process beforeunload tabs first. When that queue is empty, process
288eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // unload tabs.
289eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (!tabs_needing_before_unload_fired_.empty()) {
290eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    content::WebContents* web_contents =
291eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *(tabs_needing_before_unload_fired_.begin());
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Null check render_view_host here as this gets called on a PostTask and
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // the tab's render_view_host may have been nulled out.
294eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (web_contents->GetRenderViewHost()) {
295f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // If there's a devtools window attached to |web_contents|,
296f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // we would like devtools to call its own beforeunload handlers first,
297f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // and then call beforeunload handlers for |web_contents|.
298f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // See DevToolsWindow::InterceptPageBeforeUnload for details.
299f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if (!DevToolsWindow::InterceptPageBeforeUnload(web_contents))
300a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        web_contents->GetMainFrame()->DispatchBeforeUnload(false);
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
302eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      ClearUnloadState(web_contents, true);
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3043551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  } else if (is_calling_before_unload_handlers()) {
3053551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    on_close_confirmed_.Run(true);
306eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else if (!tabs_needing_unload_fired_.empty()) {
307eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // We've finished firing all beforeunload events and can proceed with unload
308eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // events.
309eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting
310eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // somewhere around here so that we have accurate measurements of shutdown
311eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // time.
312eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // TODO(ojan): We can probably fire all the unload events in parallel and
313eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // get a perf benefit from that in the cases where the tab hangs in it's
314eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // unload handler or takes a long time to page in.
315eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin());
316eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // Null check render_view_host here as this gets called on a PostTask and
317eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // the tab's render_view_host may have been nulled out.
318eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (web_contents->GetRenderViewHost()) {
319eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      web_contents->GetRenderViewHost()->ClosePage();
320868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    } else {
321eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      ClearUnloadState(web_contents, true);
322868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    }
323eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else {
324eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    NOTREACHED();
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::HasCompletedUnloadProcessing() const {
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return is_attempting_to_close_browser_ &&
330eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      tabs_needing_before_unload_fired_.empty() &&
331eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      tabs_needing_unload_fired_.empty();
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
334eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool UnloadController::RemoveFromSet(UnloadListenerSet* set,
335eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                     content::WebContents* web_contents) {
336eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(is_attempting_to_close_browser_);
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
338eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  UnloadListenerSet::iterator iter =
339eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      std::find(set->begin(), set->end(), web_contents);
340eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (iter != set->end()) {
341eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    set->erase(iter);
342eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return true;
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
344eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return false;
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
347eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid UnloadController::ClearUnloadState(content::WebContents* web_contents,
348eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                        bool process_now) {
349eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (is_attempting_to_close_browser_) {
350eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents);
351eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    RemoveFromSet(&tabs_needing_unload_fired_, web_contents);
352eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (process_now) {
353eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      ProcessPendingTabs();
354eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    } else {
355eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      base::MessageLoop::current()->PostTask(
356eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch          FROM_HERE,
357eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch          base::Bind(&UnloadController::ProcessPendingTabs,
358eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                     weak_factory_.GetWeakPtr()));
359eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
360eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
361868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}
362868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace chrome
364