Index: content/browser/download/download_browsertest.cc |
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc |
index 6fab6e25e38dc4f0b6836bba8233aedb996fd2e0..46030b1e08c42065384c72aa98fd76f078939263 100644 |
--- a/content/browser/download/download_browsertest.cc |
+++ b/content/browser/download/download_browsertest.cc |
@@ -8,10 +8,16 @@ |
#include "base/file_path.h" |
#include "base/file_util.h" |
#include "base/scoped_temp_dir.h" |
+#include "content/browser/download/download_file_factory.h" |
+#include "content/browser/download/download_file_impl.h" |
+#include "content/browser/download/download_file_manager.h" |
#include "content/browser/download/download_item_impl.h" |
#include "content/browser/download/download_manager_impl.h" |
+#include "content/browser/power_save_blocker.h" |
+#include "content/browser/renderer_host/resource_dispatcher_host_impl.h" |
#include "content/browser/web_contents/web_contents_impl.h" |
#include "content/public/test/download_test_observer.h" |
+#include "content/public/test/test_utils.h" |
#include "content/shell/shell.h" |
#include "content/shell/shell_browser_context.h" |
#include "content/shell/shell_download_manager_delegate.h" |
@@ -25,9 +31,197 @@ namespace content { |
namespace { |
-static DownloadManager* DownloadManagerForShell(Shell* shell) { |
- return BrowserContext::GetDownloadManager( |
- shell->web_contents()->GetBrowserContext()); |
+class DownloadFileWithDelayFactory; |
+ |
+static DownloadManagerImpl* DownloadManagerForShell(Shell* shell) { |
+ // We're in a content_browsertest; we know that the DownloadManager |
+ // is a DownloadManagerImpl. |
+ return static_cast<DownloadManagerImpl*>( |
+ BrowserContext::GetDownloadManager( |
+ shell->web_contents()->GetBrowserContext())); |
+} |
+ |
+class DownloadFileWithDelay : public DownloadFileImpl { |
+ public: |
+ DownloadFileWithDelay( |
+ const DownloadCreateInfo* info, |
+ scoped_ptr<content::ByteStreamReader> stream, |
+ DownloadRequestHandleInterface* request_handle, |
+ scoped_refptr<content::DownloadManager> download_manager, |
+ bool calculate_hash, |
+ scoped_ptr<content::PowerSaveBlocker> power_save_blocker, |
+ const net::BoundNetLog& bound_net_log, |
+ // |owner| is required to outlive the DownloadFileWithDelay. |
+ DownloadFileWithDelayFactory* owner); |
+ |
+ virtual ~DownloadFileWithDelay(); |
+ |
+ // Wraps DownloadFileImpl::Rename and intercepts the return callback, |
+ // storing it in the factory that produced this object for later |
+ // retrieval. |
+ virtual void Rename(const FilePath& full_path, |
+ bool overwrite_existing_file, |
+ const RenameCompletionCallback& callback) OVERRIDE; |
+ |
+ // Wraps DownloadFileImpl::Detach and intercepts the return callback, |
+ // storing it in the factory that produced this object for later |
+ // retrieval. |
+ virtual void Detach(base::Closure callback) OVERRIDE; |
+ |
+ private: |
+ static void RenameCallbackWrapper( |
+ DownloadFileWithDelayFactory* factory, |
+ const RenameCompletionCallback& original_callback, |
+ content::DownloadInterruptReason reason, |
+ const FilePath& path); |
+ |
+ static void DetachCallbackWrapper( |
+ DownloadFileWithDelayFactory* factory, |
+ const base::Closure& original_callback); |
+ |
+ // May only be used on the UI thread. |
+ DownloadFileWithDelayFactory* owner_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelay); |
+}; |
+ |
+class DownloadFileWithDelayFactory : public DownloadFileFactory { |
+ public: |
+ DownloadFileWithDelayFactory(); |
+ virtual ~DownloadFileWithDelayFactory(); |
+ |
+ // DownloadFileFactory interface. |
+ virtual content::DownloadFile* CreateFile( |
+ DownloadCreateInfo* info, |
+ scoped_ptr<content::ByteStreamReader> stream, |
+ DownloadManager* download_manager, |
+ bool calculate_hash, |
+ const net::BoundNetLog& bound_net_log) OVERRIDE; |
+ |
+ // Must all be called on the UI thread. |
+ void AddRenameCallback(base::Closure callback); |
+ void AddDetachCallback(base::Closure callback); |
+ void GetAllRenameCallbacks(std::vector<base::Closure>* results); |
+ void GetAllDetachCallbacks(std::vector<base::Closure>* results); |
+ |
+ // Do not return until either GetAllRenameCallbacks() or |
+ // GetAllDetachCallbacks() will return a non-empty list. |
+ void WaitForSomeCallback(); |
+ |
+ private: |
+ std::vector<base::Closure> rename_callbacks_; |
+ std::vector<base::Closure> detach_callbacks_; |
+ bool waiting_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelayFactory); |
+}; |
+ |
+DownloadFileWithDelay::DownloadFileWithDelay( |
+ const DownloadCreateInfo* info, |
+ scoped_ptr<content::ByteStreamReader> stream, |
+ DownloadRequestHandleInterface* request_handle, |
+ scoped_refptr<content::DownloadManager> download_manager, |
+ bool calculate_hash, |
+ scoped_ptr<content::PowerSaveBlocker> power_save_blocker, |
+ const net::BoundNetLog& bound_net_log, |
+ DownloadFileWithDelayFactory* owner) |
+ : DownloadFileImpl(info, stream.Pass(), request_handle, download_manager, |
+ calculate_hash, power_save_blocker.Pass(), |
+ bound_net_log), |
+ owner_(owner) {} |
+ |
+DownloadFileWithDelay::~DownloadFileWithDelay() {} |
+ |
+void DownloadFileWithDelay::Rename(const FilePath& full_path, |
+ bool overwrite_existing_file, |
+ const RenameCompletionCallback& callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ DownloadFileImpl::Rename( |
+ full_path, overwrite_existing_file, |
+ base::Bind(DownloadFileWithDelay::RenameCallbackWrapper, |
+ base::Unretained(owner_), callback)); |
+} |
+ |
+void DownloadFileWithDelay::Detach(base::Closure callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ DownloadFileImpl::Detach( |
+ base::Bind(DownloadFileWithDelay::DetachCallbackWrapper, |
+ base::Unretained(owner_), callback)); |
+} |
+ |
+// static |
+void DownloadFileWithDelay::RenameCallbackWrapper( |
+ DownloadFileWithDelayFactory* factory, |
+ const RenameCompletionCallback& original_callback, |
+ content::DownloadInterruptReason reason, |
+ const FilePath& path) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ factory->AddRenameCallback(base::Bind(original_callback, reason, path)); |
+} |
+ |
+// static |
+void DownloadFileWithDelay::DetachCallbackWrapper( |
+ DownloadFileWithDelayFactory* factory, |
+ const base::Closure& original_callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ factory->AddDetachCallback(original_callback); |
+} |
+ |
+DownloadFileWithDelayFactory::DownloadFileWithDelayFactory() |
+ : waiting_(false) {} |
+DownloadFileWithDelayFactory::~DownloadFileWithDelayFactory() {} |
+ |
+DownloadFile* DownloadFileWithDelayFactory::CreateFile( |
+ DownloadCreateInfo* info, |
+ scoped_ptr<content::ByteStreamReader> stream, |
+ DownloadManager* download_manager, |
+ bool calculate_hash, |
+ const net::BoundNetLog& bound_net_log) { |
+ |
+ return new DownloadFileWithDelay( |
+ info, stream.Pass(), new DownloadRequestHandle(info->request_handle), |
+ download_manager, calculate_hash, |
+ scoped_ptr<content::PowerSaveBlocker>( |
+ new content::PowerSaveBlocker( |
+ content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, |
+ "Download in progress")).Pass(), |
+ bound_net_log, this); |
+} |
+ |
+void DownloadFileWithDelayFactory::AddRenameCallback(base::Closure callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ rename_callbacks_.push_back(callback); |
+ if (waiting_) |
+ MessageLoopForUI::current()->Quit(); |
+} |
+ |
+void DownloadFileWithDelayFactory::AddDetachCallback(base::Closure callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ detach_callbacks_.push_back(callback); |
+ if (waiting_) |
+ MessageLoopForUI::current()->Quit(); |
+} |
+ |
+void DownloadFileWithDelayFactory::GetAllRenameCallbacks( |
+ std::vector<base::Closure>* results) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ results->swap(rename_callbacks_); |
+} |
+ |
+void DownloadFileWithDelayFactory::GetAllDetachCallbacks( |
+ std::vector<base::Closure>* results) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ results->swap(detach_callbacks_); |
+} |
+ |
+void DownloadFileWithDelayFactory::WaitForSomeCallback() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ if (rename_callbacks_.empty() && detach_callbacks_.empty()) { |
+ waiting_ = true; |
+ RunMessageLoop(); |
+ waiting_ = false; |
+ } |
} |
} // namespace |
@@ -114,6 +308,11 @@ class DownloadContentTest : public ContentBrowserTest { |
return true; |
} |
+ DownloadFileManager* GetDownloadFileManager() { |
+ ResourceDispatcherHostImpl* rdh(ResourceDispatcherHostImpl::Get()); |
+ return rdh->download_file_manager(); |
+ } |
+ |
private: |
static void EnsureNoPendingDownloadJobsOnIO(bool* result) { |
if (URLRequestSlowDownloadJob::NumberOutstandingRequests()) |
@@ -127,18 +326,18 @@ class DownloadContentTest : public ContentBrowserTest { |
}; |
IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadCancelled) { |
- // TODO(rdsmith): Fragile code warning! The code below relies on the |
- // DownloadTestObserverInProgress only finishing when the new download |
- // has reached the state of being entered into the history and being |
- // user-visible (that's what's required for the Remove to be valid and |
- // for the download shelf to be visible). By the pure semantics of |
- // DownloadTestObserverInProgress, that's not guaranteed; DownloadItems |
- // are created in the IN_PROGRESS state and made known to the DownloadManager |
- // immediately, so any ModelChanged event on the DownloadManager after |
- // navigation would allow the observer to return. However, the only |
- // ModelChanged() event the code will currently fire is in |
- // OnCreateDownloadEntryComplete, at which point the download item will |
- // be in the state we need. |
+ // TODO(rdsmith): Fragile code warning! The code below relies on |
+ // the DownloadTestObserverInProgress only finishing when the new |
+ // download has reached the state of being entered into the history |
+ // and being user-visible (that's what's required for the Remove to |
+ // be valid). By the pure semantics of |
+ // DownloadTestObserverInProgress, that's not guaranteed; |
+ // DownloadItems are created in the IN_PROGRESS state and made known |
+ // to the DownloadManager immediately, so any ModelChanged event on |
+ // the DownloadManager after navigation would allow the observer to |
+ // return. However, the only ModelChanged() event the code will |
+ // currently fire is in OnCreateDownloadEntryComplete, at which |
+ // point the download item will be in the state we need. |
// The right way to fix this is to create finer grained states on the |
// DownloadItem, and wait for the state that indicates the item has been |
// entered in the history and made visible in the UI. |
@@ -220,4 +419,110 @@ IN_PROC_BROWSER_TEST_F(DownloadContentTest, MultiDownload) { |
file2, GetTestFilePath("download", "download-test.lib"))); |
} |
+// Try to cancel just before we release the download file, by delaying final |
+// rename callback. |
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtFinalRename) { |
+ // Setup new factory. |
+ DownloadFileWithDelayFactory* file_factory = |
+ new DownloadFileWithDelayFactory(); |
+ GetDownloadFileManager()->SetFileFactoryForTesting( |
+ scoped_ptr<content::DownloadFileFactory>(file_factory).Pass()); |
+ |
+ // Create a download |
+ FilePath file(FILE_PATH_LITERAL("download-test.lib")); |
+ NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file)); |
+ |
+ // Wait until the first (intermediate file) rename and execute the callback. |
+ file_factory->WaitForSomeCallback(); |
+ std::vector<base::Closure> callbacks; |
+ file_factory->GetAllDetachCallbacks(&callbacks); |
+ ASSERT_TRUE(callbacks.empty()); |
+ file_factory->GetAllRenameCallbacks(&callbacks); |
+ ASSERT_EQ(1u, callbacks.size()); |
+ callbacks[0].Run(); |
+ callbacks.clear(); |
+ |
+ // Wait until the second (final) rename callback is posted. |
+ file_factory->WaitForSomeCallback(); |
+ file_factory->GetAllDetachCallbacks(&callbacks); |
+ ASSERT_TRUE(callbacks.empty()); |
+ file_factory->GetAllRenameCallbacks(&callbacks); |
+ ASSERT_EQ(1u, callbacks.size()); |
+ |
+ // Cancel it. |
+ std::vector<DownloadItem*> items; |
+ DownloadManagerForShell(shell())->GetAllDownloads(&items); |
+ ASSERT_EQ(1u, items.size()); |
+ items[0]->Cancel(true); |
+ RunAllPendingInMessageLoop(); |
+ |
+ // Check state. |
+ EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState()); |
+ |
+ // Run final rename callback. |
+ callbacks[0].Run(); |
+ callbacks.clear(); |
+ |
+ // Check state. |
+ EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState()); |
+} |
+ |
+// Try to cancel just after we release the download file, by delaying |
+// release. |
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtRelease) { |
+ // Setup new factory. |
+ // Setup new factory. |
+ DownloadFileWithDelayFactory* file_factory = |
+ new DownloadFileWithDelayFactory(); |
+ GetDownloadFileManager()->SetFileFactoryForTesting( |
+ scoped_ptr<content::DownloadFileFactory>(file_factory).Pass()); |
+ |
+ // Create a download |
+ FilePath file(FILE_PATH_LITERAL("download-test.lib")); |
+ NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file)); |
+ |
+ // Wait until the first (intermediate file) rename and execute the callback. |
+ file_factory->WaitForSomeCallback(); |
+ std::vector<base::Closure> callbacks; |
+ file_factory->GetAllDetachCallbacks(&callbacks); |
+ ASSERT_TRUE(callbacks.empty()); |
+ file_factory->GetAllRenameCallbacks(&callbacks); |
+ ASSERT_EQ(1u, callbacks.size()); |
+ callbacks[0].Run(); |
+ callbacks.clear(); |
+ |
+ // Wait until the second (final) rename callback is posted. |
+ file_factory->WaitForSomeCallback(); |
+ file_factory->GetAllDetachCallbacks(&callbacks); |
+ ASSERT_TRUE(callbacks.empty()); |
+ file_factory->GetAllRenameCallbacks(&callbacks); |
+ ASSERT_EQ(1u, callbacks.size()); |
+ |
+ // Call it. |
+ callbacks[0].Run(); |
+ callbacks.clear(); |
+ |
+ // Confirm download isn't complete yet. |
+ std::vector<DownloadItem*> items; |
+ DownloadManagerForShell(shell())->GetAllDownloads(&items); |
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); |
+ |
+ // Cancel the download; confirm cancel fails anyway. |
+ ASSERT_EQ(1u, items.size()); |
+ items[0]->Cancel(true); |
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); |
+ RunAllPendingInMessageLoop(); |
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState()); |
+ |
+ // Confirm detach callback and run it. |
+ file_factory->WaitForSomeCallback(); |
+ file_factory->GetAllRenameCallbacks(&callbacks); |
+ ASSERT_TRUE(callbacks.empty()); |
+ file_factory->GetAllDetachCallbacks(&callbacks); |
+ ASSERT_EQ(1u, callbacks.size()); |
+ callbacks[0].Run(); |
+ callbacks.clear(); |
+ EXPECT_EQ(DownloadItem::COMPLETE, items[0]->GetState()); |
+} |
+ |
} // namespace content |