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 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 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) { |
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 |
OLD | NEW |