| 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/message_loop/message_loop.h" | 7 #include "base/message_loop/message_loop.h" |
| 8 #include "chrome/browser/chrome_notification_types.h" | 8 #include "chrome/browser/chrome_notification_types.h" |
| 9 #include "chrome/browser/devtools/devtools_window.h" | 9 #include "chrome/browser/devtools/devtools_window.h" |
| 10 #include "chrome/browser/ui/browser.h" | 10 #include "chrome/browser/ui/browser.h" |
| (...skipping 24 matching lines...) Expand all Loading... |
| 35 bool UnloadController::CanCloseContents(content::WebContents* contents) { | 35 bool UnloadController::CanCloseContents(content::WebContents* contents) { |
| 36 // Don't try to close the tab when the whole browser is being closed, since | 36 // Don't try to close the tab when the whole browser is being closed, since |
| 37 // that avoids the fast shutdown path where we just kill all the renderers. | 37 // that avoids the fast shutdown path where we just kill all the renderers. |
| 38 if (is_attempting_to_close_browser_) | 38 if (is_attempting_to_close_browser_) |
| 39 ClearUnloadState(contents, true); | 39 ClearUnloadState(contents, true); |
| 40 return !is_attempting_to_close_browser_ || | 40 return !is_attempting_to_close_browser_ || |
| 41 is_calling_before_unload_handlers(); | 41 is_calling_before_unload_handlers(); |
| 42 } | 42 } |
| 43 | 43 |
| 44 // static | 44 // static |
| 45 bool UnloadController::ShouldRunUnloadEventsHelper( |
| 46 content::WebContents* contents) { |
| 47 // If |contents| is being inspected, devtools needs to intercept beforeunload |
| 48 // events. |
| 49 return DevToolsWindow::GetInstanceForInspectedRenderViewHost( |
| 50 contents->GetRenderViewHost()) != NULL; |
| 51 } |
| 52 |
| 53 // static |
| 45 bool UnloadController::RunUnloadEventsHelper(content::WebContents* contents) { | 54 bool UnloadController::RunUnloadEventsHelper(content::WebContents* contents) { |
| 55 // If there's a devtools window attached to |contents|, |
| 56 // we would like devtools to call its own beforeunload handlers first, |
| 57 // and then call beforeunload handlers for |contents|. |
| 58 // See DevToolsWindow::InterceptPageBeforeUnload for details. |
| 59 if (DevToolsWindow::InterceptPageBeforeUnload(contents)) { |
| 60 return true; |
| 61 } |
| 46 // If the WebContents is not connected yet, then there's no unload | 62 // If the WebContents is not connected yet, then there's no unload |
| 47 // handler we can fire even if the WebContents has an unload listener. | 63 // handler we can fire even if the WebContents has an unload listener. |
| 48 // One case where we hit this is in a tab that has an infinite loop | 64 // One case where we hit this is in a tab that has an infinite loop |
| 49 // before load. | 65 // before load. |
| 50 if (contents->NeedToFireBeforeUnload()) { | 66 if (contents->NeedToFireBeforeUnload()) { |
| 51 // If the page has unload listeners, then we tell the renderer to fire | 67 // If the page has unload listeners, then we tell the renderer to fire |
| 52 // them. Once they have fired, we'll get a message back saying whether | 68 // them. Once they have fired, we'll get a message back saying whether |
| 53 // to proceed closing the page or not, which sends us back to this method | 69 // to proceed closing the page or not, which sends us back to this method |
| 54 // with the NeedToFireBeforeUnload bit cleared. | 70 // with the NeedToFireBeforeUnload bit cleared. |
| 55 contents->GetRenderViewHost()->FirePageBeforeUnload(false); | 71 contents->GetRenderViewHost()->FirePageBeforeUnload(false); |
| 56 return true; | 72 return true; |
| 57 } | 73 } |
| 58 return false; | 74 return false; |
| 59 } | 75 } |
| 60 | 76 |
| 61 bool UnloadController::BeforeUnloadFired(content::WebContents* contents, | 77 bool UnloadController::BeforeUnloadFired(content::WebContents* contents, |
| 62 bool proceed) { | 78 bool proceed) { |
| 79 if (!proceed) |
| 80 DevToolsWindow::OnPageCloseCanceled(contents); |
| 81 |
| 63 if (!is_attempting_to_close_browser_) { | 82 if (!is_attempting_to_close_browser_) { |
| 64 if (!proceed) | 83 if (!proceed) |
| 65 contents->SetClosedByUserGesture(false); | 84 contents->SetClosedByUserGesture(false); |
| 66 return proceed; | 85 return proceed; |
| 67 } | 86 } |
| 68 | 87 |
| 69 if (!proceed) { | 88 if (!proceed) { |
| 70 CancelWindowClose(); | 89 CancelWindowClose(); |
| 71 contents->SetClosedByUserGesture(false); | 90 contents->SetClosedByUserGesture(false); |
| 72 return false; | 91 return false; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 83 return false; | 102 return false; |
| 84 } | 103 } |
| 85 | 104 |
| 86 return true; | 105 return true; |
| 87 } | 106 } |
| 88 | 107 |
| 89 bool UnloadController::ShouldCloseWindow() { | 108 bool UnloadController::ShouldCloseWindow() { |
| 90 if (HasCompletedUnloadProcessing()) | 109 if (HasCompletedUnloadProcessing()) |
| 91 return true; | 110 return true; |
| 92 | 111 |
| 112 // Special case for when we quit an application. The devtools window can |
| 113 // close if it's beforeunload event has already fired which will happen due |
| 114 // to the interception of it's content's beforeunload. |
| 115 if (browser_->is_devtools() && |
| 116 DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) { |
| 117 return true; |
| 118 } |
| 119 |
| 93 // The behavior followed here varies based on the current phase of the | 120 // The behavior followed here varies based on the current phase of the |
| 94 // operation and whether a batched shutdown is in progress. | 121 // operation and whether a batched shutdown is in progress. |
| 95 // | 122 // |
| 96 // If there are tabs with outstanding beforeunload handlers: | 123 // If there are tabs with outstanding beforeunload handlers: |
| 97 // 1. If a batched shutdown is in progress: return false. | 124 // 1. If a batched shutdown is in progress: return false. |
| 98 // This is to prevent interference with batched shutdown already in | 125 // This is to prevent interference with batched shutdown already in |
| 99 // progress. | 126 // progress. |
| 100 // 2. Otherwise: start sending beforeunload events and return false. | 127 // 2. Otherwise: start sending beforeunload events and return false. |
| 101 // | 128 // |
| 102 // Otherwise, If there are no tabs with outstanding beforeunload handlers: | 129 // Otherwise, If there are no tabs with outstanding beforeunload handlers: |
| 103 // 3. If a batched shutdown is in progress: start sending unload events and | 130 // 3. If a batched shutdown is in progress: start sending unload events and |
| 104 // return false. | 131 // return false. |
| 105 // 4. Otherwise: return true. | 132 // 4. Otherwise: return true. |
| 106 is_attempting_to_close_browser_ = true; | 133 is_attempting_to_close_browser_ = true; |
| 107 // Cases 1 and 4. | 134 // Cases 1 and 4. |
| 108 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired(); | 135 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired(); |
| 109 if (need_beforeunload_fired == is_calling_before_unload_handlers()) | 136 if (need_beforeunload_fired == is_calling_before_unload_handlers()) |
| 110 return !need_beforeunload_fired; | 137 return !need_beforeunload_fired; |
| 111 | 138 |
| 112 // Cases 2 and 3. | 139 // Cases 2 and 3. |
| 113 on_close_confirmed_.Reset(); | 140 on_close_confirmed_.Reset(); |
| 114 ProcessPendingTabs(); | 141 ProcessPendingTabs(); |
| 115 return false; | 142 return false; |
| 116 } | 143 } |
| 117 | 144 |
| 118 bool UnloadController::CallBeforeUnloadHandlers( | 145 bool UnloadController::CallBeforeUnloadHandlers( |
| 119 const base::Callback<void(bool)>& on_close_confirmed) { | 146 const base::Callback<void(bool)>& on_close_confirmed) { |
| 120 if (HasCompletedUnloadProcessing() || !TabsNeedBeforeUnloadFired()) | 147 // The devtools browser gets its beforeunload events as the results of |
| 148 // intercepting events from the inspected tab, so don't send them here as |
| 149 // well. |
| 150 if (browser_->is_devtools() || HasCompletedUnloadProcessing() || |
| 151 !TabsNeedBeforeUnloadFired()) |
| 121 return false; | 152 return false; |
| 122 | 153 |
| 123 is_attempting_to_close_browser_ = true; | 154 is_attempting_to_close_browser_ = true; |
| 124 on_close_confirmed_ = on_close_confirmed; | 155 on_close_confirmed_ = on_close_confirmed; |
| 125 | 156 |
| 126 ProcessPendingTabs(); | 157 ProcessPendingTabs(); |
| 127 return true; | 158 return true; |
| 128 } | 159 } |
| 129 | 160 |
| 130 void UnloadController::ResetBeforeUnloadHandlers() { | 161 void UnloadController::ResetBeforeUnloadHandlers() { |
| 131 if (!is_calling_before_unload_handlers()) | 162 if (!is_calling_before_unload_handlers()) |
| 132 return; | 163 return; |
| 133 CancelWindowClose(); | 164 CancelWindowClose(); |
| 134 } | 165 } |
| 135 | 166 |
| 136 bool UnloadController::TabsNeedBeforeUnloadFired() { | 167 bool UnloadController::TabsNeedBeforeUnloadFired() { |
| 137 if (tabs_needing_before_unload_fired_.empty()) { | 168 if (tabs_needing_before_unload_fired_.empty()) { |
| 138 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) { | 169 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) { |
| 139 content::WebContents* contents = | 170 content::WebContents* contents = |
| 140 browser_->tab_strip_model()->GetWebContentsAt(i); | 171 browser_->tab_strip_model()->GetWebContentsAt(i); |
| 172 bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() || |
| 173 DevToolsWindow::NeedsToInterceptBeforeUnload(contents); |
| 141 if (!ContainsKey(tabs_needing_unload_fired_, contents) && | 174 if (!ContainsKey(tabs_needing_unload_fired_, contents) && |
| 142 contents->NeedToFireBeforeUnload()) { | 175 should_fire_beforeunload) { |
| 143 tabs_needing_before_unload_fired_.insert(contents); | 176 tabs_needing_before_unload_fired_.insert(contents); |
| 144 } | 177 } |
| 145 } | 178 } |
| 146 } | 179 } |
| 147 return !tabs_needing_before_unload_fired_.empty(); | 180 return !tabs_needing_before_unload_fired_.empty(); |
| 148 } | 181 } |
| 149 | 182 |
| 150 //////////////////////////////////////////////////////////////////////////////// | 183 //////////////////////////////////////////////////////////////////////////////// |
| 151 // UnloadController, content::NotificationObserver implementation: | 184 // UnloadController, content::NotificationObserver implementation: |
| 152 | 185 |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 229 } | 262 } |
| 230 | 263 |
| 231 // Process beforeunload tabs first. When that queue is empty, process | 264 // Process beforeunload tabs first. When that queue is empty, process |
| 232 // unload tabs. | 265 // unload tabs. |
| 233 if (!tabs_needing_before_unload_fired_.empty()) { | 266 if (!tabs_needing_before_unload_fired_.empty()) { |
| 234 content::WebContents* web_contents = | 267 content::WebContents* web_contents = |
| 235 *(tabs_needing_before_unload_fired_.begin()); | 268 *(tabs_needing_before_unload_fired_.begin()); |
| 236 // Null check render_view_host here as this gets called on a PostTask and | 269 // Null check render_view_host here as this gets called on a PostTask and |
| 237 // the tab's render_view_host may have been nulled out. | 270 // the tab's render_view_host may have been nulled out. |
| 238 if (web_contents->GetRenderViewHost()) { | 271 if (web_contents->GetRenderViewHost()) { |
| 239 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false); | 272 // If there's a devtools window attached to |web_contents|, |
| 273 // we would like devtools to call its own beforeunload handlers first, |
| 274 // and then call beforeunload handlers for |web_contents|. |
| 275 // See DevToolsWindow::InterceptPageBeforeUnload for details. |
| 276 if (!DevToolsWindow::InterceptPageBeforeUnload(web_contents)) |
| 277 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false); |
| 240 } else { | 278 } else { |
| 241 ClearUnloadState(web_contents, true); | 279 ClearUnloadState(web_contents, true); |
| 242 } | 280 } |
| 243 } else if (is_calling_before_unload_handlers()) { | 281 } else if (is_calling_before_unload_handlers()) { |
| 244 on_close_confirmed_.Run(true); | 282 on_close_confirmed_.Run(true); |
| 245 } else if (!tabs_needing_unload_fired_.empty()) { | 283 } else if (!tabs_needing_unload_fired_.empty()) { |
| 246 // We've finished firing all beforeunload events and can proceed with unload | 284 // We've finished firing all beforeunload events and can proceed with unload |
| 247 // events. | 285 // events. |
| 248 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting | 286 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting |
| 249 // somewhere around here so that we have accurate measurements of shutdown | 287 // somewhere around here so that we have accurate measurements of shutdown |
| (...skipping 17 matching lines...) Expand all Loading... |
| 267 bool UnloadController::HasCompletedUnloadProcessing() const { | 305 bool UnloadController::HasCompletedUnloadProcessing() const { |
| 268 return is_attempting_to_close_browser_ && | 306 return is_attempting_to_close_browser_ && |
| 269 tabs_needing_before_unload_fired_.empty() && | 307 tabs_needing_before_unload_fired_.empty() && |
| 270 tabs_needing_unload_fired_.empty(); | 308 tabs_needing_unload_fired_.empty(); |
| 271 } | 309 } |
| 272 | 310 |
| 273 void UnloadController::CancelWindowClose() { | 311 void UnloadController::CancelWindowClose() { |
| 274 // Closing of window can be canceled from a beforeunload handler. | 312 // Closing of window can be canceled from a beforeunload handler. |
| 275 DCHECK(is_attempting_to_close_browser_); | 313 DCHECK(is_attempting_to_close_browser_); |
| 276 tabs_needing_before_unload_fired_.clear(); | 314 tabs_needing_before_unload_fired_.clear(); |
| 315 for (UnloadListenerSet::iterator it = tabs_needing_unload_fired_.begin(); |
| 316 it != tabs_needing_unload_fired_.end(); ++it) { |
| 317 DevToolsWindow::OnPageCloseCanceled(*it); |
| 318 } |
| 277 tabs_needing_unload_fired_.clear(); | 319 tabs_needing_unload_fired_.clear(); |
| 278 if (is_calling_before_unload_handlers()) { | 320 if (is_calling_before_unload_handlers()) { |
| 279 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_; | 321 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_; |
| 280 on_close_confirmed_.Reset(); | 322 on_close_confirmed_.Reset(); |
| 281 on_close_confirmed.Run(false); | 323 on_close_confirmed.Run(false); |
| 282 } | 324 } |
| 283 is_attempting_to_close_browser_ = false; | 325 is_attempting_to_close_browser_ = false; |
| 284 | 326 |
| 285 content::NotificationService::current()->Notify( | 327 content::NotificationService::current()->Notify( |
| 286 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, | 328 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, |
| (...skipping 24 matching lines...) Expand all Loading... |
| 311 } else { | 353 } else { |
| 312 base::MessageLoop::current()->PostTask( | 354 base::MessageLoop::current()->PostTask( |
| 313 FROM_HERE, | 355 FROM_HERE, |
| 314 base::Bind(&UnloadController::ProcessPendingTabs, | 356 base::Bind(&UnloadController::ProcessPendingTabs, |
| 315 weak_factory_.GetWeakPtr())); | 357 weak_factory_.GetWeakPtr())); |
| 316 } | 358 } |
| 317 } | 359 } |
| 318 } | 360 } |
| 319 | 361 |
| 320 } // namespace chrome | 362 } // namespace chrome |
| OLD | NEW |