OLD | NEW |
(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/unload_controller.h" |
| 6 |
| 7 #include "chrome/browser/ui/browser.h" |
| 8 #include "chrome/browser/ui/browser_tabstrip.h" |
| 9 #include "chrome/browser/ui/tab_contents/tab_contents.h" |
| 10 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 11 #include "chrome/common/chrome_notification_types.h" |
| 12 #include "content/public/browser/notification_service.h" |
| 13 #include "content/public/browser/notification_source.h" |
| 14 #include "content/public/browser/notification_types.h" |
| 15 #include "content/public/browser/render_view_host.h" |
| 16 #include "content/public/browser/web_contents.h" |
| 17 |
| 18 namespace chrome { |
| 19 |
| 20 //////////////////////////////////////////////////////////////////////////////// |
| 21 // UnloadController, public: |
| 22 |
| 23 UnloadController::UnloadController(Browser* browser) |
| 24 : browser_(browser), |
| 25 is_attempting_to_close_browser_(false), |
| 26 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { |
| 27 browser_->tab_strip_model()->AddObserver(this); |
| 28 } |
| 29 |
| 30 UnloadController::~UnloadController() { |
| 31 browser_->tab_strip_model()->RemoveObserver(this); |
| 32 } |
| 33 |
| 34 bool UnloadController::CanCloseContents(content::WebContents* contents) { |
| 35 // Don't try to close the tab when the whole browser is being closed, since |
| 36 // that avoids the fast shutdown path where we just kill all the renderers. |
| 37 if (is_attempting_to_close_browser_) |
| 38 ClearUnloadState(contents, true); |
| 39 return !is_attempting_to_close_browser_; |
| 40 } |
| 41 |
| 42 bool UnloadController::BeforeUnloadFired(content::WebContents* contents, |
| 43 bool proceed) { |
| 44 if (!is_attempting_to_close_browser_) { |
| 45 if (!proceed) |
| 46 contents->SetClosedByUserGesture(false); |
| 47 return proceed; |
| 48 } |
| 49 |
| 50 if (!proceed) { |
| 51 CancelWindowClose(); |
| 52 contents->SetClosedByUserGesture(false); |
| 53 return false; |
| 54 } |
| 55 |
| 56 if (RemoveFromSet(&tabs_needing_before_unload_fired_, contents)) { |
| 57 // Now that beforeunload has fired, put the tab on the queue to fire |
| 58 // unload. |
| 59 tabs_needing_unload_fired_.insert(contents); |
| 60 ProcessPendingTabs(); |
| 61 // We want to handle firing the unload event ourselves since we want to |
| 62 // fire all the beforeunload events before attempting to fire the unload |
| 63 // events should the user cancel closing the browser. |
| 64 return false; |
| 65 } |
| 66 |
| 67 return true; |
| 68 } |
| 69 |
| 70 bool UnloadController::ShouldCloseWindow() { |
| 71 if (HasCompletedUnloadProcessing()) |
| 72 return true; |
| 73 |
| 74 is_attempting_to_close_browser_ = true; |
| 75 |
| 76 if (!TabsNeedBeforeUnloadFired()) |
| 77 return true; |
| 78 |
| 79 ProcessPendingTabs(); |
| 80 return false; |
| 81 } |
| 82 |
| 83 bool UnloadController::TabsNeedBeforeUnloadFired() { |
| 84 if (tabs_needing_before_unload_fired_.empty()) { |
| 85 for (int i = 0; i < browser_->tab_count(); ++i) { |
| 86 content::WebContents* contents = |
| 87 chrome::GetTabContentsAt(browser_, i)->web_contents(); |
| 88 if (contents->NeedToFireBeforeUnload()) |
| 89 tabs_needing_before_unload_fired_.insert(contents); |
| 90 } |
| 91 } |
| 92 return !tabs_needing_before_unload_fired_.empty(); |
| 93 } |
| 94 |
| 95 //////////////////////////////////////////////////////////////////////////////// |
| 96 // UnloadController, content::NotificationObserver implementation: |
| 97 |
| 98 void UnloadController::Observe(int type, |
| 99 const content::NotificationSource& source, |
| 100 const content::NotificationDetails& details) { |
| 101 switch (type) { |
| 102 case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED: |
| 103 if (is_attempting_to_close_browser_) { |
| 104 ClearUnloadState(content::Source<content::WebContents>(source).ptr(), |
| 105 false); // See comment for ClearUnloadState(). |
| 106 } |
| 107 break; |
| 108 default: |
| 109 NOTREACHED() << "Got a notification we didn't register for."; |
| 110 } |
| 111 } |
| 112 |
| 113 //////////////////////////////////////////////////////////////////////////////// |
| 114 // UnloadController, TabStripModelObserver implementation: |
| 115 |
| 116 void UnloadController::TabInsertedAt(TabContents* contents, |
| 117 int index, |
| 118 bool foreground) { |
| 119 TabAttachedImpl(contents); |
| 120 } |
| 121 |
| 122 void UnloadController::TabDetachedAt(TabContents* contents, int index) { |
| 123 TabDetachedImpl(contents); |
| 124 } |
| 125 |
| 126 void UnloadController::TabReplacedAt(TabStripModel* tab_strip_model, |
| 127 TabContents* old_contents, |
| 128 TabContents* new_contents, |
| 129 int index) { |
| 130 TabDetachedImpl(old_contents); |
| 131 TabAttachedImpl(new_contents); |
| 132 } |
| 133 |
| 134 void UnloadController::TabStripEmpty() { |
| 135 // Set is_attempting_to_close_browser_ here, so that extensions, etc, do not |
| 136 // attempt to add tabs to the browser before it closes. |
| 137 is_attempting_to_close_browser_ = true; |
| 138 } |
| 139 |
| 140 //////////////////////////////////////////////////////////////////////////////// |
| 141 // UnloadController, private: |
| 142 |
| 143 void UnloadController::TabAttachedImpl(TabContents* contents) { |
| 144 // If the tab crashes in the beforeunload or unload handler, it won't be |
| 145 // able to ack. But we know we can close it. |
| 146 registrar_.Add( |
| 147 this, |
| 148 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 149 content::Source<content::WebContents>(contents->web_contents())); |
| 150 } |
| 151 |
| 152 void UnloadController::TabDetachedImpl(TabContents* contents) { |
| 153 if (is_attempting_to_close_browser_) |
| 154 ClearUnloadState(contents->web_contents(), false); |
| 155 registrar_.Remove( |
| 156 this, |
| 157 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, |
| 158 content::Source<content::WebContents>(contents->web_contents())); |
| 159 } |
| 160 |
| 161 void UnloadController::ProcessPendingTabs() { |
| 162 if (!is_attempting_to_close_browser_) { |
| 163 // Because we might invoke this after a delay it's possible for the value of |
| 164 // is_attempting_to_close_browser_ to have changed since we scheduled the |
| 165 // task. |
| 166 return; |
| 167 } |
| 168 |
| 169 if (HasCompletedUnloadProcessing()) { |
| 170 // We've finished all the unload events and can proceed to close the |
| 171 // browser. |
| 172 browser_->OnWindowClosing(); |
| 173 return; |
| 174 } |
| 175 |
| 176 // Process beforeunload tabs first. When that queue is empty, process |
| 177 // unload tabs. |
| 178 if (!tabs_needing_before_unload_fired_.empty()) { |
| 179 content::WebContents* web_contents = |
| 180 *(tabs_needing_before_unload_fired_.begin()); |
| 181 // Null check render_view_host here as this gets called on a PostTask and |
| 182 // the tab's render_view_host may have been nulled out. |
| 183 if (web_contents->GetRenderViewHost()) { |
| 184 web_contents->GetRenderViewHost()->FirePageBeforeUnload(false); |
| 185 } else { |
| 186 ClearUnloadState(web_contents, true); |
| 187 } |
| 188 } else if (!tabs_needing_unload_fired_.empty()) { |
| 189 // We've finished firing all beforeunload events and can proceed with unload |
| 190 // events. |
| 191 // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting |
| 192 // somewhere around here so that we have accurate measurements of shutdown |
| 193 // time. |
| 194 // TODO(ojan): We can probably fire all the unload events in parallel and |
| 195 // get a perf benefit from that in the cases where the tab hangs in it's |
| 196 // unload handler or takes a long time to page in. |
| 197 content::WebContents* web_contents = *(tabs_needing_unload_fired_.begin()); |
| 198 // Null check render_view_host here as this gets called on a PostTask and |
| 199 // the tab's render_view_host may have been nulled out. |
| 200 if (web_contents->GetRenderViewHost()) { |
| 201 web_contents->GetRenderViewHost()->ClosePage(); |
| 202 } else { |
| 203 ClearUnloadState(web_contents, true); |
| 204 } |
| 205 } else { |
| 206 NOTREACHED(); |
| 207 } |
| 208 } |
| 209 |
| 210 bool UnloadController::HasCompletedUnloadProcessing() const { |
| 211 return is_attempting_to_close_browser_ && |
| 212 tabs_needing_before_unload_fired_.empty() && |
| 213 tabs_needing_unload_fired_.empty(); |
| 214 } |
| 215 |
| 216 void UnloadController::CancelWindowClose() { |
| 217 // Closing of window can be canceled from a beforeunload handler. |
| 218 DCHECK(is_attempting_to_close_browser_); |
| 219 tabs_needing_before_unload_fired_.clear(); |
| 220 tabs_needing_unload_fired_.clear(); |
| 221 is_attempting_to_close_browser_ = false; |
| 222 |
| 223 content::NotificationService::current()->Notify( |
| 224 chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, |
| 225 content::Source<Browser>(browser_), |
| 226 content::NotificationService::NoDetails()); |
| 227 } |
| 228 |
| 229 bool UnloadController::RemoveFromSet(UnloadListenerSet* set, |
| 230 content::WebContents* web_contents) { |
| 231 DCHECK(is_attempting_to_close_browser_); |
| 232 |
| 233 UnloadListenerSet::iterator iter = |
| 234 std::find(set->begin(), set->end(), web_contents); |
| 235 if (iter != set->end()) { |
| 236 set->erase(iter); |
| 237 return true; |
| 238 } |
| 239 return false; |
| 240 } |
| 241 |
| 242 void UnloadController::ClearUnloadState(content::WebContents* web_contents, |
| 243 bool process_now) { |
| 244 if (is_attempting_to_close_browser_) { |
| 245 RemoveFromSet(&tabs_needing_before_unload_fired_, web_contents); |
| 246 RemoveFromSet(&tabs_needing_unload_fired_, web_contents); |
| 247 if (process_now) { |
| 248 ProcessPendingTabs(); |
| 249 } else { |
| 250 MessageLoop::current()->PostTask( |
| 251 FROM_HERE, |
| 252 base::Bind(&UnloadController::ProcessPendingTabs, |
| 253 weak_factory_.GetWeakPtr())); |
| 254 } |
| 255 } |
| 256 } |
| 257 |
| 258 } // namespace chrome |
OLD | NEW |