[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 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 |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "chrome/browser/ui/fast_unload_controller.h" |
| 6 | |
skyostil | 380bb222 | 2015-06-12 12:07:05 | [diff] [blame] | 7 | #include "base/location.h" |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 8 | #include "base/logging.h" |
avi | 655876a | 2015-12-25 07:18:15 | [diff] [blame] | 9 | #include "base/macros.h" |
skyostil | 380bb222 | 2015-06-12 12:07:05 | [diff] [blame] | 10 | #include "base/single_thread_task_runner.h" |
gab | b15e1907 | 2016-05-11 20:45:41 | [diff] [blame] | 11 | #include "base/threading/thread_task_runner_handle.h" |
[email protected] | dcc8fbc | 2013-07-12 00:54:09 | [diff] [blame] | 12 | #include "chrome/browser/chrome_notification_types.h" |
[email protected] | 6dfe941a | 2013-10-12 19:50:55 | [diff] [blame] | 13 | #include "chrome/browser/devtools/devtools_window.h" |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 14 | #include "chrome/browser/ui/browser.h" |
| 15 | #include "chrome/browser/ui/browser_tabstrip.h" |
| 16 | #include "chrome/browser/ui/tab_contents/core_tab_helper.h" |
| 17 | #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 18 | #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 19 | #include "content/public/browser/notification_service.h" |
| 20 | #include "content/public/browser/notification_source.h" |
| 21 | #include "content/public/browser/notification_types.h" |
| 22 | #include "content/public/browser/render_view_host.h" |
| 23 | #include "content/public/browser/web_contents.h" |
| 24 | #include "content/public/browser/web_contents_delegate.h" |
brettw | 00899e6 | 2016-11-12 02:10:17 | [diff] [blame] | 25 | #include "extensions/features/features.h" |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 26 | |
brettw | 00899e6 | 2016-11-12 02:10:17 | [diff] [blame] | 27 | #if BUILDFLAG(ENABLE_EXTENSIONS) |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 28 | #include "extensions/browser/extension_registry.h" |
| 29 | #include "extensions/common/constants.h" |
| 30 | #endif // (ENABLE_EXTENSIONS) |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 31 | |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 32 | namespace chrome { |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 33 | |
| 34 | //////////////////////////////////////////////////////////////////////////////// |
| 35 | // DetachedWebContentsDelegate will delete web contents when they close. |
| 36 | class FastUnloadController::DetachedWebContentsDelegate |
| 37 | : public content::WebContentsDelegate { |
| 38 | public: |
| 39 | DetachedWebContentsDelegate() { } |
dcheng | 5dd5ff6 | 2014-10-21 12:42:38 | [diff] [blame] | 40 | ~DetachedWebContentsDelegate() override {} |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 41 | |
| 42 | private: |
| 43 | // WebContentsDelegate implementation. |
mathiash | 72a5e46 | 2014-11-19 08:18:50 | [diff] [blame] | 44 | bool ShouldSuppressDialogs(content::WebContents* source) override { |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 45 | return true; // Return true so dialogs are suppressed. |
| 46 | } |
| 47 | |
dcheng | 5dd5ff6 | 2014-10-21 12:42:38 | [diff] [blame] | 48 | void CloseContents(content::WebContents* source) override { |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 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: |
| 60 | |
| 61 | FastUnloadController::FastUnloadController(Browser* browser) |
| 62 | : browser_(browser), |
| 63 | tab_needing_before_unload_ack_(NULL), |
| 64 | is_attempting_to_close_browser_(false), |
| 65 | detached_delegate_(new DetachedWebContentsDelegate()), |
| 66 | weak_factory_(this) { |
| 67 | browser_->tab_strip_model()->AddObserver(this); |
| 68 | } |
| 69 | |
| 70 | FastUnloadController::~FastUnloadController() { |
| 71 | browser_->tab_strip_model()->RemoveObserver(this); |
| 72 | } |
| 73 | |
| 74 | bool FastUnloadController::CanCloseContents(content::WebContents* contents) { |
| 75 | // Don't try to close the tab when the whole browser is being closed, since |
| 76 | // that avoids the fast shutdown path where we just kill all the renderers. |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 77 | return !is_attempting_to_close_browser_ || |
| 78 | is_calling_before_unload_handlers(); |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 79 | } |
| 80 | |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 81 | bool FastUnloadController::ShouldRunUnloadEventsHelper( |
| 82 | content::WebContents* contents) { |
| 83 | // If |contents| is being inspected, devtools needs to intercept beforeunload |
| 84 | // events. |
[email protected] | db90e8a | 2014-07-07 17:57:35 | [diff] [blame] | 85 | return DevToolsWindow::GetInstanceForInspectedWebContents(contents) != NULL; |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 86 | } |
| 87 | |
[email protected] | 6dfe941a | 2013-10-12 19:50:55 | [diff] [blame] | 88 | bool FastUnloadController::RunUnloadEventsHelper( |
| 89 | content::WebContents* contents) { |
brettw | 00899e6 | 2016-11-12 02:10:17 | [diff] [blame] | 90 | #if BUILDFLAG(ENABLE_EXTENSIONS) |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 91 | // Don't run for extensions that are disabled or uninstalled; the tabs will |
| 92 | // be killed if they make any network requests, and the extension shouldn't |
| 93 | // be doing any work if it's removed. |
| 94 | GURL url = contents->GetLastCommittedURL(); |
| 95 | if (url.SchemeIs(extensions::kExtensionScheme) && |
| 96 | !extensions::ExtensionRegistry::Get(browser_->profile()) |
| 97 | ->enabled_extensions() |
| 98 | .GetExtensionOrAppByURL(url)) { |
| 99 | return false; |
| 100 | } |
| 101 | #endif // (ENABLE_EXTENSIONS) |
| 102 | |
changwan | 6ed4d43 | 2016-05-19 22:03:54 | [diff] [blame] | 103 | // Special case for when we quit an application. The Devtools window can |
| 104 | // close if it's beforeunload event has already fired which will happen due |
| 105 | // to the interception of it's content's beforeunload. |
| 106 | if (browser_->is_devtools() && |
| 107 | DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) |
| 108 | return false; |
| 109 | |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 110 | // If there's a devtools window attached to |contents|, |
| 111 | // we would like devtools to call its own beforeunload handlers first, |
| 112 | // and then call beforeunload handlers for |contents|. |
| 113 | // See DevToolsWindow::InterceptPageBeforeUnload for details. |
| 114 | if (DevToolsWindow::InterceptPageBeforeUnload(contents)) { |
| 115 | return true; |
| 116 | } |
[email protected] | 6dfe941a | 2013-10-12 19:50:55 | [diff] [blame] | 117 | // If the WebContents is not connected yet, then there's no unload |
| 118 | // handler we can fire even if the WebContents has an unload listener. |
| 119 | // One case where we hit this is in a tab that has an infinite loop |
| 120 | // before load. |
| 121 | if (contents->NeedToFireBeforeUnload()) { |
| 122 | // If the page has unload listeners, then we tell the renderer to fire |
| 123 | // them. Once they have fired, we'll get a message back saying whether |
| 124 | // to proceed closing the page or not, which sends us back to this method |
| 125 | // with the NeedToFireBeforeUnload bit cleared. |
nasko | 148bb0b9 | 2016-05-10 21:03:19 | [diff] [blame] | 126 | contents->DispatchBeforeUnload(); |
[email protected] | 6dfe941a | 2013-10-12 19:50:55 | [diff] [blame] | 127 | return true; |
| 128 | } |
| 129 | return false; |
| 130 | } |
| 131 | |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 132 | bool FastUnloadController::BeforeUnloadFired(content::WebContents* contents, |
| 133 | bool proceed) { |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 134 | if (!proceed) |
| 135 | DevToolsWindow::OnPageCloseCanceled(contents); |
| 136 | |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 137 | if (!is_attempting_to_close_browser_) { |
| 138 | if (!proceed) { |
| 139 | contents->SetClosedByUserGesture(false); |
| 140 | } else { |
| 141 | // No more dialogs are possible, so remove the tab and finish |
| 142 | // running unload listeners asynchrounously. |
| 143 | browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents); |
| 144 | DetachWebContents(contents); |
| 145 | } |
| 146 | return proceed; |
| 147 | } |
| 148 | |
| 149 | if (!proceed) { |
| 150 | CancelWindowClose(); |
| 151 | contents->SetClosedByUserGesture(false); |
| 152 | return false; |
| 153 | } |
| 154 | |
| 155 | if (tab_needing_before_unload_ack_ == contents) { |
| 156 | // Now that beforeunload has fired, queue the tab to fire unload. |
| 157 | tab_needing_before_unload_ack_ = NULL; |
| 158 | tabs_needing_unload_.insert(contents); |
| 159 | ProcessPendingTabs(); |
| 160 | // 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 |
| 162 | // events should the user cancel closing the browser. |
| 163 | return false; |
| 164 | } |
| 165 | |
| 166 | return true; |
| 167 | } |
| 168 | |
| 169 | bool FastUnloadController::ShouldCloseWindow() { |
| 170 | if (HasCompletedUnloadProcessing()) |
| 171 | return true; |
| 172 | |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 173 | // Special case for when we quit an application. The Devtools window can |
| 174 | // close if it's beforeunload event has already fired which will happen due |
| 175 | // to the interception of it's content's beforeunload. |
| 176 | if (browser_->is_devtools() && |
| 177 | DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) { |
| 178 | return true; |
| 179 | } |
| 180 | |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 181 | // The behavior followed here varies based on the current phase of the |
| 182 | // operation and whether a batched shutdown is in progress. |
| 183 | // |
| 184 | // If there are tabs with outstanding beforeunload handlers: |
| 185 | // 1. If a batched shutdown is in progress: return false. |
| 186 | // This is to prevent interference with batched shutdown already in |
| 187 | // progress. |
| 188 | // 2. Otherwise: start sending beforeunload events and return false. |
| 189 | // |
| 190 | // Otherwise, If there are no tabs with outstanding beforeunload handlers: |
| 191 | // 3. If a batched shutdown is in progress: start sending unload events and |
| 192 | // return false. |
| 193 | // 4. Otherwise: return true. |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 194 | is_attempting_to_close_browser_ = true; |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 195 | // Cases 1 and 4. |
| 196 | bool need_beforeunload_fired = TabsNeedBeforeUnloadFired(); |
| 197 | if (need_beforeunload_fired == is_calling_before_unload_handlers()) |
| 198 | return !need_beforeunload_fired; |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 199 | |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 200 | // Cases 2 and 3. |
| 201 | on_close_confirmed_.Reset(); |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 202 | ProcessPendingTabs(); |
| 203 | return false; |
| 204 | } |
| 205 | |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 206 | bool FastUnloadController::CallBeforeUnloadHandlers( |
| 207 | const base::Callback<void(bool)>& on_close_confirmed) { |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 208 | // 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. |
| 210 | if (browser_->is_devtools() || !TabsNeedBeforeUnloadFired()) |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 211 | return false; |
| 212 | |
| 213 | on_close_confirmed_ = on_close_confirmed; |
| 214 | is_attempting_to_close_browser_ = true; |
| 215 | ProcessPendingTabs(); |
| 216 | return true; |
| 217 | } |
| 218 | |
| 219 | void FastUnloadController::ResetBeforeUnloadHandlers() { |
| 220 | if (!is_calling_before_unload_handlers()) |
| 221 | return; |
| 222 | CancelWindowClose(); |
| 223 | } |
| 224 | |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 225 | bool FastUnloadController::TabsNeedBeforeUnloadFired() { |
| 226 | if (!tabs_needing_before_unload_.empty() || |
| 227 | tab_needing_before_unload_ack_ != NULL) |
| 228 | return true; |
| 229 | |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 230 | if (!is_calling_before_unload_handlers() && !tabs_needing_unload_.empty()) |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 231 | return false; |
| 232 | |
| 233 | for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) { |
| 234 | content::WebContents* contents = |
| 235 | browser_->tab_strip_model()->GetWebContentsAt(i); |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 236 | bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() || |
| 237 | DevToolsWindow::NeedsToInterceptBeforeUnload(contents); |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 238 | if (!ContainsKey(tabs_needing_unload_, contents) && |
| 239 | !ContainsKey(tabs_needing_unload_ack_, contents) && |
| 240 | tab_needing_before_unload_ack_ != contents && |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 241 | should_fire_beforeunload) |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 242 | tabs_needing_before_unload_.insert(contents); |
| 243 | } |
| 244 | return !tabs_needing_before_unload_.empty(); |
| 245 | } |
| 246 | |
[email protected] | 674b65fb | 2014-02-19 02:43:42 | [diff] [blame] | 247 | bool FastUnloadController::HasCompletedUnloadProcessing() const { |
| 248 | return is_attempting_to_close_browser_ && |
| 249 | tabs_needing_before_unload_.empty() && |
| 250 | tab_needing_before_unload_ack_ == NULL && |
| 251 | tabs_needing_unload_.empty() && |
| 252 | tabs_needing_unload_ack_.empty(); |
| 253 | } |
| 254 | |
| 255 | void FastUnloadController::CancelWindowClose() { |
| 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) { |
| 260 | CoreTabHelper* core_tab_helper = |
| 261 | CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_); |
| 262 | core_tab_helper->OnCloseCanceled(); |
| 263 | DevToolsWindow::OnPageCloseCanceled(tab_needing_before_unload_ack_); |
| 264 | tab_needing_before_unload_ack_ = NULL; |
| 265 | } |
| 266 | for (WebContentsSet::iterator it = tabs_needing_unload_.begin(); |
| 267 | it != tabs_needing_unload_.end(); it++) { |
| 268 | content::WebContents* contents = *it; |
| 269 | |
| 270 | CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 271 | core_tab_helper->OnCloseCanceled(); |
| 272 | DevToolsWindow::OnPageCloseCanceled(contents); |
| 273 | } |
| 274 | tabs_needing_unload_.clear(); |
| 275 | |
| 276 | // No need to clear tabs_needing_unload_ack_. Those tabs are already detached. |
| 277 | |
| 278 | if (is_calling_before_unload_handlers()) { |
| 279 | base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_; |
| 280 | on_close_confirmed_.Reset(); |
| 281 | on_close_confirmed.Run(false); |
| 282 | } |
| 283 | |
| 284 | is_attempting_to_close_browser_ = false; |
| 285 | |
| 286 | content::NotificationService::current()->Notify( |
| 287 | chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, |
| 288 | content::Source<Browser>(browser_), |
| 289 | content::NotificationService::NoDetails()); |
| 290 | } |
| 291 | |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 292 | //////////////////////////////////////////////////////////////////////////////// |
| 293 | // FastUnloadController, content::NotificationObserver implementation: |
| 294 | |
| 295 | void FastUnloadController::Observe( |
| 296 | int type, |
| 297 | const content::NotificationSource& source, |
| 298 | const content::NotificationDetails& details) { |
thestig | 1d61924 | 2016-06-13 22:24:52 | [diff] [blame] | 299 | DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, type); |
| 300 | |
| 301 | registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 302 | source); |
| 303 | ClearUnloadState(content::Source<content::WebContents>(source).ptr()); |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 304 | } |
| 305 | |
| 306 | //////////////////////////////////////////////////////////////////////////////// |
| 307 | // FastUnloadController, TabStripModelObserver implementation: |
| 308 | |
pmonette | 9119e49 | 2016-09-20 22:14:55 | [diff] [blame] | 309 | void FastUnloadController::TabInsertedAt(TabStripModel* tab_strip_model, |
| 310 | content::WebContents* contents, |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 311 | int index, |
| 312 | bool foreground) { |
| 313 | TabAttachedImpl(contents); |
| 314 | } |
| 315 | |
| 316 | void FastUnloadController::TabDetachedAt(content::WebContents* contents, |
| 317 | int index) { |
| 318 | TabDetachedImpl(contents); |
| 319 | } |
| 320 | |
| 321 | void FastUnloadController::TabReplacedAt(TabStripModel* tab_strip_model, |
| 322 | content::WebContents* old_contents, |
| 323 | content::WebContents* new_contents, |
| 324 | int index) { |
| 325 | TabDetachedImpl(old_contents); |
| 326 | TabAttachedImpl(new_contents); |
| 327 | } |
| 328 | |
| 329 | void FastUnloadController::TabStripEmpty() { |
| 330 | // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not |
| 331 | // attempt to add tabs to the browser before it closes. |
| 332 | is_attempting_to_close_browser_ = true; |
| 333 | } |
| 334 | |
| 335 | //////////////////////////////////////////////////////////////////////////////// |
| 336 | // FastUnloadController, private: |
| 337 | |
| 338 | void FastUnloadController::TabAttachedImpl(content::WebContents* contents) { |
| 339 | // If the tab crashes in the beforeunload or unload handler, it won't be |
| 340 | // able to ack. But we know we can close it. |
| 341 | registrar_.Add( |
| 342 | this, |
| 343 | content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 344 | content::Source<content::WebContents>(contents)); |
| 345 | } |
| 346 | |
| 347 | void FastUnloadController::TabDetachedImpl(content::WebContents* contents) { |
| 348 | if (tabs_needing_unload_ack_.find(contents) != |
| 349 | tabs_needing_unload_ack_.end()) { |
| 350 | // Tab needs unload to complete. |
| 351 | // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done. |
| 352 | return; |
| 353 | } |
| 354 | |
| 355 | // If WEB_CONTENTS_DISCONNECTED was received then the notification may have |
| 356 | // already been unregistered. |
| 357 | const content::NotificationSource& source = |
| 358 | content::Source<content::WebContents>(contents); |
| 359 | if (registrar_.IsRegistered(this, |
| 360 | content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 361 | source)) { |
| 362 | registrar_.Remove(this, |
| 363 | content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 364 | source); |
| 365 | } |
| 366 | |
| 367 | if (is_attempting_to_close_browser_) |
| 368 | ClearUnloadState(contents); |
| 369 | } |
| 370 | |
| 371 | bool FastUnloadController::DetachWebContents(content::WebContents* contents) { |
| 372 | int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents); |
| 373 | if (index != TabStripModel::kNoTab && |
| 374 | contents->NeedToFireBeforeUnload()) { |
| 375 | tabs_needing_unload_ack_.insert(contents); |
| 376 | browser_->tab_strip_model()->DetachWebContentsAt(index); |
| 377 | contents->SetDelegate(detached_delegate_.get()); |
| 378 | CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 379 | core_tab_helper->OnUnloadDetachedStarted(); |
| 380 | return true; |
| 381 | } |
| 382 | return false; |
| 383 | } |
| 384 | |
| 385 | void FastUnloadController::ProcessPendingTabs() { |
| 386 | if (!is_attempting_to_close_browser_) { |
| 387 | // 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 |
| 389 | // task. |
| 390 | return; |
| 391 | } |
| 392 | |
| 393 | if (tab_needing_before_unload_ack_ != NULL) { |
| 394 | // Wait for |BeforeUnloadFired| before proceeding. |
| 395 | return; |
| 396 | } |
| 397 | |
| 398 | // Process a beforeunload handler. |
| 399 | if (!tabs_needing_before_unload_.empty()) { |
| 400 | WebContentsSet::iterator it = tabs_needing_before_unload_.begin(); |
| 401 | content::WebContents* contents = *it; |
| 402 | tabs_needing_before_unload_.erase(it); |
| 403 | // Null check render_view_host here as this gets called on a PostTask and |
| 404 | // the tab's render_view_host may have been nulled out. |
| 405 | if (contents->GetRenderViewHost()) { |
| 406 | tab_needing_before_unload_ack_ = contents; |
| 407 | |
| 408 | CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| 409 | core_tab_helper->OnCloseStarted(); |
| 410 | |
[email protected] | 9035471 | 2013-11-16 00:06:30 | [diff] [blame] | 411 | // If there's a devtools window attached to |contents|, |
| 412 | // we would like devtools to call its own beforeunload handlers first, |
| 413 | // and then call beforeunload handlers for |contents|. |
| 414 | // See DevToolsWindow::InterceptPageBeforeUnload for details. |
| 415 | if (!DevToolsWindow::InterceptPageBeforeUnload(contents)) |
nasko | 148bb0b9 | 2016-05-10 21:03:19 | [diff] [blame] | 416 | contents->DispatchBeforeUnload(); |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 417 | } else { |
| 418 | ProcessPendingTabs(); |
| 419 | } |
| 420 | return; |
| 421 | } |
| 422 | |
[email protected] | 2e9d79f | 2013-08-16 05:45:56 | [diff] [blame] | 423 | if (is_calling_before_unload_handlers()) { |
| 424 | on_close_confirmed_.Run(true); |
| 425 | return; |
| 426 | } |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 427 | // Process all the unload handlers. (The beforeunload handlers have finished.) |
| 428 | if (!tabs_needing_unload_.empty()) { |
| 429 | browser_->OnWindowClosing(); |
| 430 | |
| 431 | // Run unload handlers detached since no more interaction is possible. |
| 432 | WebContentsSet::iterator it = tabs_needing_unload_.begin(); |
| 433 | while (it != tabs_needing_unload_.end()) { |
| 434 | WebContentsSet::iterator current = it++; |
| 435 | content::WebContents* contents = *current; |
| 436 | tabs_needing_unload_.erase(current); |
| 437 | // Null check render_view_host here as this gets called on a PostTask |
| 438 | // and the tab's render_view_host may have been nulled out. |
| 439 | if (contents->GetRenderViewHost()) { |
| 440 | CoreTabHelper* core_tab_helper = |
| 441 | CoreTabHelper::FromWebContents(contents); |
| 442 | core_tab_helper->OnUnloadStarted(); |
| 443 | DetachWebContents(contents); |
nasko | c0fceff | 2015-04-30 15:53:52 | [diff] [blame] | 444 | contents->ClosePage(); |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 445 | } |
| 446 | } |
| 447 | |
| 448 | // Get the browser hidden. |
| 449 | if (browser_->tab_strip_model()->empty()) { |
| 450 | browser_->TabStripEmpty(); |
| 451 | } else { |
| 452 | browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload |
| 453 | } |
| 454 | return; |
| 455 | } |
| 456 | |
| 457 | if (HasCompletedUnloadProcessing()) { |
| 458 | browser_->OnWindowClosing(); |
| 459 | |
| 460 | // Get the browser closed. |
| 461 | if (browser_->tab_strip_model()->empty()) { |
| 462 | browser_->TabStripEmpty(); |
| 463 | } else { |
| 464 | // There may be tabs if the last tab needing beforeunload crashed. |
| 465 | browser_->tab_strip_model()->CloseAllTabs(); |
| 466 | } |
| 467 | return; |
| 468 | } |
| 469 | } |
| 470 | |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 471 | void FastUnloadController::ClearUnloadState(content::WebContents* contents) { |
| 472 | if (tabs_needing_unload_ack_.erase(contents) > 0) { |
| 473 | if (HasCompletedUnloadProcessing()) |
| 474 | PostTaskForProcessPendingTabs(); |
| 475 | return; |
| 476 | } |
| 477 | |
| 478 | if (!is_attempting_to_close_browser_) |
| 479 | return; |
| 480 | |
| 481 | if (tab_needing_before_unload_ack_ == contents) { |
| 482 | tab_needing_before_unload_ack_ = NULL; |
| 483 | PostTaskForProcessPendingTabs(); |
| 484 | return; |
| 485 | } |
| 486 | |
| 487 | if (tabs_needing_before_unload_.erase(contents) > 0 || |
| 488 | tabs_needing_unload_.erase(contents) > 0) { |
| 489 | if (tab_needing_before_unload_ack_ == NULL) |
| 490 | PostTaskForProcessPendingTabs(); |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | void FastUnloadController::PostTaskForProcessPendingTabs() { |
skyostil | 380bb222 | 2015-06-12 12:07:05 | [diff] [blame] | 495 | base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 496 | FROM_HERE, base::Bind(&FastUnloadController::ProcessPendingTabs, |
| 497 | weak_factory_.GetWeakPtr())); |
[email protected] | 88c9201 | 2013-07-02 11:56:34 | [diff] [blame] | 498 | } |
| 499 | |
| 500 | } // namespace chrome |