| 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 |