diff options
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; |