Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: chrome/browser/ui/unload_controller.cc

Issue 11016023: Quickly close tabs/window with long-running unload handlers. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Keep original TabsNeedBeforeUnloadFired implementation. Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/unload_controller.h" 5 #include "chrome/browser/ui/unload_controller.h"
6 6
7 #include <set>
8
9 #include "base/callback.h"
7 #include "base/message_loop.h" 10 #include "base/message_loop.h"
8 #include "chrome/browser/ui/browser.h" 11 #include "chrome/browser/ui/browser.h"
9 #include "chrome/browser/ui/browser_tabstrip.h" 12 #include "chrome/browser/ui/browser_tabstrip.h"
10 #include "chrome/browser/ui/tab_contents/tab_contents.h" 13 #include "chrome/browser/ui/tab_contents/tab_contents.h"
11 #include "chrome/browser/ui/tabs/tab_strip_model.h" 14 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/common/chrome_notification_types.h" 15 #include "chrome/common/chrome_notification_types.h"
13 #include "content/public/browser/notification_service.h" 16 #include "content/public/browser/notification_service.h"
14 #include "content/public/browser/notification_source.h" 17 #include "content/public/browser/notification_source.h"
15 #include "content/public/browser/notification_types.h" 18 #include "content/public/browser/notification_types.h"
16 #include "content/public/browser/render_view_host.h" 19 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/web_contents.h" 20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_contents_delegate.h"
18 22
19 namespace chrome { 23 namespace chrome {
20 24
25 typedef base::Callback<void(void)> DetachedTabsClosedCallback;
26
27 ////////////////////////////////////////////////////////////////////////////////
28 // UnloadDetachedHandler is used to close tabs quickly, http://crbug.com/142458.
29 // - Allows unload handlers to run in the background.
30 // - Comes into play after the beforeunload handlers (if any) have run.
31 // - Does not close the tabs; it holds tabs while they are closed.
32 class UnloadDetachedHandler : public content::WebContentsDelegate {
33 public:
34 explicit UnloadDetachedHandler(const DetachedTabsClosedCallback& callback)
35 : tabs_closed_callback_(callback) { }
36 virtual ~UnloadDetachedHandler() { }
37
38 // Returns true if it succeeds.
39 bool DetachWebContents(TabStripModel* tab_strip_model,
40 content::WebContents* web_contents) {
41 int index = tab_strip_model->GetIndexOfWebContents(web_contents);
42 if (index != TabStripModel::kNoTab &&
43 web_contents->NeedToFireBeforeUnload()) {
44 tab_strip_model->DetachTabContentsAndCreateHistoryAt(index);
45 detached_web_contents_.insert(web_contents);
46 web_contents->SetDelegate(this);
47 web_contents->OnUnloadDetachedStarted();
48 return true;
49 }
50 return false;
51 }
52
53 bool HasTabs() const {
54 return !detached_web_contents_.empty();
55 }
56
57 private:
58 // WebContentsDelegate implementation.
59 virtual bool ShouldSuppressDialogs() OVERRIDE {
60 return true; // Return true so dialogs are suppressed.
61 }
62 virtual void CloseContents(content::WebContents* source) OVERRIDE {
63 detached_web_contents_.erase(source);
64 TabContents* tab_contents = TabContents::FromWebContents(source);
65 DCHECK(tab_contents);
66 delete tab_contents;
67 if (detached_web_contents_.empty())
68 tabs_closed_callback_.Run();
69 }
70
71 const DetachedTabsClosedCallback tabs_closed_callback_;
72 std::set<content::WebContents*> detached_web_contents_;
73
74 DISALLOW_IMPLICIT_CONSTRUCTORS(UnloadDetachedHandler);
75 };
76
77
21 //////////////////////////////////////////////////////////////////////////////// 78 ////////////////////////////////////////////////////////////////////////////////
22 // UnloadController, public: 79 // UnloadController, public:
23 80
24 UnloadController::UnloadController(Browser* browser) 81 UnloadController::UnloadController(Browser* browser)
25 : browser_(browser), 82 : browser_(browser),
26 is_attempting_to_close_browser_(false), 83 is_attempting_to_close_browser_(false),
84 ALLOW_THIS_IN_INITIALIZER_LIST(
85 unload_detached_handler_(new UnloadDetachedHandler(
86 base::Bind(&UnloadController::ProcessPendingTabs,
87 base::Unretained(this))))),
27 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { 88 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
28 browser_->tab_strip_model()->AddObserver(this); 89 browser_->tab_strip_model()->AddObserver(this);
29 } 90 }
30 91
31 UnloadController::~UnloadController() { 92 UnloadController::~UnloadController() {
32 browser_->tab_strip_model()->RemoveObserver(this); 93 browser_->tab_strip_model()->RemoveObserver(this);
33 } 94 }
34 95
35 bool UnloadController::CanCloseContents(content::WebContents* contents) { 96 bool UnloadController::CanCloseContents(content::WebContents* contents) {
36 // Don't try to close the tab when the whole browser is being closed, since 97 // 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. 98 // that avoids the fast shutdown path where we just kill all the renderers.
38 if (is_attempting_to_close_browser_) 99 if (is_attempting_to_close_browser_)
39 ClearUnloadState(contents, true); 100 ClearUnloadState(contents, true);
40 return !is_attempting_to_close_browser_; 101 return !is_attempting_to_close_browser_;
41 } 102 }
42 103
43 bool UnloadController::BeforeUnloadFired(content::WebContents* contents, 104 bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
44 bool proceed) { 105 bool proceed) {
45 if (!is_attempting_to_close_browser_) { 106 if (!is_attempting_to_close_browser_) {
46 if (!proceed) 107 if (proceed) {
108 // No more dialogs are possible, so remove the tab and finish
109 // running unload listeners asynchrounously.
110 unload_detached_handler_->DetachWebContents(browser_->tab_strip_model(),
111 contents);
112 } else {
47 contents->SetClosedByUserGesture(false); 113 contents->SetClosedByUserGesture(false);
114 }
48 return proceed; 115 return proceed;
49 } 116 }
50 117
51 if (!proceed) { 118 if (!proceed) {
52 CancelWindowClose(); 119 CancelWindowClose();
53 contents->SetClosedByUserGesture(false); 120 contents->SetClosedByUserGesture(false);
54 return false; 121 return false;
55 } 122 }
56 123
57 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) { 124 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) {
58 // Now that beforeunload has fired, put the tab on the queue to fire 125 // Now that beforeunload has fired, queue the tab to fire unload.
59 // unload.
60 tabs_needing_unload_fired_.insert(contents); 126 tabs_needing_unload_fired_.insert(contents);
61 ProcessPendingTabs(); 127 ProcessPendingTabs();
62 // We want to handle firing the unload event ourselves since we want to 128 // We want to handle firing the unload event ourselves since we want to
63 // fire all the beforeunload events before attempting to fire the unload 129 // fire all the beforeunload events before attempting to fire the unload
64 // events should the user cancel closing the browser. 130 // events should the user cancel closing the browser.
65 return false; 131 return false;
66 } 132 }
67 133
68 return true; 134 return true;
69 } 135 }
70 136
71 bool UnloadController::ShouldCloseWindow() { 137 bool UnloadController::ShouldCloseWindow() {
72 if (HasCompletedUnloadProcessing()) 138 if (!is_attempting_to_close_browser_) {
73 return true; 139 is_attempting_to_close_browser_ = true;
74 140 if (TabsNeedBeforeUnloadFired()) {
75 is_attempting_to_close_browser_ = true; 141 ProcessPendingTabs();
76 142 } else {
77 if (!TabsNeedBeforeUnloadFired()) 143 browser_->OnWindowClosing();
78 return true; 144 }
79 145 }
80 ProcessPendingTabs(); 146 return HasCompletedUnloadProcessing();
81 return false;
82 } 147 }
83 148
84 bool UnloadController::TabsNeedBeforeUnloadFired() { 149 bool UnloadController::TabsNeedBeforeUnloadFired() {
85 if (tabs_needing_before_unload_fired_.empty()) { 150 if (tabs_needing_before_unload_fired_.empty()) {
86 for (int i = 0; i < browser_->tab_count(); ++i) { 151 for (int i = 0; i < browser_->tab_count(); ++i) {
87 content::WebContents* contents = 152 content::WebContents* contents =
88 chrome::GetTabContentsAt(browser_, i)->web_contents(); 153 chrome::GetTabContentsAt(browser_, i)->web_contents();
89 if (contents->NeedToFireBeforeUnload()) 154 if (contents->NeedToFireBeforeUnload())
90 tabs_needing_before_unload_fired_.insert(contents); 155 tabs_needing_before_unload_fired_.insert(contents);
91 } 156 }
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
153 218
154 void UnloadController::TabDetachedImpl(content::WebContents* contents) { 219 void UnloadController::TabDetachedImpl(content::WebContents* contents) {
155 if (is_attempting_to_close_browser_) 220 if (is_attempting_to_close_browser_)
156 ClearUnloadState(contents, false); 221 ClearUnloadState(contents, false);
157 registrar_.Remove(this, 222 registrar_.Remove(this,
158 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 223 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
159 content::Source<content::WebContents>(contents)); 224 content::Source<content::WebContents>(contents));
160 } 225 }
161 226
162 void UnloadController::ProcessPendingTabs() { 227 void UnloadController::ProcessPendingTabs() {
163 if (!is_attempting_to_close_browser_) {
164 // Because we might invoke this after a delay it's possible for the value of
165 // is_attempting_to_close_browser_ to have changed since we scheduled the
166 // task.
167 return;
168 }
169
170 if (HasCompletedUnloadProcessing()) { 228 if (HasCompletedUnloadProcessing()) {
171 // We've finished all the unload events and can proceed to close the 229 if (browser_->tab_strip_model()->empty()) {
172 // browser. 230 browser_->OnUnloadProcessingCompleted();
173 browser_->OnWindowClosing(); 231 } else {
174 return; 232 // Having tabs means OnWindowClosing was never called. That happens if
175 } 233 // the last tab needing beforeunload crashes (i.e. we observe that the
176 234 // web_contents is disconnected).
177 // Process beforeunload tabs first. When that queue is empty, process 235 browser_->OnWindowClosing();
178 // unload tabs. 236 }
179 if (!tabs_needing_before_unload_fired_.empty()) { 237 } else if (!tabs_needing_before_unload_fired_.empty()) {
238 // Process all the beforeunload handlers before the unload handlers.
180 content::WebContents* web_contents = 239 content::WebContents* web_contents =
181 *(tabs_needing_before_unload_fired_.begin()); 240 *(tabs_needing_before_unload_fired_.begin());
182 // Null check render_view_host here as this gets called on a PostTask and 241 // Null check render_view_host here as this gets called on a PostTask and
183 // the tab's render_view_host may have been nulled out. 242 // the tab's render_view_host may have been nulled out.
184 if (web_contents->GetRenderViewHost()) { 243 if (web_contents->GetRenderViewHost()) {
244 web_contents->OnCloseStarted();
185 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false); 245 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false);
186 } else { 246 } else {
187 ClearUnloadState(web_contents, true); 247 ClearUnloadState(web_contents, true);
188 } 248 }
189 } else if (!tabs_needing_unload_fired_.empty()) { 249 } else if (!tabs_needing_unload_fired_.empty()) {
190 // We've finished firing all beforeunload events and can proceed with unload 250 // All beforeunload handlers have fired. Proceed with unload handlers.
191 // events. 251
192 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting 252 // Copy unload tabs to avoid iterator issues when detaching tabs.
193 // somewhere around here so that we have accurate measurements of shutdown 253 UnloadListenerSet unload_tabs = tabs_needing_unload_fired_;
194 // time. 254
195 // TODO(ojan): We can probably fire all the unload events in parallel and 255 // Run unload handlers detached since no more interaction is possible.
196 // get a perf benefit from that in the cases where the tab hangs in it's 256 for (UnloadListenerSet::iterator it = unload_tabs.begin();
197 // unload handler or takes a long time to page in. 257 it != unload_tabs.end(); it++) {
198 content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin()); 258 content::WebContents* web_contents = *it;
199 // Null check render_view_host here as this gets called on a PostTask and 259 // Null check render_view_host here as this gets called on a PostTask
200 // the tab's render_view_host may have been nulled out. 260 // and the tab's render_view_host may have been nulled out.
201 if (web_contents->GetRenderViewHost()) { 261 if (web_contents->GetRenderViewHost()) {
202 web_contents->GetRenderViewHost()->ClosePage(); 262 web_contents->OnUnloadStarted();
203 } else { 263 unload_detached_handler_->DetachWebContents(
204 ClearUnloadState(web_contents, true); 264 browser_->tab_strip_model(), web_contents);
265 web_contents->GetRenderViewHost()->ClosePage();
266 }
205 } 267 }
206 } else { 268 tabs_needing_unload_fired_.clear();
207 NOTREACHED(); 269
270 // Close the remaining tabs to make the browser hide the window
271 // while the unload handlers finish.
272 browser_->OnWindowClosing();
208 } 273 }
209 } 274 }
210 275
211 bool UnloadController::HasCompletedUnloadProcessing() const { 276 bool UnloadController::HasCompletedUnloadProcessing() const {
212 return is_attempting_to_close_browser_ && 277 return is_attempting_to_close_browser_ &&
213 tabs_needing_before_unload_fired_.empty() && 278 tabs_needing_before_unload_fired_.empty() &&
214 tabs_needing_unload_fired_.empty(); 279 tabs_needing_unload_fired_.empty() &&
280 !unload_detached_handler_->HasTabs();
215 } 281 }
216 282
217 void UnloadController::CancelWindowClose() { 283 void UnloadController::CancelWindowClose() {
218 // Closing of window can be canceled from a beforeunload handler. 284 // Closing of window can be canceled from a beforeunload handler.
219 DCHECK(is_attempting_to_close_browser_); 285 DCHECK(is_attempting_to_close_browser_);
286 for (UnloadListenerSet::iterator it = tabs_needing_unload_fired_.begin();
287 it != tabs_needing_unload_fired_.end(); it++) {
288 content::WebContents* web_contents = *it;
289 web_contents->OnCloseCanceled();
290 }
220 tabs_needing_before_unload_fired_.clear(); 291 tabs_needing_before_unload_fired_.clear();
221 tabs_needing_unload_fired_.clear(); 292 tabs_needing_unload_fired_.clear();
222 is_attempting_to_close_browser_ = false; 293 is_attempting_to_close_browser_ = false;
223 294
224 content::NotificationService::current()->Notify( 295 content::NotificationService::current()->Notify(
225 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 296 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
226 content::Source<Browser>(browser_), 297 content::Source<Browser>(browser_),
227 content::NotificationService::NoDetails()); 298 content::NotificationService::NoDetails());
228 } 299 }
229 300
(...skipping 20 matching lines...) Expand all
250 } else { 321 } else {
251 MessageLoop::current()->PostTask( 322 MessageLoop::current()->PostTask(
252 FROM_HERE, 323 FROM_HERE,
253 base::Bind(&UnloadController::ProcessPendingTabs, 324 base::Bind(&UnloadController::ProcessPendingTabs,
254 weak_factory_.GetWeakPtr())); 325 weak_factory_.GetWeakPtr()));
255 } 326 }
256 } 327 }
257 } 328 }
258 329
259 } // namespace chrome 330 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698