app_shell: Add version number in user agent
[chromium-blink-merge.git] / chrome / browser / ui / fast_unload_controller.cc
blob7062872e7dae948a33549cbe13a4f16f71310dc4
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.
5 #include "chrome/browser/ui/fast_unload_controller.h"
7 #include "base/logging.h"
8 #include "base/message_loop/message_loop.h"
9 #include "chrome/browser/chrome_notification_types.h"
10 #include "chrome/browser/devtools/devtools_window.h"
11 #include "chrome/browser/ui/browser.h"
12 #include "chrome/browser/ui/browser_tabstrip.h"
13 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
14 #include "chrome/browser/ui/tabs/tab_strip_model.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
16 #include "content/public/browser/notification_service.h"
17 #include "content/public/browser/notification_source.h"
18 #include "content/public/browser/notification_types.h"
19 #include "content/public/browser/render_view_host.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_contents_delegate.h"
23 namespace chrome {
26 ////////////////////////////////////////////////////////////////////////////////
27 // DetachedWebContentsDelegate will delete web contents when they close.
28 class FastUnloadController::DetachedWebContentsDelegate
29 : public content::WebContentsDelegate {
30 public:
31 DetachedWebContentsDelegate() { }
32 virtual ~DetachedWebContentsDelegate() { }
34 private:
35 // WebContentsDelegate implementation.
36 virtual bool ShouldSuppressDialogs() OVERRIDE {
37 return true; // Return true so dialogs are suppressed.
40 virtual void CloseContents(content::WebContents* source) OVERRIDE {
41 // Finished detached close.
42 // FastUnloadController will observe
43 // |NOTIFICATION_WEB_CONTENTS_DISCONNECTED|.
44 delete source;
47 DISALLOW_COPY_AND_ASSIGN(DetachedWebContentsDelegate);
50 ////////////////////////////////////////////////////////////////////////////////
51 // FastUnloadController, public:
53 FastUnloadController::FastUnloadController(Browser* browser)
54 : browser_(browser),
55 tab_needing_before_unload_ack_(NULL),
56 is_attempting_to_close_browser_(false),
57 detached_delegate_(new DetachedWebContentsDelegate()),
58 weak_factory_(this) {
59 browser_->tab_strip_model()->AddObserver(this);
62 FastUnloadController::~FastUnloadController() {
63 browser_->tab_strip_model()->RemoveObserver(this);
66 bool FastUnloadController::CanCloseContents(content::WebContents* contents) {
67 // Don't try to close the tab when the whole browser is being closed, since
68 // that avoids the fast shutdown path where we just kill all the renderers.
69 return !is_attempting_to_close_browser_ ||
70 is_calling_before_unload_handlers();
73 // static
74 bool FastUnloadController::ShouldRunUnloadEventsHelper(
75 content::WebContents* contents) {
76 // If |contents| is being inspected, devtools needs to intercept beforeunload
77 // events.
78 return DevToolsWindow::GetInstanceForInspectedWebContents(contents) != NULL;
81 // static
82 bool FastUnloadController::RunUnloadEventsHelper(
83 content::WebContents* contents) {
84 // If there's a devtools window attached to |contents|,
85 // we would like devtools to call its own beforeunload handlers first,
86 // and then call beforeunload handlers for |contents|.
87 // See DevToolsWindow::InterceptPageBeforeUnload for details.
88 if (DevToolsWindow::InterceptPageBeforeUnload(contents)) {
89 return true;
91 // If the WebContents is not connected yet, then there's no unload
92 // handler we can fire even if the WebContents has an unload listener.
93 // One case where we hit this is in a tab that has an infinite loop
94 // before load.
95 if (contents->NeedToFireBeforeUnload()) {
96 // If the page has unload listeners, then we tell the renderer to fire
97 // them. Once they have fired, we'll get a message back saying whether
98 // to proceed closing the page or not, which sends us back to this method
99 // with the NeedToFireBeforeUnload bit cleared.
100 contents->DispatchBeforeUnload(false);
101 return true;
103 return false;
106 bool FastUnloadController::BeforeUnloadFired(content::WebContents* contents,
107 bool proceed) {
108 if (!proceed)
109 DevToolsWindow::OnPageCloseCanceled(contents);
111 if (!is_attempting_to_close_browser_) {
112 if (!proceed) {
113 contents->SetClosedByUserGesture(false);
114 } else {
115 // No more dialogs are possible, so remove the tab and finish
116 // running unload listeners asynchrounously.
117 browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents);
118 DetachWebContents(contents);
120 return proceed;
123 if (!proceed) {
124 CancelWindowClose();
125 contents->SetClosedByUserGesture(false);
126 return false;
129 if (tab_needing_before_unload_ack_ == contents) {
130 // Now that beforeunload has fired, queue the tab to fire unload.
131 tab_needing_before_unload_ack_ = NULL;
132 tabs_needing_unload_.insert(contents);
133 ProcessPendingTabs();
134 // We want to handle firing the unload event ourselves since we want to
135 // fire all the beforeunload events before attempting to fire the unload
136 // events should the user cancel closing the browser.
137 return false;
140 return true;
143 bool FastUnloadController::ShouldCloseWindow() {
144 if (HasCompletedUnloadProcessing())
145 return true;
147 // Special case for when we quit an application. The Devtools window can
148 // close if it's beforeunload event has already fired which will happen due
149 // to the interception of it's content's beforeunload.
150 if (browser_->is_devtools() &&
151 DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) {
152 return true;
155 // The behavior followed here varies based on the current phase of the
156 // operation and whether a batched shutdown is in progress.
158 // If there are tabs with outstanding beforeunload handlers:
159 // 1. If a batched shutdown is in progress: return false.
160 // This is to prevent interference with batched shutdown already in
161 // progress.
162 // 2. Otherwise: start sending beforeunload events and return false.
164 // Otherwise, If there are no tabs with outstanding beforeunload handlers:
165 // 3. If a batched shutdown is in progress: start sending unload events and
166 // return false.
167 // 4. Otherwise: return true.
168 is_attempting_to_close_browser_ = true;
169 // Cases 1 and 4.
170 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
171 if (need_beforeunload_fired == is_calling_before_unload_handlers())
172 return !need_beforeunload_fired;
174 // Cases 2 and 3.
175 on_close_confirmed_.Reset();
176 ProcessPendingTabs();
177 return false;
180 bool FastUnloadController::CallBeforeUnloadHandlers(
181 const base::Callback<void(bool)>& on_close_confirmed) {
182 // The devtools browser gets its beforeunload events as the results of
183 // intercepting events from the inspected tab, so don't send them here as well.
184 if (browser_->is_devtools() || !TabsNeedBeforeUnloadFired())
185 return false;
187 on_close_confirmed_ = on_close_confirmed;
188 is_attempting_to_close_browser_ = true;
189 ProcessPendingTabs();
190 return true;
193 void FastUnloadController::ResetBeforeUnloadHandlers() {
194 if (!is_calling_before_unload_handlers())
195 return;
196 CancelWindowClose();
199 bool FastUnloadController::TabsNeedBeforeUnloadFired() {
200 if (!tabs_needing_before_unload_.empty() ||
201 tab_needing_before_unload_ack_ != NULL)
202 return true;
204 if (!is_calling_before_unload_handlers() && !tabs_needing_unload_.empty())
205 return false;
207 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
208 content::WebContents* contents =
209 browser_->tab_strip_model()->GetWebContentsAt(i);
210 bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() ||
211 DevToolsWindow::NeedsToInterceptBeforeUnload(contents);
212 if (!ContainsKey(tabs_needing_unload_, contents) &&
213 !ContainsKey(tabs_needing_unload_ack_, contents) &&
214 tab_needing_before_unload_ack_ != contents &&
215 should_fire_beforeunload)
216 tabs_needing_before_unload_.insert(contents);
218 return !tabs_needing_before_unload_.empty();
221 bool FastUnloadController::HasCompletedUnloadProcessing() const {
222 return is_attempting_to_close_browser_ &&
223 tabs_needing_before_unload_.empty() &&
224 tab_needing_before_unload_ack_ == NULL &&
225 tabs_needing_unload_.empty() &&
226 tabs_needing_unload_ack_.empty();
229 void FastUnloadController::CancelWindowClose() {
230 // Closing of window can be canceled from a beforeunload handler.
231 DCHECK(is_attempting_to_close_browser_);
232 tabs_needing_before_unload_.clear();
233 if (tab_needing_before_unload_ack_ != NULL) {
234 CoreTabHelper* core_tab_helper =
235 CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_);
236 core_tab_helper->OnCloseCanceled();
237 DevToolsWindow::OnPageCloseCanceled(tab_needing_before_unload_ack_);
238 tab_needing_before_unload_ack_ = NULL;
240 for (WebContentsSet::iterator it = tabs_needing_unload_.begin();
241 it != tabs_needing_unload_.end(); it++) {
242 content::WebContents* contents = *it;
244 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
245 core_tab_helper->OnCloseCanceled();
246 DevToolsWindow::OnPageCloseCanceled(contents);
248 tabs_needing_unload_.clear();
250 // No need to clear tabs_needing_unload_ack_. Those tabs are already detached.
252 if (is_calling_before_unload_handlers()) {
253 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
254 on_close_confirmed_.Reset();
255 on_close_confirmed.Run(false);
258 is_attempting_to_close_browser_ = false;
260 content::NotificationService::current()->Notify(
261 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
262 content::Source<Browser>(browser_),
263 content::NotificationService::NoDetails());
266 ////////////////////////////////////////////////////////////////////////////////
267 // FastUnloadController, content::NotificationObserver implementation:
269 void FastUnloadController::Observe(
270 int type,
271 const content::NotificationSource& source,
272 const content::NotificationDetails& details) {
273 switch (type) {
274 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: {
275 registrar_.Remove(this,
276 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
277 source);
278 content::WebContents* contents =
279 content::Source<content::WebContents>(source).ptr();
280 ClearUnloadState(contents);
281 break;
283 default:
284 NOTREACHED() << "Got a notification we didn't register for.";
288 ////////////////////////////////////////////////////////////////////////////////
289 // FastUnloadController, TabStripModelObserver implementation:
291 void FastUnloadController::TabInsertedAt(content::WebContents* contents,
292 int index,
293 bool foreground) {
294 TabAttachedImpl(contents);
297 void FastUnloadController::TabDetachedAt(content::WebContents* contents,
298 int index) {
299 TabDetachedImpl(contents);
302 void FastUnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
303 content::WebContents* old_contents,
304 content::WebContents* new_contents,
305 int index) {
306 TabDetachedImpl(old_contents);
307 TabAttachedImpl(new_contents);
310 void FastUnloadController::TabStripEmpty() {
311 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
312 // attempt to add tabs to the browser before it closes.
313 is_attempting_to_close_browser_ = true;
316 ////////////////////////////////////////////////////////////////////////////////
317 // FastUnloadController, private:
319 void FastUnloadController::TabAttachedImpl(content::WebContents* contents) {
320 // If the tab crashes in the beforeunload or unload handler, it won't be
321 // able to ack. But we know we can close it.
322 registrar_.Add(
323 this,
324 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
325 content::Source<content::WebContents>(contents));
328 void FastUnloadController::TabDetachedImpl(content::WebContents* contents) {
329 if (tabs_needing_unload_ack_.find(contents) !=
330 tabs_needing_unload_ack_.end()) {
331 // Tab needs unload to complete.
332 // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done.
333 return;
336 // If WEB_CONTENTS_DISCONNECTED was received then the notification may have
337 // already been unregistered.
338 const content::NotificationSource& source =
339 content::Source<content::WebContents>(contents);
340 if (registrar_.IsRegistered(this,
341 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
342 source)) {
343 registrar_.Remove(this,
344 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
345 source);
348 if (is_attempting_to_close_browser_)
349 ClearUnloadState(contents);
352 bool FastUnloadController::DetachWebContents(content::WebContents* contents) {
353 int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents);
354 if (index != TabStripModel::kNoTab &&
355 contents->NeedToFireBeforeUnload()) {
356 tabs_needing_unload_ack_.insert(contents);
357 browser_->tab_strip_model()->DetachWebContentsAt(index);
358 contents->SetDelegate(detached_delegate_.get());
359 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
360 core_tab_helper->OnUnloadDetachedStarted();
361 return true;
363 return false;
366 void FastUnloadController::ProcessPendingTabs() {
367 if (!is_attempting_to_close_browser_) {
368 // Because we might invoke this after a delay it's possible for the value of
369 // is_attempting_to_close_browser_ to have changed since we scheduled the
370 // task.
371 return;
374 if (tab_needing_before_unload_ack_ != NULL) {
375 // Wait for |BeforeUnloadFired| before proceeding.
376 return;
379 // Process a beforeunload handler.
380 if (!tabs_needing_before_unload_.empty()) {
381 WebContentsSet::iterator it = tabs_needing_before_unload_.begin();
382 content::WebContents* contents = *it;
383 tabs_needing_before_unload_.erase(it);
384 // Null check render_view_host here as this gets called on a PostTask and
385 // the tab's render_view_host may have been nulled out.
386 if (contents->GetRenderViewHost()) {
387 tab_needing_before_unload_ack_ = contents;
389 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
390 core_tab_helper->OnCloseStarted();
392 // If there's a devtools window attached to |contents|,
393 // we would like devtools to call its own beforeunload handlers first,
394 // and then call beforeunload handlers for |contents|.
395 // See DevToolsWindow::InterceptPageBeforeUnload for details.
396 if (!DevToolsWindow::InterceptPageBeforeUnload(contents))
397 contents->DispatchBeforeUnload(false);
398 } else {
399 ProcessPendingTabs();
401 return;
404 if (is_calling_before_unload_handlers()) {
405 on_close_confirmed_.Run(true);
406 return;
408 // Process all the unload handlers. (The beforeunload handlers have finished.)
409 if (!tabs_needing_unload_.empty()) {
410 browser_->OnWindowClosing();
412 // Run unload handlers detached since no more interaction is possible.
413 WebContentsSet::iterator it = tabs_needing_unload_.begin();
414 while (it != tabs_needing_unload_.end()) {
415 WebContentsSet::iterator current = it++;
416 content::WebContents* contents = *current;
417 tabs_needing_unload_.erase(current);
418 // Null check render_view_host here as this gets called on a PostTask
419 // and the tab's render_view_host may have been nulled out.
420 if (contents->GetRenderViewHost()) {
421 CoreTabHelper* core_tab_helper =
422 CoreTabHelper::FromWebContents(contents);
423 core_tab_helper->OnUnloadStarted();
424 DetachWebContents(contents);
425 contents->GetRenderViewHost()->ClosePage();
429 // Get the browser hidden.
430 if (browser_->tab_strip_model()->empty()) {
431 browser_->TabStripEmpty();
432 } else {
433 browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload
435 return;
438 if (HasCompletedUnloadProcessing()) {
439 browser_->OnWindowClosing();
441 // Get the browser closed.
442 if (browser_->tab_strip_model()->empty()) {
443 browser_->TabStripEmpty();
444 } else {
445 // There may be tabs if the last tab needing beforeunload crashed.
446 browser_->tab_strip_model()->CloseAllTabs();
448 return;
452 void FastUnloadController::ClearUnloadState(content::WebContents* contents) {
453 if (tabs_needing_unload_ack_.erase(contents) > 0) {
454 if (HasCompletedUnloadProcessing())
455 PostTaskForProcessPendingTabs();
456 return;
459 if (!is_attempting_to_close_browser_)
460 return;
462 if (tab_needing_before_unload_ack_ == contents) {
463 tab_needing_before_unload_ack_ = NULL;
464 PostTaskForProcessPendingTabs();
465 return;
468 if (tabs_needing_before_unload_.erase(contents) > 0 ||
469 tabs_needing_unload_.erase(contents) > 0) {
470 if (tab_needing_before_unload_ack_ == NULL)
471 PostTaskForProcessPendingTabs();
475 void FastUnloadController::PostTaskForProcessPendingTabs() {
476 base::MessageLoop::current()->PostTask(
477 FROM_HERE,
478 base::Bind(&FastUnloadController::ProcessPendingTabs,
479 weak_factory_.GetWeakPtr()));
482 } // namespace chrome