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

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: Patch for landing 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
« no previous file with comments | « chrome/browser/ui/unload_controller.h ('k') | chrome/browser/ui/views/frame/browser_view.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 registrar_.Remove(this,
104 ClearUnloadState(content::Source<content::WebContents>(source).ptr(), 142 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
105 false); // See comment for ClearUnloadState(). 143 source);
106 } 144 content::WebContents* contents =
145 content::Source<content::WebContents>(source).ptr();
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) {
195 if (tabs_needing_unload_ack_.find(contents) !=
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
202 // If WEB_CONTENTS_DISCONNECTED was received then the notification may have
203 // already been unregistered.
204 const content::NotificationSource& source =
205 content::Source<content::WebContents>(contents);
206 if (registrar_.IsRegistered(this,
207 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
208 source)) {
209 registrar_.Remove(this,
210 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
211 source);
212 }
213
154 if (is_attempting_to_close_browser_) 214 if (is_attempting_to_close_browser_)
155 ClearUnloadState(contents, false); 215 ClearUnloadState(contents);
156 registrar_.Remove(this, 216 }
157 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 217
158 content::Source<content::WebContents>(contents)); 218 bool UnloadController::DetachWebContents(content::WebContents* contents) {
219 int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents);
220 if (index != TabStripModel::kNoTab &&
221 contents->NeedToFireBeforeUnload()) {
222 tabs_needing_unload_ack_.insert(contents);
223 browser_->tab_strip_model()->DetachWebContentsAt(index);
224 contents->SetDelegate(detached_delegate_.get());
225 contents->OnUnloadDetachedStarted();
226 return true;
227 }
228 return false;
159 } 229 }
160 230
161 void UnloadController::ProcessPendingTabs() { 231 void UnloadController::ProcessPendingTabs() {
162 if (!is_attempting_to_close_browser_) { 232 if (!is_attempting_to_close_browser_) {
163 // Because we might invoke this after a delay it's possible for the value of 233 // 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 234 // is_attempting_to_close_browser_ to have changed since we scheduled the
165 // task. 235 // task.
166 return; 236 return;
167 } 237 }
168 238
239 if (tab_needing_before_unload_ack_ != NULL) {
240 // Wait for |BeforeUnloadFired| before proceeding.
241 return;
242 }
243
244 // Process a beforeunload handler.
245 if (!tabs_needing_before_unload_.empty()) {
246 WebContentsSet::iterator it = tabs_needing_before_unload_.begin();
247 content::WebContents* contents = *it;
248 tabs_needing_before_unload_.erase(it);
249 // Null check render_view_host here as this gets called on a PostTask and
250 // the tab's render_view_host may have been nulled out.
251 if (contents->GetRenderViewHost()) {
252 tab_needing_before_unload_ack_ = contents;
253 contents->OnCloseStarted();
254 contents->GetRenderViewHost()->FirePageBeforeUnload(false);
255 } else {
256 ProcessPendingTabs();
257 }
258 return;
259 }
260
261 // Process all the unload handlers. (The beforeunload handlers have finished.)
262 if (!tabs_needing_unload_.empty()) {
263 browser_->OnWindowClosing();
264
265 // Run unload handlers detached since no more interaction is possible.
266 WebContentsSet::iterator it = tabs_needing_unload_.begin();
267 while (it != tabs_needing_unload_.end()) {
268 WebContentsSet::iterator current = it++;
269 content::WebContents* contents = *current;
270 tabs_needing_unload_.erase(current);
271 // Null check render_view_host here as this gets called on a PostTask
272 // and the tab's render_view_host may have been nulled out.
273 if (contents->GetRenderViewHost()) {
274 contents->OnUnloadStarted();
275 DetachWebContents(contents);
276 contents->GetRenderViewHost()->ClosePage();
277 }
278 }
279
280 // Get the browser hidden.
281 if (browser_->tab_strip_model()->empty()) {
282 browser_->TabStripEmpty();
283 } else {
284 browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload
285 }
286 return;
287 }
288
169 if (HasCompletedUnloadProcessing()) { 289 if (HasCompletedUnloadProcessing()) {
170 // We've finished all the unload events and can proceed to close the
171 // browser.
172 browser_->OnWindowClosing(); 290 browser_->OnWindowClosing();
291
292 // Get the browser closed.
293 if (browser_->tab_strip_model()->empty()) {
294 browser_->TabStripEmpty();
295 } else {
296 // There may be tabs if the last tab needing beforeunload crashed.
297 browser_->tab_strip_model()->CloseAllTabs();
298 }
173 return; 299 return;
174 } 300 }
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 } 301 }
209 302
210 bool UnloadController::HasCompletedUnloadProcessing() const { 303 bool UnloadController::HasCompletedUnloadProcessing() const {
211 return is_attempting_to_close_browser_ && 304 return is_attempting_to_close_browser_ &&
212 tabs_needing_before_unload_fired_.empty() && 305 tabs_needing_before_unload_.empty() &&
213 tabs_needing_unload_fired_.empty(); 306 tab_needing_before_unload_ack_ == NULL &&
307 tabs_needing_unload_.empty() &&
308 tabs_needing_unload_ack_.empty();
214 } 309 }
215 310
216 void UnloadController::CancelWindowClose() { 311 void UnloadController::CancelWindowClose() {
217 // Closing of window can be canceled from a beforeunload handler. 312 // Closing of window can be canceled from a beforeunload handler.
218 DCHECK(is_attempting_to_close_browser_); 313 DCHECK(is_attempting_to_close_browser_);
219 tabs_needing_before_unload_fired_.clear(); 314 tabs_needing_before_unload_.clear();
220 tabs_needing_unload_fired_.clear(); 315 if (tab_needing_before_unload_ack_ != NULL) {
316 tab_needing_before_unload_ack_->OnCloseCanceled();
317 tab_needing_before_unload_ack_ = NULL;
318 }
319 for (WebContentsSet::iterator it = tabs_needing_unload_.begin();
320 it != tabs_needing_unload_.end(); it++) {
321 content::WebContents* contents = *it;
322 contents->OnCloseCanceled();
323 }
324 tabs_needing_unload_.clear();
325
326 // No need to clear tabs_needing_unload_ack_. Those tabs are already detached.
327
221 is_attempting_to_close_browser_ = false; 328 is_attempting_to_close_browser_ = false;
222 329
223 content::NotificationService::current()->Notify( 330 content::NotificationService::current()->Notify(
224 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 331 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
225 content::Source<Browser>(browser_), 332 content::Source<Browser>(browser_),
226 content::NotificationService::NoDetails()); 333 content::NotificationService::NoDetails());
227 } 334 }
228 335
229 bool UnloadController::RemoveFromSet(UnloadListenerSet* set, 336 void UnloadController::ClearUnloadState(content::WebContents* contents) {
230 content::WebContents* web_contents) { 337 if (tabs_needing_unload_ack_.erase(contents) > 0) {
231 DCHECK(is_attempting_to_close_browser_); 338 if (HasCompletedUnloadProcessing())
339 PostTaskForProcessPendingTabs();
340 return;
341 }
232 342
233 UnloadListenerSet::iterator iter = 343 if (!is_attempting_to_close_browser_)
234 std::find(set->begin(), set->end(), web_contents); 344 return;
235 if (iter != set->end()) { 345
236 set->erase(iter); 346 if (tab_needing_before_unload_ack_ == contents) {
237 return true; 347 tab_needing_before_unload_ack_ = NULL;
348 PostTaskForProcessPendingTabs();
349 return;
238 } 350 }
239 return false;
240 }
241 351
242 void UnloadController::ClearUnloadState(content::WebContents* web_contents, 352 if (tabs_needing_before_unload_.erase(contents) > 0 ||
243 bool process_now) { 353 tabs_needing_unload_.erase(contents) > 0) {
244 if (is_attempting_to_close_browser_) { 354 if (tab_needing_before_unload_ack_ == NULL)
245 RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents); 355 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 } 356 }
256 } 357 }
257 358
359 void UnloadController::PostTaskForProcessPendingTabs() {
360 base::MessageLoop::current()->PostTask(
361 FROM_HERE,
362 base::Bind(&UnloadController::ProcessPendingTabs,
363 weak_factory_.GetWeakPtr()));
364 }
365
258 } // namespace chrome 366 } // namespace chrome
OLDNEW
« no previous file with comments | « chrome/browser/ui/unload_controller.h ('k') | chrome/browser/ui/views/frame/browser_view.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698