diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-15 17:09:01 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-15 17:09:01 +0000 |
commit | 4129132ccbbc05abb8bfd89b3d7d5316c4d7d136 (patch) | |
tree | b01682d5ad9a9e73c64217299c383f2d281a9864 /chrome/browser/download/download_request_limiter.cc | |
parent | 8bad4795cbf8afd699f6c3dc111c8dba11a280d3 (diff) | |
download | chromium_src-4129132ccbbc05abb8bfd89b3d7d5316c4d7d136.zip chromium_src-4129132ccbbc05abb8bfd89b3d7d5316c4d7d136.tar.gz chromium_src-4129132ccbbc05abb8bfd89b3d7d5316c4d7d136.tar.bz2 |
Rename DownloadRequestManager to DownloadRequestLimiter.
We already have too many classes named Manager in the download code.
This also contains some minor cleanup changes like comment updates.
TEST=unit_tests, browser_tests, ui_tests
BUG=48913
Review URL: http://codereview.chromium.org/3011001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52493 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/download/download_request_limiter.cc')
-rw-r--r-- | chrome/browser/download/download_request_limiter.cc | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/chrome/browser/download/download_request_limiter.cc b/chrome/browser/download/download_request_limiter.cc new file mode 100644 index 0000000..472a2c5 --- /dev/null +++ b/chrome/browser/download/download_request_limiter.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2010 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_limiter.h" + +#include "base/stl_util-inl.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/download/download_request_infobar_delegate.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" +#include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/notification_service.h" + +// TabDownloadState ------------------------------------------------------------ + +DownloadRequestLimiter::TabDownloadState::TabDownloadState( + DownloadRequestLimiter* host, + NavigationController* controller, + NavigationController* originating_controller) + : host_(host), + controller_(controller), + status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD), + download_count_(0), + infobar_(NULL) { + Source<NavigationController> notification_source(controller); + registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING, + notification_source); + registrar_.Add(this, NotificationType::TAB_CLOSED, notification_source); + + NavigationEntry* active_entry = originating_controller ? + originating_controller->GetActiveEntry() : controller->GetActiveEntry(); + if (active_entry) + initial_page_host_ = active_entry->url().host(); +} + +DownloadRequestLimiter::TabDownloadState::~TabDownloadState() { + // We should only be destroyed after the callbacks have been notified. + DCHECK(callbacks_.empty()); + + // And we should have closed the infobar. + DCHECK(!infobar_); +} + +void DownloadRequestLimiter::TabDownloadState::OnUserGesture() { + if (is_showing_prompt()) { + // Don't change the state if the user clicks on the page some where. + return; + } + + if (status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS && + status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) { + // Revert to default status. + host_->Remove(this); + // WARNING: We've been deleted. + return; + } +} + +void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload( + TabContents* tab, + DownloadRequestLimiter::Callback* callback) { + callbacks_.push_back(callback); + + if (is_showing_prompt()) + return; // Already showing prompt. + + if (DownloadRequestLimiter::delegate_) { + NotifyCallbacks(DownloadRequestLimiter::delegate_->ShouldAllowDownload()); + } else { + infobar_ = new DownloadRequestInfoBarDelegate(tab, this); + } +} + +void DownloadRequestLimiter::TabDownloadState::Cancel() { + NotifyCallbacks(false); +} + +void DownloadRequestLimiter::TabDownloadState::Accept() { + NotifyCallbacks(true); +} + +void DownloadRequestLimiter::TabDownloadState::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if ((type != NotificationType::NAV_ENTRY_PENDING && + type != NotificationType::TAB_CLOSED) || + Source<NavigationController>(source).ptr() != controller_) { + NOTREACHED(); + return; + } + + switch (type.value) { + case NotificationType::NAV_ENTRY_PENDING: { + // NOTE: resetting state on a pending navigate isn't ideal. In particular + // it is possible that queued up downloads for the page before the + // pending navigate will be delivered to us after we process this + // request. If this happens we may let a download through that we + // shouldn't have. But this is rather rare, and it is difficult to get + // 100% right, so we don't deal with it. + NavigationEntry* entry = controller_->pending_entry(); + if (!entry) + return; + + if (PageTransition::IsRedirect(entry->transition_type())) { + // Redirects don't count. + return; + } + + if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS || + status_ == DownloadRequestLimiter::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). + if (!initial_page_host_.empty() && !entry->url().host().empty() && + entry->url().host() == initial_page_host_) { + return; + } + } + break; + } + + case NotificationType::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 DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) { + status_ = allow ? + DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS : + DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED; + std::vector<DownloadRequestLimiter::Callback*> callbacks; + bool change_status = false; + + // Selectively send first few notifications only if number of downloads exceed + // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and + // don't close it. If allow is false, we send all the notifications to cancel + // all remaining downloads and close the infobar. + if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) { + if (infobar_) { + // Reset the delegate so we don't get notified again. + infobar_->set_host(NULL); + infobar_ = NULL; + } + callbacks.swap(callbacks_); + } else { + std::vector<DownloadRequestLimiter::Callback*>::iterator start, end; + start = callbacks_.begin(); + end = callbacks_.begin() + kMaxDownloadsAtOnce; + callbacks.assign(start, end); + callbacks_.erase(start, end); + change_status = true; + } + + for (size_t i = 0; i < callbacks.size(); ++i) + host_->ScheduleNotification(callbacks[i], allow); + + if (change_status) + status_ = DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD; +} + +// DownloadRequestLimiter ------------------------------------------------------ + +DownloadRequestLimiter::DownloadRequestLimiter() { +} + +DownloadRequestLimiter::~DownloadRequestLimiter() { + // 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()); +} + +DownloadRequestLimiter::DownloadStatus + DownloadRequestLimiter::GetDownloadStatus(TabContents* tab) { + TabDownloadState* state = GetDownloadState(&tab->controller(), NULL, false); + return state ? state->download_status() : ALLOW_ONE_DOWNLOAD; +} + +void DownloadRequestLimiter::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(ChromeThread::CurrentlyOn(ChromeThread::IO)); + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &DownloadRequestLimiter::CanDownload, + render_process_host_id, render_view_id, callback)); +} + +void DownloadRequestLimiter::OnUserGesture(TabContents* tab) { + TabDownloadState* state = GetDownloadState(&tab->controller(), NULL, false); + if (!state) + return; + + state->OnUserGesture(); +} + +// static +void DownloadRequestLimiter::SetTestingDelegate(TestingDelegate* delegate) { + delegate_ = delegate; +} + +DownloadRequestLimiter::TabDownloadState* DownloadRequestLimiter:: + 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 DownloadRequestLimiter::CanDownload(int render_process_host_id, + int render_view_id, + Callback* callback) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + 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 DownloadRequestLimiter::CanDownloadImpl( + TabContents* originating_tab, + Callback* callback) { + // FYI: Chrome Frame overrides CanDownload in ExternalTabContainer in order + // to cancel the download operation in chrome and let the host browser + // take care of it. + if (!originating_tab->CanDownload(callback->GetRequestId())) { + ScheduleNotification(callback, false); + return; + } + + // If the tab requesting the download is a constrained popup that is not + // shown, treat the request as if it came from the parent. + TabContents* effective_tab = originating_tab; + if (effective_tab->delegate()) { + effective_tab = + effective_tab->delegate()->GetConstrainingContents(effective_tab); + } + + TabDownloadState* state = GetDownloadState( + &effective_tab->controller(), &originating_tab->controller(), true); + switch (state->download_status()) { + case ALLOW_ALL_DOWNLOADS: + if (state->download_count() && !(state->download_count() % + DownloadRequestLimiter::kMaxDownloadsAtOnce)) + state->set_download_status(PROMPT_BEFORE_DOWNLOAD); + ScheduleNotification(callback, true); + state->increment_download_count(); + 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); + state->increment_download_count(); + break; + + default: + NOTREACHED(); + } +} + +void DownloadRequestLimiter::ScheduleNotification(Callback* callback, + bool allow) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod( + this, &DownloadRequestLimiter::NotifyCallback, callback, allow)); +} + +void DownloadRequestLimiter::NotifyCallback(Callback* callback, bool allow) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + if (allow) + callback->ContinueDownload(); + else + callback->CancelDownload(); +} + +void DownloadRequestLimiter::Remove(TabDownloadState* state) { + DCHECK(ContainsKey(state_map_, state->controller())); + state_map_.erase(state->controller()); + delete state; +} + +// static +DownloadRequestLimiter::TestingDelegate* DownloadRequestLimiter::delegate_ = + NULL; |