// 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. #ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_LIMITER_H_ #define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_LIMITER_H_ #include #include #include #include "base/callback.h" #include "base/gtest_prod_util.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/web_contents_observer.h" 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 // (ALLOW_ONE_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 // reset to ALLOW_ONE_DOWNLOAD. // . 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 { public: // Download status for a particular page. See class description for details. enum DownloadStatus { ALLOW_ONE_DOWNLOAD, PROMPT_BEFORE_DOWNLOAD, ALLOW_ALL_DOWNLOADS, DOWNLOADS_NOT_ALLOWED }; // 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 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. class TabDownloadState : public content::NotificationObserver, public content::WebContentsObserver { public: // 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() { download_count_++; } size_t download_count() const { return download_count_; } // Promote protected accessor to public. content::WebContents* web_contents() { return content::WebContentsObserver::web_contents(); } // content::WebContentsObserver overrides. // Invoked when a user gesture occurs (mouse click, enter or space). This // may result in invoking Remove on DownloadRequestLimiter. virtual void DidGetUserGesture() OVERRIDE; // Asks the user if they really want to allow the download. // See description above CanDownloadOnIOThread for details on lifetime of // callback. void PromptUserForDownload( content::WebContents* tab, const DownloadRequestLimiter::Callback& callback); // Invoked from DownloadRequestDialogDelegate. Notifies the delegates and // changes the status appropriately. Virtual for testing. virtual void Cancel(); virtual void Accept(); protected: // Used for testing. TabDownloadState(); private: // 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. bool is_showing_prompt() const { return factory_.HasWeakPtrs(); } // content::NotificationObserver method. virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE; // Notifies the callbacks as to whether the download is allowed or not. // Updates status_ appropriately. void NotifyCallbacks(bool allow); 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 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 factory_; DISALLOW_COPY_AND_ASSIGN(TabDownloadState); }; DownloadRequestLimiter(); // 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, int request_id, const std::string& request_method, const Callback& callback); private: FRIEND_TEST_ALL_PREFIXES(DownloadTest, DownloadResourceThrottleCancels); friend class base::RefCountedThreadSafe; friend class DownloadRequestLimiterTest; friend class TabDownloadState; ~DownloadRequestLimiter(); // For unit tests. If non-null this is used instead of creating a dialog. class TestingDelegate { public: virtual bool ShouldAllowDownload() = 0; protected: virtual ~TestingDelegate() {} }; static void SetTestingDelegate(TestingDelegate* delegate); // 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, int request_id, 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, int request_id, const std::string& request_method, const Callback& callback); // 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 // ALLOW_ONE_DOWNLOAD. void Remove(TabDownloadState* state); // 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 StateMap; StateMap state_map_; static TestingDelegate* delegate_; DISALLOW_COPY_AND_ASSIGN(DownloadRequestLimiter); }; #endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_LIMITER_H_