diff options
author | rdsmith@chromium.org <rdsmith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-15 14:24:24 +0000 |
---|---|---|
committer | rdsmith@chromium.org <rdsmith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-15 14:24:24 +0000 |
commit | 340c91ba33fcdb81885b353635c16b9ff91d1f37 (patch) | |
tree | 767c69ea5b8b29a4602aa9e687d700a27aef75cb /chrome/browser/download/download_test_observer.cc | |
parent | dabc45c18830592f272eb74e9b2620055c3a7188 (diff) | |
download | chromium_src-340c91ba33fcdb81885b353635c16b9ff91d1f37.zip chromium_src-340c91ba33fcdb81885b353635c16b9ff91d1f37.tar.gz chromium_src-340c91ba33fcdb81885b353635c16b9ff91d1f37.tar.bz2 |
Fix warning prompting on closing a window that will cancel downloads.
Handles two cases: Last window close (-> shuts download browser), and
last incognito window in an incognito profile (-> cancels downloads
on that profile).
Note that this doesn't cover the macintosh, which goes through different
code (http://crbug.com/88419) and the warning for incognito close is not
ideal (http://crbug.com/88421). This CL includes some modularization
to make resolving those issues easier.
BUG=61257
Review URL: http://codereview.chromium.org/7466033
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@105662 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/download/download_test_observer.cc')
-rw-r--r-- | chrome/browser/download/download_test_observer.cc | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/chrome/browser/download/download_test_observer.cc b/chrome/browser/download/download_test_observer.cc new file mode 100644 index 0000000..1d0c5b5 --- /dev/null +++ b/chrome/browser/download/download_test_observer.cc @@ -0,0 +1,297 @@ +// Copyright (c) 2011 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 <vector> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/stl_util.h" +#include "base/task.h" +#include "chrome/browser/download/download_test_observer.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/browser/browser_thread.h" + +// These functions take scoped_refptr's to DownloadManager because they +// are posted to message queues, and hence may execute arbitrarily after +// their actual posting. Once posted, there is no connection between +// these routines and the DownloadTestObserver class from which they came, +// so the DownloadTestObserver's reference to the DownloadManager cannot +// be counted on to keep the DownloadManager around. + +// Fake user click on "Accept". +void AcceptDangerousDownload(scoped_refptr<DownloadManager> download_manager, + int32 download_id) { + DownloadItem* download = download_manager->GetDownloadItem(download_id); + download->DangerousDownloadValidated(); +} + +// Fake user click on "Deny". +void DenyDangerousDownload(scoped_refptr<DownloadManager> download_manager, + int32 download_id) { + DownloadItem* download = download_manager->GetDownloadItem(download_id); + ASSERT_TRUE(download->IsPartialDownload()); + download->Cancel(true); + download->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD); +} + +DownloadTestObserver::DownloadTestObserver( + DownloadManager* download_manager, + size_t wait_count, + DownloadItem::DownloadState download_finished_state, + bool finish_on_select_file, + DangerousDownloadAction dangerous_download_action) + : download_manager_(download_manager), + wait_count_(wait_count), + finished_downloads_at_construction_(0), + waiting_(false), + download_finished_state_(download_finished_state), + finish_on_select_file_(finish_on_select_file), + select_file_dialog_seen_(false), + dangerous_download_action_(dangerous_download_action) { + download_manager_->AddObserver(this); // Will call initial ModelChanged(). + finished_downloads_at_construction_ = finished_downloads_.size(); + EXPECT_NE(DownloadItem::REMOVING, download_finished_state) + << "Waiting for REMOVING is not supported. Try COMPLETE."; + } + +DownloadTestObserver::~DownloadTestObserver() { + for (DownloadSet::iterator it = downloads_observed_.begin(); + it != downloads_observed_.end(); ++it) + (*it)->RemoveObserver(this); + + download_manager_->RemoveObserver(this); +} + +void DownloadTestObserver::WaitForFinished() { + if (!IsFinished()) { + waiting_ = true; + ui_test_utils::RunMessageLoop(); + waiting_ = false; + } +} + +bool DownloadTestObserver::IsFinished() const { + if (finished_downloads_.size() - finished_downloads_at_construction_ >= + wait_count_) + return true; + return (finish_on_select_file_ && select_file_dialog_seen_); +} + +void DownloadTestObserver::OnDownloadUpdated(DownloadItem* download) { + // The REMOVING state indicates that the download is being destroyed. + // Stop observing. Do not do anything with it, as it is about to be gone. + if (download->state() == DownloadItem::REMOVING) { + DownloadSet::iterator it = downloads_observed_.find(download); + ASSERT_TRUE(it != downloads_observed_.end()); + downloads_observed_.erase(it); + download->RemoveObserver(this); + return; + } + + // Real UI code gets the user's response after returning from the observer. + if (download->safety_state() == DownloadItem::DANGEROUS && + !ContainsKey(dangerous_downloads_seen_, download->id())) { + dangerous_downloads_seen_.insert(download->id()); + + // Calling DangerousDownloadValidated() at this point will + // cause the download to be completed twice. Do what the real UI + // code does: make the call as a delayed task. + switch (dangerous_download_action_) { + case ON_DANGEROUS_DOWNLOAD_ACCEPT: + // Fake user click on "Accept". Delay the actual click, as the + // real UI would. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction( + &AcceptDangerousDownload, + download_manager_, + download->id())); + break; + + case ON_DANGEROUS_DOWNLOAD_DENY: + // Fake a user click on "Deny". Delay the actual click, as the + // real UI would. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction( + &DenyDangerousDownload, + download_manager_, + download->id())); + break; + + case ON_DANGEROUS_DOWNLOAD_FAIL: + ADD_FAILURE() << "Unexpected dangerous download item."; + break; + + default: + NOTREACHED(); + } + } + + if (download->state() == download_finished_state_) { + DownloadInFinalState(download); + } +} + +void DownloadTestObserver::ModelChanged() { + // Regenerate DownloadItem observers. If there are any download items + // in our final state, note them in |finished_downloads_| + // (done by |OnDownloadUpdated()|). + std::vector<DownloadItem*> downloads; + download_manager_->GetAllDownloads(FilePath(), &downloads); + + for (std::vector<DownloadItem*>::iterator it = downloads.begin(); + it != downloads.end(); ++it) { + OnDownloadUpdated(*it); // Safe to call multiple times; checks state. + + DownloadSet::const_iterator finished_it(finished_downloads_.find(*it)); + DownloadSet::iterator observed_it(downloads_observed_.find(*it)); + + // If it isn't finished and we're aren't observing it, start. + if (finished_it == finished_downloads_.end() && + observed_it == downloads_observed_.end()) { + (*it)->AddObserver(this); + downloads_observed_.insert(*it); + continue; + } + + // If it is finished and we are observing it, stop. + if (finished_it != finished_downloads_.end() && + observed_it != downloads_observed_.end()) { + (*it)->RemoveObserver(this); + downloads_observed_.erase(observed_it); + continue; + } + } +} + +void DownloadTestObserver::SelectFileDialogDisplayed(int32 /* id */) { + select_file_dialog_seen_ = true; + SignalIfFinished(); +} + +size_t DownloadTestObserver::NumDangerousDownloadsSeen() const { + return dangerous_downloads_seen_.size(); +} + +void DownloadTestObserver::DownloadInFinalState(DownloadItem* download) { + if (finished_downloads_.find(download) != finished_downloads_.end()) { + // We've already seen terminal state on this download. + return; + } + + // Record the transition. + finished_downloads_.insert(download); + + SignalIfFinished(); +} + +void DownloadTestObserver::SignalIfFinished() { + if (waiting_ && IsFinished()) + MessageLoopForUI::current()->Quit(); +} + +DownloadTestFlushObserver::DownloadTestFlushObserver( + DownloadManager* download_manager) + : download_manager_(download_manager), + waiting_for_zero_inprogress_(true) {} + +void DownloadTestFlushObserver::WaitForFlush() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + download_manager_->AddObserver(this); + ui_test_utils::RunMessageLoop(); +} + +void DownloadTestFlushObserver::ModelChanged() { + // Model has changed, so there may be more DownloadItems to observe. + CheckDownloadsInProgress(true); +} + +void DownloadTestFlushObserver::OnDownloadUpdated(DownloadItem* download) { + // The REMOVING state indicates that the download is being destroyed. + // Stop observing. Do not do anything with it, as it is about to be gone. + if (download->state() == DownloadItem::REMOVING) { + DownloadSet::iterator it = downloads_observed_.find(download); + ASSERT_TRUE(it != downloads_observed_.end()); + downloads_observed_.erase(it); + download->RemoveObserver(this); + return; + } + + // No change in DownloadItem set on manager. + CheckDownloadsInProgress(false); +} + +DownloadTestFlushObserver::~DownloadTestFlushObserver() { + download_manager_->RemoveObserver(this); + for (DownloadSet::iterator it = downloads_observed_.begin(); + it != downloads_observed_.end(); ++it) { + (*it)->RemoveObserver(this); + } +} + +// If we're waiting for that flush point, check the number +// of downloads in the IN_PROGRESS state and take appropriate +// action. If requested, also observes all downloads while iterating. +void DownloadTestFlushObserver::CheckDownloadsInProgress( + bool observe_downloads) { + if (waiting_for_zero_inprogress_) { + int count = 0; + + std::vector<DownloadItem*> downloads; + download_manager_->SearchDownloads(string16(), &downloads); + for (std::vector<DownloadItem*>::iterator it = downloads.begin(); + it != downloads.end(); ++it) { + if ((*it)->state() == DownloadItem::IN_PROGRESS) + count++; + if (observe_downloads) { + if (downloads_observed_.find(*it) == downloads_observed_.end()) { + (*it)->AddObserver(this); + downloads_observed_.insert(*it); + } + // Download items are forever, and we don't want to make + // assumptions about future state transitions, so once we + // start observing them, we don't stop until destruction. + } + } + + if (count == 0) { + waiting_for_zero_inprogress_ = false; + // Stop observing DownloadItems. We maintain the observation + // of DownloadManager so that we don't have to independently track + // whether we are observing it for conditional destruction. + for (DownloadSet::iterator it = downloads_observed_.begin(); + it != downloads_observed_.end(); ++it) { + (*it)->RemoveObserver(this); + } + downloads_observed_.clear(); + + // Trigger next step. We need to go past the IO thread twice, as + // there's a self-task posting in the IO thread cancel path. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, + &DownloadTestFlushObserver::PingFileThread, 2)); + } + } +} + +void DownloadTestFlushObserver::PingFileThread(int cycle) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &DownloadTestFlushObserver::PingIOThread, + cycle)); +} + +void DownloadTestFlushObserver::PingIOThread(int cycle) { + if (--cycle) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, &DownloadTestFlushObserver::PingFileThread, + cycle)); + } else { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, new MessageLoop::QuitTask()); + } +} |