summaryrefslogtreecommitdiffstats
path: root/chrome/browser/download/download_request_limiter.cc
diff options
context:
space:
mode:
authorphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-15 17:09:01 +0000
committerphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-15 17:09:01 +0000
commit4129132ccbbc05abb8bfd89b3d7d5316c4d7d136 (patch)
treeb01682d5ad9a9e73c64217299c383f2d281a9864 /chrome/browser/download/download_request_limiter.cc
parent8bad4795cbf8afd699f6c3dc111c8dba11a280d3 (diff)
downloadchromium_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.cc321
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;