Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / fast_unload_controller.cc
blobd89b21d283ea33c71f340f1eff0b40518b3a2e0c
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/location.h"
8 #include "base/logging.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/thread_task_runner_handle.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/devtools/devtools_window.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_tabstrip.h"
15 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
18 #include "content/public/browser/notification_service.h"
19 #include "content/public/browser/notification_source.h"
20 #include "content/public/browser/notification_types.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_delegate.h"
25 namespace chrome {
28 ////////////////////////////////////////////////////////////////////////////////
29 // DetachedWebContentsDelegate will delete web contents when they close.
30 class FastUnloadController::DetachedWebContentsDelegate
31 : public content::WebContentsDelegate {
32 public:
33 DetachedWebContentsDelegate() { }
34 ~DetachedWebContentsDelegate() override {}
36 private:
37 // WebContentsDelegate implementation.
38 bool ShouldSuppressDialogs(content::WebContents* source) override {
39 return true; // Return true so dialogs are suppressed.
42 void CloseContents(content::WebContents* source) override {
43 // Finished detached close.
44 // FastUnloadController will observe
45 // |NOTIFICATION_WEB_CONTENTS_DISCONNECTED|.
46 delete source;
49 DISALLOW_COPY_AND_ASSIGN(DetachedWebContentsDelegate);
52 ////////////////////////////////////////////////////////////////////////////////
53 // FastUnloadController, public:
55 FastUnloadController::FastUnloadController(Browser* browser)
56 : browser_(browser),
57 tab_needing_before_unload_ack_(NULL),
58 is_attempting_to_close_browser_(false),
59 detached_delegate_(new DetachedWebContentsDelegate()),
60 weak_factory_(this) {
61 browser_->tab_strip_model()->AddObserver(this);
64 FastUnloadController::~FastUnloadController() {
65 browser_->tab_strip_model()->RemoveObserver(this);
68 bool FastUnloadController::CanCloseContents(content::WebContents* contents) {
69 // Don't try to close the tab when the whole browser is being closed, since
70 // that avoids the fast shutdown path where we just kill all the renderers.
71 return !is_attempting_to_close_browser_ ||
72 is_calling_before_unload_handlers();
75 // static
76 bool FastUnloadController::ShouldRunUnloadEventsHelper(
77 content::WebContents* contents) {
78 // If |contents| is being inspected, devtools needs to intercept beforeunload
79 // events.
80 return DevToolsWindow::GetInstanceForInspectedWebContents(contents) != NULL;
83 // static
84 bool FastUnloadController::RunUnloadEventsHelper(
85 content::WebContents* contents) {
86 // If there's a devtools window attached to |contents|,
87 // we would like devtools to call its own beforeunload handlers first,
88 // and then call beforeunload handlers for |contents|.
89 // See DevToolsWindow::InterceptPageBeforeUnload for details.
90 if (DevToolsWindow::InterceptPageBeforeUnload(contents)) {
91 return true;
93 // If the WebContents is not connected yet, then there's no unload
94 // handler we can fire even if the WebContents has an unload listener.
95 // One case where we hit this is in a tab that has an infinite loop
96 // before load.
97 if (contents->NeedToFireBeforeUnload()) {
98 // If the page has unload listeners, then we tell the renderer to fire
99 // them. Once they have fired, we'll get a message back saying whether
100 // to proceed closing the page or not, which sends us back to this method
101 // with the NeedToFireBeforeUnload bit cleared.
102 contents->DispatchBeforeUnload(false);
103 return true;
105 return false;
108 bool FastUnloadController::BeforeUnloadFired(content::WebContents* contents,
109 bool proceed) {
110 if (!proceed)
111 DevToolsWindow::OnPageCloseCanceled(contents);
113 if (!is_attempting_to_close_browser_) {
114 if (!proceed) {
115 contents->SetClosedByUserGesture(false);
116 } else {
117 // No more dialogs are possible, so remove the tab and finish
118 // running unload listeners asynchrounously.
119 browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents);
120 DetachWebContents(contents);
122 return proceed;
125 if (!proceed) {
126 CancelWindowClose();
127 contents->SetClosedByUserGesture(false);
128 return false;
131 if (tab_needing_before_unload_ack_ == contents) {
132 // Now that beforeunload has fired, queue the tab to fire unload.
133 tab_needing_before_unload_ack_ = NULL;
134 tabs_needing_unload_.insert(contents);
135 ProcessPendingTabs();
136 // We want to handle firing the unload event ourselves since we want to
137 // fire all the beforeunload events before attempting to fire the unload
138 // events should the user cancel closing the browser.
139 return false;
142 return true;
145 bool FastUnloadController::ShouldCloseWindow() {
146 if (HasCompletedUnloadProcessing())
147 return true;
149 // Special case for when we quit an application. The Devtools window can
150 // close if it's beforeunload event has already fired which will happen due
151 // to the interception of it's content's beforeunload.
152 if (browser_->is_devtools() &&
153 DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) {
154 return true;
157 // The behavior followed here varies based on the current phase of the
158 // operation and whether a batched shutdown is in progress.
160 // If there are tabs with outstanding beforeunload handlers:
161 // 1. If a batched shutdown is in progress: return false.
162 // This is to prevent interference with batched shutdown already in
163 // progress.
164 // 2. Otherwise: start sending beforeunload events and return false.
166 // Otherwise, If there are no tabs with outstanding beforeunload handlers:
167 // 3. If a batched shutdown is in progress: start sending unload events and
168 // return false.
169 // 4. Otherwise: return true.
170 is_attempting_to_close_browser_ = true;
171 // Cases 1 and 4.
172 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
173 if (need_beforeunload_fired == is_calling_before_unload_handlers())
174 return !need_beforeunload_fired;
176 // Cases 2 and 3.
177 on_close_confirmed_.Reset();
178 ProcessPendingTabs();
179 return false;
182 bool FastUnloadController::CallBeforeUnloadHandlers(
183 const base::Callback<void(bool)>& on_close_confirmed) {
184 // The devtools browser gets its beforeunload events as the results of
185 // intercepting events from the inspected tab, so don't send them here as well.
186 if (browser_->is_devtools() || !TabsNeedBeforeUnloadFired())
187 return false;
189 on_close_confirmed_ = on_close_confirmed;
190 is_attempting_to_close_browser_ = true;
191 ProcessPendingTabs();
192 return true;
195 void FastUnloadController::ResetBeforeUnloadHandlers() {
196 if (!is_calling_before_unload_handlers())
197 return;
198 CancelWindowClose();
201 bool FastUnloadController::TabsNeedBeforeUnloadFired() {
202 if (!tabs_needing_before_unload_.empty() ||
203 tab_needing_before_unload_ack_ != NULL)
204 return true;
206 if (!is_calling_before_unload_handlers() && !tabs_needing_unload_.empty())
207 return false;
209 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
210 content::WebContents* contents =
211 browser_->tab_strip_model()->GetWebContentsAt(i);
212 bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() ||
213 DevToolsWindow::NeedsToInterceptBeforeUnload(contents);
214 if (!ContainsKey(tabs_needing_unload_, contents) &&
215 !ContainsKey(tabs_needing_unload_ack_, contents) &&
216 tab_needing_before_unload_ack_ != contents &&
217 should_fire_beforeunload)
218 tabs_needing_before_unload_.insert(contents);
220 return !tabs_needing_before_unload_.empty();
223 bool FastUnloadController::HasCompletedUnloadProcessing() const {
224 return is_attempting_to_close_browser_ &&
225 tabs_needing_before_unload_.empty() &&
226 tab_needing_before_unload_ack_ == NULL &&
227 tabs_needing_unload_.empty() &&
228 tabs_needing_unload_ack_.empty();
231 void FastUnloadController::CancelWindowClose() {
232 // Closing of window can be canceled from a beforeunload handler.
233 DCHECK(is_attempting_to_close_browser_);
234 tabs_needing_before_unload_.clear();
235 if (tab_needing_before_unload_ack_ != NULL) {
236 CoreTabHelper* core_tab_helper =
237 CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_);
238 core_tab_helper->OnCloseCanceled();
239 DevToolsWindow::OnPageCloseCanceled(tab_needing_before_unload_ack_);
240 tab_needing_before_unload_ack_ = NULL;
242 for (WebContentsSet::iterator it = tabs_needing_unload_.begin();
243 it != tabs_needing_unload_.end(); it++) {
244 content::WebContents* contents = *it;
246 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
247 core_tab_helper->OnCloseCanceled();
248 DevToolsWindow::OnPageCloseCanceled(contents);
250 tabs_needing_unload_.clear();
252 // No need to clear tabs_needing_unload_ack_. Those tabs are already detached.
254 if (is_calling_before_unload_handlers()) {
255 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
256 on_close_confirmed_.Reset();
257 on_close_confirmed.Run(false);
260 is_attempting_to_close_browser_ = false;
262 content::NotificationService::current()->Notify(
263 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
264 content::Source<Browser>(browser_),
265 content::NotificationService::NoDetails());
268 ////////////////////////////////////////////////////////////////////////////////
269 // FastUnloadController, content::NotificationObserver implementation:
271 void FastUnloadController::Observe(
272 int type,
273 const content::NotificationSource& source,
274 const content::NotificationDetails& details) {
275 switch (type) {
276 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: {
277 registrar_.Remove(this,
278 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
279 source);
280 content::WebContents* contents =
281 content::Source<content::WebContents>(source).ptr();
282 ClearUnloadState(contents);
283 break;
285 default:
286 NOTREACHED() << "Got a notification we didn't register for.";
290 ////////////////////////////////////////////////////////////////////////////////
291 // FastUnloadController, TabStripModelObserver implementation:
293 void FastUnloadController::TabInsertedAt(content::WebContents* contents,
294 int index,
295 bool foreground) {
296 TabAttachedImpl(contents);
299 void FastUnloadController::TabDetachedAt(content::WebContents* contents,
300 int index) {
301 TabDetachedImpl(contents);
304 void FastUnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
305 content::WebContents* old_contents,
306 content::WebContents* new_contents,
307 int index) {
308 TabDetachedImpl(old_contents);
309 TabAttachedImpl(new_contents);
312 void FastUnloadController::TabStripEmpty() {
313 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
314 // attempt to add tabs to the browser before it closes.
315 is_attempting_to_close_browser_ = true;
318 ////////////////////////////////////////////////////////////////////////////////
319 // FastUnloadController, private:
321 void FastUnloadController::TabAttachedImpl(content::WebContents* contents) {
322 // If the tab crashes in the beforeunload or unload handler, it won't be
323 // able to ack. But we know we can close it.
324 registrar_.Add(
325 this,
326 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
327 content::Source<content::WebContents>(contents));
330 void FastUnloadController::TabDetachedImpl(content::WebContents* contents) {
331 if (tabs_needing_unload_ack_.find(contents) !=
332 tabs_needing_unload_ack_.end()) {
333 // Tab needs unload to complete.
334 // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done.
335 return;
338 // If WEB_CONTENTS_DISCONNECTED was received then the notification may have
339 // already been unregistered.
340 const content::NotificationSource& source =
341 content::Source<content::WebContents>(contents);
342 if (registrar_.IsRegistered(this,
343 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
344 source)) {
345 registrar_.Remove(this,
346 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
347 source);
350 if (is_attempting_to_close_browser_)
351 ClearUnloadState(contents);
354 bool FastUnloadController::DetachWebContents(content::WebContents* contents) {
355 int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents);
356 if (index != TabStripModel::kNoTab &&
357 contents->NeedToFireBeforeUnload()) {
358 tabs_needing_unload_ack_.insert(contents);
359 browser_->tab_strip_model()->DetachWebContentsAt(index);
360 contents->SetDelegate(detached_delegate_.get());
361 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
362 core_tab_helper->OnUnloadDetachedStarted();
363 return true;
365 return false;
368 void FastUnloadController::ProcessPendingTabs() {
369 if (!is_attempting_to_close_browser_) {
370 // Because we might invoke this after a delay it's possible for the value of
371 // is_attempting_to_close_browser_ to have changed since we scheduled the
372 // task.
373 return;
376 if (tab_needing_before_unload_ack_ != NULL) {
377 // Wait for |BeforeUnloadFired| before proceeding.
378 return;
381 // Process a beforeunload handler.
382 if (!tabs_needing_before_unload_.empty()) {
383 WebContentsSet::iterator it = tabs_needing_before_unload_.begin();
384 content::WebContents* contents = *it;
385 tabs_needing_before_unload_.erase(it);
386 // Null check render_view_host here as this gets called on a PostTask and
387 // the tab's render_view_host may have been nulled out.
388 if (contents->GetRenderViewHost()) {
389 tab_needing_before_unload_ack_ = contents;
391 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
392 core_tab_helper->OnCloseStarted();
394 // If there's a devtools window attached to |contents|,
395 // we would like devtools to call its own beforeunload handlers first,
396 // and then call beforeunload handlers for |contents|.
397 // See DevToolsWindow::InterceptPageBeforeUnload for details.
398 if (!DevToolsWindow::InterceptPageBeforeUnload(contents))
399 contents->DispatchBeforeUnload(false);
400 } else {
401 ProcessPendingTabs();
403 return;
406 if (is_calling_before_unload_handlers()) {
407 on_close_confirmed_.Run(true);
408 return;
410 // Process all the unload handlers. (The beforeunload handlers have finished.)
411 if (!tabs_needing_unload_.empty()) {
412 browser_->OnWindowClosing();
414 // Run unload handlers detached since no more interaction is possible.
415 WebContentsSet::iterator it = tabs_needing_unload_.begin();
416 while (it != tabs_needing_unload_.end()) {
417 WebContentsSet::iterator current = it++;
418 content::WebContents* contents = *current;
419 tabs_needing_unload_.erase(current);
420 // Null check render_view_host here as this gets called on a PostTask
421 // and the tab's render_view_host may have been nulled out.
422 if (contents->GetRenderViewHost()) {
423 CoreTabHelper* core_tab_helper =
424 CoreTabHelper::FromWebContents(contents);
425 core_tab_helper->OnUnloadStarted();
426 DetachWebContents(contents);
427 contents->ClosePage();
431 // Get the browser hidden.
432 if (browser_->tab_strip_model()->empty()) {
433 browser_->TabStripEmpty();
434 } else {
435 browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload
437 return;
440 if (HasCompletedUnloadProcessing()) {
441 browser_->OnWindowClosing();
443 // Get the browser closed.
444 if (browser_->tab_strip_model()->empty()) {
445 browser_->TabStripEmpty();
446 } else {
447 // There may be tabs if the last tab needing beforeunload crashed.
448 browser_->tab_strip_model()->CloseAllTabs();
450 return;
454 void FastUnloadController::ClearUnloadState(content::WebContents* contents) {
455 if (tabs_needing_unload_ack_.erase(contents) > 0) {
456 if (HasCompletedUnloadProcessing())
457 PostTaskForProcessPendingTabs();
458 return;
461 if (!is_attempting_to_close_browser_)
462 return;
464 if (tab_needing_before_unload_ack_ == contents) {
465 tab_needing_before_unload_ack_ = NULL;
466 PostTaskForProcessPendingTabs();
467 return;
470 if (tabs_needing_before_unload_.erase(contents) > 0 ||
471 tabs_needing_unload_.erase(contents) > 0) {
472 if (tab_needing_before_unload_ack_ == NULL)
473 PostTaskForProcessPendingTabs();
477 void FastUnloadController::PostTaskForProcessPendingTabs() {
478 base::ThreadTaskRunnerHandle::Get()->PostTask(
479 FROM_HERE, base::Bind(&FastUnloadController::ProcessPendingTabs,
480 weak_factory_.GetWeakPtr()));
483 } // namespace chrome