Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / unload_controller.cc
blobdb8b2476ddda62c63ab6d73e2d85685df8acb5c4
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
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/unload_controller.h"
7 #include "base/message_loop/message_loop.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/devtools/devtools_window.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_tabstrip.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "content/public/browser/notification_service.h"
14 #include "content/public/browser/notification_source.h"
15 #include "content/public/browser/notification_types.h"
16 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/web_contents.h"
19 namespace chrome {
21 ////////////////////////////////////////////////////////////////////////////////
22 // UnloadController, public:
24 UnloadController::UnloadController(Browser* browser)
25 : browser_(browser),
26 is_attempting_to_close_browser_(false),
27 weak_factory_(this) {
28 browser_->tab_strip_model()->AddObserver(this);
31 UnloadController::~UnloadController() {
32 browser_->tab_strip_model()->RemoveObserver(this);
35 bool UnloadController::CanCloseContents(content::WebContents* contents) {
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.
38 if (is_attempting_to_close_browser_)
39 ClearUnloadState(contents, true);
40 return !is_attempting_to_close_browser_ ||
41 is_calling_before_unload_handlers();
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;
53 // static
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;
62 // If the WebContents is not connected yet, then there's no unload
63 // handler we can fire even if the WebContents has an unload listener.
64 // One case where we hit this is in a tab that has an infinite loop
65 // before load.
66 if (contents->NeedToFireBeforeUnload()) {
67 // If the page has unload listeners, then we tell the renderer to fire
68 // them. Once they have fired, we'll get a message back saying whether
69 // to proceed closing the page or not, which sends us back to this method
70 // with the NeedToFireBeforeUnload bit cleared.
71 contents->GetRenderViewHost()->FirePageBeforeUnload(false);
72 return true;
74 return false;
77 bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
78 bool proceed) {
79 if (!proceed)
80 DevToolsWindow::OnPageCloseCanceled(contents);
82 if (!is_attempting_to_close_browser_) {
83 if (!proceed)
84 contents->SetClosedByUserGesture(false);
85 return proceed;
88 if (!proceed) {
89 CancelWindowClose();
90 contents->SetClosedByUserGesture(false);
91 return false;
94 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) {
95 // Now that beforeunload has fired, put the tab on the queue to fire
96 // unload.
97 tabs_needing_unload_fired_.insert(contents);
98 ProcessPendingTabs();
99 // We want to handle firing the unload event ourselves since we want to
100 // fire all the beforeunload events before attempting to fire the unload
101 // events should the user cancel closing the browser.
102 return false;
105 return true;
108 bool UnloadController::ShouldCloseWindow() {
109 if (HasCompletedUnloadProcessing())
110 return true;
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;
120 // The behavior followed here varies based on the current phase of the
121 // operation and whether a batched shutdown is in progress.
123 // If there are tabs with outstanding beforeunload handlers:
124 // 1. If a batched shutdown is in progress: return false.
125 // This is to prevent interference with batched shutdown already in
126 // progress.
127 // 2. Otherwise: start sending beforeunload events and return false.
129 // Otherwise, If there are no tabs with outstanding beforeunload handlers:
130 // 3. If a batched shutdown is in progress: start sending unload events and
131 // return false.
132 // 4. Otherwise: return true.
133 is_attempting_to_close_browser_ = true;
134 // Cases 1 and 4.
135 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
136 if (need_beforeunload_fired == is_calling_before_unload_handlers())
137 return !need_beforeunload_fired;
139 // Cases 2 and 3.
140 on_close_confirmed_.Reset();
141 ProcessPendingTabs();
142 return false;
145 bool UnloadController::CallBeforeUnloadHandlers(
146 const base::Callback<void(bool)>& on_close_confirmed) {
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())
152 return false;
154 is_attempting_to_close_browser_ = true;
155 on_close_confirmed_ = on_close_confirmed;
157 ProcessPendingTabs();
158 return true;
161 void UnloadController::ResetBeforeUnloadHandlers() {
162 if (!is_calling_before_unload_handlers())
163 return;
164 CancelWindowClose();
167 bool UnloadController::TabsNeedBeforeUnloadFired() {
168 if (tabs_needing_before_unload_fired_.empty()) {
169 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
170 content::WebContents* contents =
171 browser_->tab_strip_model()->GetWebContentsAt(i);
172 bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() ||
173 DevToolsWindow::NeedsToInterceptBeforeUnload(contents);
174 if (!ContainsKey(tabs_needing_unload_fired_, contents) &&
175 should_fire_beforeunload) {
176 tabs_needing_before_unload_fired_.insert(contents);
180 return !tabs_needing_before_unload_fired_.empty();
183 ////////////////////////////////////////////////////////////////////////////////
184 // UnloadController, content::NotificationObserver implementation:
186 void UnloadController::Observe(int type,
187 const content::NotificationSource& source,
188 const content::NotificationDetails& details) {
189 switch (type) {
190 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
191 if (is_attempting_to_close_browser_) {
192 ClearUnloadState(content::Source<content::WebContents>(source).ptr(),
193 false); // See comment for ClearUnloadState().
195 break;
196 default:
197 NOTREACHED() << "Got a notification we didn't register for.";
201 ////////////////////////////////////////////////////////////////////////////////
202 // UnloadController, TabStripModelObserver implementation:
204 void UnloadController::TabInsertedAt(content::WebContents* contents,
205 int index,
206 bool foreground) {
207 TabAttachedImpl(contents);
210 void UnloadController::TabDetachedAt(content::WebContents* contents,
211 int index) {
212 TabDetachedImpl(contents);
215 void UnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
216 content::WebContents* old_contents,
217 content::WebContents* new_contents,
218 int index) {
219 TabDetachedImpl(old_contents);
220 TabAttachedImpl(new_contents);
223 void UnloadController::TabStripEmpty() {
224 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
225 // attempt to add tabs to the browser before it closes.
226 is_attempting_to_close_browser_ = true;
229 ////////////////////////////////////////////////////////////////////////////////
230 // UnloadController, private:
232 void UnloadController::TabAttachedImpl(content::WebContents* contents) {
233 // If the tab crashes in the beforeunload or unload handler, it won't be
234 // able to ack. But we know we can close it.
235 registrar_.Add(
236 this,
237 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
238 content::Source<content::WebContents>(contents));
241 void UnloadController::TabDetachedImpl(content::WebContents* contents) {
242 if (is_attempting_to_close_browser_)
243 ClearUnloadState(contents, false);
244 registrar_.Remove(this,
245 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
246 content::Source<content::WebContents>(contents));
249 void UnloadController::ProcessPendingTabs() {
250 if (!is_attempting_to_close_browser_) {
251 // Because we might invoke this after a delay it's possible for the value of
252 // is_attempting_to_close_browser_ to have changed since we scheduled the
253 // task.
254 return;
257 if (HasCompletedUnloadProcessing()) {
258 // We've finished all the unload events and can proceed to close the
259 // browser.
260 browser_->OnWindowClosing();
261 return;
264 // Process beforeunload tabs first. When that queue is empty, process
265 // unload tabs.
266 if (!tabs_needing_before_unload_fired_.empty()) {
267 content::WebContents* web_contents =
268 *(tabs_needing_before_unload_fired_.begin());
269 // Null check render_view_host here as this gets called on a PostTask and
270 // the tab's render_view_host may have been nulled out.
271 if (web_contents->GetRenderViewHost()) {
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);
278 } else {
279 ClearUnloadState(web_contents, true);
281 } else if (is_calling_before_unload_handlers()) {
282 on_close_confirmed_.Run(true);
283 } else if (!tabs_needing_unload_fired_.empty()) {
284 // We've finished firing all beforeunload events and can proceed with unload
285 // events.
286 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting
287 // somewhere around here so that we have accurate measurements of shutdown
288 // time.
289 // TODO(ojan): We can probably fire all the unload events in parallel and
290 // get a perf benefit from that in the cases where the tab hangs in it's
291 // unload handler or takes a long time to page in.
292 content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin());
293 // Null check render_view_host here as this gets called on a PostTask and
294 // the tab's render_view_host may have been nulled out.
295 if (web_contents->GetRenderViewHost()) {
296 web_contents->GetRenderViewHost()->ClosePage();
297 } else {
298 ClearUnloadState(web_contents, true);
300 } else {
301 NOTREACHED();
305 bool UnloadController::HasCompletedUnloadProcessing() const {
306 return is_attempting_to_close_browser_ &&
307 tabs_needing_before_unload_fired_.empty() &&
308 tabs_needing_unload_fired_.empty();
311 void UnloadController::CancelWindowClose() {
312 // Closing of window can be canceled from a beforeunload handler.
313 DCHECK(is_attempting_to_close_browser_);
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);
319 tabs_needing_unload_fired_.clear();
320 if (is_calling_before_unload_handlers()) {
321 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
322 on_close_confirmed_.Reset();
323 on_close_confirmed.Run(false);
325 is_attempting_to_close_browser_ = false;
327 content::NotificationService::current()->Notify(
328 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
329 content::Source<Browser>(browser_),
330 content::NotificationService::NoDetails());
333 bool UnloadController::RemoveFromSet(UnloadListenerSet* set,
334 content::WebContents* web_contents) {
335 DCHECK(is_attempting_to_close_browser_);
337 UnloadListenerSet::iterator iter =
338 std::find(set->begin(), set->end(), web_contents);
339 if (iter != set->end()) {
340 set->erase(iter);
341 return true;
343 return false;
346 void UnloadController::ClearUnloadState(content::WebContents* web_contents,
347 bool process_now) {
348 if (is_attempting_to_close_browser_) {
349 RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents);
350 RemoveFromSet(&tabs_needing_unload_fired_, web_contents);
351 if (process_now) {
352 ProcessPendingTabs();
353 } else {
354 base::MessageLoop::current()->PostTask(
355 FROM_HERE,
356 base::Bind(&UnloadController::ProcessPendingTabs,
357 weak_factory_.GetWeakPtr()));
362 } // namespace chrome