OLD | NEW |
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" | |
10 #include "base/message_loop.h" | 7 #include "base/message_loop.h" |
11 #include "chrome/browser/ui/browser.h" | 8 #include "chrome/browser/ui/browser.h" |
12 #include "chrome/browser/ui/browser_tabstrip.h" | 9 #include "chrome/browser/ui/browser_tabstrip.h" |
13 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 10 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
14 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" | |
15 #include "chrome/common/chrome_notification_types.h" | 11 #include "chrome/common/chrome_notification_types.h" |
16 #include "content/public/browser/notification_service.h" | 12 #include "content/public/browser/notification_service.h" |
17 #include "content/public/browser/notification_source.h" | 13 #include "content/public/browser/notification_source.h" |
18 #include "content/public/browser/notification_types.h" | 14 #include "content/public/browser/notification_types.h" |
19 #include "content/public/browser/render_view_host.h" | 15 #include "content/public/browser/render_view_host.h" |
20 #include "content/public/browser/web_contents.h" | 16 #include "content/public/browser/web_contents.h" |
21 #include "content/public/browser/web_contents_delegate.h" | |
22 | 17 |
23 namespace chrome { | 18 namespace chrome { |
24 | 19 |
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->DetachWebContentsAt(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 delete source; | |
65 if (detached_web_contents_.empty()) | |
66 tabs_closed_callback_.Run(); | |
67 } | |
68 | |
69 const DetachedTabsClosedCallback tabs_closed_callback_; | |
70 std::set<content::WebContents*> detached_web_contents_; | |
71 | |
72 DISALLOW_IMPLICIT_CONSTRUCTORS(UnloadDetachedHandler); | |
73 }; | |
74 | |
75 | |
76 //////////////////////////////////////////////////////////////////////////////// | 20 //////////////////////////////////////////////////////////////////////////////// |
77 // UnloadController, public: | 21 // UnloadController, public: |
78 | 22 |
79 UnloadController::UnloadController(Browser* browser) | 23 UnloadController::UnloadController(Browser* browser) |
80 : browser_(browser), | 24 : browser_(browser), |
81 is_attempting_to_close_browser_(false), | 25 is_attempting_to_close_browser_(false), |
82 ALLOW_THIS_IN_INITIALIZER_LIST( | |
83 unload_detached_handler_(new UnloadDetachedHandler( | |
84 base::Bind(&UnloadController::ProcessPendingTabs, | |
85 base::Unretained(this))))), | |
86 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { | 26 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { |
87 browser_->tab_strip_model()->AddObserver(this); | 27 browser_->tab_strip_model()->AddObserver(this); |
88 } | 28 } |
89 | 29 |
90 UnloadController::~UnloadController() { | 30 UnloadController::~UnloadController() { |
91 browser_->tab_strip_model()->RemoveObserver(this); | 31 browser_->tab_strip_model()->RemoveObserver(this); |
92 } | 32 } |
93 | 33 |
94 bool UnloadController::CanCloseContents(content::WebContents* contents) { | 34 bool UnloadController::CanCloseContents(content::WebContents* contents) { |
95 // Don't try to close the tab when the whole browser is being closed, since | 35 // Don't try to close the tab when the whole browser is being closed, since |
96 // that avoids the fast shutdown path where we just kill all the renderers. | 36 // that avoids the fast shutdown path where we just kill all the renderers. |
97 if (is_attempting_to_close_browser_) | 37 if (is_attempting_to_close_browser_) |
98 ClearUnloadState(contents, true); | 38 ClearUnloadState(contents, true); |
99 return !is_attempting_to_close_browser_; | 39 return !is_attempting_to_close_browser_; |
100 } | 40 } |
101 | 41 |
102 bool UnloadController::BeforeUnloadFired(content::WebContents* contents, | 42 bool UnloadController::BeforeUnloadFired(content::WebContents* contents, |
103 bool proceed) { | 43 bool proceed) { |
104 if (!is_attempting_to_close_browser_) { | 44 if (!is_attempting_to_close_browser_) { |
105 if (!proceed) { | 45 if (!proceed) |
106 contents->SetClosedByUserGesture(false); | 46 contents->SetClosedByUserGesture(false); |
107 } else { | |
108 // No more dialogs are possible, so remove the tab and finish | |
109 // running unload listeners asynchrounously. | |
110 TabStripModel* model = browser_->tab_strip_model(); | |
111 model->delegate()->CreateHistoricalTab(contents); | |
112 unload_detached_handler_->DetachWebContents(model, contents); | |
113 } | |
114 return proceed; | 47 return proceed; |
115 } | 48 } |
116 | 49 |
117 if (!proceed) { | 50 if (!proceed) { |
118 CancelWindowClose(); | 51 CancelWindowClose(); |
119 contents->SetClosedByUserGesture(false); | 52 contents->SetClosedByUserGesture(false); |
120 return false; | 53 return false; |
121 } | 54 } |
122 | 55 |
123 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) { | 56 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) { |
124 // Now that beforeunload has fired, queue the tab to fire unload. | 57 // Now that beforeunload has fired, put the tab on the queue to fire |
| 58 // unload. |
125 tabs_needing_unload_fired_.insert(contents); | 59 tabs_needing_unload_fired_.insert(contents); |
126 ProcessPendingTabs(); | 60 ProcessPendingTabs(); |
127 // We want to handle firing the unload event ourselves since we want to | 61 // We want to handle firing the unload event ourselves since we want to |
128 // fire all the beforeunload events before attempting to fire the unload | 62 // fire all the beforeunload events before attempting to fire the unload |
129 // events should the user cancel closing the browser. | 63 // events should the user cancel closing the browser. |
130 return false; | 64 return false; |
131 } | 65 } |
132 | 66 |
133 return true; | 67 return true; |
134 } | 68 } |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
210 void UnloadController::TabAttachedImpl(content::WebContents* contents) { | 144 void UnloadController::TabAttachedImpl(content::WebContents* contents) { |
211 // If the tab crashes in the beforeunload or unload handler, it won't be | 145 // If the tab crashes in the beforeunload or unload handler, it won't be |
212 // able to ack. But we know we can close it. | 146 // able to ack. But we know we can close it. |
213 registrar_.Add( | 147 registrar_.Add( |
214 this, | 148 this, |
215 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, | 149 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
216 content::Source<content::WebContents>(contents)); | 150 content::Source<content::WebContents>(contents)); |
217 } | 151 } |
218 | 152 |
219 void UnloadController::TabDetachedImpl(content::WebContents* contents) { | 153 void UnloadController::TabDetachedImpl(content::WebContents* contents) { |
220 if (is_attempting_to_close_browser_) { | 154 if (is_attempting_to_close_browser_) |
221 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents) && | |
222 tabs_needing_before_unload_fired_.empty() && | |
223 tabs_needing_unload_fired_.empty()) { | |
224 // The last tab needing beforeunload crashed. | |
225 // Continue with the close (ProcessPendingTabs would miss this). | |
226 browser_->OnWindowClosing(); | |
227 if (browser_->tab_strip_model()->empty()) { | |
228 browser_->TabStripEmpty(); | |
229 } else { | |
230 browser_->tab_strip_model()->CloseAllTabs(); | |
231 } | |
232 } | |
233 | |
234 ClearUnloadState(contents, false); | 155 ClearUnloadState(contents, false); |
235 } | |
236 registrar_.Remove(this, | 156 registrar_.Remove(this, |
237 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, | 157 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
238 content::Source<content::WebContents>(contents)); | 158 content::Source<content::WebContents>(contents)); |
239 } | 159 } |
240 | 160 |
241 void UnloadController::ProcessPendingTabs() { | 161 void UnloadController::ProcessPendingTabs() { |
| 162 if (!is_attempting_to_close_browser_) { |
| 163 // 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 |
| 165 // task. |
| 166 return; |
| 167 } |
| 168 |
242 if (HasCompletedUnloadProcessing()) { | 169 if (HasCompletedUnloadProcessing()) { |
| 170 // We've finished all the unload events and can proceed to close the |
| 171 // browser. |
243 browser_->OnWindowClosing(); | 172 browser_->OnWindowClosing(); |
244 browser_->OnUnloadProcessingCompleted(); | 173 return; |
245 } else if (!tabs_needing_before_unload_fired_.empty()) { | 174 } |
246 // Process all the beforeunload handlers before the unload handlers. | 175 |
| 176 // Process beforeunload tabs first. When that queue is empty, process |
| 177 // unload tabs. |
| 178 if (!tabs_needing_before_unload_fired_.empty()) { |
247 content::WebContents* web_contents = | 179 content::WebContents* web_contents = |
248 *(tabs_needing_before_unload_fired_.begin()); | 180 *(tabs_needing_before_unload_fired_.begin()); |
249 // Null check render_view_host here as this gets called on a PostTask and | 181 // 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. | 182 // the tab's render_view_host may have been nulled out. |
251 if (web_contents->GetRenderViewHost()) { | 183 if (web_contents->GetRenderViewHost()) { |
252 web_contents->OnCloseStarted(); | |
253 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false); | 184 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false); |
254 } else { | 185 } else { |
255 ClearUnloadState(web_contents, true); | 186 ClearUnloadState(web_contents, true); |
256 } | 187 } |
257 } else if (!tabs_needing_unload_fired_.empty()) { | 188 } else if (!tabs_needing_unload_fired_.empty()) { |
258 // All beforeunload handlers have fired. Proceed with unload handlers. | 189 // We've finished firing all beforeunload events and can proceed with unload |
259 | 190 // events. |
260 browser_->OnWindowClosing(); | 191 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting |
261 | 192 // somewhere around here so that we have accurate measurements of shutdown |
262 // Copy unload tabs to avoid iterator issues when detaching tabs. | 193 // time. |
263 UnloadListenerSet unload_tabs = tabs_needing_unload_fired_; | 194 // TODO(ojan): We can probably fire all the unload events in parallel and |
264 | 195 // get a perf benefit from that in the cases where the tab hangs in it's |
265 // Run unload handlers detached since no more interaction is possible. | 196 // unload handler or takes a long time to page in. |
266 for (UnloadListenerSet::iterator it = unload_tabs.begin(); | 197 content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin()); |
267 it != unload_tabs.end(); it++) { | 198 // Null check render_view_host here as this gets called on a PostTask and |
268 content::WebContents* web_contents = *it; | 199 // the tab's render_view_host may have been nulled out. |
269 // Null check render_view_host here as this gets called on a PostTask | 200 if (web_contents->GetRenderViewHost()) { |
270 // and the tab's render_view_host may have been nulled out. | 201 web_contents->GetRenderViewHost()->ClosePage(); |
271 if (web_contents->GetRenderViewHost()) { | 202 } else { |
272 web_contents->OnUnloadStarted(); | 203 ClearUnloadState(web_contents, true); |
273 unload_detached_handler_->DetachWebContents( | |
274 browser_->tab_strip_model(), web_contents); | |
275 web_contents->GetRenderViewHost()->ClosePage(); | |
276 } | |
277 } | 204 } |
278 tabs_needing_unload_fired_.clear(); | 205 } else { |
279 if (browser_->tab_strip_model()->empty()) { | 206 NOTREACHED(); |
280 browser_->TabStripEmpty(); | |
281 } else { | |
282 browser_->tab_strip_model()->CloseAllTabs(); | |
283 } | |
284 } | 207 } |
285 } | 208 } |
286 | 209 |
287 bool UnloadController::HasCompletedUnloadProcessing() const { | 210 bool UnloadController::HasCompletedUnloadProcessing() const { |
288 return is_attempting_to_close_browser_ && | 211 return is_attempting_to_close_browser_ && |
289 tabs_needing_before_unload_fired_.empty() && | 212 tabs_needing_before_unload_fired_.empty() && |
290 tabs_needing_unload_fired_.empty() && | 213 tabs_needing_unload_fired_.empty(); |
291 !unload_detached_handler_->HasTabs(); | |
292 } | 214 } |
293 | 215 |
294 void UnloadController::CancelWindowClose() { | 216 void UnloadController::CancelWindowClose() { |
295 // Closing of window can be canceled from a beforeunload handler. | 217 // Closing of window can be canceled from a beforeunload handler. |
296 DCHECK(is_attempting_to_close_browser_); | 218 DCHECK(is_attempting_to_close_browser_); |
297 for (UnloadListenerSet::iterator it = tabs_needing_unload_fired_.begin(); | |
298 it != tabs_needing_unload_fired_.end(); it++) { | |
299 content::WebContents* web_contents = *it; | |
300 web_contents->OnCloseCanceled(); | |
301 } | |
302 tabs_needing_before_unload_fired_.clear(); | 219 tabs_needing_before_unload_fired_.clear(); |
303 tabs_needing_unload_fired_.clear(); | 220 tabs_needing_unload_fired_.clear(); |
304 is_attempting_to_close_browser_ = false; | 221 is_attempting_to_close_browser_ = false; |
305 | 222 |
306 content::NotificationService::current()->Notify( | 223 content::NotificationService::current()->Notify( |
307 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, | 224 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, |
308 content::Source<Browser>(browser_), | 225 content::Source<Browser>(browser_), |
309 content::NotificationService::NoDetails()); | 226 content::NotificationService::NoDetails()); |
310 } | 227 } |
311 | 228 |
(...skipping 20 matching lines...) Expand all Loading... |
332 } else { | 249 } else { |
333 MessageLoop::current()->PostTask( | 250 MessageLoop::current()->PostTask( |
334 FROM_HERE, | 251 FROM_HERE, |
335 base::Bind(&UnloadController::ProcessPendingTabs, | 252 base::Bind(&UnloadController::ProcessPendingTabs, |
336 weak_factory_.GetWeakPtr())); | 253 weak_factory_.GetWeakPtr())); |
337 } | 254 } |
338 } | 255 } |
339 } | 256 } |
340 | 257 |
341 } // namespace chrome | 258 } // namespace chrome |
OLD | NEW |