OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/ui/fast_unload_controller.h" |
| 6 |
| 7 #include "base/logging.h" |
| 8 #include "base/message_loop.h" |
| 9 #include "chrome/browser/ui/browser.h" |
| 10 #include "chrome/browser/ui/browser_tabstrip.h" |
| 11 #include "chrome/browser/ui/tab_contents/core_tab_helper.h" |
| 12 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 13 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" |
| 14 #include "chrome/common/chrome_notification_types.h" |
| 15 #include "content/public/browser/notification_service.h" |
| 16 #include "content/public/browser/notification_source.h" |
| 17 #include "content/public/browser/notification_types.h" |
| 18 #include "content/public/browser/render_view_host.h" |
| 19 #include "content/public/browser/web_contents.h" |
| 20 #include "content/public/browser/web_contents_delegate.h" |
| 21 |
| 22 namespace chrome { |
| 23 |
| 24 |
| 25 //////////////////////////////////////////////////////////////////////////////// |
| 26 // DetachedWebContentsDelegate will delete web contents when they close. |
| 27 class FastUnloadController::DetachedWebContentsDelegate |
| 28 : public content::WebContentsDelegate { |
| 29 public: |
| 30 DetachedWebContentsDelegate() { } |
| 31 virtual ~DetachedWebContentsDelegate() { } |
| 32 |
| 33 private: |
| 34 // WebContentsDelegate implementation. |
| 35 virtual bool ShouldSuppressDialogs() OVERRIDE { |
| 36 return true; // Return true so dialogs are suppressed. |
| 37 } |
| 38 |
| 39 virtual void CloseContents(content::WebContents* source) OVERRIDE { |
| 40 // Finished detached close. |
| 41 // FastUnloadController will observe |
| 42 // |NOTIFICATION_WEB_CONTENTS_DISCONNECTED|. |
| 43 delete source; |
| 44 } |
| 45 |
| 46 DISALLOW_COPY_AND_ASSIGN(DetachedWebContentsDelegate); |
| 47 }; |
| 48 |
| 49 //////////////////////////////////////////////////////////////////////////////// |
| 50 // FastUnloadController, public: |
| 51 |
| 52 FastUnloadController::FastUnloadController(Browser* browser) |
| 53 : browser_(browser), |
| 54 tab_needing_before_unload_ack_(NULL), |
| 55 is_attempting_to_close_browser_(false), |
| 56 detached_delegate_(new DetachedWebContentsDelegate()), |
| 57 weak_factory_(this) { |
| 58 browser_->tab_strip_model()->AddObserver(this); |
| 59 } |
| 60 |
| 61 FastUnloadController::~FastUnloadController() { |
| 62 browser_->tab_strip_model()->RemoveObserver(this); |
| 63 } |
| 64 |
| 65 bool FastUnloadController::CanCloseContents(content::WebContents* contents) { |
| 66 // Don't try to close the tab when the whole browser is being closed, since |
| 67 // that avoids the fast shutdown path where we just kill all the renderers. |
| 68 return !is_attempting_to_close_browser_; |
| 69 } |
| 70 |
| 71 bool FastUnloadController::BeforeUnloadFired(content::WebContents* contents, |
| 72 bool proceed) { |
| 73 if (!is_attempting_to_close_browser_) { |
| 74 if (!proceed) { |
| 75 contents->SetClosedByUserGesture(false); |
| 76 } else { |
| 77 // No more dialogs are possible, so remove the tab and finish |
| 78 // running unload listeners asynchrounously. |
| 79 browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents); |
| 80 DetachWebContents(contents); |
| 81 } |
| 82 return proceed; |
| 83 } |
| 84 |
| 85 if (!proceed) { |
| 86 CancelWindowClose(); |
| 87 contents->SetClosedByUserGesture(false); |
| 88 return false; |
| 89 } |
| 90 |
| 91 if (tab_needing_before_unload_ack_ == contents) { |
| 92 // Now that beforeunload has fired, queue the tab to fire unload. |
| 93 tab_needing_before_unload_ack_ = NULL; |
| 94 tabs_needing_unload_.insert(contents); |
| 95 ProcessPendingTabs(); |
| 96 // We want to handle firing the unload event ourselves since we want to |
| 97 // fire all the beforeunload events before attempting to fire the unload |
| 98 // events should the user cancel closing the browser. |
| 99 return false; |
| 100 } |
| 101 |
| 102 return true; |
| 103 } |
| 104 |
| 105 bool FastUnloadController::ShouldCloseWindow() { |
| 106 if (HasCompletedUnloadProcessing()) |
| 107 return true; |
| 108 |
| 109 is_attempting_to_close_browser_ = true; |
| 110 |
| 111 if (!TabsNeedBeforeUnloadFired()) |
| 112 return true; |
| 113 |
| 114 ProcessPendingTabs(); |
| 115 return false; |
| 116 } |
| 117 |
| 118 bool FastUnloadController::TabsNeedBeforeUnloadFired() { |
| 119 if (!tabs_needing_before_unload_.empty() || |
| 120 tab_needing_before_unload_ack_ != NULL) |
| 121 return true; |
| 122 |
| 123 if (!tabs_needing_unload_.empty()) |
| 124 return false; |
| 125 |
| 126 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) { |
| 127 content::WebContents* contents = |
| 128 browser_->tab_strip_model()->GetWebContentsAt(i); |
| 129 if (contents->NeedToFireBeforeUnload()) |
| 130 tabs_needing_before_unload_.insert(contents); |
| 131 } |
| 132 return !tabs_needing_before_unload_.empty(); |
| 133 } |
| 134 |
| 135 //////////////////////////////////////////////////////////////////////////////// |
| 136 // FastUnloadController, content::NotificationObserver implementation: |
| 137 |
| 138 void FastUnloadController::Observe( |
| 139 int type, |
| 140 const content::NotificationSource& source, |
| 141 const content::NotificationDetails& details) { |
| 142 switch (type) { |
| 143 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: { |
| 144 registrar_.Remove(this, |
| 145 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 146 source); |
| 147 content::WebContents* contents = |
| 148 content::Source<content::WebContents>(source).ptr(); |
| 149 ClearUnloadState(contents); |
| 150 break; |
| 151 } |
| 152 default: |
| 153 NOTREACHED() << "Got a notification we didn't register for."; |
| 154 } |
| 155 } |
| 156 |
| 157 //////////////////////////////////////////////////////////////////////////////// |
| 158 // FastUnloadController, TabStripModelObserver implementation: |
| 159 |
| 160 void FastUnloadController::TabInsertedAt(content::WebContents* contents, |
| 161 int index, |
| 162 bool foreground) { |
| 163 TabAttachedImpl(contents); |
| 164 } |
| 165 |
| 166 void FastUnloadController::TabDetachedAt(content::WebContents* contents, |
| 167 int index) { |
| 168 TabDetachedImpl(contents); |
| 169 } |
| 170 |
| 171 void FastUnloadController::TabReplacedAt(TabStripModel* tab_strip_model, |
| 172 content::WebContents* old_contents, |
| 173 content::WebContents* new_contents, |
| 174 int index) { |
| 175 TabDetachedImpl(old_contents); |
| 176 TabAttachedImpl(new_contents); |
| 177 } |
| 178 |
| 179 void FastUnloadController::TabStripEmpty() { |
| 180 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not |
| 181 // attempt to add tabs to the browser before it closes. |
| 182 is_attempting_to_close_browser_ = true; |
| 183 } |
| 184 |
| 185 //////////////////////////////////////////////////////////////////////////////// |
| 186 // FastUnloadController, private: |
| 187 |
| 188 void FastUnloadController::TabAttachedImpl(content::WebContents* contents) { |
| 189 // If the tab crashes in the beforeunload or unload handler, it won't be |
| 190 // able to ack. But we know we can close it. |
| 191 registrar_.Add( |
| 192 this, |
| 193 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 194 content::Source<content::WebContents>(contents)); |
| 195 } |
| 196 |
| 197 void FastUnloadController::TabDetachedImpl(content::WebContents* contents) { |
| 198 if (tabs_needing_unload_ack_.find(contents) != |
| 199 tabs_needing_unload_ack_.end()) { |
| 200 // Tab needs unload to complete. |
| 201 // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done. |
| 202 return; |
| 203 } |
| 204 |
| 205 // If WEB_CONTENTS_DISCONNECTED was received then the notification may have |
| 206 // already been unregistered. |
| 207 const content::NotificationSource& source = |
| 208 content::Source<content::WebContents>(contents); |
| 209 if (registrar_.IsRegistered(this, |
| 210 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 211 source)) { |
| 212 registrar_.Remove(this, |
| 213 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 214 source); |
| 215 } |
| 216 |
| 217 if (is_attempting_to_close_browser_) |
| 218 ClearUnloadState(contents); |
| 219 } |
| 220 |
| 221 bool FastUnloadController::DetachWebContents(content::WebContents* contents) { |
| 222 int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents); |
| 223 if (index != TabStripModel::kNoTab && |
| 224 contents->NeedToFireBeforeUnload()) { |
| 225 tabs_needing_unload_ack_.insert(contents); |
| 226 browser_->tab_strip_model()->DetachWebContentsAt(index); |
| 227 contents->SetDelegate(detached_delegate_.get()); |
| 228 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 229 core_tab_helper->OnUnloadDetachedStarted(); |
| 230 return true; |
| 231 } |
| 232 return false; |
| 233 } |
| 234 |
| 235 void FastUnloadController::ProcessPendingTabs() { |
| 236 if (!is_attempting_to_close_browser_) { |
| 237 // Because we might invoke this after a delay it's possible for the value of |
| 238 // is_attempting_to_close_browser_ to have changed since we scheduled the |
| 239 // task. |
| 240 return; |
| 241 } |
| 242 |
| 243 if (tab_needing_before_unload_ack_ != NULL) { |
| 244 // Wait for |BeforeUnloadFired| before proceeding. |
| 245 return; |
| 246 } |
| 247 |
| 248 // Process a beforeunload handler. |
| 249 if (!tabs_needing_before_unload_.empty()) { |
| 250 WebContentsSet::iterator it = tabs_needing_before_unload_.begin(); |
| 251 content::WebContents* contents = *it; |
| 252 tabs_needing_before_unload_.erase(it); |
| 253 // Null check render_view_host here as this gets called on a PostTask and |
| 254 // the tab's render_view_host may have been nulled out. |
| 255 if (contents->GetRenderViewHost()) { |
| 256 tab_needing_before_unload_ack_ = contents; |
| 257 |
| 258 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 259 core_tab_helper->OnCloseStarted(); |
| 260 |
| 261 contents->GetRenderViewHost()->FirePageBeforeUnload(false); |
| 262 } else { |
| 263 ProcessPendingTabs(); |
| 264 } |
| 265 return; |
| 266 } |
| 267 |
| 268 // Process all the unload handlers. (The beforeunload handlers have finished.) |
| 269 if (!tabs_needing_unload_.empty()) { |
| 270 browser_->OnWindowClosing(); |
| 271 |
| 272 // Run unload handlers detached since no more interaction is possible. |
| 273 WebContentsSet::iterator it = tabs_needing_unload_.begin(); |
| 274 while (it != tabs_needing_unload_.end()) { |
| 275 WebContentsSet::iterator current = it++; |
| 276 content::WebContents* contents = *current; |
| 277 tabs_needing_unload_.erase(current); |
| 278 // Null check render_view_host here as this gets called on a PostTask |
| 279 // and the tab's render_view_host may have been nulled out. |
| 280 if (contents->GetRenderViewHost()) { |
| 281 CoreTabHelper* core_tab_helper = |
| 282 CoreTabHelper::FromWebContents(contents); |
| 283 core_tab_helper->OnUnloadStarted(); |
| 284 DetachWebContents(contents); |
| 285 contents->GetRenderViewHost()->ClosePage(); |
| 286 } |
| 287 } |
| 288 |
| 289 // Get the browser hidden. |
| 290 if (browser_->tab_strip_model()->empty()) { |
| 291 browser_->TabStripEmpty(); |
| 292 } else { |
| 293 browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload |
| 294 } |
| 295 return; |
| 296 } |
| 297 |
| 298 if (HasCompletedUnloadProcessing()) { |
| 299 browser_->OnWindowClosing(); |
| 300 |
| 301 // Get the browser closed. |
| 302 if (browser_->tab_strip_model()->empty()) { |
| 303 browser_->TabStripEmpty(); |
| 304 } else { |
| 305 // There may be tabs if the last tab needing beforeunload crashed. |
| 306 browser_->tab_strip_model()->CloseAllTabs(); |
| 307 } |
| 308 return; |
| 309 } |
| 310 } |
| 311 |
| 312 bool FastUnloadController::HasCompletedUnloadProcessing() const { |
| 313 return is_attempting_to_close_browser_ && |
| 314 tabs_needing_before_unload_.empty() && |
| 315 tab_needing_before_unload_ack_ == NULL && |
| 316 tabs_needing_unload_.empty() && |
| 317 tabs_needing_unload_ack_.empty(); |
| 318 } |
| 319 |
| 320 void FastUnloadController::CancelWindowClose() { |
| 321 // Closing of window can be canceled from a beforeunload handler. |
| 322 DCHECK(is_attempting_to_close_browser_); |
| 323 tabs_needing_before_unload_.clear(); |
| 324 if (tab_needing_before_unload_ack_ != NULL) { |
| 325 |
| 326 CoreTabHelper* core_tab_helper = |
| 327 CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_); |
| 328 core_tab_helper->OnCloseCanceled(); |
| 329 tab_needing_before_unload_ack_ = NULL; |
| 330 } |
| 331 for (WebContentsSet::iterator it = tabs_needing_unload_.begin(); |
| 332 it != tabs_needing_unload_.end(); it++) { |
| 333 content::WebContents* contents = *it; |
| 334 |
| 335 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 336 core_tab_helper->OnCloseCanceled(); |
| 337 } |
| 338 tabs_needing_unload_.clear(); |
| 339 |
| 340 // No need to clear tabs_needing_unload_ack_. Those tabs are already detached. |
| 341 |
| 342 is_attempting_to_close_browser_ = false; |
| 343 |
| 344 content::NotificationService::current()->Notify( |
| 345 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, |
| 346 content::Source<Browser>(browser_), |
| 347 content::NotificationService::NoDetails()); |
| 348 } |
| 349 |
| 350 void FastUnloadController::ClearUnloadState(content::WebContents* contents) { |
| 351 if (tabs_needing_unload_ack_.erase(contents) > 0) { |
| 352 if (HasCompletedUnloadProcessing()) |
| 353 PostTaskForProcessPendingTabs(); |
| 354 return; |
| 355 } |
| 356 |
| 357 if (!is_attempting_to_close_browser_) |
| 358 return; |
| 359 |
| 360 if (tab_needing_before_unload_ack_ == contents) { |
| 361 tab_needing_before_unload_ack_ = NULL; |
| 362 PostTaskForProcessPendingTabs(); |
| 363 return; |
| 364 } |
| 365 |
| 366 if (tabs_needing_before_unload_.erase(contents) > 0 || |
| 367 tabs_needing_unload_.erase(contents) > 0) { |
| 368 if (tab_needing_before_unload_ack_ == NULL) |
| 369 PostTaskForProcessPendingTabs(); |
| 370 } |
| 371 } |
| 372 |
| 373 void FastUnloadController::PostTaskForProcessPendingTabs() { |
| 374 base::MessageLoop::current()->PostTask( |
| 375 FROM_HERE, |
| 376 base::Bind(&FastUnloadController::ProcessPendingTabs, |
| 377 weak_factory_.GetWeakPtr())); |
| 378 } |
| 379 |
| 380 } // namespace chrome |
OLD | NEW |