// 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_