| // Copyright (c) 2011 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_ |
| #pragma once |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include "base/memory/ref_counted.h" |
| #include "content/common/notification_observer.h" |
| #include "content/common/notification_registrar.h" |
| |
| class DownloadRequestInfoBarDelegate; |
| class NavigationController; |
| class TabContents; |
| |
| // 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<DownloadRequestLimiter> { |
| 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. |
| class Callback { |
| public: |
| virtual void ContinueDownload() = 0; |
| virtual void CancelDownload() = 0; |
| |
| protected: |
| virtual ~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 NotificationObserver { |
| 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, |
| NavigationController* controller, |
| NavigationController* originating_controller); |
| 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_; |
| } |
| |
| // Invoked when a user gesture occurs (mouse click, enter or space). This |
| // may result in invoking Remove on DownloadRequestLimiter. |
| void OnUserGesture(); |
| |
| // Asks the user if they really want to allow the download. |
| // See description above CanDownloadOnIOThread for details on lifetime of |
| // callback. |
| void PromptUserForDownload(TabContents* tab, |
| DownloadRequestLimiter::Callback* callback); |
| |
| // Are we showing a prompt to the user? |
| bool is_showing_prompt() const { return (infobar_ != NULL); } |
| |
| // NavigationController we're tracking. |
| NavigationController* controller() const { return controller_; } |
| |
| // 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() |
| : host_(NULL), |
| controller_(NULL), |
| status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD), |
| download_count_(0), |
| infobar_(NULL) { |
| } |
| |
| private: |
| // NotificationObserver method. |
| virtual void Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details); |
| |
| // Notifies the callbacks as to whether the download is allowed or not. |
| // Updates status_ appropriately. |
| void NotifyCallbacks(bool allow); |
| |
| DownloadRequestLimiter* host_; |
| |
| NavigationController* controller_; |
| |
| // 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. |
| NotificationRegistrar registrar_; |
| |
| // Handles showing the infobar to the user, may be null. |
| DownloadRequestInfoBarDelegate* infobar_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TabDownloadState); |
| }; |
| |
| DownloadRequestLimiter(); |
| |
| // Returns the download status for a page. This does not change the state in |
| // anyway. |
| DownloadStatus GetDownloadStatus(TabContents* 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. |
| // |
| // DownloadRequestLimiter does not retain/release the Callback. It is up to |
| // the caller to ensure the callback is valid until the request is complete. |
| void CanDownloadOnIOThread(int render_process_host_id, |
| int render_view_id, |
| int request_id, |
| Callback* callback); |
| |
| // Invoked when the user presses the mouse, enter key or space bar. This may |
| // change the download status for the page. See the class description for |
| // details. |
| void OnUserGesture(TabContents* tab); |
| |
| private: |
| friend class base::RefCountedThreadSafe<DownloadRequestLimiter>; |
| 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( |
| NavigationController* controller, |
| NavigationController* originating_controller, |
| 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, |
| Callback* callback); |
| |
| // Does the work of updating the download status on the UI thread and |
| // potentially prompting the user. |
| void CanDownloadImpl(TabContents* originating_tab, |
| int request_id, |
| Callback* callback); |
| |
| // Invoked on the UI thread. Schedules a call to NotifyCallback on the io |
| // thread. |
| void ScheduleNotification(Callback* callback, bool allow); |
| |
| // Notifies the callback. This *must* be invoked on the IO thread. |
| void NotifyCallback(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<NavigationController*, TabDownloadState*> StateMap; |
| StateMap state_map_; |
| |
| static TestingDelegate* delegate_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DownloadRequestLimiter); |
| }; |
| |
| #endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_LIMITER_H_ |