diff options
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())); +} |