blob: 3a1eb2cd299ecb679d34896cfb2f118e6d4e02ec [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "chrome/common/content_settings.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/web_contents_observer.h"
class HostContentSettingsMap;
class DownloadRequestInfoBarDelegate;
namespace content {
class NavigationController;
class WebContents;
// DownloadRequestLimiter is responsible for determining whether a download
// should be allowed or not. It is designed to keep pages from downloading
// multiple files without user interaction. DownloadRequestLimiter is invoked
// from ResourceDispatcherHost any time a download begins
// (CanDownloadOnIOThread). The request is processed on the UI thread, and the
// request is notified (back on the IO thread) as to whether the download should
// be allowed or denied.
// Invoking CanDownloadOnIOThread notifies the callback and may update the
// download status. The following details the various states:
// . Each NavigationController initially starts out allowing a download
// . The first time CanDownloadOnIOThread is invoked the download is allowed and
// the state changes to PROMPT_BEFORE_DOWNLOAD.
// . If the state is PROMPT_BEFORE_DOWNLOAD and the user clicks the mouse,
// presses enter, the space bar or navigates to another page the state is
// . If a download is attempted and the state is PROMPT_BEFORE_DOWNLOAD the user
// is prompted as to whether the download is allowed or disallowed. The users
// choice stays until the user navigates to a different host. For example, if
// the user allowed the download, multiple downloads are allowed without any
// user intervention until the user navigates to a different host.
class DownloadRequestLimiter
: public base::RefCountedThreadSafe<DownloadRequestLimiter> {
// Download status for a particular page. See class description for details.
enum DownloadStatus {
// Max number of downloads before a "Prompt Before Download" Dialog is shown.
static const size_t kMaxDownloadsAtOnce = 50;
// The callback from CanDownloadOnIOThread. This is invoked on the io thread.
// The boolean parameter indicates whether or not the download is allowed.
typedef base::Callback<void(bool /*allow*/)> Callback;
// TabDownloadState maintains the download state for a particular tab.
// TabDownloadState prompts the user with an infobar as necessary.
// TabDownloadState deletes itself (by invoking
// DownloadRequestLimiter::Remove) as necessary.
// TODO(gbillock): just make this class implement PermissionBubbleRequest.
class TabDownloadState : public content::NotificationObserver,
public content::WebContentsObserver {
// Creates a new TabDownloadState. |controller| is the controller the
// TabDownloadState tracks the state of and is the host for any dialogs that
// are displayed. |originating_controller| is used to determine the host of
// the initial download. If |originating_controller| is null, |controller|
// is used. |originating_controller| is typically null, but differs from
// |controller| in the case of a constrained popup requesting the download.
TabDownloadState(DownloadRequestLimiter* host,
content::WebContents* web_contents,
content::WebContents* originating_web_contents);
virtual ~TabDownloadState();
// Status of the download.
void set_download_status(DownloadRequestLimiter::DownloadStatus status) {
status_ = status;
DownloadRequestLimiter::DownloadStatus download_status() const {
return status_;
// Number of "ALLOWED" downloads.
void increment_download_count() {
size_t download_count() const {
return download_count_;
// Promote protected accessor to public.
content::WebContents* web_contents() const {
return content::WebContentsObserver::web_contents();
// content::WebContentsObserver overrides.
virtual void AboutToNavigateRenderView(
content::RenderViewHost* render_view_host) OVERRIDE;
// Invoked when a user gesture occurs (mouse click, enter or space). This
// may result in invoking Remove on DownloadRequestLimiter.
virtual void DidGetUserGesture() OVERRIDE;
virtual void WebContentsDestroyed() OVERRIDE;
// Asks the user if they really want to allow the download.
// See description above CanDownloadOnIOThread for details on lifetime of
// callback.
void PromptUserForDownload(
const DownloadRequestLimiter::Callback& callback);
// Invoked from DownloadRequestDialogDelegate. Notifies the delegates and
// changes the status appropriately. Virtual for testing.
virtual void Cancel();
virtual void CancelOnce();
virtual void Accept();
// Used for testing.
// Are we showing a prompt to the user? Determined by whether
// we have an outstanding weak pointer--weak pointers are only
// given to the info bar delegate or permission bubble request.
bool is_showing_prompt() const;
// content::NotificationObserver method.
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
// Remember to either block or allow automatic downloads from this origin.
void SetContentSetting(ContentSetting setting);
// Notifies the callbacks as to whether the download is allowed or not.
// Updates status_ appropriately.
void NotifyCallbacks(bool allow);
content::WebContents* web_contents_;
DownloadRequestLimiter* host_;
// Host of the first page the download started on. This may be empty.
std::string initial_page_host_;
DownloadRequestLimiter::DownloadStatus status_;
size_t download_count_;
// Callbacks we need to notify. This is only non-empty if we're showing a
// dialog.
// See description above CanDownloadOnIOThread for details on lifetime of
// callbacks.
std::vector<DownloadRequestLimiter::Callback> callbacks_;
// Used to remove observers installed on NavigationController.
content::NotificationRegistrar registrar_;
// Weak pointer factory for generating a weak pointer to pass to the
// infobar. User responses to the throttling prompt will be returned
// through this channel, and it can be revoked if the user prompt result
// becomes moot.
base::WeakPtrFactory<DownloadRequestLimiter::TabDownloadState> factory_;
static void SetContentSettingsForTesting(HostContentSettingsMap* settings);
// Returns the download status for a page. This does not change the state in
// anyway.
DownloadStatus GetDownloadStatus(content::WebContents* tab);
// Updates the state of the page as necessary and notifies the callback.
// WARNING: both this call and the callback are invoked on the io thread.
void CanDownloadOnIOThread(int render_process_host_id,
int render_view_id,
const GURL& url,
const std::string& request_method,
const Callback& callback);
FRIEND_TEST_ALL_PREFIXES(DownloadTest, DownloadResourceThrottleCancels);
friend class base::RefCountedThreadSafe<DownloadRequestLimiter>;
friend class DownloadRequestLimiterTest;
friend class TabDownloadState;
// Gets the download state for the specified controller. If the
// TabDownloadState does not exist and |create| is true, one is created.
// See TabDownloadState's constructor description for details on the two
// controllers.
// The returned TabDownloadState is owned by the DownloadRequestLimiter and
// deleted when no longer needed (the Remove method is invoked).
TabDownloadState* GetDownloadState(
content::WebContents* web_contents,
content::WebContents* originating_web_contents,
bool create);
// CanDownloadOnIOThread invokes this on the UI thread. This determines the
// tab and invokes CanDownloadImpl.
void CanDownload(int render_process_host_id,
int render_view_id,
const GURL& url,
const std::string& request_method,
const Callback& callback);
// Does the work of updating the download status on the UI thread and
// potentially prompting the user.
void CanDownloadImpl(content::WebContents* originating_contents,
const std::string& request_method,
const Callback& callback);
// Invoked when decision to download has been made.
void OnCanDownloadDecided(int render_process_host_id,
int render_view_id,
const std::string& request_method,
const Callback& orig_callback,
bool allow);
// Invoked on the UI thread. Schedules a call to NotifyCallback on the io
// thread.
void ScheduleNotification(const Callback& callback, bool allow);
// Removes the specified TabDownloadState from the internal map and deletes
// it. This has the effect of resetting the status for the tab to
void Remove(TabDownloadState* state, content::WebContents* contents);
static HostContentSettingsMap* content_settings_;
static HostContentSettingsMap* GetContentSettings(
content::WebContents* contents);
// Maps from tab to download state. The download state for a tab only exists
// if the state is other than ALLOW_ONE_DOWNLOAD. Similarly once the state
// transitions from anything but ALLOW_ONE_DOWNLOAD back to ALLOW_ONE_DOWNLOAD
// the TabDownloadState is removed and deleted (by way of Remove).
typedef std::map<content::WebContents*, TabDownloadState*> StateMap;
StateMap state_map_;
// Weak ptr factory used when |CanDownload| asks the delegate asynchronously
// about the download.
base::WeakPtrFactory<DownloadRequestLimiter> factory_;