• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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