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

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: Properly cleanup registrar Created 7 years, 6 months 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 "base/logging.h"
7 #include "base/message_loop.h" 8 #include "base/message_loop.h"
8 #include "chrome/browser/ui/browser.h" 9 #include "chrome/browser/ui/browser.h"
9 #include "chrome/browser/ui/browser_tabstrip.h" 10 #include "chrome/browser/ui/browser_tabstrip.h"
10 #include "chrome/browser/ui/tabs/tab_strip_model.h" 11 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
11 #include "chrome/common/chrome_notification_types.h" 13 #include "chrome/common/chrome_notification_types.h"
12 #include "content/public/browser/notification_service.h" 14 #include "content/public/browser/notification_service.h"
13 #include "content/public/browser/notification_source.h" 15 #include "content/public/browser/notification_source.h"
14 #include "content/public/browser/notification_types.h" 16 #include "content/public/browser/notification_types.h"
15 #include "content/public/browser/render_view_host.h" 17 #include "content/public/browser/render_view_host.h"
16 #include "content/public/browser/web_contents.h" 18 #include "content/public/browser/web_contents.h"
19 #include "content/public/browser/web_contents_delegate.h"
17 20
18 namespace chrome { 21 namespace chrome {
19 22
23
24 ////////////////////////////////////////////////////////////////////////////////
25 // DetachedWebContentsDelegate will delete web contents when they close.
26 class UnloadController::DetachedWebContentsDelegate
27 : public content::WebContentsDelegate {
28 public:
29 DetachedWebContentsDelegate() { }
30 virtual ~DetachedWebContentsDelegate() { }
31
32 private:
33 // WebContentsDelegate implementation.
34 virtual bool ShouldSuppressDialogs() OVERRIDE {
35 return true; // Return true so dialogs are suppressed.
36 }
37
38 virtual void CloseContents(content::WebContents* source) OVERRIDE {
39 // Finished detached close.
40 // UnloadController will observe |NOTIFICATION_WEB_CONTENTS_DISCONNECTED|.
41 delete source;
42 }
43
44 DISALLOW_COPY_AND_ASSIGN(DetachedWebContentsDelegate);
45 };
46
20 //////////////////////////////////////////////////////////////////////////////// 47 ////////////////////////////////////////////////////////////////////////////////
21 // UnloadController, public: 48 // UnloadController, public:
22 49
23 UnloadController::UnloadController(Browser* browser) 50 UnloadController::UnloadController(Browser* browser)
24 : browser_(browser), 51 : browser_(browser),
52 tab_needing_before_unload_ack_(NULL),
25 is_attempting_to_close_browser_(false), 53 is_attempting_to_close_browser_(false),
54 detached_delegate_(new DetachedWebContentsDelegate()),
26 weak_factory_(this) { 55 weak_factory_(this) {
27 browser_->tab_strip_model()->AddObserver(this); 56 browser_->tab_strip_model()->AddObserver(this);
28 } 57 }
29 58
30 UnloadController::~UnloadController() { 59 UnloadController::~UnloadController() {
31 browser_->tab_strip_model()->RemoveObserver(this); 60 browser_->tab_strip_model()->RemoveObserver(this);
32 } 61 }
33 62
34 bool UnloadController::CanCloseContents(content::WebContents* contents) { 63 bool UnloadController::CanCloseContents(content::WebContents* contents) {
35 // Don't try to close the tab when the whole browser is being closed, since 64 // Don't try to close the tab when the whole browser is being closed, since
36 // that avoids the fast shutdown path where we just kill all the renderers. 65 // that avoids the fast shutdown path where we just kill all the renderers.
37 if (is_attempting_to_close_browser_)
38 ClearUnloadState(contents, true);
39 return !is_attempting_to_close_browser_; 66 return !is_attempting_to_close_browser_;
40 } 67 }
41 68
42 bool UnloadController::BeforeUnloadFired(content::WebContents* contents, 69 bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
43 bool proceed) { 70 bool proceed) {
44 if (!is_attempting_to_close_browser_) { 71 if (!is_attempting_to_close_browser_) {
45 if (!proceed) 72 if (!proceed) {
46 contents->SetClosedByUserGesture(false); 73 contents->SetClosedByUserGesture(false);
74 } else {
75 // No more dialogs are possible, so remove the tab and finish
76 // running unload listeners asynchrounously.
77 browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents);
78 DetachWebContents(contents);
79 }
47 return proceed; 80 return proceed;
48 } 81 }
49 82
50 if (!proceed) { 83 if (!proceed) {
51 CancelWindowClose(); 84 CancelWindowClose();
52 contents->SetClosedByUserGesture(false); 85 contents->SetClosedByUserGesture(false);
53 return false; 86 return false;
54 } 87 }
55 88
56 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) { 89 if (tab_needing_before_unload_ack_ == contents) {
57 // Now that beforeunload has fired, put the tab on the queue to fire 90 // Now that beforeunload has fired, queue the tab to fire unload.
58 // unload. 91 tab_needing_before_unload_ack_ = NULL;
59 tabs_needing_unload_fired_.insert(contents); 92 tabs_needing_unload_.insert(contents);
60 ProcessPendingTabs(); 93 ProcessPendingTabs();
61 // We want to handle firing the unload event ourselves since we want to 94 // We want to handle firing the unload event ourselves since we want to
62 // fire all the beforeunload events before attempting to fire the unload 95 // fire all the beforeunload events before attempting to fire the unload
63 // events should the user cancel closing the browser. 96 // events should the user cancel closing the browser.
64 return false; 97 return false;
65 } 98 }
66 99
67 return true; 100 return true;
68 } 101 }
69 102
70 bool UnloadController::ShouldCloseWindow() { 103 bool UnloadController::ShouldCloseWindow() {
71 if (HasCompletedUnloadProcessing()) 104 if (HasCompletedUnloadProcessing())
72 return true; 105 return true;
73 106
74 is_attempting_to_close_browser_ = true; 107 is_attempting_to_close_browser_ = true;
75 108
76 if (!TabsNeedBeforeUnloadFired()) 109 if (!TabsNeedBeforeUnloadFired())
77 return true; 110 return true;
78 111
79 ProcessPendingTabs(); 112 ProcessPendingTabs();
80 return false; 113 return false;
81 } 114 }
82 115
83 bool UnloadController::TabsNeedBeforeUnloadFired() { 116 bool UnloadController::TabsNeedBeforeUnloadFired() {
84 if (tabs_needing_before_unload_fired_.empty()) { 117 if (!tabs_needing_before_unload_.empty() ||
85 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) { 118 tab_needing_before_unload_ack_ != NULL)
86 content::WebContents* contents = 119 return true;
87 browser_->tab_strip_model()->GetWebContentsAt(i); 120
88 if (contents->NeedToFireBeforeUnload()) 121 if (!tabs_needing_unload_.empty())
89 tabs_needing_before_unload_fired_.insert(contents); 122 return false;
90 } 123
124 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
125 content::WebContents* contents =
126 browser_->tab_strip_model()->GetWebContentsAt(i);
127 if (contents->NeedToFireBeforeUnload())
128 tabs_needing_before_unload_.insert(contents);
91 } 129 }
92 return !tabs_needing_before_unload_fired_.empty(); 130 return !tabs_needing_before_unload_.empty();
93 } 131 }
94 132
95 //////////////////////////////////////////////////////////////////////////////// 133 ////////////////////////////////////////////////////////////////////////////////
96 // UnloadController, content::NotificationObserver implementation: 134 // UnloadController, content::NotificationObserver implementation:
97 135
98 void UnloadController::Observe(int type, 136 void UnloadController::Observe(int type,
99 const content::NotificationSource& source, 137 const content::NotificationSource& source,
100 const content::NotificationDetails& details) { 138 const content::NotificationDetails& details) {
101 switch (type) { 139 switch (type) {
102 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: 140 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: {
103 if (is_attempting_to_close_browser_) { 141 content::WebContents* contents =
104 ClearUnloadState(content::Source<content::WebContents>(source).ptr(), 142 content::Source<content::WebContents>(source).ptr();
105 false); // See comment for ClearUnloadState(). 143 registrar_.Remove(this,
106 } 144 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
145 source);
146 ClearUnloadState(contents);
107 break; 147 break;
148 }
108 default: 149 default:
109 NOTREACHED() << "Got a notification we didn't register for."; 150 NOTREACHED() << "Got a notification we didn't register for.";
110 } 151 }
111 } 152 }
112 153
113 //////////////////////////////////////////////////////////////////////////////// 154 ////////////////////////////////////////////////////////////////////////////////
114 // UnloadController, TabStripModelObserver implementation: 155 // UnloadController, TabStripModelObserver implementation:
115 156
116 void UnloadController::TabInsertedAt(content::WebContents* contents, 157 void UnloadController::TabInsertedAt(content::WebContents* contents,
117 int index, 158 int index,
(...skipping 26 matching lines...) Expand all
144 void UnloadController::TabAttachedImpl(content::WebContents* contents) { 185 void UnloadController::TabAttachedImpl(content::WebContents* contents) {
145 // If the tab crashes in the beforeunload or unload handler, it won't be 186 // If the tab crashes in the beforeunload or unload handler, it won't be
146 // able to ack. But we know we can close it. 187 // able to ack. But we know we can close it.
147 registrar_.Add( 188 registrar_.Add(
148 this, 189 this,
149 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 190 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
150 content::Source<content::WebContents>(contents)); 191 content::Source<content::WebContents>(contents));
151 } 192 }
152 193
153 void UnloadController::TabDetachedImpl(content::WebContents* contents) { 194 void UnloadController::TabDetachedImpl(content::WebContents* contents) {
154 if (is_attempting_to_close_browser_) 195 if (tabs_needing_unload_ack_.find(contents) !=
155 ClearUnloadState(contents, false); 196 tabs_needing_unload_ack_.end()) {
197 // Tab needs unload to complete.
198 // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done.
199 return;
200 }
201
156 registrar_.Remove(this, 202 registrar_.Remove(this,
157 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 203 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
158 content::Source<content::WebContents>(contents)); 204 content::Source<content::WebContents>(contents));
205
206 if (is_attempting_to_close_browser_)
207 ClearUnloadState(contents);
208 }
209
210 bool UnloadController::DetachWebContents(content::WebContents* contents) {
211 int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents);
212 if (index != TabStripModel::kNoTab &&
213 contents->NeedToFireBeforeUnload()) {
214 tabs_needing_unload_ack_.insert(contents);
215 browser_->tab_strip_model()->DetachWebContentsAt(index);
216 contents->SetDelegate(detached_delegate_.get());
217 contents->OnUnloadDetachedStarted();
218 return true;
219 }
220 return false;
159 } 221 }
160 222
161 void UnloadController::ProcessPendingTabs() { 223 void UnloadController::ProcessPendingTabs() {
162 if (!is_attempting_to_close_browser_) { 224 if (!is_attempting_to_close_browser_) {
163 // Because we might invoke this after a delay it's possible for the value of 225 // Because we might invoke this after a delay it's possible for the value of
164 // is_attempting_to_close_browser_ to have changed since we scheduled the 226 // is_attempting_to_close_browser_ to have changed since we scheduled the
165 // task. 227 // task.
166 return; 228 return;
167 } 229 }
168 230
231 if (tab_needing_before_unload_ack_ != NULL) {
232 // Wait for |BeforeUnloadFired| before proceeding.
233 return;
234 }
235
236 // Process a beforeunload handler.
237 if (!tabs_needing_before_unload_.empty()) {
238 WebContentsSet::iterator it = tabs_needing_before_unload_.begin();
239 content::WebContents* contents = *it;
240 tabs_needing_before_unload_.erase(it);
241 // Null check render_view_host here as this gets called on a PostTask and
242 // the tab's render_view_host may have been nulled out.
243 if (contents->GetRenderViewHost()) {
244 tab_needing_before_unload_ack_ = contents;
245 contents->OnCloseStarted();
246 contents->GetRenderViewHost()->FirePageBeforeUnload(false);
247 } else {
248 ProcessPendingTabs();
249 }
250 return;
251 }
252
253 // Process all the unload handlers. (The beforeunload handlers have finished.)
254 if (!tabs_needing_unload_.empty()) {
255 browser_->OnWindowClosing();
256
257 // Run unload handlers detached since no more interaction is possible.
258 WebContentsSet::iterator it = tabs_needing_unload_.begin();
259 while (it != tabs_needing_unload_.end()) {
260 WebContentsSet::iterator current = it++;
261 content::WebContents* contents = *current;
262 tabs_needing_unload_.erase(current);
263 // Null check render_view_host here as this gets called on a PostTask
264 // and the tab's render_view_host may have been nulled out.
265 if (contents->GetRenderViewHost()) {
266 contents->OnUnloadStarted();
267 DetachWebContents(contents);
268 contents->GetRenderViewHost()->ClosePage();
269 }
270 }
271
272 // Get the browser hidden.
273 if (browser_->tab_strip_model()->empty()) {
274 browser_->TabStripEmpty();
275 } else {
276 browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload
277 }
278 return;
279 }
280
169 if (HasCompletedUnloadProcessing()) { 281 if (HasCompletedUnloadProcessing()) {
170 // We've finished all the unload events and can proceed to close the
171 // browser.
172 browser_->OnWindowClosing(); 282 browser_->OnWindowClosing();
283
284 // Get the browser closed.
285 if (browser_->tab_strip_model()->empty()) {
286 browser_->TabStripEmpty();
287 } else {
288 // There may be tabs if the last tab needing beforeunload crashed.
289 browser_->tab_strip_model()->CloseAllTabs();
290 }
173 return; 291 return;
174 } 292 }
175
176 // Process beforeunload tabs first. When that queue is empty, process
177 // unload tabs.
178 if (!tabs_needing_before_unload_fired_.empty()) {
179 content::WebContents* web_contents =
180 *(tabs_needing_before_unload_fired_.begin());
181 // Null check render_view_host here as this gets called on a PostTask and
182 // the tab's render_view_host may have been nulled out.
183 if (web_contents->GetRenderViewHost()) {
184 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false);
185 } else {
186 ClearUnloadState(web_contents, true);
187 }
188 } else if (!tabs_needing_unload_fired_.empty()) {
189 // We've finished firing all beforeunload events and can proceed with unload
190 // events.
191 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting
192 // somewhere around here so that we have accurate measurements of shutdown
193 // time.
194 // TODO(ojan): We can probably fire all the unload events in parallel and
195 // get a perf benefit from that in the cases where the tab hangs in it's
196 // unload handler or takes a long time to page in.
197 content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin());
198 // Null check render_view_host here as this gets called on a PostTask and
199 // the tab's render_view_host may have been nulled out.
200 if (web_contents->GetRenderViewHost()) {
201 web_contents->GetRenderViewHost()->ClosePage();
202 } else {
203 ClearUnloadState(web_contents, true);
204 }
205 } else {
206 NOTREACHED();
207 }
208 } 293 }
209 294
210 bool UnloadController::HasCompletedUnloadProcessing() const { 295 bool UnloadController::HasCompletedUnloadProcessing() const {
211 return is_attempting_to_close_browser_ && 296 return is_attempting_to_close_browser_ &&
212 tabs_needing_before_unload_fired_.empty() && 297 tabs_needing_before_unload_.empty() &&
213 tabs_needing_unload_fired_.empty(); 298 tab_needing_before_unload_ack_ == NULL &&
299 tabs_needing_unload_.empty() &&
300 tabs_needing_unload_ack_.empty();
214 } 301 }
215 302
216 void UnloadController::CancelWindowClose() { 303 void UnloadController::CancelWindowClose() {
217 // Closing of window can be canceled from a beforeunload handler. 304 // Closing of window can be canceled from a beforeunload handler.
218 DCHECK(is_attempting_to_close_browser_); 305 DCHECK(is_attempting_to_close_browser_);
219 tabs_needing_before_unload_fired_.clear(); 306 tabs_needing_before_unload_.clear();
220 tabs_needing_unload_fired_.clear(); 307 if (tab_needing_before_unload_ack_ != NULL) {
308 tab_needing_before_unload_ack_->OnCloseCanceled();
309 tab_needing_before_unload_ack_ = NULL;
310 }
311 for (WebContentsSet::iterator it = tabs_needing_unload_.begin();
312 it != tabs_needing_unload_.end(); it++) {
313 content::WebContents* contents = *it;
314 contents->OnCloseCanceled();
315 }
316 tabs_needing_unload_.clear();
317
318 // No need to clear tabs_needing_unload_ack_. Those tabs are already detached.
319
221 is_attempting_to_close_browser_ = false; 320 is_attempting_to_close_browser_ = false;
222 321
223 content::NotificationService::current()->Notify( 322 content::NotificationService::current()->Notify(
224 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 323 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
225 content::Source<Browser>(browser_), 324 content::Source<Browser>(browser_),
226 content::NotificationService::NoDetails()); 325 content::NotificationService::NoDetails());
227 } 326 }
228 327
229 bool UnloadController::RemoveFromSet(UnloadListenerSet* set, 328 void UnloadController::ClearUnloadState(content::WebContents* contents) {
230 content::WebContents* web_contents) { 329 if (tabs_needing_unload_ack_.erase(contents) > 0) {
231 DCHECK(is_attempting_to_close_browser_); 330 if (HasCompletedUnloadProcessing())
331 PostTaskForProcessPendingTabs();
332 return;
333 }
232 334
233 UnloadListenerSet::iterator iter = 335 if (!is_attempting_to_close_browser_)
234 std::find(set->begin(), set->end(), web_contents); 336 return;
235 if (iter != set->end()) { 337
236 set->erase(iter); 338 if (tab_needing_before_unload_ack_ == contents) {
237 return true; 339 tab_needing_before_unload_ack_ = NULL;
340 PostTaskForProcessPendingTabs();
341 return;
238 } 342 }
239 return false;
240 }
241 343
242 void UnloadController::ClearUnloadState(content::WebContents* web_contents, 344 if (tabs_needing_before_unload_.erase(contents) > 0 ||
243 bool process_now) { 345 tabs_needing_unload_.erase(contents) > 0) {
244 if (is_attempting_to_close_browser_) { 346 if (tab_needing_before_unload_ack_ == NULL)
245 RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents); 347 PostTaskForProcessPendingTabs();
246 RemoveFromSet(&tabs_needing_unload_fired_, web_contents);
247 if (process_now) {
248 ProcessPendingTabs();
249 } else {
250 base::MessageLoop::current()->PostTask(
251 FROM_HERE,
252 base::Bind(&UnloadController::ProcessPendingTabs,
253 weak_factory_.GetWeakPtr()));
254 }
255 } 348 }
256 } 349 }
257 350
351 void UnloadController::PostTaskForProcessPendingTabs() {
352 base::MessageLoop::current()->PostTask(
353 FROM_HERE,
354 base::Bind(&UnloadController::ProcessPendingTabs,
355 weak_factory_.GetWeakPtr()));
356 }
357
258 } // namespace chrome 358 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698