Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / download / download_request_limiter.cc
blobbe62ebf1ec948168361245f6110112d7e9a6b55e
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.
5 #include "chrome/browser/download/download_request_limiter.h"
7 #include "base/bind.h"
8 #include "base/stl_util.h"
9 #include "chrome/browser/content_settings/tab_specific_content_settings.h"
10 #include "chrome/browser/download/download_permission_request.h"
11 #include "chrome/browser/infobars/infobar_service.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/tab_contents/tab_util.h"
14 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
15 #include "components/content_settings/core/browser/host_content_settings_map.h"
16 #include "content/public/browser/browser_context.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/navigation_controller.h"
19 #include "content/public/browser/navigation_entry.h"
20 #include "content/public/browser/notification_source.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_process_host.h"
23 #include "content/public/browser/resource_dispatcher_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/browser/web_contents_delegate.h"
26 #include "url/gurl.h"
28 #if defined(OS_ANDROID)
29 #include "chrome/browser/download/download_request_infobar_delegate.h"
30 #endif
32 using content::BrowserThread;
33 using content::NavigationController;
34 using content::NavigationEntry;
36 // TabDownloadState ------------------------------------------------------------
38 DownloadRequestLimiter::TabDownloadState::TabDownloadState(
39 DownloadRequestLimiter* host,
40 content::WebContents* contents,
41 content::WebContents* originating_web_contents)
42 : content::WebContentsObserver(contents),
43 web_contents_(contents),
44 host_(host),
45 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
46 download_count_(0),
47 factory_(this) {
48 content::Source<NavigationController> notification_source(
49 &contents->GetController());
50 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
51 notification_source);
52 NavigationEntry* active_entry = originating_web_contents ?
53 originating_web_contents->GetController().GetActiveEntry() :
54 contents->GetController().GetActiveEntry();
55 if (active_entry)
56 initial_page_host_ = active_entry->GetURL().host();
59 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
60 // We should only be destroyed after the callbacks have been notified.
61 DCHECK(callbacks_.empty());
63 // And we should have invalidated the back pointer.
64 DCHECK(!factory_.HasWeakPtrs());
67 void DownloadRequestLimiter::TabDownloadState::DidNavigateMainFrame(
68 const content::LoadCommittedDetails& details,
69 const content::FrameNavigateParams& params) {
70 switch (status_) {
71 case ALLOW_ONE_DOWNLOAD:
72 case PROMPT_BEFORE_DOWNLOAD:
73 // When the user reloads the page without responding to the infobar, they
74 // are expecting DownloadRequestLimiter to behave as if they had just
75 // initially navigated to this page. See http://crbug.com/171372
76 NotifyCallbacks(false);
77 host_->Remove(this, web_contents());
78 // WARNING: We've been deleted.
79 break;
80 case DOWNLOADS_NOT_ALLOWED:
81 case ALLOW_ALL_DOWNLOADS:
82 // Don't drop this information. The user has explicitly said that they
83 // do/don't want downloads from this host. If they accidentally Accepted
84 // or Canceled, tough luck, they don't get another chance. They can copy
85 // the URL into a new tab, which will make a new DownloadRequestLimiter.
86 // See also the initial_page_host_ logic in Observe() for
87 // NOTIFICATION_NAV_ENTRY_PENDING.
88 break;
89 default:
90 NOTREACHED();
94 void DownloadRequestLimiter::TabDownloadState::DidGetUserGesture() {
95 if (is_showing_prompt()) {
96 // Don't change the state if the user clicks on the page somewhere.
97 return;
100 bool promptable = (InfoBarService::FromWebContents(web_contents()) != NULL);
101 if (PermissionBubbleManager::Enabled()) {
102 promptable =
103 (PermissionBubbleManager::FromWebContents(web_contents()) != NULL);
106 // See PromptUserForDownload(): if there's no InfoBarService, then
107 // DOWNLOADS_NOT_ALLOWED is functionally equivalent to PROMPT_BEFORE_DOWNLOAD.
108 if ((status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS) &&
109 (!promptable ||
110 (status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED))) {
111 // Revert to default status.
112 host_->Remove(this, web_contents());
113 // WARNING: We've been deleted.
117 void DownloadRequestLimiter::TabDownloadState::WebContentsDestroyed() {
118 // Tab closed, no need to handle closing the dialog as it's owned by the
119 // WebContents.
121 NotifyCallbacks(false);
122 host_->Remove(this, web_contents());
123 // WARNING: We've been deleted.
126 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
127 const DownloadRequestLimiter::Callback& callback) {
128 callbacks_.push_back(callback);
129 DCHECK(web_contents_);
130 if (is_showing_prompt())
131 return;
133 #if defined(OS_ANDROID)
134 DownloadRequestInfoBarDelegate::Create(
135 InfoBarService::FromWebContents(web_contents_), factory_.GetWeakPtr());
136 #else
137 PermissionBubbleManager* bubble_manager =
138 PermissionBubbleManager::FromWebContents(web_contents_);
139 if (bubble_manager) {
140 bubble_manager->AddRequest(new DownloadPermissionRequest(
141 factory_.GetWeakPtr()));
142 } else {
143 Cancel();
145 #endif
148 void DownloadRequestLimiter::TabDownloadState::SetContentSetting(
149 ContentSetting setting) {
150 if (!web_contents_)
151 return;
152 HostContentSettingsMap* settings =
153 DownloadRequestLimiter::GetContentSettings(web_contents_);
154 ContentSettingsPattern pattern(
155 ContentSettingsPattern::FromURL(web_contents_->GetURL()));
156 if (!settings || !pattern.IsValid())
157 return;
158 settings->SetContentSetting(
159 pattern,
160 ContentSettingsPattern::Wildcard(),
161 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
162 std::string(),
163 setting);
166 void DownloadRequestLimiter::TabDownloadState::Cancel() {
167 SetContentSetting(CONTENT_SETTING_BLOCK);
168 NotifyCallbacks(false);
171 void DownloadRequestLimiter::TabDownloadState::CancelOnce() {
172 NotifyCallbacks(false);
175 void DownloadRequestLimiter::TabDownloadState::Accept() {
176 SetContentSetting(CONTENT_SETTING_ALLOW);
177 NotifyCallbacks(true);
180 DownloadRequestLimiter::TabDownloadState::TabDownloadState()
181 : web_contents_(NULL),
182 host_(NULL),
183 status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
184 download_count_(0),
185 factory_(this) {
188 bool DownloadRequestLimiter::TabDownloadState::is_showing_prompt() const {
189 return factory_.HasWeakPtrs();
192 void DownloadRequestLimiter::TabDownloadState::Observe(
193 int type,
194 const content::NotificationSource& source,
195 const content::NotificationDetails& details) {
196 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_PENDING, type);
197 content::NavigationController* controller = &web_contents()->GetController();
198 DCHECK_EQ(controller, content::Source<NavigationController>(source).ptr());
200 // NOTE: Resetting state on a pending navigate isn't ideal. In particular it
201 // is possible that queued up downloads for the page before the pending
202 // navigation will be delivered to us after we process this request. If this
203 // happens we may let a download through that we shouldn't have. But this is
204 // rather rare, and it is difficult to get 100% right, so we don't deal with
205 // it.
206 NavigationEntry* entry = controller->GetPendingEntry();
207 if (!entry)
208 return;
210 // Redirects don't count.
211 if (ui::PageTransitionIsRedirect(entry->GetTransitionType()))
212 return;
214 if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS ||
215 status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
216 // User has either allowed all downloads or canceled all downloads. Only
217 // reset the download state if the user is navigating to a different host
218 // (or host is empty).
219 if (!initial_page_host_.empty() && !entry->GetURL().host().empty() &&
220 entry->GetURL().host() == initial_page_host_)
221 return;
224 NotifyCallbacks(false);
225 host_->Remove(this, web_contents());
228 void DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) {
229 set_download_status(allow ?
230 DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS :
231 DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED);
232 std::vector<DownloadRequestLimiter::Callback> callbacks;
233 bool change_status = false;
235 // Selectively send first few notifications only if number of downloads exceed
236 // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and
237 // don't close it. If allow is false, we send all the notifications to cancel
238 // all remaining downloads and close the infobar.
239 if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) {
240 // Null the generated weak pointer so we don't get notified again.
241 factory_.InvalidateWeakPtrs();
242 callbacks.swap(callbacks_);
243 } else {
244 std::vector<DownloadRequestLimiter::Callback>::iterator start, end;
245 start = callbacks_.begin();
246 end = callbacks_.begin() + kMaxDownloadsAtOnce;
247 callbacks.assign(start, end);
248 callbacks_.erase(start, end);
249 change_status = true;
252 for (const auto& callback : callbacks) {
253 // When callback runs, it can cause the WebContents to be destroyed.
254 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
255 base::Bind(callback, allow));
258 if (change_status)
259 set_download_status(DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD);
262 // DownloadRequestLimiter ------------------------------------------------------
264 HostContentSettingsMap* DownloadRequestLimiter::content_settings_ = NULL;
266 void DownloadRequestLimiter::SetContentSettingsForTesting(
267 HostContentSettingsMap* content_settings) {
268 content_settings_ = content_settings;
271 DownloadRequestLimiter::DownloadRequestLimiter()
272 : factory_(this) {
275 DownloadRequestLimiter::~DownloadRequestLimiter() {
276 // All the tabs should have closed before us, which sends notification and
277 // removes from state_map_. As such, there should be no pending callbacks.
278 DCHECK(state_map_.empty());
281 DownloadRequestLimiter::DownloadStatus
282 DownloadRequestLimiter::GetDownloadStatus(content::WebContents* web_contents) {
283 TabDownloadState* state = GetDownloadState(web_contents, NULL, false);
284 return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
287 DownloadRequestLimiter::TabDownloadState*
288 DownloadRequestLimiter::GetDownloadState(
289 content::WebContents* web_contents,
290 content::WebContents* originating_web_contents,
291 bool create) {
292 DCHECK(web_contents);
293 StateMap::iterator i = state_map_.find(web_contents);
294 if (i != state_map_.end())
295 return i->second;
297 if (!create)
298 return NULL;
300 TabDownloadState* state =
301 new TabDownloadState(this, web_contents, originating_web_contents);
302 state_map_[web_contents] = state;
303 return state;
306 void DownloadRequestLimiter::CanDownload(int render_process_host_id,
307 int render_view_id,
308 const GURL& url,
309 const std::string& request_method,
310 const Callback& callback) {
311 DCHECK_CURRENTLY_ON(BrowserThread::UI);
313 content::WebContents* originating_contents =
314 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
315 if (!originating_contents) {
316 // The WebContents was closed, don't allow the download.
317 callback.Run(false);
318 return;
321 if (!originating_contents->GetDelegate()) {
322 callback.Run(false);
323 return;
326 // Note that because |originating_contents| might go away before
327 // OnCanDownloadDecided is invoked, we look it up by |render_process_host_id|
328 // and |render_view_id|.
329 base::Callback<void(bool)> can_download_callback = base::Bind(
330 &DownloadRequestLimiter::OnCanDownloadDecided,
331 factory_.GetWeakPtr(),
332 render_process_host_id,
333 render_view_id,
334 request_method,
335 callback);
337 originating_contents->GetDelegate()->CanDownload(
338 url,
339 request_method,
340 can_download_callback);
343 void DownloadRequestLimiter::OnCanDownloadDecided(
344 int render_process_host_id,
345 int render_view_id,
346 const std::string& request_method,
347 const Callback& orig_callback, bool allow) {
348 DCHECK_CURRENTLY_ON(BrowserThread::UI);
349 content::WebContents* originating_contents =
350 tab_util::GetWebContentsByID(render_process_host_id, render_view_id);
351 if (!originating_contents || !allow) {
352 orig_callback.Run(false);
353 return;
356 CanDownloadImpl(originating_contents,
357 request_method,
358 orig_callback);
361 HostContentSettingsMap* DownloadRequestLimiter::GetContentSettings(
362 content::WebContents* contents) {
363 return content_settings_ ? content_settings_ : Profile::FromBrowserContext(
364 contents->GetBrowserContext())->GetHostContentSettingsMap();
367 void DownloadRequestLimiter::CanDownloadImpl(
368 content::WebContents* originating_contents,
369 const std::string& request_method,
370 const Callback& callback) {
371 DCHECK(originating_contents);
373 TabDownloadState* state = GetDownloadState(
374 originating_contents, originating_contents, true);
375 switch (state->download_status()) {
376 case ALLOW_ALL_DOWNLOADS:
377 if (state->download_count() && !(state->download_count() %
378 DownloadRequestLimiter::kMaxDownloadsAtOnce))
379 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
380 callback.Run(true);
381 state->increment_download_count();
382 break;
384 case ALLOW_ONE_DOWNLOAD:
385 state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
386 callback.Run(true);
387 state->increment_download_count();
388 break;
390 case DOWNLOADS_NOT_ALLOWED:
391 callback.Run(false);
392 break;
394 case PROMPT_BEFORE_DOWNLOAD: {
395 HostContentSettingsMap* content_settings = GetContentSettings(
396 originating_contents);
397 ContentSetting setting = CONTENT_SETTING_ASK;
398 if (content_settings)
399 setting = content_settings->GetContentSetting(
400 originating_contents->GetURL(),
401 originating_contents->GetURL(),
402 CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
403 std::string());
404 switch (setting) {
405 case CONTENT_SETTING_ALLOW: {
406 TabSpecificContentSettings* settings =
407 TabSpecificContentSettings::FromWebContents(
408 originating_contents);
409 if (settings)
410 settings->SetDownloadsBlocked(false);
411 callback.Run(true);
412 state->increment_download_count();
413 return;
415 case CONTENT_SETTING_BLOCK: {
416 TabSpecificContentSettings* settings =
417 TabSpecificContentSettings::FromWebContents(
418 originating_contents);
419 if (settings)
420 settings->SetDownloadsBlocked(true);
421 callback.Run(false);
422 return;
424 case CONTENT_SETTING_DEFAULT:
425 case CONTENT_SETTING_ASK:
426 case CONTENT_SETTING_SESSION_ONLY:
427 state->PromptUserForDownload(callback);
428 state->increment_download_count();
429 break;
430 case CONTENT_SETTING_NUM_SETTINGS:
431 default:
432 NOTREACHED();
433 return;
435 break;
438 default:
439 NOTREACHED();
443 void DownloadRequestLimiter::Remove(TabDownloadState* state,
444 content::WebContents* contents) {
445 DCHECK(ContainsKey(state_map_, contents));
446 state_map_.erase(contents);
447 delete state;