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"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/render_view_host.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/web_contents.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace chrome {
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, public:
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)UnloadController::UnloadController(Browser* browser)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : browser_(browser),
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      is_attempting_to_close_browser_(false),
27c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      weak_factory_(this) {
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  browser_->tab_strip_model()->AddObserver(this);
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)UnloadController::~UnloadController() {
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  browser_->tab_strip_model()->RemoveObserver(this);
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::CanCloseContents(content::WebContents* contents) {
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Don't try to close the tab when the whole browser is being closed, since
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // that avoids the fast shutdown path where we just kill all the renderers.
38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (is_attempting_to_close_browser_)
39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ClearUnloadState(contents, true);
403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  return !is_attempting_to_close_browser_ ||
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      is_calling_before_unload_handlers();
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// static
45f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)bool UnloadController::ShouldRunUnloadEventsHelper(
46f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    content::WebContents* contents) {
47f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // If |contents| is being inspected, devtools needs to intercept beforeunload
48f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // events.
49116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return DevToolsWindow::GetInstanceForInspectedWebContents(contents) != NULL;
50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
51f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
52f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// static
534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)bool UnloadController::RunUnloadEventsHelper(content::WebContents* contents) {
54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // If there's a devtools window attached to |contents|,
55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // we would like devtools to call its own beforeunload handlers first,
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // and then call beforeunload handlers for |contents|.
57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // See DevToolsWindow::InterceptPageBeforeUnload for details.
58f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (DevToolsWindow::InterceptPageBeforeUnload(contents)) {
59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return true;
60f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // If the WebContents is not connected yet, then there's no unload
624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // handler we can fire even if the WebContents has an unload listener.
634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // One case where we hit this is in a tab that has an infinite loop
644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // before load.
654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (contents->NeedToFireBeforeUnload()) {
664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // If the page has unload listeners, then we tell the renderer to fire
674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // them. Once they have fired, we'll get a message back saying whether
684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // to proceed closing the page or not, which sends us back to this method
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // with the NeedToFireBeforeUnload bit cleared.
70c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    contents->DispatchBeforeUnload(false);
714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return true;
724e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  return false;
744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         bool proceed) {
78f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (!proceed)
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    DevToolsWindow::OnPageCloseCanceled(contents);
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!is_attempting_to_close_browser_) {
82eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (!proceed)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      contents->SetClosedByUserGesture(false);
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return proceed;
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!proceed) {
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CancelWindowClose();
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    contents->SetClosedByUserGesture(false);
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
93eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) {
94eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // Now that beforeunload has fired, put the tab on the queue to fire
95eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // unload.
96eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    tabs_needing_unload_fired_.insert(contents);
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ProcessPendingTabs();
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // We want to handle firing the unload event ourselves since we want to
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // fire all the beforeunload events before attempting to fire the unload
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // events should the user cancel closing the browser.
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::ShouldCloseWindow() {
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (HasCompletedUnloadProcessing())
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
111f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Special case for when we quit an application. The devtools window can
112f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // close if it's beforeunload event has already fired which will happen due
113f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // to the interception of it's content's beforeunload.
114f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (browser_->is_devtools() &&
115f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) {
116f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return true;
117f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
118f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // The behavior followed here varies based on the current phase of the
1203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // operation and whether a batched shutdown is in progress.
1213551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //
1223551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // If there are tabs with outstanding beforeunload handlers:
1233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 1. If a batched shutdown is in progress: return false.
1243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //    This is to prevent interference with batched shutdown already in
1253551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //    progress.
1263551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 2. Otherwise: start sending beforeunload events and return false.
1273551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //
1283551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // Otherwise, If there are no tabs with outstanding beforeunload handlers:
1293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 3. If a batched shutdown is in progress: start sending unload events and
1303551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  //    return false.
1313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // 4. Otherwise: return true.
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_attempting_to_close_browser_ = true;
1333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // Cases 1 and 4.
1343551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
1353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (need_beforeunload_fired == is_calling_before_unload_handlers())
1363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return !need_beforeunload_fired;
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // Cases 2 and 3.
1393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  on_close_confirmed_.Reset();
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ProcessPendingTabs();
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1443551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)bool UnloadController::CallBeforeUnloadHandlers(
1453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    const base::Callback<void(bool)>& on_close_confirmed) {
146f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // The devtools browser gets its beforeunload events as the results of
147f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // intercepting events from the inspected tab, so don't send them here as
148f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // well.
149f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (browser_->is_devtools() || HasCompletedUnloadProcessing() ||
150f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      !TabsNeedBeforeUnloadFired())
1513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return false;
1523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1533551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  is_attempting_to_close_browser_ = true;
1543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  on_close_confirmed_ = on_close_confirmed;
1553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  ProcessPendingTabs();
1573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  return true;
1583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)}
1593551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1603551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void UnloadController::ResetBeforeUnloadHandlers() {
1613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (!is_calling_before_unload_handlers())
1623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return;
1633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  CancelWindowClose();
1643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)}
1653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::TabsNeedBeforeUnloadFired() {
167eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (tabs_needing_before_unload_fired_.empty()) {
168eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
169eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      content::WebContents* contents =
170eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch          browser_->tab_strip_model()->GetWebContentsAt(i);
171f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() ||
172f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          DevToolsWindow::NeedsToInterceptBeforeUnload(contents);
1733551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      if (!ContainsKey(tabs_needing_unload_fired_, contents) &&
174f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          should_fire_beforeunload) {
175eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        tabs_needing_before_unload_fired_.insert(contents);
1763551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      }
177eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return !tabs_needing_before_unload_fired_.empty();
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
182a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)void UnloadController::CancelWindowClose() {
183a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Closing of window can be canceled from a beforeunload handler.
184a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK(is_attempting_to_close_browser_);
185a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  tabs_needing_before_unload_fired_.clear();
186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  for (UnloadListenerSet::iterator it = tabs_needing_unload_fired_.begin();
187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      it != tabs_needing_unload_fired_.end(); ++it) {
188a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    DevToolsWindow::OnPageCloseCanceled(*it);
189a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  tabs_needing_unload_fired_.clear();
191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (is_calling_before_unload_handlers()) {
192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    on_close_confirmed_.Reset();
194a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    on_close_confirmed.Run(false);
195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
196a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  is_attempting_to_close_browser_ = false;
197a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
198a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  content::NotificationService::current()->Notify(
199a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      content::Source<Browser>(browser_),
201a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      content::NotificationService::NoDetails());
202a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
203a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, content::NotificationObserver implementation:
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::Observe(int type,
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               const content::NotificationSource& source,
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               const content::NotificationDetails& details) {
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  switch (type) {
211eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
212eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if (is_attempting_to_close_browser_) {
213eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        ClearUnloadState(content::Source<content::WebContents>(source).ptr(),
214eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                         false);  // See comment for ClearUnloadState().
215eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      }
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    default:
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      NOTREACHED() << "Got a notification we didn't register for.";
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, TabStripModelObserver implementation:
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabInsertedAt(content::WebContents* contents,
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     int index,
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     bool foreground) {
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TabAttachedImpl(contents);
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabDetachedAt(content::WebContents* contents,
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     int index) {
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TabDetachedImpl(contents);
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
2372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                     content::WebContents* old_contents,
2382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                     content::WebContents* new_contents,
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     int index) {
2402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  TabDetachedImpl(old_contents);
2412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  TabAttachedImpl(new_contents);
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabStripEmpty() {
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // attempt to add tabs to the browser before it closes.
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_attempting_to_close_browser_ = true;
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)////////////////////////////////////////////////////////////////////////////////
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// UnloadController, private:
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabAttachedImpl(content::WebContents* contents) {
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the tab crashes in the beforeunload or unload handler, it won't be
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // able to ack. But we know we can close it.
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  registrar_.Add(
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this,
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      content::Source<content::WebContents>(contents));
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::TabDetachedImpl(content::WebContents* contents) {
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (is_attempting_to_close_browser_)
264eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    ClearUnloadState(contents, false);
265eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  registrar_.Remove(this,
266eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                    content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
267eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                    content::Source<content::WebContents>(contents));
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void UnloadController::ProcessPendingTabs() {
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!is_attempting_to_close_browser_) {
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Because we might invoke this after a delay it's possible for the value of
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // is_attempting_to_close_browser_ to have changed since we scheduled the
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // task.
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
278eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (HasCompletedUnloadProcessing()) {
279eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // We've finished all the unload events and can proceed to close the
280eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // browser.
281eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    browser_->OnWindowClosing();
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
285eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // Process beforeunload tabs first. When that queue is empty, process
286eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // unload tabs.
287eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (!tabs_needing_before_unload_fired_.empty()) {
288eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    content::WebContents* web_contents =
289eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        *(tabs_needing_before_unload_fired_.begin());
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Null check render_view_host here as this gets called on a PostTask and
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // the tab's render_view_host may have been nulled out.
292eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (web_contents->GetRenderViewHost()) {
293f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // If there's a devtools window attached to |web_contents|,
294f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // we would like devtools to call its own beforeunload handlers first,
295f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // and then call beforeunload handlers for |web_contents|.
296f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // See DevToolsWindow::InterceptPageBeforeUnload for details.
297f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if (!DevToolsWindow::InterceptPageBeforeUnload(web_contents))
298c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        web_contents->DispatchBeforeUnload(false);
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
300eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      ClearUnloadState(web_contents, true);
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3023551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  } else if (is_calling_before_unload_handlers()) {
3031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
3041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // Reset |on_close_confirmed_| in case the callback tests
3051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // |is_calling_before_unload_handlers()|, we want to return that calling
3061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // is complete.
3071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if (tabs_needing_unload_fired_.empty())
3081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      on_close_confirmed_.Reset();
3091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    on_close_confirmed.Run(true);
310eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else if (!tabs_needing_unload_fired_.empty()) {
311eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // We've finished firing all beforeunload events and can proceed with unload
312eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // events.
313eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting
314eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // somewhere around here so that we have accurate measurements of shutdown
315eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // time.
316eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // TODO(ojan): We can probably fire all the unload events in parallel and
317eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // get a perf benefit from that in the cases where the tab hangs in it's
318eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // unload handler or takes a long time to page in.
319eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin());
320eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // Null check render_view_host here as this gets called on a PostTask and
321eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // the tab's render_view_host may have been nulled out.
322eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (web_contents->GetRenderViewHost()) {
323eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      web_contents->GetRenderViewHost()->ClosePage();
324868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    } else {
325eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      ClearUnloadState(web_contents, true);
326868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    }
327eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  } else {
328eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    NOTREACHED();
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool UnloadController::HasCompletedUnloadProcessing() const {
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return is_attempting_to_close_browser_ &&
334eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      tabs_needing_before_unload_fired_.empty() &&
335eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      tabs_needing_unload_fired_.empty();
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
338eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool UnloadController::RemoveFromSet(UnloadListenerSet* set,
339eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                     content::WebContents* web_contents) {
340eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  DCHECK(is_attempting_to_close_browser_);
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
342eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  UnloadListenerSet::iterator iter =
343eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      std::find(set->begin(), set->end(), web_contents);
344eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (iter != set->end()) {
345eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    set->erase(iter);
346eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return true;
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
348eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  return false;
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
351eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid UnloadController::ClearUnloadState(content::WebContents* web_contents,
352eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                        bool process_now) {
353eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (is_attempting_to_close_browser_) {
354eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents);
355eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    RemoveFromSet(&tabs_needing_unload_fired_, web_contents);
356eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (process_now) {
357eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      ProcessPendingTabs();
358eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    } else {
359eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      base::MessageLoop::current()->PostTask(
360eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch          FROM_HERE,
361eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch          base::Bind(&UnloadController::ProcessPendingTabs,
362eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                     weak_factory_.GetWeakPtr()));
363eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    }
364eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
365868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}
366868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace chrome
368