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/download_request_manager.cc | |
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/download_request_manager.cc')
-rw-r--r-- | chrome/browser/download/download_request_manager.cc | 479 |
1 files changed, 479 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; |