diff options
author | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-17 18:07:02 +0000 |
---|---|---|
committer | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-17 18:07:02 +0000 |
commit | 4cdac10757c9a774c3defdaf705f0d8c99290d19 (patch) | |
tree | d175d1d492401a5b96f3880df45c603fe05aee8a /chrome/browser/download | |
parent | 09da32d543461c90fcf3490966e0c4a670b9a2a8 (diff) | |
download | chromium_src-4cdac10757c9a774c3defdaf705f0d8c99290d19.zip chromium_src-4cdac10757c9a774c3defdaf705f0d8c99290d19.tar.gz chromium_src-4cdac10757c9a774c3defdaf705f0d8c99290d19.tar.bz2 |
Adds the anti-carpet bombing dialog. More specifically a new
EventHandler now exists between the buffered event handler and
download event handler. This new event handler asks the
DownloadRequestManager whether the download is allowed. This may
prompt the user and then the download continues or is canceled. The
DownloadRequestManager receives the request on the IO thread, forwards
to the UI thread, makes the decision, then notifies back on the IO
thread.
BUG=3422
TEST=make sure you don't see any problems downloading content.
Review URL: http://codereview.chromium.org/7479
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3543 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/download')
-rw-r--r-- | chrome/browser/download/download_request_manager.cc | 479 | ||||
-rw-r--r-- | chrome/browser/download/download_request_manager.h | 144 | ||||
-rw-r--r-- | chrome/browser/download/download_request_manager_unittest.cc | 189 |
3 files changed, 812 insertions, 0 deletions
diff --git a/chrome/browser/download/download_request_manager.cc b/chrome/browser/download/download_request_manager.cc new file mode 100644 index 0000000..57d3b43 --- /dev/null +++ b/chrome/browser/download/download_request_manager.cc @@ -0,0 +1,479 @@ +// Copyright (c) 2006-2008 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 "chrome/browser/download/download_request_manager.h" + +#include "base/message_loop.h" +#include "base/thread.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/constrained_window.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/tab_contents_delegate.h" +#include "chrome/browser/tab_util.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/message_box_view.h" + +#include "generated_resources.h" + +namespace { + +// DialogDelegateImpl ---------------------------------------------------------- + +// DialogDelegateImpl is the DialogDelegate implementation used to prompt the +// the user as to whether they want to allow multiple downloads. +// DialogDelegateImpl delegates the allow/cancel methods to the +// TabDownloadState. +// +// TabDownloadState does not directly implement DialogDelegate, rather it is +// split into DialogDelegateImpl as TabDownloadState may be deleted before +// the dialog. + +class DialogDelegateImpl : public views::DialogDelegate { + public: + DialogDelegateImpl(TabContents* tab, + DownloadRequestManager::TabDownloadState* host); + + void set_host(DownloadRequestManager::TabDownloadState* host) { + host_ = host; + } + + // Closes the prompt. + void CloseWindow(); + + private: + // DialogDelegate methods; + virtual bool Cancel(); + virtual bool Accept(); + virtual views::View* GetContentsView() { return message_view_; } + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual int GetDefaultDialogButton() const { + return DIALOGBUTTON_CANCEL; + } + virtual void WindowClosing(); + + // The TabDownloadState we're displaying the dialog for. May be null. + DownloadRequestManager::TabDownloadState* host_; + + MessageBoxView* message_view_; + + ConstrainedWindow* window_; + + DISALLOW_COPY_AND_ASSIGN(DialogDelegateImpl); +}; + +} // namespace + +// TabDownloadState ------------------------------------------------------------ + +// TabDownloadState maintains the download state for a particular tab. +// TabDownloadState installs observers to update the download status +// appropriately. Additionally TabDownloadState prompts the user as necessary. +// TabDownloadState deletes itself (by invoking DownloadRequestManager::Remove) +// as necessary. + +class DownloadRequestManager::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(DownloadRequestManager* host, + NavigationController* controller, + NavigationController* originating_controller); + ~TabDownloadState(); + + // Status of the download. + void set_download_status(DownloadRequestManager::DownloadStatus status) { + status_ = status; + } + DownloadRequestManager::DownloadStatus download_status() const { + return status_; + } + + // Invoked when a user gesture occurs (mouse click, enter or space). This + // may result in invoking Remove on DownloadRequestManager. + 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, + DownloadRequestManager::Callback* callback); + + // Are we showing a prompt to the user? + bool is_showing_prompt() const { return (dialog_delegate_ != NULL); } + + // NavigationController we're tracking. + NavigationController* controller() const { return controller_; } + + // Invoked from DialogDelegateImpl. Notifies the delegates and changes the + // status appropriately. + void Cancel(); + void Accept(); + + private: + // NotificationObserver method. + 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); + + DownloadRequestManager* host_; + + NavigationController* controller_; + + // Host of the first page the download started on. This may be empty. + std::string initial_page_host_; + + DownloadRequestManager::DownloadStatus status_; + + // 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<DownloadRequestManager::Callback*> callbacks_; + + // Used to remove observers installed on NavigationController. + NotificationRegistrar registrar_; + + // Handles showing the dialog to the user, may be null. + DialogDelegateImpl* dialog_delegate_; + + DISALLOW_COPY_AND_ASSIGN(TabDownloadState); +}; + +DownloadRequestManager::TabDownloadState::TabDownloadState( + DownloadRequestManager* host, + NavigationController* controller, + NavigationController* originating_controller) + : host_(host), + controller_(controller), + status_(DownloadRequestManager::ALLOW_ONE_DOWNLOAD), + dialog_delegate_(NULL) { + Source<NavigationController> notification_source(controller); + registrar_.Add(this, NOTIFY_NAV_ENTRY_COMMITTED, notification_source); + registrar_.Add(this, NOTIFY_TAB_CLOSED, notification_source); + + NavigationEntry* active_entry = originating_controller ? + originating_controller->GetActiveEntry() : controller->GetActiveEntry(); + if (active_entry) + initial_page_host_ = active_entry->url().host(); +} + +DownloadRequestManager::TabDownloadState::~TabDownloadState() { + // We should only be destroyed after the callbacks have been notified. + DCHECK(callbacks_.empty()); + + // And we should have closed the message box. + DCHECK(!dialog_delegate_); +} + +void DownloadRequestManager::TabDownloadState::OnUserGesture() { + if (is_showing_prompt()) { + // Don't change the state if the user clicks on the page some where. + return; + } + + if (status_ != DownloadRequestManager::ALLOW_ALL_DOWNLOADS && + status_ != DownloadRequestManager::DOWNLOADS_NOT_ALLOWED) { + // Revert to default status. + host_->Remove(this); + // WARNING: We've been deleted. + return; + } +} + +void DownloadRequestManager::TabDownloadState::PromptUserForDownload( + TabContents* tab, + DownloadRequestManager::Callback* callback) { + callbacks_.push_back(callback); + + if (is_showing_prompt()) + return; // Already showing prompt. + + if (DownloadRequestManager::delegate_) + NotifyCallbacks(DownloadRequestManager::delegate_->ShouldAllowDownload()); + else + dialog_delegate_ = new DialogDelegateImpl(tab, this); +} + +void DownloadRequestManager::TabDownloadState::Cancel() { + NotifyCallbacks(false); +} + +void DownloadRequestManager::TabDownloadState::Accept() { + NotifyCallbacks(true); +} + +void DownloadRequestManager::TabDownloadState::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if ((type != NOTIFY_NAV_ENTRY_COMMITTED && type != NOTIFY_TAB_CLOSED) || + Source<NavigationController>(source).ptr() != controller_) { + NOTREACHED(); + return; + } + + switch(type) { + case NOTIFY_NAV_ENTRY_COMMITTED: { + if (is_showing_prompt()) { + // We're prompting the user and they navigated away. Close the popup and + // cancel the downloads. + dialog_delegate_->CloseWindow(); + // After switch we'll notify callbacks and get deleted. + } else if (status_ == DownloadRequestManager::ALLOW_ALL_DOWNLOADS || + status_ == DownloadRequestManager::DOWNLOADS_NOT_ALLOWED) { + // User has either allowed all downloads or canceled all downloads. Only + // reset the download state if the user is navigating to a different + // host (or host is empty). + NavigationController::LoadCommittedDetails* load_details = + Details<NavigationController::LoadCommittedDetails>(details).ptr(); + NavigationEntry* entry = load_details->entry; + if (load_details->is_auto || !entry || + (!initial_page_host_.empty() && + !entry->url().host().empty() && + entry->url().host() == initial_page_host_)) { + return; + } + } // else case: we're not prompting user and user hasn't allowed or + // disallowed downloads, break so that we get deleted after switch. + break; + } + + case NOTIFY_TAB_CLOSED: + // Tab closed, no need to handle closing the dialog as it's owned by the + // TabContents, break so that we get deleted after switch. + break; + + default: + NOTREACHED(); + } + + NotifyCallbacks(false); + host_->Remove(this); +} + +void DownloadRequestManager::TabDownloadState::NotifyCallbacks(bool allow) { + if (dialog_delegate_) { + // Reset the delegate so we don't get notified again. + dialog_delegate_->set_host(NULL); + dialog_delegate_ = NULL; + } + status_ = allow ? + DownloadRequestManager::ALLOW_ALL_DOWNLOADS : + DownloadRequestManager::DOWNLOADS_NOT_ALLOWED; + std::vector<DownloadRequestManager::Callback*> callbacks; + callbacks.swap(callbacks_); + for (size_t i = 0; i < callbacks.size(); ++i) + host_->ScheduleNotification(callbacks[i], allow); +} + +namespace { + +// DialogDelegateImpl ---------------------------------------------------------- + +DialogDelegateImpl::DialogDelegateImpl( + TabContents* tab, + DownloadRequestManager::TabDownloadState* host) + : host_(host) { + message_view_ = new MessageBoxView( + MessageBoxView::kIsConfirmMessageBox, + l10n_util::GetString(IDS_MULTI_DOWNLOAD_WARNING), + std::wstring()); + window_ = tab->CreateConstrainedDialog(this, message_view_); +} + +void DialogDelegateImpl::CloseWindow() { + window_->CloseConstrainedWindow(); +} + +bool DialogDelegateImpl::Cancel() { + if (host_) + host_->Cancel(); + return true; +} + +bool DialogDelegateImpl::Accept() { + if (host_) + host_->Accept(); + return true; +} + +std::wstring DialogDelegateImpl::GetDialogButtonLabel( + DialogButton button) const { + if (button == DIALOGBUTTON_OK) + return l10n_util::GetString(IDS_MULTI_DOWNLOAD_WARNING_ALLOW); + if (button == DIALOGBUTTON_CANCEL) + return l10n_util::GetString(IDS_MULTI_DOWNLOAD_WARNING_DENY); + return std::wstring(); +} + +void DialogDelegateImpl::WindowClosing() { + DCHECK(!host_); + delete this; +} + +} // namespace + +// DownloadRequestManager ------------------------------------------------------ + +DownloadRequestManager::DownloadRequestManager(MessageLoop* io_loop, + MessageLoop* ui_loop) + : io_loop_(io_loop), + ui_loop_(ui_loop) { +} + +DownloadRequestManager::~DownloadRequestManager() { + // All the tabs should have closed before us, which sends notification and + // removes from state_map_. As such, there should be no pending callbacks. + DCHECK(state_map_.empty()); +} + +DownloadRequestManager::DownloadStatus + DownloadRequestManager::GetDownloadStatus(TabContents* tab) { + TabDownloadState* state = GetDownloadState(tab->controller(), NULL, false); + return state ? state->download_status() : ALLOW_ONE_DOWNLOAD; +} + +void DownloadRequestManager::CanDownloadOnIOThread(int render_process_host_id, + int render_view_id, + Callback* callback) { + // This is invoked on the IO thread. Schedule the task to run on the UI + // thread so that we can query UI state. + DCHECK(!io_loop_ || io_loop_ == MessageLoop::current()); + ui_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &DownloadRequestManager::CanDownload, + render_process_host_id, render_view_id, callback)); +} + +void DownloadRequestManager::OnUserGesture(TabContents* tab) { + NavigationController* controller = tab->controller(); + if (!controller) { + NOTREACHED(); + return; + } + + TabDownloadState* state = GetDownloadState(controller, NULL, false); + if (!state) + return; + + state->OnUserGesture(); +} + +// static +void DownloadRequestManager::SetTestingDelegate(TestingDelegate* delegate) { + delegate_ = delegate; +} + +DownloadRequestManager::TabDownloadState* DownloadRequestManager:: + GetDownloadState(NavigationController* controller, + NavigationController* originating_controller, + bool create) { + DCHECK(controller); + StateMap::iterator i = state_map_.find(controller); + if (i != state_map_.end()) + return i->second; + + if (!create) + return NULL; + + TabDownloadState* state = + new TabDownloadState(this, controller, originating_controller); + state_map_[controller] = state; + return state; +} + +void DownloadRequestManager::CanDownload(int render_process_host_id, + int render_view_id, + Callback* callback) { + DCHECK(!ui_loop_ || MessageLoop::current() == ui_loop_); + + TabContents* originating_tab = + tab_util::GetTabContentsByID(render_process_host_id, render_view_id); + if (!originating_tab) { + // The tab was closed, don't allow the download. + ScheduleNotification(callback, false); + return; + } + CanDownloadImpl(originating_tab, callback); +} + +void DownloadRequestManager::CanDownloadImpl( + TabContents* originating_tab, + Callback* callback) { + TabContents* effective_tab = originating_tab; + if (effective_tab->delegate() && + effective_tab->delegate()->GetConstrainingContents(effective_tab)) { + // The tab requesting the download is a constrained popup that is not + // shown, treat the request as if it came from the parent. + effective_tab = + effective_tab->delegate()->GetConstrainingContents(effective_tab); + } + + NavigationController* controller = effective_tab->controller(); + DCHECK(controller); + TabDownloadState* state = GetDownloadState( + controller, originating_tab->controller(), true); + switch (state->download_status()) { + case ALLOW_ALL_DOWNLOADS: + ScheduleNotification(callback, true); + break; + + case ALLOW_ONE_DOWNLOAD: + state->set_download_status(PROMPT_BEFORE_DOWNLOAD); + ScheduleNotification(callback, true); + break; + + case DOWNLOADS_NOT_ALLOWED: + ScheduleNotification(callback, false); + break; + + case PROMPT_BEFORE_DOWNLOAD: + state->PromptUserForDownload(effective_tab, callback); + break; + + default: + NOTREACHED(); + } +} + +void DownloadRequestManager::ScheduleNotification(Callback* callback, + bool allow) { + if (io_loop_) { + io_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &DownloadRequestManager::NotifyCallback, + callback, allow)); + } else { + NotifyCallback(callback, allow); + } +} + +void DownloadRequestManager::NotifyCallback(Callback* callback, bool allow) { + // We better be on the IO thread now. + DCHECK(!io_loop_ || MessageLoop::current() == io_loop_); + if (allow) + callback->ContinueDownload(); + else + callback->CancelDownload(); +} + +void DownloadRequestManager::Remove(TabDownloadState* state) { + DCHECK(state_map_.find(state->controller()) != state_map_.end()); + state_map_.erase(state->controller()); + delete state; +} + +// static +DownloadRequestManager::TestingDelegate* DownloadRequestManager::delegate_ = + NULL; diff --git a/chrome/browser/download/download_request_manager.h b/chrome/browser/download/download_request_manager.h new file mode 100644 index 0000000..90180f8 --- /dev/null +++ b/chrome/browser/download/download_request_manager.h @@ -0,0 +1,144 @@ +// Copyright (c) 2006-2008 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_MANAGER_H_ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_MANAGER_H_ + +#include <map> +#include <vector> + +#include "base/ref_counted.h" + +class MessageLoop; +class NavigationController; +class TabContents; + +// DownloadRequestManager 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. DownloadRequestManager 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 DownloadRequestManager : + public base::RefCountedThreadSafe<DownloadRequestManager> { + public: + class TabDownloadState; + + // 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 + }; + + DownloadRequestManager(MessageLoop* io_loop, MessageLoop* ui_loop); + ~DownloadRequestManager(); + + // The callback from CanDownloadOnIOThread. This is invoked on the io thread. + class Callback { + public: + virtual void ContinueDownload() = 0; + virtual void CancelDownload() = 0; + }; + + // 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. + // + // DownloadRequestManager 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, + 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 DownloadRequestManagerTest; + friend class TabDownloadState; + + // For unit tests. If non-null this is used instead of creating a dialog. + class TestingDelegate { + public: + virtual bool ShouldAllowDownload() = 0; + }; + 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 DownloadRequestManager 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, + Callback* callback); + + // Does the work of updating the download status on the UI thread and + // potentially prompting the user. + void CanDownloadImpl(TabContents* originating_tab, + 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); + + // Two threads we use. NULL during testing, in which case messages are + // dispatched immediately. + MessageLoop* io_loop_; + MessageLoop* ui_loop_; + + // 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(DownloadRequestManager); +}; + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_MANAGER_H_ diff --git a/chrome/browser/download/download_request_manager_unittest.cc b/chrome/browser/download/download_request_manager_unittest.cc new file mode 100644 index 0000000..9293426 --- /dev/null +++ b/chrome/browser/download/download_request_manager_unittest.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2006-2008 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 "chrome/browser/download/download_request_manager.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/test/test_tab_contents.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +class DownloadRequestManagerTest : public testing::Test, + public DownloadRequestManager::Callback, + public DownloadRequestManager::TestingDelegate { + public: + virtual void SetUp() { + allow_download_ = true; + ask_allow_count_ = cancel_count_ = continue_count_ = 0; + factory_.reset(TestTabContentsFactory::CreateAndRegisterFactory()); + TestTabContents* contents = factory_->CreateInstanceImpl(); + contents->set_commit_on_navigate(true); + controller_ = new NavigationController(contents, &profile_); + download_request_manager_ = new DownloadRequestManager(NULL, NULL); + DownloadRequestManager::SetTestingDelegate(this); + } + + virtual void TearDown() { + controller_->Destroy(); + DownloadRequestManager::SetTestingDelegate(NULL); + } + + virtual void ContinueDownload() { + continue_count_++; + } + virtual void CancelDownload() { + cancel_count_++; + } + + void CanDownload() { + download_request_manager_->CanDownloadImpl( + controller_->active_contents(), this); + } + + virtual bool ShouldAllowDownload() { + ask_allow_count_++; + return allow_download_; + } + + protected: + TestingProfile profile_; + scoped_ptr<TestTabContentsFactory> factory_; + NavigationController* controller_; + scoped_refptr<DownloadRequestManager> download_request_manager_; + + // Number of times ContinueDownload was invoked. + int continue_count_; + + // Number of times CancelDownload was invoked. + int cancel_count_; + + // Whether the download should be allowed. + bool allow_download_; + + // Number of times ShouldAllowDownload was invoked. + int ask_allow_count_; +}; + +TEST_F(DownloadRequestManagerTest, Allow) { + // All tabs should initially start at ALLOW_ONE_DOWNLOAD. + ASSERT_EQ(DownloadRequestManager::ALLOW_ONE_DOWNLOAD, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + + // Ask if the tab can do a download. This moves to PROMPT_BEFORE_DOWNLOAD. + CanDownload(); + ASSERT_EQ(DownloadRequestManager::PROMPT_BEFORE_DOWNLOAD, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + // We should have been told we can download. + ASSERT_EQ(1, continue_count_); + ASSERT_EQ(0, cancel_count_); + ASSERT_EQ(0, ask_allow_count_); + continue_count_ = 0; + + // Ask again. This triggers asking the delegate for allow/disallow. + allow_download_ = true; + CanDownload(); + // This should ask us if the download is allowed. + ASSERT_EQ(1, ask_allow_count_); + ask_allow_count_ = 0; + ASSERT_EQ(DownloadRequestManager::ALLOW_ALL_DOWNLOADS, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + // We should have been told we can download. + ASSERT_EQ(1, continue_count_); + ASSERT_EQ(0, cancel_count_); + continue_count_ = 0; + + // Ask again and make sure continue is invoked. + CanDownload(); + // The state is at allow_all, which means the delegate shouldn't be asked. + ASSERT_EQ(0, ask_allow_count_); + ASSERT_EQ(DownloadRequestManager::ALLOW_ALL_DOWNLOADS, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + // We should have been told we can download. + ASSERT_EQ(1, continue_count_); + ASSERT_EQ(0, cancel_count_); + continue_count_ = 0; +} + +TEST_F(DownloadRequestManagerTest, ResetOnNavigation) { + controller_->LoadURL(GURL(factory_->scheme() + "://foo.com/bar"), 0); + + // Do two downloads, allowing the second so that we end up with allow all. + CanDownload(); + allow_download_ = true; + CanDownload(); + ask_allow_count_ = continue_count_ = cancel_count_ = 0; + ASSERT_EQ(DownloadRequestManager::ALLOW_ALL_DOWNLOADS, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + + // Navigate to a new URL with the same host, which shouldn't reset the allow + // all state. + controller_->LoadURL(GURL(factory_->scheme() + "://foo.com/bar2"), 0); + CanDownload(); + ASSERT_EQ(1, continue_count_); + ASSERT_EQ(0, cancel_count_); + ASSERT_EQ(0, ask_allow_count_); + ask_allow_count_ = continue_count_ = cancel_count_ = 0; + ASSERT_EQ(DownloadRequestManager::ALLOW_ALL_DOWNLOADS, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + + // Do a user gesture, because we're at allow all, this shouldn't change the + // state. + download_request_manager_->OnUserGesture(controller_->active_contents()); + ASSERT_EQ(DownloadRequestManager::ALLOW_ALL_DOWNLOADS, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + + // Navigate to a completely different host, which should reset the state. + controller_->LoadURL(GURL(factory_->scheme() + "://fooey.com"), 0); + ASSERT_EQ(DownloadRequestManager::ALLOW_ONE_DOWNLOAD, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); +} + +TEST_F(DownloadRequestManagerTest, ResetOnUserGesture) { + controller_->LoadURL(GURL(factory_->scheme() + "://foo.com/bar"), 0); + + // Do one download, which should change to prompt before download. + CanDownload(); + ask_allow_count_ = continue_count_ = cancel_count_ = 0; + ASSERT_EQ(DownloadRequestManager::PROMPT_BEFORE_DOWNLOAD, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + + // Do a user gesture, which should reset back to allow one. + download_request_manager_->OnUserGesture(controller_->active_contents()); + ASSERT_EQ(DownloadRequestManager::ALLOW_ONE_DOWNLOAD, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + + // Ask twice, which triggers calling the delegate. Don't allow the download + // so that we end up with not allowed. + allow_download_ = false; + CanDownload(); + CanDownload(); + ASSERT_EQ(DownloadRequestManager::DOWNLOADS_NOT_ALLOWED, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + + // A user gesture now should NOT change the state. + download_request_manager_->OnUserGesture(controller_->active_contents()); + ASSERT_EQ(DownloadRequestManager::DOWNLOADS_NOT_ALLOWED, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); + // And make sure we really can't download. + ask_allow_count_ = continue_count_ = cancel_count_ = 0; + CanDownload(); + ASSERT_EQ(0, ask_allow_count_); + ASSERT_EQ(0, continue_count_); + ASSERT_EQ(1, cancel_count_); + // And the state shouldn't have changed. + ASSERT_EQ(DownloadRequestManager::DOWNLOADS_NOT_ALLOWED, + download_request_manager_->GetDownloadStatus( + controller_->active_contents())); +} |