| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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/fast_unload_controller.h" | 5 #include "chrome/browser/ui/fast_unload_controller.h" |
| 6 | 6 |
| 7 #include "base/location.h" | 7 #include "base/location.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/macros.h" | 9 #include "base/macros.h" |
| 10 #include "base/single_thread_task_runner.h" | 10 #include "base/single_thread_task_runner.h" |
| 11 #include "base/threading/thread_task_runner_handle.h" | 11 #include "base/threading/thread_task_runner_handle.h" |
| 12 #include "chrome/browser/chrome_notification_types.h" | 12 #include "chrome/browser/chrome_notification_types.h" |
| 13 #include "chrome/browser/devtools/devtools_window.h" | 13 #include "chrome/browser/devtools/devtools_window.h" |
| 14 #include "chrome/browser/ui/browser.h" | 14 #include "chrome/browser/ui/browser.h" |
| 15 #include "chrome/browser/ui/browser_tabstrip.h" | 15 #include "chrome/browser/ui/browser_tabstrip.h" |
| 16 #include "chrome/browser/ui/tab_contents/core_tab_helper.h" | 16 #include "chrome/browser/ui/tab_contents/core_tab_helper.h" |
| 17 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 17 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 18 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" | 18 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" |
| 19 #include "chrome/browser/ui/unload_controller_web_contents_delegate.h" |
| 19 #include "content/public/browser/notification_service.h" | 20 #include "content/public/browser/notification_service.h" |
| 20 #include "content/public/browser/notification_source.h" | 21 #include "content/public/browser/notification_source.h" |
| 21 #include "content/public/browser/notification_types.h" | 22 #include "content/public/browser/notification_types.h" |
| 22 #include "content/public/browser/render_view_host.h" | 23 #include "content/public/browser/render_view_host.h" |
| 23 #include "content/public/browser/web_contents.h" | 24 #include "content/public/browser/web_contents.h" |
| 24 #include "content/public/browser/web_contents_delegate.h" | |
| 25 #include "extensions/features/features.h" | 25 #include "extensions/features/features.h" |
| 26 | 26 |
| 27 #if BUILDFLAG(ENABLE_EXTENSIONS) | 27 #if BUILDFLAG(ENABLE_EXTENSIONS) |
| 28 #include "extensions/browser/extension_registry.h" | 28 #include "extensions/browser/extension_registry.h" |
| 29 #include "extensions/common/constants.h" | 29 #include "extensions/common/constants.h" |
| 30 #endif // (ENABLE_EXTENSIONS) | 30 #endif // (ENABLE_EXTENSIONS) |
| 31 | 31 |
| 32 namespace chrome { | 32 namespace chrome { |
| 33 | 33 |
| 34 //////////////////////////////////////////////////////////////////////////////// | 34 //////////////////////////////////////////////////////////////////////////////// |
| 35 // DetachedWebContentsDelegate will delete web contents when they close. | |
| 36 class FastUnloadController::DetachedWebContentsDelegate | |
| 37 : public content::WebContentsDelegate { | |
| 38 public: | |
| 39 DetachedWebContentsDelegate() { } | |
| 40 ~DetachedWebContentsDelegate() override {} | |
| 41 | |
| 42 private: | |
| 43 // WebContentsDelegate implementation. | |
| 44 bool ShouldSuppressDialogs(content::WebContents* source) override { | |
| 45 return true; // Return true so dialogs are suppressed. | |
| 46 } | |
| 47 | |
| 48 void CloseContents(content::WebContents* source) override { | |
| 49 // Finished detached close. | |
| 50 // FastUnloadController will observe | |
| 51 // |NOTIFICATION_WEB_CONTENTS_DISCONNECTED|. | |
| 52 delete source; | |
| 53 } | |
| 54 | |
| 55 DISALLOW_COPY_AND_ASSIGN(DetachedWebContentsDelegate); | |
| 56 }; | |
| 57 | |
| 58 //////////////////////////////////////////////////////////////////////////////// | |
| 59 // FastUnloadController, public: | 35 // FastUnloadController, public: |
| 60 | 36 |
| 61 FastUnloadController::FastUnloadController(Browser* browser) | 37 FastUnloadController::FastUnloadController(Browser* browser) |
| 62 : browser_(browser), | 38 : browser_(browser), |
| 63 tab_needing_before_unload_ack_(NULL), | 39 tab_needing_before_unload_ack_(NULL), |
| 64 is_attempting_to_close_browser_(false), | 40 is_attempting_to_close_browser_(false), |
| 65 detached_delegate_(new DetachedWebContentsDelegate()), | 41 detached_delegate_(new UnloadControllerWebContentsDelegate()), |
| 66 weak_factory_(this) { | 42 weak_factory_(this) { |
| 67 browser_->tab_strip_model()->AddObserver(this); | 43 browser_->tab_strip_model()->AddObserver(this); |
| 68 } | 44 } |
| 69 | 45 |
| 70 FastUnloadController::~FastUnloadController() { | 46 FastUnloadController::~FastUnloadController() { |
| 71 browser_->tab_strip_model()->RemoveObserver(this); | 47 browser_->tab_strip_model()->RemoveObserver(this); |
| 72 } | 48 } |
| 73 | 49 |
| 74 bool FastUnloadController::CanCloseContents(content::WebContents* contents) { | 50 bool FastUnloadController::CanCloseContents(content::WebContents* contents) { |
| 75 // Don't try to close the tab when the whole browser is being closed, since | 51 // Don't try to close the tab when the whole browser is being closed, since |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 149 if (!proceed) { | 125 if (!proceed) { |
| 150 CancelWindowClose(); | 126 CancelWindowClose(); |
| 151 contents->SetClosedByUserGesture(false); | 127 contents->SetClosedByUserGesture(false); |
| 152 return false; | 128 return false; |
| 153 } | 129 } |
| 154 | 130 |
| 155 if (tab_needing_before_unload_ack_ == contents) { | 131 if (tab_needing_before_unload_ack_ == contents) { |
| 156 // Now that beforeunload has fired, queue the tab to fire unload. | 132 // Now that beforeunload has fired, queue the tab to fire unload. |
| 157 tab_needing_before_unload_ack_ = NULL; | 133 tab_needing_before_unload_ack_ = NULL; |
| 158 tabs_needing_unload_.insert(contents); | 134 tabs_needing_unload_.insert(contents); |
| 159 ProcessPendingTabs(); | 135 ProcessPendingTabs(false); |
| 160 // We want to handle firing the unload event ourselves since we want to | 136 // We want to handle firing the unload event ourselves since we want to |
| 161 // fire all the beforeunload events before attempting to fire the unload | 137 // fire all the beforeunload events before attempting to fire the unload |
| 162 // events should the user cancel closing the browser. | 138 // events should the user cancel closing the browser. |
| 163 return false; | 139 return false; |
| 164 } | 140 } |
| 165 | 141 |
| 166 return true; | 142 return true; |
| 167 } | 143 } |
| 168 | 144 |
| 169 bool FastUnloadController::ShouldCloseWindow() { | 145 bool FastUnloadController::ShouldCloseWindow() { |
| (...skipping 22 matching lines...) Expand all Loading... |
| 192 // return false. | 168 // return false. |
| 193 // 4. Otherwise: return true. | 169 // 4. Otherwise: return true. |
| 194 is_attempting_to_close_browser_ = true; | 170 is_attempting_to_close_browser_ = true; |
| 195 // Cases 1 and 4. | 171 // Cases 1 and 4. |
| 196 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired(); | 172 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired(); |
| 197 if (need_beforeunload_fired == is_calling_before_unload_handlers()) | 173 if (need_beforeunload_fired == is_calling_before_unload_handlers()) |
| 198 return !need_beforeunload_fired; | 174 return !need_beforeunload_fired; |
| 199 | 175 |
| 200 // Cases 2 and 3. | 176 // Cases 2 and 3. |
| 201 on_close_confirmed_.Reset(); | 177 on_close_confirmed_.Reset(); |
| 202 ProcessPendingTabs(); | 178 ProcessPendingTabs(false); |
| 203 return false; | 179 return false; |
| 204 } | 180 } |
| 205 | 181 |
| 206 bool FastUnloadController::CallBeforeUnloadHandlers( | 182 bool FastUnloadController::TryToCloseWindow( |
| 183 bool skip_before_unload_event, |
| 207 const base::Callback<void(bool)>& on_close_confirmed) { | 184 const base::Callback<void(bool)>& on_close_confirmed) { |
| 208 // The devtools browser gets its beforeunload events as the results of | 185 // The devtools browser gets its beforeunload events as the results of |
| 209 // intercepting events from the inspected tab, so don't send them here as well. | 186 // intercepting events from the inspected tab, so don't send them here as |
| 187 // well. |
| 210 if (browser_->is_devtools() || !TabsNeedBeforeUnloadFired()) | 188 if (browser_->is_devtools() || !TabsNeedBeforeUnloadFired()) |
| 211 return false; | 189 return false; |
| 212 | 190 |
| 213 on_close_confirmed_ = on_close_confirmed; | 191 on_close_confirmed_ = on_close_confirmed; |
| 214 is_attempting_to_close_browser_ = true; | 192 is_attempting_to_close_browser_ = true; |
| 215 ProcessPendingTabs(); | 193 ProcessPendingTabs(skip_before_unload_event); |
| 216 return true; | 194 return true; |
| 217 } | 195 } |
| 218 | 196 |
| 219 void FastUnloadController::ResetBeforeUnloadHandlers() { | 197 void FastUnloadController::ResetCloseWindow() { |
| 220 if (!is_calling_before_unload_handlers()) | 198 if (!is_calling_before_unload_handlers()) |
| 221 return; | 199 return; |
| 222 CancelWindowClose(); | 200 CancelWindowClose(); |
| 223 } | 201 } |
| 224 | 202 |
| 225 bool FastUnloadController::TabsNeedBeforeUnloadFired() { | 203 bool FastUnloadController::TabsNeedBeforeUnloadFired() { |
| 226 if (!tabs_needing_before_unload_.empty() || | 204 if (!tabs_needing_before_unload_.empty() || |
| 227 tab_needing_before_unload_ack_ != NULL) | 205 tab_needing_before_unload_ack_ != NULL) |
| 228 return true; | 206 return true; |
| 229 | 207 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 245 } | 223 } |
| 246 | 224 |
| 247 bool FastUnloadController::HasCompletedUnloadProcessing() const { | 225 bool FastUnloadController::HasCompletedUnloadProcessing() const { |
| 248 return is_attempting_to_close_browser_ && | 226 return is_attempting_to_close_browser_ && |
| 249 tabs_needing_before_unload_.empty() && | 227 tabs_needing_before_unload_.empty() && |
| 250 tab_needing_before_unload_ack_ == NULL && | 228 tab_needing_before_unload_ack_ == NULL && |
| 251 tabs_needing_unload_.empty() && | 229 tabs_needing_unload_.empty() && |
| 252 tabs_needing_unload_ack_.empty(); | 230 tabs_needing_unload_ack_.empty(); |
| 253 } | 231 } |
| 254 | 232 |
| 255 void FastUnloadController::CancelWindowClose() { | 233 void FastUnloadController::CancelTabNeedingBeforeUnloadAck() { |
| 256 // Closing of window can be canceled from a beforeunload handler. | |
| 257 DCHECK(is_attempting_to_close_browser_); | |
| 258 tabs_needing_before_unload_.clear(); | |
| 259 if (tab_needing_before_unload_ack_ != NULL) { | 234 if (tab_needing_before_unload_ack_ != NULL) { |
| 260 CoreTabHelper* core_tab_helper = | 235 CoreTabHelper* core_tab_helper = |
| 261 CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_); | 236 CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_); |
| 262 core_tab_helper->OnCloseCanceled(); | 237 core_tab_helper->OnCloseCanceled(); |
| 263 DevToolsWindow::OnPageCloseCanceled(tab_needing_before_unload_ack_); | 238 DevToolsWindow::OnPageCloseCanceled(tab_needing_before_unload_ack_); |
| 264 tab_needing_before_unload_ack_ = NULL; | 239 tab_needing_before_unload_ack_ = NULL; |
| 265 } | 240 } |
| 241 } |
| 242 |
| 243 void FastUnloadController::CancelWindowClose() { |
| 244 // Closing of window can be canceled from a beforeunload handler. |
| 245 DCHECK(is_attempting_to_close_browser_); |
| 246 tabs_needing_before_unload_.clear(); |
| 247 CancelTabNeedingBeforeUnloadAck(); |
| 266 for (WebContentsSet::iterator it = tabs_needing_unload_.begin(); | 248 for (WebContentsSet::iterator it = tabs_needing_unload_.begin(); |
| 267 it != tabs_needing_unload_.end(); it++) { | 249 it != tabs_needing_unload_.end(); it++) { |
| 268 content::WebContents* contents = *it; | 250 content::WebContents* contents = *it; |
| 269 | 251 |
| 270 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); | 252 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 271 core_tab_helper->OnCloseCanceled(); | 253 core_tab_helper->OnCloseCanceled(); |
| 272 DevToolsWindow::OnPageCloseCanceled(contents); | 254 DevToolsWindow::OnPageCloseCanceled(contents); |
| 273 } | 255 } |
| 274 tabs_needing_unload_.clear(); | 256 tabs_needing_unload_.clear(); |
| 275 | 257 |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 375 tabs_needing_unload_ack_.insert(contents); | 357 tabs_needing_unload_ack_.insert(contents); |
| 376 browser_->tab_strip_model()->DetachWebContentsAt(index); | 358 browser_->tab_strip_model()->DetachWebContentsAt(index); |
| 377 contents->SetDelegate(detached_delegate_.get()); | 359 contents->SetDelegate(detached_delegate_.get()); |
| 378 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); | 360 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 379 core_tab_helper->OnUnloadDetachedStarted(); | 361 core_tab_helper->OnUnloadDetachedStarted(); |
| 380 return true; | 362 return true; |
| 381 } | 363 } |
| 382 return false; | 364 return false; |
| 383 } | 365 } |
| 384 | 366 |
| 385 void FastUnloadController::ProcessPendingTabs() { | 367 void FastUnloadController::ProcessPendingTabs(bool skip_before_unload_event) { |
| 386 if (!is_attempting_to_close_browser_) { | 368 if (!is_attempting_to_close_browser_) { |
| 387 // Because we might invoke this after a delay it's possible for the value of | 369 // Because we might invoke this after a delay it's possible for the value of |
| 388 // is_attempting_to_close_browser_ to have changed since we scheduled the | 370 // is_attempting_to_close_browser_ to have changed since we scheduled the |
| 389 // task. | 371 // task. |
| 390 return; | 372 return; |
| 391 } | 373 } |
| 392 | 374 |
| 393 if (tab_needing_before_unload_ack_ != NULL) { | 375 if (tab_needing_before_unload_ack_ != NULL) { |
| 394 // Wait for |BeforeUnloadFired| before proceeding. | 376 if (skip_before_unload_event) { |
| 395 return; | 377 // Cancel and skip the ongoing before unload event. |
| 378 tabs_needing_before_unload_.insert(tab_needing_before_unload_ack_); |
| 379 CancelTabNeedingBeforeUnloadAck(); |
| 380 } else { |
| 381 // Wait for |BeforeUnloadFired| before proceeding. |
| 382 return; |
| 383 } |
| 396 } | 384 } |
| 397 | 385 |
| 398 // Process a beforeunload handler. | 386 // Process a beforeunload handler. |
| 399 if (!tabs_needing_before_unload_.empty()) { | 387 if (!tabs_needing_before_unload_.empty()) { |
| 400 WebContentsSet::iterator it = tabs_needing_before_unload_.begin(); | 388 if (skip_before_unload_event) { |
| 401 content::WebContents* contents = *it; | 389 tabs_needing_unload_.insert(tabs_needing_before_unload_.begin(), |
| 402 tabs_needing_before_unload_.erase(it); | 390 tabs_needing_before_unload_.end()); |
| 403 // Null check render_view_host here as this gets called on a PostTask and | 391 tabs_needing_before_unload_.clear(); |
| 404 // the tab's render_view_host may have been nulled out. | 392 } else { |
| 405 if (contents->GetRenderViewHost()) { | 393 WebContentsSet::iterator it = tabs_needing_before_unload_.begin(); |
| 406 tab_needing_before_unload_ack_ = contents; | 394 content::WebContents* contents = *it; |
| 395 tabs_needing_before_unload_.erase(it); |
| 396 // Null check render_view_host here as this gets called on a PostTask and |
| 397 // the tab's render_view_host may have been nulled out. |
| 398 if (contents->GetRenderViewHost()) { |
| 399 tab_needing_before_unload_ack_ = contents; |
| 407 | 400 |
| 408 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); | 401 CoreTabHelper* core_tab_helper = |
| 409 core_tab_helper->OnCloseStarted(); | 402 CoreTabHelper::FromWebContents(contents); |
| 403 core_tab_helper->OnCloseStarted(); |
| 410 | 404 |
| 411 // If there's a devtools window attached to |contents|, | 405 // If there's a devtools window attached to |contents|, |
| 412 // we would like devtools to call its own beforeunload handlers first, | 406 // we would like devtools to call its own beforeunload handlers first, |
| 413 // and then call beforeunload handlers for |contents|. | 407 // and then call beforeunload handlers for |contents|. |
| 414 // See DevToolsWindow::InterceptPageBeforeUnload for details. | 408 // See DevToolsWindow::InterceptPageBeforeUnload for details. |
| 415 if (!DevToolsWindow::InterceptPageBeforeUnload(contents)) | 409 if (!DevToolsWindow::InterceptPageBeforeUnload(contents)) |
| 416 contents->DispatchBeforeUnload(); | 410 contents->DispatchBeforeUnload(); |
| 417 } else { | 411 } else { |
| 418 ProcessPendingTabs(); | 412 ProcessPendingTabs(skip_before_unload_event); |
| 413 } |
| 414 return; |
| 419 } | 415 } |
| 420 return; | |
| 421 } | 416 } |
| 422 | 417 |
| 423 if (is_calling_before_unload_handlers()) { | 418 if (is_calling_before_unload_handlers()) { |
| 424 on_close_confirmed_.Run(true); | 419 on_close_confirmed_.Run(true); |
| 425 return; | 420 return; |
| 426 } | 421 } |
| 422 |
| 427 // Process all the unload handlers. (The beforeunload handlers have finished.) | 423 // Process all the unload handlers. (The beforeunload handlers have finished.) |
| 428 if (!tabs_needing_unload_.empty()) { | 424 if (!tabs_needing_unload_.empty()) { |
| 429 browser_->OnWindowClosing(); | 425 browser_->OnWindowClosing(); |
| 430 | 426 |
| 431 // Run unload handlers detached since no more interaction is possible. | 427 // Run unload handlers detached since no more interaction is possible. |
| 432 WebContentsSet::iterator it = tabs_needing_unload_.begin(); | 428 WebContentsSet::iterator it = tabs_needing_unload_.begin(); |
| 433 while (it != tabs_needing_unload_.end()) { | 429 while (it != tabs_needing_unload_.end()) { |
| 434 WebContentsSet::iterator current = it++; | 430 WebContentsSet::iterator current = it++; |
| 435 content::WebContents* contents = *current; | 431 content::WebContents* contents = *current; |
| 436 tabs_needing_unload_.erase(current); | 432 tabs_needing_unload_.erase(current); |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 487 if (tabs_needing_before_unload_.erase(contents) > 0 || | 483 if (tabs_needing_before_unload_.erase(contents) > 0 || |
| 488 tabs_needing_unload_.erase(contents) > 0) { | 484 tabs_needing_unload_.erase(contents) > 0) { |
| 489 if (tab_needing_before_unload_ack_ == NULL) | 485 if (tab_needing_before_unload_ack_ == NULL) |
| 490 PostTaskForProcessPendingTabs(); | 486 PostTaskForProcessPendingTabs(); |
| 491 } | 487 } |
| 492 } | 488 } |
| 493 | 489 |
| 494 void FastUnloadController::PostTaskForProcessPendingTabs() { | 490 void FastUnloadController::PostTaskForProcessPendingTabs() { |
| 495 base::ThreadTaskRunnerHandle::Get()->PostTask( | 491 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 496 FROM_HERE, base::Bind(&FastUnloadController::ProcessPendingTabs, | 492 FROM_HERE, base::Bind(&FastUnloadController::ProcessPendingTabs, |
| 497 weak_factory_.GetWeakPtr())); | 493 weak_factory_.GetWeakPtr(), false)); |
| 498 } | 494 } |
| 499 | 495 |
| 500 } // namespace chrome | 496 } // namespace chrome |
| OLD | NEW |