1 // Copyright (c) 2011 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/download/download_request_limiter.h" 6 7 #include "base/stl_util-inl.h" 8 #include "chrome/browser/download/download_request_infobar_delegate.h" 9 #include "chrome/browser/tab_contents/tab_util.h" 10 #include "content/browser/browser_thread.h" 11 #include "content/browser/tab_contents/navigation_controller.h" 12 #include "content/browser/tab_contents/navigation_entry.h" 13 #include "content/browser/tab_contents/tab_contents.h" 14 #include "content/browser/tab_contents/tab_contents_delegate.h" 15 #include "content/common/notification_source.h" 16 17 // TabDownloadState ------------------------------------------------------------ 18 19 DownloadRequestLimiter::TabDownloadState::TabDownloadState( 20 DownloadRequestLimiter* host, 21 NavigationController* controller, 22 NavigationController* originating_controller) 23 : host_(host), 24 controller_(controller), 25 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD), 26 download_count_(0), 27 infobar_(NULL) { 28 Source<NavigationController> notification_source(controller); 29 registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING, 30 notification_source); 31 registrar_.Add(this, NotificationType::TAB_CLOSED, notification_source); 32 33 NavigationEntry* active_entry = originating_controller ? 34 originating_controller->GetActiveEntry() : controller->GetActiveEntry(); 35 if (active_entry) 36 initial_page_host_ = active_entry->url().host(); 37 } 38 39 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() { 40 // We should only be destroyed after the callbacks have been notified. 41 DCHECK(callbacks_.empty()); 42 43 // And we should have closed the infobar. 44 DCHECK(!infobar_); 45 } 46 47 void DownloadRequestLimiter::TabDownloadState::OnUserGesture() { 48 if (is_showing_prompt()) { 49 // Don't change the state if the user clicks on the page some where. 50 return; 51 } 52 53 if (status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS && 54 status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) { 55 // Revert to default status. 56 host_->Remove(this); 57 // WARNING: We've been deleted. 58 return; 59 } 60 } 61 62 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload( 63 TabContents* tab, 64 DownloadRequestLimiter::Callback* callback) { 65 callbacks_.push_back(callback); 66 67 if (is_showing_prompt()) 68 return; // Already showing prompt. 69 70 if (DownloadRequestLimiter::delegate_) { 71 NotifyCallbacks(DownloadRequestLimiter::delegate_->ShouldAllowDownload()); 72 } else { 73 infobar_ = new DownloadRequestInfoBarDelegate(tab, this); 74 tab->AddInfoBar(infobar_); 75 } 76 } 77 78 void DownloadRequestLimiter::TabDownloadState::Cancel() { 79 NotifyCallbacks(false); 80 } 81 82 void DownloadRequestLimiter::TabDownloadState::Accept() { 83 NotifyCallbacks(true); 84 } 85 86 void DownloadRequestLimiter::TabDownloadState::Observe( 87 NotificationType type, 88 const NotificationSource& source, 89 const NotificationDetails& details) { 90 if ((type != NotificationType::NAV_ENTRY_PENDING && 91 type != NotificationType::TAB_CLOSED) || 92 Source<NavigationController>(source).ptr() != controller_) { 93 NOTREACHED(); 94 return; 95 } 96 97 switch (type.value) { 98 case NotificationType::NAV_ENTRY_PENDING: { 99 // NOTE: resetting state on a pending navigate isn't ideal. In particular 100 // it is possible that queued up downloads for the page before the 101 // pending navigate will be delivered to us after we process this 102 // request. If this happens we may let a download through that we 103 // shouldn't have. But this is rather rare, and it is difficult to get 104 // 100% right, so we don't deal with it. 105 NavigationEntry* entry = controller_->pending_entry(); 106 if (!entry) 107 return; 108 109 if (PageTransition::IsRedirect(entry->transition_type())) { 110 // Redirects don't count. 111 return; 112 } 113 114 if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS || 115 status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) { 116 // User has either allowed all downloads or canceled all downloads. Only 117 // reset the download state if the user is navigating to a different 118 // host (or host is empty). 119 if (!initial_page_host_.empty() && !entry->url().host().empty() && 120 entry->url().host() == initial_page_host_) { 121 return; 122 } 123 } 124 break; 125 } 126 127 case NotificationType::TAB_CLOSED: 128 // Tab closed, no need to handle closing the dialog as it's owned by the 129 // TabContents, break so that we get deleted after switch. 130 break; 131 132 default: 133 NOTREACHED(); 134 } 135 136 NotifyCallbacks(false); 137 host_->Remove(this); 138 } 139 140 void DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) { 141 status_ = allow ? 142 DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS : 143 DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED; 144 std::vector<DownloadRequestLimiter::Callback*> callbacks; 145 bool change_status = false; 146 147 // Selectively send first few notifications only if number of downloads exceed 148 // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and 149 // don't close it. If allow is false, we send all the notifications to cancel 150 // all remaining downloads and close the infobar. 151 if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) { 152 if (infobar_) { 153 // Reset the delegate so we don't get notified again. 154 infobar_->set_host(NULL); 155 infobar_ = NULL; 156 } 157 callbacks.swap(callbacks_); 158 } else { 159 std::vector<DownloadRequestLimiter::Callback*>::iterator start, end; 160 start = callbacks_.begin(); 161 end = callbacks_.begin() + kMaxDownloadsAtOnce; 162 callbacks.assign(start, end); 163 callbacks_.erase(start, end); 164 change_status = true; 165 } 166 167 for (size_t i = 0; i < callbacks.size(); ++i) 168 host_->ScheduleNotification(callbacks[i], allow); 169 170 if (change_status) 171 status_ = DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD; 172 } 173 174 // DownloadRequestLimiter ------------------------------------------------------ 175 176 DownloadRequestLimiter::DownloadRequestLimiter() { 177 } 178 179 DownloadRequestLimiter::~DownloadRequestLimiter() { 180 // All the tabs should have closed before us, which sends notification and 181 // removes from state_map_. As such, there should be no pending callbacks. 182 DCHECK(state_map_.empty()); 183 } 184 185 DownloadRequestLimiter::DownloadStatus 186 DownloadRequestLimiter::GetDownloadStatus(TabContents* tab) { 187 TabDownloadState* state = GetDownloadState(&tab->controller(), NULL, false); 188 return state ? state->download_status() : ALLOW_ONE_DOWNLOAD; 189 } 190 191 void DownloadRequestLimiter::CanDownloadOnIOThread(int render_process_host_id, 192 int render_view_id, 193 int request_id, 194 Callback* callback) { 195 // This is invoked on the IO thread. Schedule the task to run on the UI 196 // thread so that we can query UI state. 197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 198 BrowserThread::PostTask( 199 BrowserThread::UI, FROM_HERE, 200 NewRunnableMethod(this, &DownloadRequestLimiter::CanDownload, 201 render_process_host_id, render_view_id, request_id, 202 callback)); 203 } 204 205 void DownloadRequestLimiter::OnUserGesture(TabContents* tab) { 206 TabDownloadState* state = GetDownloadState(&tab->controller(), NULL, false); 207 if (!state) 208 return; 209 210 state->OnUserGesture(); 211 } 212 213 // static 214 void DownloadRequestLimiter::SetTestingDelegate(TestingDelegate* delegate) { 215 delegate_ = delegate; 216 } 217 218 DownloadRequestLimiter::TabDownloadState* DownloadRequestLimiter:: 219 GetDownloadState(NavigationController* controller, 220 NavigationController* originating_controller, 221 bool create) { 222 DCHECK(controller); 223 StateMap::iterator i = state_map_.find(controller); 224 if (i != state_map_.end()) 225 return i->second; 226 227 if (!create) 228 return NULL; 229 230 TabDownloadState* state = 231 new TabDownloadState(this, controller, originating_controller); 232 state_map_[controller] = state; 233 return state; 234 } 235 236 void DownloadRequestLimiter::CanDownload(int render_process_host_id, 237 int render_view_id, 238 int request_id, 239 Callback* callback) { 240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 241 242 TabContents* originating_tab = 243 tab_util::GetTabContentsByID(render_process_host_id, render_view_id); 244 if (!originating_tab) { 245 // The tab was closed, don't allow the download. 246 ScheduleNotification(callback, false); 247 return; 248 } 249 CanDownloadImpl(originating_tab, request_id, callback); 250 } 251 252 void DownloadRequestLimiter::CanDownloadImpl( 253 TabContents* originating_tab, 254 int request_id, 255 Callback* callback) { 256 // FYI: Chrome Frame overrides CanDownload in ExternalTabContainer in order 257 // to cancel the download operation in chrome and let the host browser 258 // take care of it. 259 if (!originating_tab->CanDownload(request_id)) { 260 ScheduleNotification(callback, false); 261 return; 262 } 263 264 // If the tab requesting the download is a constrained popup that is not 265 // shown, treat the request as if it came from the parent. 266 TabContents* effective_tab = originating_tab; 267 if (effective_tab->delegate()) { 268 effective_tab = 269 effective_tab->delegate()->GetConstrainingContents(effective_tab); 270 } 271 272 TabDownloadState* state = GetDownloadState( 273 &effective_tab->controller(), &originating_tab->controller(), true); 274 switch (state->download_status()) { 275 case ALLOW_ALL_DOWNLOADS: 276 if (state->download_count() && !(state->download_count() % 277 DownloadRequestLimiter::kMaxDownloadsAtOnce)) 278 state->set_download_status(PROMPT_BEFORE_DOWNLOAD); 279 ScheduleNotification(callback, true); 280 state->increment_download_count(); 281 break; 282 283 case ALLOW_ONE_DOWNLOAD: 284 state->set_download_status(PROMPT_BEFORE_DOWNLOAD); 285 ScheduleNotification(callback, true); 286 break; 287 288 case DOWNLOADS_NOT_ALLOWED: 289 ScheduleNotification(callback, false); 290 break; 291 292 case PROMPT_BEFORE_DOWNLOAD: 293 state->PromptUserForDownload(effective_tab, callback); 294 state->increment_download_count(); 295 break; 296 297 default: 298 NOTREACHED(); 299 } 300 } 301 302 void DownloadRequestLimiter::ScheduleNotification(Callback* callback, 303 bool allow) { 304 BrowserThread::PostTask( 305 BrowserThread::IO, FROM_HERE, 306 NewRunnableMethod( 307 this, &DownloadRequestLimiter::NotifyCallback, callback, allow)); 308 } 309 310 void DownloadRequestLimiter::NotifyCallback(Callback* callback, bool allow) { 311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 312 if (allow) 313 callback->ContinueDownload(); 314 else 315 callback->CancelDownload(); 316 } 317 318 void DownloadRequestLimiter::Remove(TabDownloadState* state) { 319 DCHECK(ContainsKey(state_map_, state->controller())); 320 state_map_.erase(state->controller()); 321 delete state; 322 } 323 324 // static 325 DownloadRequestLimiter::TestingDelegate* DownloadRequestLimiter::delegate_ = 326 NULL; 327