Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(637)

Side by Side Diff: chrome/browser/ui/fast_unload_controller.cc

Issue 17571018: Reland fast tab closure behind a flag (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebased, 2nd attempt to land. Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/ui/fast_unload_controller.h"
6
7 #include "base/logging.h"
8 #include "base/message_loop.h"
9 #include "chrome/browser/ui/browser.h"
10 #include "chrome/browser/ui/browser_tabstrip.h"
11 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
14 #include "chrome/common/chrome_notification_types.h"
15 #include "content/public/browser/notification_service.h"
16 #include "content/public/browser/notification_source.h"
17 #include "content/public/browser/notification_types.h"
18 #include "content/public/browser/render_view_host.h"
19 #include "content/public/browser/web_contents.h"
20 #include "content/public/browser/web_contents_delegate.h"
21
22 namespace chrome {
23
24
25 ////////////////////////////////////////////////////////////////////////////////
26 // DetachedWebContentsDelegate will delete web contents when they close.
27 class FastUnloadController::DetachedWebContentsDelegate
28 : public content::WebContentsDelegate {
29 public:
30 DetachedWebContentsDelegate() { }
31 virtual ~DetachedWebContentsDelegate() { }
32
33 private:
34 // WebContentsDelegate implementation.
35 virtual bool ShouldSuppressDialogs() OVERRIDE {
36 return true; // Return true so dialogs are suppressed.
37 }
38
39 virtual void CloseContents(content::WebContents* source) OVERRIDE {
40 // Finished detached close.
41 // FastUnloadController will observe
42 // |NOTIFICATION_WEB_CONTENTS_DISCONNECTED|.
43 delete source;
44 }
45
46 DISALLOW_COPY_AND_ASSIGN(DetachedWebContentsDelegate);
47 };
48
49 ////////////////////////////////////////////////////////////////////////////////
50 // FastUnloadController, public:
51
52 FastUnloadController::FastUnloadController(Browser* browser)
53 : browser_(browser),
54 tab_needing_before_unload_ack_(NULL),
55 is_attempting_to_close_browser_(false),
56 detached_delegate_(new DetachedWebContentsDelegate()),
57 weak_factory_(this) {
58 browser_->tab_strip_model()->AddObserver(this);
59 }
60
61 FastUnloadController::~FastUnloadController() {
62 browser_->tab_strip_model()->RemoveObserver(this);
63 }
64
65 bool FastUnloadController::CanCloseContents(content::WebContents* contents) {
66 // Don't try to close the tab when the whole browser is being closed, since
67 // that avoids the fast shutdown path where we just kill all the renderers.
68 return !is_attempting_to_close_browser_;
69 }
70
71 bool FastUnloadController::BeforeUnloadFired(content::WebContents* contents,
72 bool proceed) {
73 if (!is_attempting_to_close_browser_) {
74 if (!proceed) {
75 contents->SetClosedByUserGesture(false);
76 } else {
77 // No more dialogs are possible, so remove the tab and finish
78 // running unload listeners asynchrounously.
79 browser_->tab_strip_model()->delegate()->CreateHistoricalTab(contents);
80 DetachWebContents(contents);
81 }
82 return proceed;
83 }
84
85 if (!proceed) {
86 CancelWindowClose();
87 contents->SetClosedByUserGesture(false);
88 return false;
89 }
90
91 if (tab_needing_before_unload_ack_ == contents) {
92 // Now that beforeunload has fired, queue the tab to fire unload.
93 tab_needing_before_unload_ack_ = NULL;
94 tabs_needing_unload_.insert(contents);
95 ProcessPendingTabs();
96 // We want to handle firing the unload event ourselves since we want to
97 // fire all the beforeunload events before attempting to fire the unload
98 // events should the user cancel closing the browser.
99 return false;
100 }
101
102 return true;
103 }
104
105 bool FastUnloadController::ShouldCloseWindow() {
106 if (HasCompletedUnloadProcessing())
107 return true;
108
109 is_attempting_to_close_browser_ = true;
110
111 if (!TabsNeedBeforeUnloadFired())
112 return true;
113
114 ProcessPendingTabs();
115 return false;
116 }
117
118 bool FastUnloadController::TabsNeedBeforeUnloadFired() {
119 if (!tabs_needing_before_unload_.empty() ||
120 tab_needing_before_unload_ack_ != NULL)
121 return true;
122
123 if (!tabs_needing_unload_.empty())
124 return false;
125
126 for (int i = 0; i < browser_->tab_strip_model()->count(); ++i) {
127 content::WebContents* contents =
128 browser_->tab_strip_model()->GetWebContentsAt(i);
129 if (contents->NeedToFireBeforeUnload())
130 tabs_needing_before_unload_.insert(contents);
131 }
132 return !tabs_needing_before_unload_.empty();
133 }
134
135 ////////////////////////////////////////////////////////////////////////////////
136 // FastUnloadController, content::NotificationObserver implementation:
137
138 void FastUnloadController::Observe(
139 int type,
140 const content::NotificationSource& source,
141 const content::NotificationDetails& details) {
142 switch (type) {
143 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: {
144 registrar_.Remove(this,
145 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
146 source);
147 content::WebContents* contents =
148 content::Source<content::WebContents>(source).ptr();
149 ClearUnloadState(contents);
150 break;
151 }
152 default:
153 NOTREACHED() << "Got a notification we didn't register for.";
154 }
155 }
156
157 ////////////////////////////////////////////////////////////////////////////////
158 // FastUnloadController, TabStripModelObserver implementation:
159
160 void FastUnloadController::TabInsertedAt(content::WebContents* contents,
161 int index,
162 bool foreground) {
163 TabAttachedImpl(contents);
164 }
165
166 void FastUnloadController::TabDetachedAt(content::WebContents* contents,
167 int index) {
168 TabDetachedImpl(contents);
169 }
170
171 void FastUnloadController::TabReplacedAt(TabStripModel* tab_strip_model,
172 content::WebContents* old_contents,
173 content::WebContents* new_contents,
174 int index) {
175 TabDetachedImpl(old_contents);
176 TabAttachedImpl(new_contents);
177 }
178
179 void FastUnloadController::TabStripEmpty() {
180 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not
181 // attempt to add tabs to the browser before it closes.
182 is_attempting_to_close_browser_ = true;
183 }
184
185 ////////////////////////////////////////////////////////////////////////////////
186 // FastUnloadController, private:
187
188 void FastUnloadController::TabAttachedImpl(content::WebContents* contents) {
189 // If the tab crashes in the beforeunload or unload handler, it won't be
190 // able to ack. But we know we can close it.
191 registrar_.Add(
192 this,
193 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
194 content::Source<content::WebContents>(contents));
195 }
196
197 void FastUnloadController::TabDetachedImpl(content::WebContents* contents) {
198 if (tabs_needing_unload_ack_.find(contents) !=
199 tabs_needing_unload_ack_.end()) {
200 // Tab needs unload to complete.
201 // It will send |NOTIFICATION_WEB_CONTENTS_DISCONNECTED| when done.
202 return;
203 }
204
205 // If WEB_CONTENTS_DISCONNECTED was received then the notification may have
206 // already been unregistered.
207 const content::NotificationSource& source =
208 content::Source<content::WebContents>(contents);
209 if (registrar_.IsRegistered(this,
210 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
211 source)) {
212 registrar_.Remove(this,
213 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
214 source);
215 }
216
217 if (is_attempting_to_close_browser_)
218 ClearUnloadState(contents);
219 }
220
221 bool FastUnloadController::DetachWebContents(content::WebContents* contents) {
222 int index = browser_->tab_strip_model()->GetIndexOfWebContents(contents);
223 if (index != TabStripModel::kNoTab &&
224 contents->NeedToFireBeforeUnload()) {
225 tabs_needing_unload_ack_.insert(contents);
226 browser_->tab_strip_model()->DetachWebContentsAt(index);
227 contents->SetDelegate(detached_delegate_.get());
228 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
229 core_tab_helper->OnUnloadDetachedStarted();
230 return true;
231 }
232 return false;
233 }
234
235 void FastUnloadController::ProcessPendingTabs() {
236 if (!is_attempting_to_close_browser_) {
237 // Because we might invoke this after a delay it's possible for the value of
238 // is_attempting_to_close_browser_ to have changed since we scheduled the
239 // task.
240 return;
241 }
242
243 if (tab_needing_before_unload_ack_ != NULL) {
244 // Wait for |BeforeUnloadFired| before proceeding.
245 return;
246 }
247
248 // Process a beforeunload handler.
249 if (!tabs_needing_before_unload_.empty()) {
250 WebContentsSet::iterator it = tabs_needing_before_unload_.begin();
251 content::WebContents* contents = *it;
252 tabs_needing_before_unload_.erase(it);
253 // Null check render_view_host here as this gets called on a PostTask and
254 // the tab's render_view_host may have been nulled out.
255 if (contents->GetRenderViewHost()) {
256 tab_needing_before_unload_ack_ = contents;
257
258 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
259 core_tab_helper->OnCloseStarted();
260
261 contents->GetRenderViewHost()->FirePageBeforeUnload(false);
262 } else {
263 ProcessPendingTabs();
264 }
265 return;
266 }
267
268 // Process all the unload handlers. (The beforeunload handlers have finished.)
269 if (!tabs_needing_unload_.empty()) {
270 browser_->OnWindowClosing();
271
272 // Run unload handlers detached since no more interaction is possible.
273 WebContentsSet::iterator it = tabs_needing_unload_.begin();
274 while (it != tabs_needing_unload_.end()) {
275 WebContentsSet::iterator current = it++;
276 content::WebContents* contents = *current;
277 tabs_needing_unload_.erase(current);
278 // Null check render_view_host here as this gets called on a PostTask
279 // and the tab's render_view_host may have been nulled out.
280 if (contents->GetRenderViewHost()) {
281 CoreTabHelper* core_tab_helper =
282 CoreTabHelper::FromWebContents(contents);
283 core_tab_helper->OnUnloadStarted();
284 DetachWebContents(contents);
285 contents->GetRenderViewHost()->ClosePage();
286 }
287 }
288
289 // Get the browser hidden.
290 if (browser_->tab_strip_model()->empty()) {
291 browser_->TabStripEmpty();
292 } else {
293 browser_->tab_strip_model()->CloseAllTabs(); // tabs not needing unload
294 }
295 return;
296 }
297
298 if (HasCompletedUnloadProcessing()) {
299 browser_->OnWindowClosing();
300
301 // Get the browser closed.
302 if (browser_->tab_strip_model()->empty()) {
303 browser_->TabStripEmpty();
304 } else {
305 // There may be tabs if the last tab needing beforeunload crashed.
306 browser_->tab_strip_model()->CloseAllTabs();
307 }
308 return;
309 }
310 }
311
312 bool FastUnloadController::HasCompletedUnloadProcessing() const {
313 return is_attempting_to_close_browser_ &&
314 tabs_needing_before_unload_.empty() &&
315 tab_needing_before_unload_ack_ == NULL &&
316 tabs_needing_unload_.empty() &&
317 tabs_needing_unload_ack_.empty();
318 }
319
320 void FastUnloadController::CancelWindowClose() {
321 // Closing of window can be canceled from a beforeunload handler.
322 DCHECK(is_attempting_to_close_browser_);
323 tabs_needing_before_unload_.clear();
324 if (tab_needing_before_unload_ack_ != NULL) {
325
326 CoreTabHelper* core_tab_helper =
327 CoreTabHelper::FromWebContents(tab_needing_before_unload_ack_);
328 core_tab_helper->OnCloseCanceled();
329 tab_needing_before_unload_ack_ = NULL;
330 }
331 for (WebContentsSet::iterator it = tabs_needing_unload_.begin();
332 it != tabs_needing_unload_.end(); it++) {
333 content::WebContents* contents = *it;
334
335 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
336 core_tab_helper->OnCloseCanceled();
337 }
338 tabs_needing_unload_.clear();
339
340 // No need to clear tabs_needing_unload_ack_. Those tabs are already detached.
341
342 is_attempting_to_close_browser_ = false;
343
344 content::NotificationService::current()->Notify(
345 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
346 content::Source<Browser>(browser_),
347 content::NotificationService::NoDetails());
348 }
349
350 void FastUnloadController::ClearUnloadState(content::WebContents* contents) {
351 if (tabs_needing_unload_ack_.erase(contents) > 0) {
352 if (HasCompletedUnloadProcessing())
353 PostTaskForProcessPendingTabs();
354 return;
355 }
356
357 if (!is_attempting_to_close_browser_)
358 return;
359
360 if (tab_needing_before_unload_ack_ == contents) {
361 tab_needing_before_unload_ack_ = NULL;
362 PostTaskForProcessPendingTabs();
363 return;
364 }
365
366 if (tabs_needing_before_unload_.erase(contents) > 0 ||
367 tabs_needing_unload_.erase(contents) > 0) {
368 if (tab_needing_before_unload_ack_ == NULL)
369 PostTaskForProcessPendingTabs();
370 }
371 }
372
373 void FastUnloadController::PostTaskForProcessPendingTabs() {
374 base::MessageLoop::current()->PostTask(
375 FROM_HERE,
376 base::Bind(&FastUnloadController::ProcessPendingTabs,
377 weak_factory_.GetWeakPtr()));
378 }
379
380 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698