Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / fast_unload_controller.cc
blob8da78ec707c1a19bae849127aa55c235040855c4
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::GetInstanceForInspectedRenderViewHost(
79 contents->GetRenderViewHost()) != NULL;
82 // static
83 bool FastUnloadController::RunUnloadEventsHelper(
84 content::WebContents* contents) {
85 // If there's a devtools window attached to |contents|,
86 // we would like devtools to call its own beforeunload handlers first,
87 // and then call beforeunload handlers for |contents|.
88 // See DevToolsWindow::InterceptPageBeforeUnload for details.
89 if (DevToolsWindow::InterceptPageBeforeUnload(contents)) {
90 return true;
92 // If the WebContents is not connected yet, then there's no unload
93 // handler we can fire even if the WebContents has an unload listener.
94 // One case where we hit this is in a tab that has an infinite loop
95 // before load.
96 if (contents->NeedToFireBeforeUnload()) {
97 // If the page has unload listeners, then we tell the renderer to fire
98 // them. Once they have fired, we'll get a message back saying whether
99 // to proceed closing the page or not, which sends us back to this method
100 // with the NeedToFireBeforeUnload bit cleared.
101 contents->GetRenderViewHost()->FirePageBeforeUnload(false);
102 return true;
104 return false;
107 bool FastUnloadController::BeforeUnloadFired(content::WebContents* contents,
108 bool proceed) {
109 if (!proceed)
110 DevToolsWindow::OnPageCloseCanceled(contents);
112 if (!is_attempting_to_close_browser_) {
113 if (!proceed) {
114 contents->SetClosedByUserGesture(false);
115 } else {
116 // No more dialogs are possible, so remove the tab and finish
117 // running unload listeners asynchrounously.
118 browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents);
119 DetachWebContents(contents);
121 return proceed;
124 if (!proceed) {
125 CancelWindowClose();
126 contents->SetClosedByUserGesture(false);
127 return false;
130 if (tab_needing_before_unload_ack_ == contents) {
131 // Now that beforeunload has fired, queue the tab to fire unload.
132 tab_needing_before_unload_ack_ = NULL;
133 tabs_needing_unload_.insert(contents);
134 ProcessPendingTabs();
135 // We want to handle firing the unload event ourselves since we want to
136 // fire all the beforeunload events before attempting to fire the unload
137 // events should the user cancel closing the browser.
138 return false;
141 return true;
144 bool FastUnloadController::ShouldCloseWindow() {
145 if (HasCompletedUnloadProcessing())
146 return true;
148 // Special case for when we quit an application. The Devtools window can
149 // close if it's beforeunload event has already fired which will happen due
150 // to the interception of it's content's beforeunload.
151 if (browser_->is_devtools() &&
152 DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) {
153 return true;
156 // The behavior followed here varies based on the current phase of the
157 // operation and whether a batched shutdown is in progress.
159 // If there are tabs with outstanding beforeunload handlers:
160 // 1. If a batched shutdown is in progress: return false.
161 // This is to prevent interference with batched shutdown already in
162 // progress.
163 // 2. Otherwise: start sending beforeunload events and return false.
165 // Otherwise, If there are no tabs with outstanding beforeunload handlers:
166 // 3. If a batched shutdown is in progress: start sending unload events and
167 // return false.
168 // 4. Otherwise: return true.
169 is_attempting_to_close_browser_ = true;
170 // Cases 1 and 4.
171 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
172 if (need_beforeunload_fired == is_calling_before_unload_handlers())
173 return !need_beforeunload_fired;
175 // Cases 2 and 3.
176 on_close_confirmed_.Reset();
177 ProcessPendingTabs();
178 return false;
181 bool FastUnloadController::CallBeforeUnloadHandlers(
182 const base::Callback<void(bool)>& on_close_confirmed) {
183 // The devtools browser gets its beforeunload events as the results of
184 // intercepting events from the inspected tab, so don't send them here as well.
185 if (browser_->is_devtools() || !TabsNeedBeforeUnloadFired())
186 return false;
188 on_close_confirmed_ = on_close_confirmed;
189 is_attempting_to_close_browser_ = true;
190 ProcessPendingTabs();
191 return true;
194 void FastUnloadController::ResetBeforeUnloadHandlers() {
195 if (!is_calling_before_unload_handlers())
196 return;
197 CancelWindowClose();
200 bool FastUnloadController::TabsNeedBeforeUnloadFired() {
201 if (!tabs_needing_before_unload_.empty() ||
202 tab_needing_before_unload_ack_ != NULL)
203 return true;
205 if (!is_calling_before_unload_handlers() && !tabs_needing_unload_.empty())
206 return false;
208 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
209 content::WebContents* contents =
210 browser_->tab_strip_model()->GetWebContentsAt(i);
211 bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() ||
212 DevToolsWindow::NeedsToInterceptBeforeUnload(contents);
213 if (!ContainsKey(tabs_needing_unload_, contents) &&
214 !ContainsKey(tabs_needing_unload_ack_, contents) &&
215 tab_needing_before_unload_ack_ != contents &&
216 should_fire_beforeunload)
217 tabs_needing_before_unload_.insert(contents);
219 return !tabs_needing_before_unload_.empty();
222 ////////////////////////////////////////////////////////////////////////////////
223 // FastUnloadController, content::NotificationObserver implementation:
225 void FastUnloadController::Observe(
226 int type,
227 const content::NotificationSource& source,
228 const content::NotificationDetails& details) {
229 switch (type) {
230 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: {
231 registrar_.Remove(this,
232 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
233 source);
234 content::WebContents* contents =
235 content::Source<content::WebContents>(source).ptr();
236 ClearUnloadState(contents);
237 break;
239 default:
240 NOTREACHED() << "Got a notification we didn't register for.";
244 ////////////////////////////////////////////////////////////////////////////////
245 // FastUnloadController, TabStripModelObserver implementation:
247 void FastUnloadController::TabInsertedAt(content::WebContents* contents,
248 int index,
249 bool foreground) {
250 TabAttachedImpl(contents);
253 void FastUnloadController::TabDetachedAt(content::WebContents* contents,
254 int index) {
255 TabDetachedImpl(contents);
258 void FastUnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
259 content::WebContents* old_contents,
260 content::WebContents* new_contents,
261 int index) {
262 TabDetachedImpl(old_contents);
263 TabAttachedImpl(new_contents);
266 void FastUnloadController::TabStripEmpty() {
267 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
268 // attempt to add tabs to the browser before it closes.
269 is_attempting_to_close_browser_ = true;
272 ////////////////////////////////////////////////////////////////////////////////
273 // FastUnloadController, private:
275 void FastUnloadController::TabAttachedImpl(content::WebContents* contents) {
276 // If the tab crashes in the beforeunload or unload handler, it won't be
277 // able to ack. But we know we can close it.
278 registrar_.Add(
279 this,
280 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
281 content::Source<content::WebContents>(contents));
284 void FastUnloadController::TabDetachedImpl(content::WebContents* contents) {
285 if (tabs_needing_unload_ack_.find(contents) !=
286 tabs_needing_unload_ack_.end()) {
287 // Tab needs unload to complete.
288 // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done.
289 return;
292 // If WEB_CONTENTS_DISCONNECTED was received then the notification may have
293 // already been unregistered.
294 const content::NotificationSource& source =
295 content::Source<content::WebContents>(contents);
296 if (registrar_.IsRegistered(this,
297 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
298 source)) {
299 registrar_.Remove(this,
300 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
301 source);
304 if (is_attempting_to_close_browser_)
305 ClearUnloadState(contents);
308 bool FastUnloadController::DetachWebContents(content::WebContents* contents) {
309 int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents);
310 if (index != TabStripModel::kNoTab &&
311 contents->NeedToFireBeforeUnload()) {
312 tabs_needing_unload_ack_.insert(contents);
313 browser_->tab_strip_model()->DetachWebContentsAt(index);
314 contents->SetDelegate(detached_delegate_.get());
315 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
316 core_tab_helper->OnUnloadDetachedStarted();
317 return true;
319 return false;
322 void FastUnloadController::ProcessPendingTabs() {
323 if (!is_attempting_to_close_browser_) {
324 // Because we might invoke this after a delay it's possible for the value of
325 // is_attempting_to_close_browser_ to have changed since we scheduled the
326 // task.
327 return;
330 if (tab_needing_before_unload_ack_ != NULL) {
331 // Wait for |BeforeUnloadFired| before proceeding.
332 return;
335 // Process a beforeunload handler.
336 if (!tabs_needing_before_unload_.empty()) {
337 WebContentsSet::iterator it = tabs_needing_before_unload_.begin();
338 content::WebContents* contents = *it;
339 tabs_needing_before_unload_.erase(it);
340 // Null check render_view_host here as this gets called on a PostTask and
341 // the tab's render_view_host may have been nulled out.
342 if (contents->GetRenderViewHost()) {
343 tab_needing_before_unload_ack_ = contents;
345 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
346 core_tab_helper->OnCloseStarted();
348 // If there's a devtools window attached to |contents|,
349 // we would like devtools to call its own beforeunload handlers first,
350 // and then call beforeunload handlers for |contents|.
351 // See DevToolsWindow::InterceptPageBeforeUnload for details.
352 if (!DevToolsWindow::InterceptPageBeforeUnload(contents))
353 contents->GetRenderViewHost()->FirePageBeforeUnload(false);
354 } else {
355 ProcessPendingTabs();
357 return;
360 if (is_calling_before_unload_handlers()) {
361 on_close_confirmed_.Run(true);
362 return;
364 // Process all the unload handlers. (The beforeunload handlers have finished.)
365 if (!tabs_needing_unload_.empty()) {
366 browser_->OnWindowClosing();
368 // Run unload handlers detached since no more interaction is possible.
369 WebContentsSet::iterator it = tabs_needing_unload_.begin();
370 while (it != tabs_needing_unload_.end()) {
371 WebContentsSet::iterator current = it++;
372 content::WebContents* contents = *current;
373 tabs_needing_unload_.erase(current);
374 // Null check render_view_host here as this gets called on a PostTask
375 // and the tab's render_view_host may have been nulled out.
376 if (contents->GetRenderViewHost()) {
377 CoreTabHelper* core_tab_helper =
378 CoreTabHelper::FromWebContents(contents);
379 core_tab_helper->OnUnloadStarted();
380 DetachWebContents(contents);
381 contents->GetRenderViewHost()->ClosePage();
385 // Get the browser hidden.
386 if (browser_->tab_strip_model()->empty()) {
387 browser_->TabStripEmpty();
388 } else {
389 browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload
391 return;
394 if (HasCompletedUnloadProcessing()) {
395 browser_->OnWindowClosing();
397 // Get the browser closed.
398 if (browser_->tab_strip_model()->empty()) {
399 browser_->TabStripEmpty();
400 } else {
401 // There may be tabs if the last tab needing beforeunload crashed.
402 browser_->tab_strip_model()->CloseAllTabs();
404 return;
408 bool FastUnloadController::HasCompletedUnloadProcessing() const {
409 return is_attempting_to_close_browser_ &&
410 tabs_needing_before_unload_.empty() &&
411 tab_needing_before_unload_ack_ == NULL &&
412 tabs_needing_unload_.empty() &&
413 tabs_needing_unload_ack_.empty();
416 void FastUnloadController::CancelWindowClose() {
417 // Closing of window can be canceled from a beforeunload handler.
418 DCHECK(is_attempting_to_close_browser_);
419 tabs_needing_before_unload_.clear();
420 if (tab_needing_before_unload_ack_ != NULL) {
421 CoreTabHelper* core_tab_helper =
422 CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_);
423 core_tab_helper->OnCloseCanceled();
424 DevToolsWindow::OnPageCloseCanceled(tab_needing_before_unload_ack_);
425 tab_needing_before_unload_ack_ = NULL;
427 for (WebContentsSet::iterator it = tabs_needing_unload_.begin();
428 it != tabs_needing_unload_.end(); it++) {
429 content::WebContents* contents = *it;
431 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
432 core_tab_helper->OnCloseCanceled();
433 DevToolsWindow::OnPageCloseCanceled(contents);
435 tabs_needing_unload_.clear();
437 // No need to clear tabs_needing_unload_ack_. Those tabs are already detached.
439 if (is_calling_before_unload_handlers()) {
440 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
441 on_close_confirmed_.Reset();
442 on_close_confirmed.Run(false);
445 is_attempting_to_close_browser_ = false;
447 content::NotificationService::current()->Notify(
448 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
449 content::Source<Browser>(browser_),
450 content::NotificationService::NoDetails());
453 void FastUnloadController::ClearUnloadState(content::WebContents* contents) {
454 if (tabs_needing_unload_ack_.erase(contents) > 0) {
455 if (HasCompletedUnloadProcessing())
456 PostTaskForProcessPendingTabs();
457 return;
460 if (!is_attempting_to_close_browser_)
461 return;
463 if (tab_needing_before_unload_ack_ == contents) {
464 tab_needing_before_unload_ack_ = NULL;
465 PostTaskForProcessPendingTabs();
466 return;
469 if (tabs_needing_before_unload_.erase(contents) > 0 ||
470 tabs_needing_unload_.erase(contents) > 0) {
471 if (tab_needing_before_unload_ack_ == NULL)
472 PostTaskForProcessPendingTabs();
476 void FastUnloadController::PostTaskForProcessPendingTabs() {
477 base::MessageLoop::current()->PostTask(
478 FROM_HERE,
479 base::Bind(&FastUnloadController::ProcessPendingTabs,
480 weak_factory_.GetWeakPtr()));
483 } // namespace chrome