summaryrefslogtreecommitdiffstats
path: root/chrome/browser/download
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/download')
-rw-r--r--chrome/browser/download/download_request_manager.cc479
-rw-r--r--chrome/browser/download/download_request_manager.h144
-rw-r--r--chrome/browser/download/download_request_manager_unittest.cc189
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()));
+}