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" |
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 Loading... |
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 Loading... |
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 |
OLD | NEW |