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