summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/fast_unload_controller.h
blob: 7a8a45227cc481f5ade78c282347b5cf3ee23303 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright 2013 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.

#ifndef CHROME_BROWSER_UI_FAST_UNLOAD_CONTROLLER_H_
#define CHROME_BROWSER_UI_FAST_UNLOAD_CONTROLLER_H_

#include <set>

#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_piece.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"

class Browser;
class TabStripModel;

namespace content {
class NotificationSource;
class NotificationDetails;
class WebContents;
}

namespace chrome {
// FastUnloadController manages closing tabs and windows -- especially in
// regards to beforeunload handlers (have proceed/cancel dialogs) and
// unload handlers (have no user interaction).
//
// Typical flow of closing a tab:
//  1. Browser calls CanCloseContents().
//     If true, browser calls contents::CloseWebContents().
//  2. WebContents notifies us via its delegate and BeforeUnloadFired()
//     that the beforeunload handler was run. If the user allowed the
//     close to continue, we detached the tab and hold onto it while the
//     close finishes.
//
// Typical flow of closing a window:
//  1. BrowserView::CanClose() calls TabsNeedBeforeUnloadFired().
//     If beforeunload/unload handlers need to run, FastUnloadController returns
//     true and calls ProcessPendingTabs() (private method).
//  2. For each tab with a beforeunload/unload handler, ProcessPendingTabs()
//        calls |CoreTabHelper::OnCloseStarted()|
//        and   |web_contents->GetRenderViewHost()->FirePageBeforeUnload()|.
//  3. If the user allowed the close to continue, we detach all the tabs with
//     unload handlers, remove them from the tab strip, and finish closing
//     the tabs in the background.
//  4. The browser gets notified that the tab strip is empty and calls
//     CloseFrame where the empty tab strip causes the window to hide.
//     Once the detached tabs finish, the browser calls CloseFrame again and
//     the window is finally closed.
//
class FastUnloadController : public content::NotificationObserver,
                             public TabStripModelObserver {
 public:
  explicit FastUnloadController(Browser* browser);
  virtual ~FastUnloadController();

  // Returns true if |contents| can be cleanly closed. When |browser_| is being
  // closed, this function will return false to indicate |contents| should not
  // be cleanly closed, since the fast shutdown path will just kill its
  // renderer.
  bool CanCloseContents(content::WebContents* contents);

  // Returns true if we need to run unload events for the |contents|.
  static bool ShouldRunUnloadEventsHelper(content::WebContents* contents);

  // Helper function to run beforeunload listeners on a WebContents.
  // Returns true if |contents| beforeunload listeners were invoked.
  static bool RunUnloadEventsHelper(content::WebContents* contents);

  // Called when a BeforeUnload handler is fired for |contents|. |proceed|
  // indicates the user's response to the Y/N BeforeUnload handler dialog. If
  // this parameter is false, any pending attempt to close the whole browser
  // will be canceled. Returns true if Unload handlers should be fired. When the
  // |browser_| is being closed, Unload handlers for any particular WebContents
  // will not be run until every WebContents being closed has a chance to run
  // its BeforeUnloadHandler.
  bool BeforeUnloadFired(content::WebContents* contents, bool proceed);

  bool is_attempting_to_close_browser() const {
    return is_attempting_to_close_browser_;
  }

  // Called in response to a request to close |browser_|'s window. Returns true
  // when there are no remaining beforeunload handlers to be run.
  bool ShouldCloseWindow();

  // Begins the process of confirming whether the associated browser can be
  // closed.
  bool CallBeforeUnloadHandlers(
      const base::Callback<void(bool)>& on_close_confirmed);

  // Clears the results of any beforeunload confirmation dialogs triggered by a
  // CallBeforeUnloadHandlers call.
  void ResetBeforeUnloadHandlers();

  // Returns true if |browser_| has any tabs that have BeforeUnload handlers
  // that have not been fired. This method is non-const because it builds a list
  // of tabs that need their BeforeUnloadHandlers fired.
  // TODO(beng): This seems like it could be private but it is used by
  //             AreAllBrowsersCloseable() in application_lifetime.cc. It seems
  //             very similar to ShouldCloseWindow() and some consolidation
  //             could be pursued.
  bool TabsNeedBeforeUnloadFired();

  // Returns true if all tabs' beforeunload/unload events have fired.
  bool HasCompletedUnloadProcessing() const;

  // Clears all the state associated with processing tabs' beforeunload/unload
  // events since the user cancelled closing the window.
  void CancelWindowClose();

 private:
  // Overridden from content::NotificationObserver:
  virtual void Observe(int type,
                       const content::NotificationSource& source,
                       const content::NotificationDetails& details) OVERRIDE;

  // Overridden from TabStripModelObserver:
  virtual void TabInsertedAt(content::WebContents* contents,
                             int index,
                             bool foreground) OVERRIDE;
  virtual void TabDetachedAt(content::WebContents* contents,
                             int index) OVERRIDE;
  virtual void TabReplacedAt(TabStripModel* tab_strip_model,
                             content::WebContents* old_contents,
                             content::WebContents* new_contents,
                             int index) OVERRIDE;
  virtual void TabStripEmpty() OVERRIDE;

  void TabAttachedImpl(content::WebContents* contents);
  void TabDetachedImpl(content::WebContents* contents);

  // Detach |contents| and wait for it to finish closing.
  // The close must be inititiated outside of this method.
  // Returns true if it succeeds.
  bool DetachWebContents(content::WebContents* contents);

  // Processes the next tab that needs it's beforeunload/unload event fired.
  void ProcessPendingTabs();

  // Cleans up state appropriately when we are trying to close the
  // browser or close a tab in the background. We also use this in the
  // cases where a tab crashes or hangs even if the
  // beforeunload/unload haven't successfully fired.
  void ClearUnloadState(content::WebContents* contents);

  // Helper for |ClearUnloadState| to unwind stack before proceeding.
  void PostTaskForProcessPendingTabs();

  // Log a step of the unload processing.
  void LogUnloadStep(const base::StringPiece& step_name,
                     content::WebContents* contents) const;

  bool is_calling_before_unload_handlers() {
    return !on_close_confirmed_.is_null();
  }

  Browser* browser_;

  content::NotificationRegistrar registrar_;

  typedef std::set<content::WebContents*> WebContentsSet;

  // Tracks tabs that need their beforeunload event started.
  // Only gets populated when we try to close the browser.
  WebContentsSet tabs_needing_before_unload_;

  // Tracks the tab that needs its beforeunload event result.
  // Only gets populated when we try to close the browser.
  content::WebContents* tab_needing_before_unload_ack_;

  // Tracks tabs that need their unload event started.
  // Only gets populated when we try to close the browser.
  WebContentsSet tabs_needing_unload_;

  // Tracks tabs that need to finish running their unload event.
  // Populated both when closing individual tabs and when closing the browser.
  WebContentsSet tabs_needing_unload_ack_;

  // Whether we are processing the beforeunload and unload events of each tab
  // in preparation for closing the browser. FastUnloadController owns this
  // state rather than Browser because unload handlers are the only reason that
  // a Browser window isn't just immediately closed.
  bool is_attempting_to_close_browser_;

  // A callback to call to report whether the user chose to close all tabs of
  // |browser_| that have beforeunload event handlers. This is set only if we
  // are currently confirming that the browser is closable.
  base::Callback<void(bool)> on_close_confirmed_;

  // Manage tabs with beforeunload/unload handlers that close detached.
  class DetachedWebContentsDelegate;
  scoped_ptr<DetachedWebContentsDelegate> detached_delegate_;

  base::WeakPtrFactory<FastUnloadController> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(FastUnloadController);
};

}  // namespace chrome

#endif  // CHROME_BROWSER_UI_FAST_UNLOAD_CONTROLLER_H_