NaCl docs: add sanitizers to GSoC ideas
[chromium-blink-merge.git] / chrome / browser / ui / unload_controller.cc
blob8791f7056ebab71a2aa608d4d66f070b0be60a79
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::GetInstanceForInspectedWebContents(contents) != NULL;
52 // static
53 bool UnloadController::RunUnloadEventsHelper(content::WebContents* contents) {
54 // If there's a devtools window attached to |contents|,
55 // we would like devtools to call its own beforeunload handlers first,
56 // and then call beforeunload handlers for |contents|.
57 // See DevToolsWindow::InterceptPageBeforeUnload for details.
58 if (DevToolsWindow::InterceptPageBeforeUnload(contents)) {
59 return true;
61 // If the WebContents is not connected yet, then there's no unload
62 // handler we can fire even if the WebContents has an unload listener.
63 // One case where we hit this is in a tab that has an infinite loop
64 // before load.
65 if (contents->NeedToFireBeforeUnload()) {
66 // If the page has unload listeners, then we tell the renderer to fire
67 // them. Once they have fired, we'll get a message back saying whether
68 // to proceed closing the page or not, which sends us back to this method
69 // with the NeedToFireBeforeUnload bit cleared.
70 contents->DispatchBeforeUnload(false);
71 return true;
73 return false;
76 bool UnloadController::BeforeUnloadFired(content::WebContents* contents,
77 bool proceed) {
78 if (!proceed)
79 DevToolsWindow::OnPageCloseCanceled(contents);
81 if (!is_attempting_to_close_browser_) {
82 if (!proceed)
83 contents->SetClosedByUserGesture(false);
84 return proceed;
87 if (!proceed) {
88 CancelWindowClose();
89 contents->SetClosedByUserGesture(false);
90 return false;
93 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) {
94 // Now that beforeunload has fired, put the tab on the queue to fire
95 // unload.
96 tabs_needing_unload_fired_.insert(contents);
97 ProcessPendingTabs();
98 // We want to handle firing the unload event ourselves since we want to
99 // fire all the beforeunload events before attempting to fire the unload
100 // events should the user cancel closing the browser.
101 return false;
104 return true;
107 bool UnloadController::ShouldCloseWindow() {
108 if (HasCompletedUnloadProcessing())
109 return true;
111 // Special case for when we quit an application. The devtools window can
112 // close if it's beforeunload event has already fired which will happen due
113 // to the interception of it's content's beforeunload.
114 if (browser_->is_devtools() &&
115 DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(browser_)) {
116 return true;
119 // The behavior followed here varies based on the current phase of the
120 // operation and whether a batched shutdown is in progress.
122 // If there are tabs with outstanding beforeunload handlers:
123 // 1. If a batched shutdown is in progress: return false.
124 // This is to prevent interference with batched shutdown already in
125 // progress.
126 // 2. Otherwise: start sending beforeunload events and return false.
128 // Otherwise, If there are no tabs with outstanding beforeunload handlers:
129 // 3. If a batched shutdown is in progress: start sending unload events and
130 // return false.
131 // 4. Otherwise: return true.
132 is_attempting_to_close_browser_ = true;
133 // Cases 1 and 4.
134 bool need_beforeunload_fired = TabsNeedBeforeUnloadFired();
135 if (need_beforeunload_fired == is_calling_before_unload_handlers())
136 return !need_beforeunload_fired;
138 // Cases 2 and 3.
139 on_close_confirmed_.Reset();
140 ProcessPendingTabs();
141 return false;
144 bool UnloadController::CallBeforeUnloadHandlers(
145 const base::Callback<void(bool)>& on_close_confirmed) {
146 // The devtools browser gets its beforeunload events as the results of
147 // intercepting events from the inspected tab, so don't send them here as
148 // well.
149 if (browser_->is_devtools() || HasCompletedUnloadProcessing() ||
150 !TabsNeedBeforeUnloadFired())
151 return false;
153 is_attempting_to_close_browser_ = true;
154 on_close_confirmed_ = on_close_confirmed;
156 ProcessPendingTabs();
157 return true;
160 void UnloadController::ResetBeforeUnloadHandlers() {
161 if (!is_calling_before_unload_handlers())
162 return;
163 CancelWindowClose();
166 bool UnloadController::TabsNeedBeforeUnloadFired() {
167 if (tabs_needing_before_unload_fired_.empty()) {
168 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
169 content::WebContents* contents =
170 browser_->tab_strip_model()->GetWebContentsAt(i);
171 bool should_fire_beforeunload = contents->NeedToFireBeforeUnload() ||
172 DevToolsWindow::NeedsToInterceptBeforeUnload(contents);
173 if (!ContainsKey(tabs_needing_unload_fired_, contents) &&
174 should_fire_beforeunload) {
175 tabs_needing_before_unload_fired_.insert(contents);
179 return !tabs_needing_before_unload_fired_.empty();
182 void UnloadController::CancelWindowClose() {
183 // Closing of window can be canceled from a beforeunload handler.
184 DCHECK(is_attempting_to_close_browser_);
185 tabs_needing_before_unload_fired_.clear();
186 for (UnloadListenerSet::iterator it = tabs_needing_unload_fired_.begin();
187 it != tabs_needing_unload_fired_.end(); ++it) {
188 DevToolsWindow::OnPageCloseCanceled(*it);
190 tabs_needing_unload_fired_.clear();
191 if (is_calling_before_unload_handlers()) {
192 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
193 on_close_confirmed_.Reset();
194 on_close_confirmed.Run(false);
196 is_attempting_to_close_browser_ = false;
198 content::NotificationService::current()->Notify(
199 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
200 content::Source<Browser>(browser_),
201 content::NotificationService::NoDetails());
204 ////////////////////////////////////////////////////////////////////////////////
205 // UnloadController, content::NotificationObserver implementation:
207 void UnloadController::Observe(int type,
208 const content::NotificationSource& source,
209 const content::NotificationDetails& details) {
210 switch (type) {
211 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
212 if (is_attempting_to_close_browser_) {
213 ClearUnloadState(content::Source<content::WebContents>(source).ptr(),
214 false); // See comment for ClearUnloadState().
216 break;
217 default:
218 NOTREACHED() << "Got a notification we didn't register for.";
222 ////////////////////////////////////////////////////////////////////////////////
223 // UnloadController, TabStripModelObserver implementation:
225 void UnloadController::TabInsertedAt(content::WebContents* contents,
226 int index,
227 bool foreground) {
228 TabAttachedImpl(contents);
231 void UnloadController::TabDetachedAt(content::WebContents* contents,
232 int index) {
233 TabDetachedImpl(contents);
236 void UnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
237 content::WebContents* old_contents,
238 content::WebContents* new_contents,
239 int index) {
240 TabDetachedImpl(old_contents);
241 TabAttachedImpl(new_contents);
244 void UnloadController::TabStripEmpty() {
245 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
246 // attempt to add tabs to the browser before it closes.
247 is_attempting_to_close_browser_ = true;
250 ////////////////////////////////////////////////////////////////////////////////
251 // UnloadController, private:
253 void UnloadController::TabAttachedImpl(content::WebContents* contents) {
254 // If the tab crashes in the beforeunload or unload handler, it won't be
255 // able to ack. But we know we can close it.
256 registrar_.Add(
257 this,
258 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
259 content::Source<content::WebContents>(contents));
262 void UnloadController::TabDetachedImpl(content::WebContents* contents) {
263 if (is_attempting_to_close_browser_)
264 ClearUnloadState(contents, false);
265 registrar_.Remove(this,
266 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
267 content::Source<content::WebContents>(contents));
270 void UnloadController::ProcessPendingTabs() {
271 if (!is_attempting_to_close_browser_) {
272 // Because we might invoke this after a delay it's possible for the value of
273 // is_attempting_to_close_browser_ to have changed since we scheduled the
274 // task.
275 return;
278 if (HasCompletedUnloadProcessing()) {
279 // We've finished all the unload events and can proceed to close the
280 // browser.
281 browser_->OnWindowClosing();
282 return;
285 // Process beforeunload tabs first. When that queue is empty, process
286 // unload tabs.
287 if (!tabs_needing_before_unload_fired_.empty()) {
288 content::WebContents* web_contents =
289 *(tabs_needing_before_unload_fired_.begin());
290 // Null check render_view_host here as this gets called on a PostTask and
291 // the tab's render_view_host may have been nulled out.
292 if (web_contents->GetRenderViewHost()) {
293 // If there's a devtools window attached to |web_contents|,
294 // we would like devtools to call its own beforeunload handlers first,
295 // and then call beforeunload handlers for |web_contents|.
296 // See DevToolsWindow::InterceptPageBeforeUnload for details.
297 if (!DevToolsWindow::InterceptPageBeforeUnload(web_contents))
298 web_contents->DispatchBeforeUnload(false);
299 } else {
300 ClearUnloadState(web_contents, true);
302 } else if (is_calling_before_unload_handlers()) {
303 base::Callback<void(bool)> on_close_confirmed = on_close_confirmed_;
304 // Reset |on_close_confirmed_| in case the callback tests
305 // |is_calling_before_unload_handlers()|, we want to return that calling
306 // is complete.
307 if (tabs_needing_unload_fired_.empty())
308 on_close_confirmed_.Reset();
309 on_close_confirmed.Run(true);
310 } else if (!tabs_needing_unload_fired_.empty()) {
311 // We've finished firing all beforeunload events and can proceed with unload
312 // events.
313 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting
314 // somewhere around here so that we have accurate measurements of shutdown
315 // time.
316 // TODO(ojan): We can probably fire all the unload events in parallel and
317 // get a perf benefit from that in the cases where the tab hangs in it's
318 // unload handler or takes a long time to page in.
319 content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin());
320 // Null check render_view_host here as this gets called on a PostTask and
321 // the tab's render_view_host may have been nulled out.
322 if (web_contents->GetRenderViewHost()) {
323 web_contents->GetRenderViewHost()->ClosePage();
324 } else {
325 ClearUnloadState(web_contents, true);
327 } else {
328 NOTREACHED();
332 bool UnloadController::HasCompletedUnloadProcessing() const {
333 return is_attempting_to_close_browser_ &&
334 tabs_needing_before_unload_fired_.empty() &&
335 tabs_needing_unload_fired_.empty();
338 bool UnloadController::RemoveFromSet(UnloadListenerSet* set,
339 content::WebContents* web_contents) {
340 DCHECK(is_attempting_to_close_browser_);
342 UnloadListenerSet::iterator iter =
343 std::find(set->begin(), set->end(), web_contents);
344 if (iter != set->end()) {
345 set->erase(iter);
346 return true;
348 return false;
351 void UnloadController::ClearUnloadState(content::WebContents* web_contents,
352 bool process_now) {
353 if (is_attempting_to_close_browser_) {
354 RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents);
355 RemoveFromSet(&tabs_needing_unload_fired_, web_contents);
356 if (process_now) {
357 ProcessPendingTabs();
358 } else {
359 base::MessageLoop::current()->PostTask(
360 FROM_HERE,
361 base::Bind(&UnloadController::ProcessPendingTabs,
362 weak_factory_.GetWeakPtr()));
367 } // namespace chrome