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
TabDownloadState(DownloadRequestLimiter * host,NavigationController * controller,NavigationController * originating_controller)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
~TabDownloadState()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
OnUserGesture()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
PromptUserForDownload(TabContents * tab,DownloadRequestLimiter::Callback * callback)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
Cancel()78 void DownloadRequestLimiter::TabDownloadState::Cancel() {
79 NotifyCallbacks(false);
80 }
81
Accept()82 void DownloadRequestLimiter::TabDownloadState::Accept() {
83 NotifyCallbacks(true);
84 }
85
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
NotifyCallbacks(bool allow)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
DownloadRequestLimiter()176 DownloadRequestLimiter::DownloadRequestLimiter() {
177 }
178
~DownloadRequestLimiter()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
GetDownloadStatus(TabContents * tab)186 DownloadRequestLimiter::GetDownloadStatus(TabContents* tab) {
187 TabDownloadState* state = GetDownloadState(&tab->controller(), NULL, false);
188 return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
189 }
190
CanDownloadOnIOThread(int render_process_host_id,int render_view_id,int request_id,Callback * callback)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
OnUserGesture(TabContents * tab)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
SetTestingDelegate(TestingDelegate * delegate)214 void DownloadRequestLimiter::SetTestingDelegate(TestingDelegate* delegate) {
215 delegate_ = delegate;
216 }
217
218 DownloadRequestLimiter::TabDownloadState* DownloadRequestLimiter::
GetDownloadState(NavigationController * controller,NavigationController * originating_controller,bool create)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
CanDownload(int render_process_host_id,int render_view_id,int request_id,Callback * callback)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
CanDownloadImpl(TabContents * originating_tab,int request_id,Callback * callback)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
ScheduleNotification(Callback * callback,bool allow)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
NotifyCallback(Callback * callback,bool allow)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
Remove(TabDownloadState * state)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