1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/unload_controller.h"
7 #include "base/message_loop/message_loop.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/devtools/devtools_window.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_tabstrip.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "content/public/browser/notification_service.h"
14 #include "content/public/browser/notification_source.h"
15 #include "content/public/browser/notification_types.h"
16 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/web_contents.h"
21 ////////////////////////////////////////////////////////////////////////////////
22 // UnloadController, public:
24 UnloadController::UnloadController(Browser* browser)
26 is_attempting_to_close_browser_(false),
28 browser_->tab_strip_model()->AddObserver(this);
31 UnloadController::~UnloadController() {
32 browser_->tab_strip_model()->RemoveObserver(this);
35 bool UnloadController::CanCloseContents(content::WebContents* contents) {
36 // Don't try to close the tab when the whole browser is being closed, since
37 // that avoids the fast shutdown path where we just kill all the renderers.
38 if (is_attempting_to_close_browser_)
39 ClearUnloadState(contents, true);
40 return !is_attempting_to_close_browser_ ||
41 is_calling_before_unload_handlers();
45 bool UnloadController::RunUnloadEventsHelper(content::WebContents* contents) {
46 // If the WebContents is not connected yet, then there's no unload
47 // handler we can fire even if the WebContents has an unload listener.
48 // One case where we hit this is in a tab that has an infinite loop
50 if (contents->NeedToFireBeforeUnload()) {
51 // If the page has unload listeners, then we tell the renderer to fire
52 // them. Once they have fired, we'll get a message back saying whether
53 // to proceed closing the page or not, which sends us back to this method
54 // with the NeedToFireBeforeUnload bit cleared.
55 contents->GetRenderViewHost()->FirePageBeforeUnload(false);
61 bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
63 if (!is_attempting_to_close_browser_) {
65 contents->SetClosedByUserGesture(false);
71 contents->SetClosedByUserGesture(false);
75 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) {
76 // Now that beforeunload has fired, put the tab on the queue to fire
78 tabs_needing_unload_fired_.insert(contents);
80 // We want to handle firing the unload event ourselves since we want to
81 // fire all the beforeunload events before attempting to fire the unload
82 // events should the user cancel closing the browser.
89 bool UnloadController::ShouldCloseWindow() {
90 if (HasCompletedUnloadProcessing())
93 // The behavior followed here varies based on the current phase of the
94 // operation and whether a batched shutdown is in progress.
96 // If there are tabs with outstanding beforeunload handlers:
97 // 1. If a batched shutdown is in progress: return false.
98 // This is to prevent interference with batched shutdown already in
100 // 2. Otherwise: start sending beforeunload events and return false.
102 // Otherwise, If there are no tabs with outstanding beforeunload handlers:
103 // 3. If a batched shutdown is in progress: start sending unload events and
105 // 4. Otherwise: return true.
106 is_attempting_to_close_browser_ = true;
108 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
109 if (need_beforeunload_fired == is_calling_before_unload_handlers())
110 return !need_beforeunload_fired;
113 on_close_confirmed_.Reset();
114 ProcessPendingTabs();
118 bool UnloadController::CallBeforeUnloadHandlers(
119 const base::Callback<void(bool)>& on_close_confirmed) {
120 if (HasCompletedUnloadProcessing() || !TabsNeedBeforeUnloadFired())
123 is_attempting_to_close_browser_ = true;
124 on_close_confirmed_ = on_close_confirmed;
126 ProcessPendingTabs();
130 void UnloadController::ResetBeforeUnloadHandlers() {
131 if (!is_calling_before_unload_handlers())
136 bool UnloadController::TabsNeedBeforeUnloadFired() {
137 if (tabs_needing_before_unload_fired_.empty()) {
138 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
139 content::WebContents* contents =
140 browser_->tab_strip_model()->GetWebContentsAt(i);
141 if (!ContainsKey(tabs_needing_unload_fired_, contents) &&
142 contents->NeedToFireBeforeUnload()) {
143 tabs_needing_before_unload_fired_.insert(contents);
147 return !tabs_needing_before_unload_fired_.empty();
150 ////////////////////////////////////////////////////////////////////////////////
151 // UnloadController, content::NotificationObserver implementation:
153 void UnloadController::Observe(int type,
154 const content::NotificationSource& source,
155 const content::NotificationDetails& details) {
157 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
158 if (is_attempting_to_close_browser_) {
159 ClearUnloadState(content::Source<content::WebContents>(source).ptr(),
160 false); // See comment for ClearUnloadState().
164 NOTREACHED() << "Got a notification we didn't register for.";
168 ////////////////////////////////////////////////////////////////////////////////
169 // UnloadController, TabStripModelObserver implementation:
171 void UnloadController::TabInsertedAt(content::WebContents* contents,
174 TabAttachedImpl(contents);
177 void UnloadController::TabDetachedAt(content::WebContents* contents,
179 TabDetachedImpl(contents);
182 void UnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
183 content::WebContents* old_contents,
184 content::WebContents* new_contents,
186 TabDetachedImpl(old_contents);
187 TabAttachedImpl(new_contents);
190 void UnloadController::TabStripEmpty() {
191 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
192 // attempt to add tabs to the browser before it closes.
193 is_attempting_to_close_browser_ = true;
196 ////////////////////////////////////////////////////////////////////////////////
197 // UnloadController, private:
199 void UnloadController::TabAttachedImpl(content::WebContents* contents) {
200 // If the tab crashes in the beforeunload or unload handler, it won't be
201 // able to ack. But we know we can close it.
204 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
205 content::Source<content::WebContents>(contents));
208 void UnloadController::TabDetachedImpl(content::WebContents* contents) {
209 if (is_attempting_to_close_browser_)
210 ClearUnloadState(contents, false);
211 registrar_.Remove(this,
212 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
213 content::Source<content::WebContents>(contents));
216 void UnloadController::ProcessPendingTabs() {
217 if (!is_attempting_to_close_browser_) {
218 // Because we might invoke this after a delay it's possible for the value of
219 // is_attempting_to_close_browser_ to have changed since we scheduled the
224 if (HasCompletedUnloadProcessing()) {
225 // We've finished all the unload events and can proceed to close the
227 browser_->OnWindowClosing();
231 // Process beforeunload tabs first. When that queue is empty, process
233 if (!tabs_needing_before_unload_fired_.empty()) {
234 content::WebContents* web_contents =
235 *(tabs_needing_before_unload_fired_.begin());
236 // Null check render_view_host here as this gets called on a PostTask and
237 // the tab's render_view_host may have been nulled out.
238 if (web_contents->GetRenderViewHost()) {
239 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false);
241 ClearUnloadState(web_contents, true);
243 } else if (is_calling_before_unload_handlers()) {
244 on_close_confirmed_.Run(true);
245 } else if (!tabs_needing_unload_fired_.empty()) {
246 // We've finished firing all beforeunload events and can proceed with unload
248 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting
249 // somewhere around here so that we have accurate measurements of shutdown
251 // TODO(ojan): We can probably fire all the unload events in parallel and
252 // get a perf benefit from that in the cases where the tab hangs in it's
253 // unload handler or takes a long time to page in.
254 content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin());
255 // Null check render_view_host here as this gets called on a PostTask and
256 // the tab's render_view_host may have been nulled out.
257 if (web_contents->GetRenderViewHost()) {
258 web_contents->GetRenderViewHost()->ClosePage();
260 ClearUnloadState(web_contents, true);
267 bool UnloadController::HasCompletedUnloadProcessing() const {
268 return is_attempting_to_close_browser_ &&
269 tabs_needing_before_unload_fired_.empty() &&
270 tabs_needing_unload_fired_.empty();
273 void UnloadController::CancelWindowClose() {
274 // Closing of window can be canceled from a beforeunload handler.
275 DCHECK(is_attempting_to_close_browser_);
276 tabs_needing_before_unload_fired_.clear();
277 tabs_needing_unload_fired_.clear();
278 if (is_calling_before_unload_handlers()) {
279 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
280 on_close_confirmed_.Reset();
281 on_close_confirmed.Run(false);
283 is_attempting_to_close_browser_ = false;
285 content::NotificationService::current()->Notify(
286 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
287 content::Source<Browser>(browser_),
288 content::NotificationService::NoDetails());
291 bool UnloadController::RemoveFromSet(UnloadListenerSet* set,
292 content::WebContents* web_contents) {
293 DCHECK(is_attempting_to_close_browser_);
295 UnloadListenerSet::iterator iter =
296 std::find(set->begin(), set->end(), web_contents);
297 if (iter != set->end()) {
304 void UnloadController::ClearUnloadState(content::WebContents* web_contents,
306 if (is_attempting_to_close_browser_) {
307 RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents);
308 RemoveFromSet(&tabs_needing_unload_fired_, web_contents);
310 ProcessPendingTabs();
312 base::MessageLoop::current()->PostTask(
314 base::Bind(&UnloadController::ProcessPendingTabs,
315 weak_factory_.GetWeakPtr()));
320 } // namespace chrome