summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc')
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc3615
1 files changed, 3615 insertions, 0 deletions
diff --git a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
new file mode 100644
index 0000000..5060617
--- /dev/null
+++ b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
@@ -0,0 +1,3615 @@
+// Copyright (c) 2012 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 <algorithm>
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_reader.h"
+#include "base/message_loop/message_loop.h"
+#include "base/prefs/pref_service.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/download/download_file_icon_extractor.h"
+#include "chrome/browser/download/download_service.h"
+#include "chrome/browser/download/download_service_factory.h"
+#include "chrome/browser/download/download_test_file_activity_observer.h"
+#include "chrome/browser/extensions/api/downloads/downloads_api.h"
+#include "chrome/browser/extensions/event_names.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/history/download_row.h"
+#include "chrome/browser/net/url_request_mock_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/test/net/url_request_slow_download_job.h"
+#include "net/base/data_url.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/blob/blob_url_request_job.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/common/blob/blob_data.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+using content::DownloadItem;
+using content::DownloadManager;
+using content::URLRequestSlowDownloadJob;
+
+namespace events = extensions::event_names;
+
+namespace errors = download_extension_errors;
+
+namespace api = extensions::api::downloads;
+
+namespace {
+
+// Comparator that orders download items by their ID. Can be used with
+// std::sort.
+struct DownloadIdComparator {
+ bool operator() (DownloadItem* first, DownloadItem* second) {
+ return first->GetId() < second->GetId();
+ }
+};
+
+class DownloadsEventsListener : public content::NotificationObserver {
+ public:
+ DownloadsEventsListener()
+ : waiting_(false) {
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
+ content::NotificationService::AllSources());
+ }
+
+ virtual ~DownloadsEventsListener() {
+ registrar_.Remove(this, chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
+ content::NotificationService::AllSources());
+ STLDeleteElements(&events_);
+ }
+
+ void ClearEvents() {
+ STLDeleteElements(&events_);
+ events_.clear();
+ }
+
+ class Event {
+ public:
+ Event(Profile* profile,
+ const std::string& event_name,
+ const std::string& json_args,
+ base::Time caught)
+ : profile_(profile),
+ event_name_(event_name),
+ json_args_(json_args),
+ args_(base::JSONReader::Read(json_args)),
+ caught_(caught) {
+ }
+
+ const base::Time& caught() { return caught_; }
+
+ bool Satisfies(const Event& other) const {
+ return other.SatisfiedBy(*this);
+ }
+
+ bool SatisfiedBy(const Event& other) const {
+ if ((profile_ != other.profile_) ||
+ (event_name_ != other.event_name_))
+ return false;
+ if (((event_name_ == events::kOnDownloadDeterminingFilename) ||
+ (event_name_ == events::kOnDownloadCreated) ||
+ (event_name_ == events::kOnDownloadChanged)) &&
+ args_.get() &&
+ other.args_.get()) {
+ base::ListValue* left_list = NULL;
+ base::DictionaryValue* left_dict = NULL;
+ base::ListValue* right_list = NULL;
+ base::DictionaryValue* right_dict = NULL;
+ if (!args_->GetAsList(&left_list) ||
+ !other.args_->GetAsList(&right_list) ||
+ !left_list->GetDictionary(0, &left_dict) ||
+ !right_list->GetDictionary(0, &right_dict))
+ return false;
+ for (base::DictionaryValue::Iterator iter(*left_dict);
+ !iter.IsAtEnd(); iter.Advance()) {
+ base::Value* right_value = NULL;
+ if (!right_dict->HasKey(iter.key()) ||
+ (right_dict->Get(iter.key(), &right_value) &&
+ !iter.value().Equals(right_value))) {
+ return false;
+ }
+ }
+ return true;
+ } else if ((event_name_ == events::kOnDownloadErased) &&
+ args_.get() &&
+ other.args_.get()) {
+ int my_id = -1, other_id = -1;
+ return (args_->GetAsInteger(&my_id) &&
+ other.args_->GetAsInteger(&other_id) &&
+ my_id == other_id);
+ }
+ return json_args_ == other.json_args_;
+ }
+
+ std::string Debug() {
+ return base::StringPrintf("Event(%p, %s, %s, %f)",
+ profile_,
+ event_name_.c_str(),
+ json_args_.c_str(),
+ caught_.ToJsTime());
+ }
+
+ private:
+ Profile* profile_;
+ std::string event_name_;
+ std::string json_args_;
+ scoped_ptr<base::Value> args_;
+ base::Time caught_;
+
+ DISALLOW_COPY_AND_ASSIGN(Event);
+ };
+
+ typedef ExtensionDownloadsEventRouter::DownloadsNotificationSource
+ DownloadsNotificationSource;
+
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT:
+ {
+ DownloadsNotificationSource* dns =
+ content::Source<DownloadsNotificationSource>(source).ptr();
+ Event* new_event = new Event(
+ dns->profile,
+ dns->event_name,
+ *content::Details<std::string>(details).ptr(), base::Time::Now());
+ events_.push_back(new_event);
+ if (waiting_ &&
+ waiting_for_.get() &&
+ new_event->Satisfies(*waiting_for_)) {
+ waiting_ = false;
+ base::MessageLoopForUI::current()->Quit();
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+ }
+
+ bool WaitFor(Profile* profile,
+ const std::string& event_name,
+ const std::string& json_args) {
+ waiting_for_.reset(new Event(profile, event_name, json_args, base::Time()));
+ for (std::deque<Event*>::const_iterator iter = events_.begin();
+ iter != events_.end(); ++iter) {
+ if ((*iter)->Satisfies(*waiting_for_.get())) {
+ return true;
+ }
+ }
+ waiting_ = true;
+ content::RunMessageLoop();
+ bool success = !waiting_;
+ if (waiting_) {
+ // Print the events that were caught since the last WaitFor() call to help
+ // find the erroneous event.
+ // TODO(benjhayden) Fuzzy-match and highlight the erroneous event.
+ for (std::deque<Event*>::const_iterator iter = events_.begin();
+ iter != events_.end(); ++iter) {
+ if ((*iter)->caught() > last_wait_) {
+ LOG(INFO) << "Caught " << (*iter)->Debug();
+ }
+ }
+ if (waiting_for_.get()) {
+ LOG(INFO) << "Timed out waiting for " << waiting_for_->Debug();
+ }
+ waiting_ = false;
+ }
+ waiting_for_.reset();
+ last_wait_ = base::Time::Now();
+ return success;
+ }
+
+ private:
+ bool waiting_;
+ base::Time last_wait_;
+ scoped_ptr<Event> waiting_for_;
+ content::NotificationRegistrar registrar_;
+ std::deque<Event*> events_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadsEventsListener);
+};
+
+class DownloadExtensionTest : public ExtensionApiTest {
+ public:
+ DownloadExtensionTest()
+ : extension_(NULL),
+ incognito_browser_(NULL),
+ current_browser_(NULL) {
+ }
+
+ protected:
+ // Used with CreateHistoryDownloads
+ struct HistoryDownloadInfo {
+ // Filename to use. CreateHistoryDownloads will append this filename to the
+ // temporary downloads directory specified by downloads_directory().
+ const base::FilePath::CharType* filename;
+
+ // State for the download. Note that IN_PROGRESS downloads will be created
+ // as CANCELLED.
+ DownloadItem::DownloadState state;
+
+ // Danger type for the download. Only use DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS
+ // and DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT.
+ content::DownloadDangerType danger_type;
+ };
+
+ void LoadExtension(const char* name) {
+ // Store the created Extension object so that we can attach it to
+ // ExtensionFunctions. Also load the extension in incognito profiles for
+ // testing incognito.
+ extension_ = LoadExtensionIncognito(test_data_dir_.AppendASCII(name));
+ CHECK(extension_);
+ content::WebContents* tab = chrome::AddSelectedTabWithURL(
+ current_browser(),
+ extension_->GetResourceURL("empty.html"),
+ content::PAGE_TRANSITION_LINK);
+ extensions::ExtensionSystem::Get(current_browser()->profile())->
+ event_router()->AddEventListener(
+ extensions::event_names::kOnDownloadCreated,
+ tab->GetRenderProcessHost(),
+ GetExtensionId());
+ extensions::ExtensionSystem::Get(current_browser()->profile())->
+ event_router()->AddEventListener(
+ extensions::event_names::kOnDownloadChanged,
+ tab->GetRenderProcessHost(),
+ GetExtensionId());
+ extensions::ExtensionSystem::Get(current_browser()->profile())->
+ event_router()->AddEventListener(
+ extensions::event_names::kOnDownloadErased,
+ tab->GetRenderProcessHost(),
+ GetExtensionId());
+ }
+
+ content::RenderProcessHost* AddFilenameDeterminer() {
+ content::WebContents* tab = chrome::AddSelectedTabWithURL(
+ current_browser(),
+ extension_->GetResourceURL("empty.html"),
+ content::PAGE_TRANSITION_LINK);
+ extensions::ExtensionSystem::Get(current_browser()->profile())->
+ event_router()->AddEventListener(
+ extensions::event_names::kOnDownloadDeterminingFilename,
+ tab->GetRenderProcessHost(),
+ GetExtensionId());
+ return tab->GetRenderProcessHost();
+ }
+
+ void RemoveFilenameDeterminer(content::RenderProcessHost* host) {
+ extensions::ExtensionSystem::Get(current_browser()->profile())->
+ event_router()->RemoveEventListener(
+ extensions::event_names::kOnDownloadDeterminingFilename,
+ host,
+ GetExtensionId());
+ }
+
+ Browser* current_browser() { return current_browser_; }
+
+ // InProcessBrowserTest
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionApiTest::SetUpOnMainThread();
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true));
+ InProcessBrowserTest::SetUpOnMainThread();
+ GoOnTheRecord();
+ CreateAndSetDownloadsDirectory();
+ current_browser()->profile()->GetPrefs()->SetBoolean(
+ prefs::kPromptForDownload, false);
+ GetOnRecordManager()->RemoveAllDownloads();
+ events_listener_.reset(new DownloadsEventsListener());
+ // Disable file chooser for current profile.
+ DownloadTestFileActivityObserver observer(current_browser()->profile());
+ observer.EnableFileChooser(false);
+ }
+
+ void GoOnTheRecord() { current_browser_ = browser(); }
+
+ void GoOffTheRecord() {
+ if (!incognito_browser_) {
+ incognito_browser_ = CreateIncognitoBrowser();
+ GetOffRecordManager()->RemoveAllDownloads();
+ // Disable file chooser for incognito profile.
+ DownloadTestFileActivityObserver observer(incognito_browser_->profile());
+ observer.EnableFileChooser(false);
+ }
+ current_browser_ = incognito_browser_;
+ }
+
+ bool WaitFor(const std::string& event_name, const std::string& json_args) {
+ return events_listener_->WaitFor(
+ current_browser()->profile(), event_name, json_args);
+ }
+
+ bool WaitForInterruption(
+ DownloadItem* item,
+ content::DownloadInterruptReason expected_error,
+ const std::string& on_created_event) {
+ if (!WaitFor(events::kOnDownloadCreated, on_created_event))
+ return false;
+ // Now, onCreated is always fired before interruption.
+ return WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"error\": {\"current\": \"%s\"},"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"interrupted\"}}]",
+ item->GetId(),
+ content::InterruptReasonDebugString(
+ expected_error).c_str()));
+ }
+
+ void ClearEvents() {
+ events_listener_->ClearEvents();
+ }
+
+ std::string GetExtensionURL() {
+ return extension_->url().spec();
+ }
+ std::string GetExtensionId() {
+ return extension_->id();
+ }
+
+ std::string GetFilename(const char* path) {
+ std::string result =
+ downloads_directory_.path().AppendASCII(path).AsUTF8Unsafe();
+#if defined(OS_WIN)
+ for (std::string::size_type next = result.find("\\");
+ next != std::string::npos;
+ next = result.find("\\", next)) {
+ result.replace(next, 1, "\\\\");
+ next += 2;
+ }
+#endif
+ return result;
+ }
+
+ DownloadManager* GetOnRecordManager() {
+ return BrowserContext::GetDownloadManager(browser()->profile());
+ }
+ DownloadManager* GetOffRecordManager() {
+ return BrowserContext::GetDownloadManager(
+ browser()->profile()->GetOffTheRecordProfile());
+ }
+ DownloadManager* GetCurrentManager() {
+ return (current_browser_ == incognito_browser_) ?
+ GetOffRecordManager() : GetOnRecordManager();
+ }
+
+ // Creates a set of history downloads based on the provided |history_info|
+ // array. |count| is the number of elements in |history_info|. On success,
+ // |items| will contain |count| DownloadItems in the order that they were
+ // specified in |history_info|. Returns true on success and false otherwise.
+ bool CreateHistoryDownloads(const HistoryDownloadInfo* history_info,
+ size_t count,
+ DownloadManager::DownloadVector* items) {
+ DownloadIdComparator download_id_comparator;
+ base::Time current = base::Time::Now();
+ items->clear();
+ GetOnRecordManager()->GetAllDownloads(items);
+ CHECK_EQ(0, static_cast<int>(items->size()));
+ std::vector<GURL> url_chain;
+ url_chain.push_back(GURL());
+ for (size_t i = 0; i < count; ++i) {
+ DownloadItem* item = GetOnRecordManager()->CreateDownloadItem(
+ content::DownloadItem::kInvalidId + 1 + i,
+ downloads_directory().Append(history_info[i].filename),
+ downloads_directory().Append(history_info[i].filename),
+ url_chain, GURL(), // URL Chain, referrer
+ current, current, // start_time, end_time
+ 1, 1, // received_bytes, total_bytes
+ history_info[i].state, // state
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+ content::DOWNLOAD_INTERRUPT_REASON_NONE,
+ false); // opened
+ items->push_back(item);
+ }
+
+ // Order by ID so that they are in the order that we created them.
+ std::sort(items->begin(), items->end(), download_id_comparator);
+ // Set the danger type if necessary.
+ for (size_t i = 0; i < count; ++i) {
+ if (history_info[i].danger_type !=
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) {
+ EXPECT_EQ(content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT,
+ history_info[i].danger_type);
+ items->at(i)->OnContentCheckCompleted(history_info[i].danger_type);
+ }
+ }
+ return true;
+ }
+
+ void CreateSlowTestDownloads(
+ size_t count, DownloadManager::DownloadVector* items) {
+ for (size_t i = 0; i < count; ++i) {
+ scoped_ptr<content::DownloadTestObserver> observer(
+ CreateInProgressDownloadObserver(1));
+ GURL slow_download_url(URLRequestSlowDownloadJob::kUnknownSizeUrl);
+ ui_test_utils::NavigateToURLWithDisposition(
+ current_browser(), slow_download_url, CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+ observer->WaitForFinished();
+ EXPECT_EQ(
+ 1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS));
+ }
+ GetCurrentManager()->GetAllDownloads(items);
+ ASSERT_EQ(count, items->size());
+ }
+
+ DownloadItem* CreateSlowTestDownload() {
+ scoped_ptr<content::DownloadTestObserver> observer(
+ CreateInProgressDownloadObserver(1));
+ GURL slow_download_url(URLRequestSlowDownloadJob::kUnknownSizeUrl);
+ DownloadManager* manager = GetCurrentManager();
+
+ EXPECT_EQ(0, manager->InProgressCount());
+ if (manager->InProgressCount() != 0)
+ return NULL;
+
+ ui_test_utils::NavigateToURLWithDisposition(
+ current_browser(), slow_download_url, CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+
+ observer->WaitForFinished();
+ EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS));
+
+ DownloadManager::DownloadVector items;
+ manager->GetAllDownloads(&items);
+
+ DownloadItem* new_item = NULL;
+ for (DownloadManager::DownloadVector::iterator iter = items.begin();
+ iter != items.end(); ++iter) {
+ if ((*iter)->GetState() == DownloadItem::IN_PROGRESS) {
+ // There should be only one IN_PROGRESS item.
+ EXPECT_EQ(NULL, new_item);
+ new_item = *iter;
+ }
+ }
+ return new_item;
+ }
+
+ void FinishPendingSlowDownloads() {
+ scoped_ptr<content::DownloadTestObserver> observer(
+ CreateDownloadObserver(1));
+ GURL finish_url(URLRequestSlowDownloadJob::kFinishDownloadUrl);
+ ui_test_utils::NavigateToURLWithDisposition(
+ current_browser(), finish_url, NEW_FOREGROUND_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+ observer->WaitForFinished();
+ EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::COMPLETE));
+ }
+
+ content::DownloadTestObserver* CreateDownloadObserver(size_t download_count) {
+ return new content::DownloadTestObserverTerminal(
+ GetCurrentManager(), download_count,
+ content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+ }
+
+ content::DownloadTestObserver* CreateInProgressDownloadObserver(
+ size_t download_count) {
+ return new content::DownloadTestObserverInProgress(
+ GetCurrentManager(), download_count);
+ }
+
+ bool RunFunction(UIThreadExtensionFunction* function,
+ const std::string& args) {
+ scoped_refptr<UIThreadExtensionFunction> delete_function(function);
+ SetUpExtensionFunction(function);
+ return extension_function_test_utils::RunFunction(
+ function, args, browser(), GetFlags());
+ }
+
+ extension_function_test_utils::RunFunctionFlags GetFlags() {
+ return current_browser()->profile()->IsOffTheRecord() ?
+ extension_function_test_utils::INCLUDE_INCOGNITO :
+ extension_function_test_utils::NONE;
+ }
+
+ // extension_function_test_utils::RunFunction*() only uses browser for its
+ // profile(), so pass it the on-record browser so that it always uses the
+ // on-record profile to match real-life behavior.
+
+ base::Value* RunFunctionAndReturnResult(
+ scoped_refptr<UIThreadExtensionFunction> function,
+ const std::string& args) {
+ SetUpExtensionFunction(function.get());
+ return extension_function_test_utils::RunFunctionAndReturnSingleResult(
+ function.get(), args, browser(), GetFlags());
+ }
+
+ std::string RunFunctionAndReturnError(
+ scoped_refptr<UIThreadExtensionFunction> function,
+ const std::string& args) {
+ SetUpExtensionFunction(function.get());
+ return extension_function_test_utils::RunFunctionAndReturnError(
+ function.get(), args, browser(), GetFlags());
+ }
+
+ bool RunFunctionAndReturnString(
+ scoped_refptr<UIThreadExtensionFunction> function,
+ const std::string& args,
+ std::string* result_string) {
+ SetUpExtensionFunction(function.get());
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(function, args));
+ EXPECT_TRUE(result.get());
+ return result.get() && result->GetAsString(result_string);
+ }
+
+ std::string DownloadItemIdAsArgList(const DownloadItem* download_item) {
+ return base::StringPrintf("[%d]", download_item->GetId());
+ }
+
+ const base::FilePath& downloads_directory() {
+ return downloads_directory_.path();
+ }
+
+ DownloadsEventsListener* events_listener() { return events_listener_.get(); }
+
+ private:
+ void SetUpExtensionFunction(UIThreadExtensionFunction* function) {
+ if (extension_) {
+ // Recreate the tab each time for insulation.
+ content::WebContents* tab = chrome::AddSelectedTabWithURL(
+ current_browser(),
+ extension_->GetResourceURL("empty.html"),
+ content::PAGE_TRANSITION_LINK);
+ function->set_extension(extension_);
+ function->SetRenderViewHost(tab->GetRenderViewHost());
+ }
+ }
+
+ void CreateAndSetDownloadsDirectory() {
+ ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
+ current_browser()->profile()->GetPrefs()->SetFilePath(
+ prefs::kDownloadDefaultDirectory,
+ downloads_directory_.path());
+ }
+
+ base::ScopedTempDir downloads_directory_;
+ const extensions::Extension* extension_;
+ Browser* incognito_browser_;
+ Browser* current_browser_;
+ scoped_ptr<DownloadsEventsListener> events_listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadExtensionTest);
+};
+
+class MockIconExtractorImpl : public DownloadFileIconExtractor {
+ public:
+ MockIconExtractorImpl(const base::FilePath& path,
+ IconLoader::IconSize icon_size,
+ const std::string& response)
+ : expected_path_(path),
+ expected_icon_size_(icon_size),
+ response_(response) {
+ }
+ virtual ~MockIconExtractorImpl() {}
+
+ virtual bool ExtractIconURLForPath(const base::FilePath& path,
+ IconLoader::IconSize icon_size,
+ IconURLCallback callback) OVERRIDE {
+ EXPECT_STREQ(expected_path_.value().c_str(), path.value().c_str());
+ EXPECT_EQ(expected_icon_size_, icon_size);
+ if (expected_path_ == path &&
+ expected_icon_size_ == icon_size) {
+ callback_ = callback;
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&MockIconExtractorImpl::RunCallback,
+ base::Unretained(this)));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private:
+ void RunCallback() {
+ callback_.Run(response_);
+ }
+
+ base::FilePath expected_path_;
+ IconLoader::IconSize expected_icon_size_;
+ std::string response_;
+ IconURLCallback callback_;
+};
+
+bool ItemNotInProgress(DownloadItem* item) {
+ return item->GetState() != DownloadItem::IN_PROGRESS;
+}
+
+// Cancels the underlying DownloadItem when the ScopedCancellingItem goes out of
+// scope. Like a scoped_ptr, but for DownloadItems.
+class ScopedCancellingItem {
+ public:
+ explicit ScopedCancellingItem(DownloadItem* item) : item_(item) {}
+ ~ScopedCancellingItem() {
+ item_->Cancel(true);
+ content::DownloadUpdatedObserver observer(
+ item_, base::Bind(&ItemNotInProgress));
+ observer.WaitForEvent();
+ }
+ DownloadItem* get() { return item_; }
+ private:
+ DownloadItem* item_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedCancellingItem);
+};
+
+// Cancels all the underlying DownloadItems when the ScopedItemVectorCanceller
+// goes out of scope. Generalization of ScopedCancellingItem to many
+// DownloadItems.
+class ScopedItemVectorCanceller {
+ public:
+ explicit ScopedItemVectorCanceller(DownloadManager::DownloadVector* items)
+ : items_(items) {
+ }
+ ~ScopedItemVectorCanceller() {
+ for (DownloadManager::DownloadVector::const_iterator item = items_->begin();
+ item != items_->end(); ++item) {
+ if ((*item)->GetState() == DownloadItem::IN_PROGRESS)
+ (*item)->Cancel(true);
+ content::DownloadUpdatedObserver observer(
+ (*item), base::Bind(&ItemNotInProgress));
+ observer.WaitForEvent();
+ }
+ }
+
+ private:
+ DownloadManager::DownloadVector* items_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedItemVectorCanceller);
+};
+
+class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ explicit TestProtocolHandler(
+ webkit_blob::BlobStorageController* blob_storage_controller,
+ fileapi::FileSystemContext* file_system_context)
+ : blob_storage_controller_(blob_storage_controller),
+ file_system_context_(file_system_context) {}
+
+ virtual ~TestProtocolHandler() {}
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ return new webkit_blob::BlobURLRequestJob(
+ request,
+ network_delegate,
+ blob_storage_controller_->GetBlobDataFromUrl(request->url()),
+ file_system_context_,
+ base::MessageLoopProxy::current().get());
+ }
+
+ private:
+ webkit_blob::BlobStorageController* const blob_storage_controller_;
+ fileapi::FileSystemContext* const file_system_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestProtocolHandler);
+};
+
+class TestURLRequestContext : public net::URLRequestContext {
+ public:
+ explicit TestURLRequestContext(
+ fileapi::FileSystemContext* file_system_context)
+ : blob_storage_controller_(new webkit_blob::BlobStorageController) {
+ // Job factory owns the protocol handler.
+ job_factory_.SetProtocolHandler(
+ "blob", new TestProtocolHandler(blob_storage_controller_.get(),
+ file_system_context));
+ set_job_factory(&job_factory_);
+ }
+
+ virtual ~TestURLRequestContext() {}
+
+ webkit_blob::BlobStorageController* blob_storage_controller() const {
+ return blob_storage_controller_.get();
+ }
+
+ private:
+ net::URLRequestJobFactoryImpl job_factory_;
+ scoped_ptr<webkit_blob::BlobStorageController> blob_storage_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestURLRequestContext);
+};
+
+// Writes an HTML5 file so that it can be downloaded.
+class HTML5FileWriter {
+ public:
+ HTML5FileWriter(
+ Profile* profile,
+ const std::string& filename,
+ const std::string& origin,
+ DownloadsEventsListener* events_listener,
+ const std::string& payload)
+ : profile_(profile),
+ filename_(filename),
+ origin_(origin),
+ events_listener_(events_listener),
+ blob_data_(new webkit_blob::BlobData()),
+ payload_(payload),
+ fs_(BrowserContext::GetDefaultStoragePartition(profile_)->
+ GetFileSystemContext()) {
+ CHECK(profile_);
+ CHECK(events_listener_);
+ CHECK(fs_);
+ }
+
+ ~HTML5FileWriter() {
+ CHECK(BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &HTML5FileWriter::TearDownURLRequestContext, base::Unretained(this))));
+ events_listener_->WaitFor(
+ profile_, kURLRequestContextToreDown, std::string());
+ }
+
+ bool WriteFile() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ fs_->OpenFileSystem(
+ GURL(origin_),
+ fileapi::kFileSystemTypeTemporary,
+ fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&HTML5FileWriter::OpenFileSystemCallback,
+ base::Unretained(this)));
+ return events_listener_->WaitFor(profile_, kHTML5FileWritten, filename_);
+ }
+
+ private:
+ static const char kHTML5FileWritten[];
+ static const char kURLRequestContextToreDown[];
+ static const bool kExclusive = true;
+
+ GURL blob_url() const { return GURL("blob:" + filename_); }
+
+ void OpenFileSystemCallback(
+ base::PlatformFileError result,
+ const std::string& fs_name,
+ const GURL& root) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ root_ = root.spec();
+ CHECK(BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &HTML5FileWriter::CreateFile, base::Unretained(this))));
+ }
+
+ fileapi::FileSystemOperationRunner* operation_runner() {
+ return fs_->operation_runner();
+ }
+
+ void CreateFile() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ operation_runner()->CreateFile(fs_->CrackURL(GURL(root_ + filename_)),
+ kExclusive, base::Bind(
+ &HTML5FileWriter::CreateFileCallback, base::Unretained(this)));
+ }
+
+ void CreateFileCallback(base::PlatformFileError result) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ CHECK_EQ(base::PLATFORM_FILE_OK, result);
+ blob_data_->AppendData(payload_);
+ url_request_context_.reset(new TestURLRequestContext(fs_));
+ url_request_context_->blob_storage_controller()
+ ->AddFinishedBlob(blob_url(), blob_data_.get());
+ operation_runner()->Write(
+ url_request_context_.get(),
+ fs_->CrackURL(GURL(root_ + filename_)),
+ blob_url(),
+ 0, // offset
+ base::Bind(&HTML5FileWriter::WriteCallback, base::Unretained(this)));
+ }
+
+ void WriteCallback(
+ base::PlatformFileError result,
+ int64 bytes,
+ bool complete) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ CHECK_EQ(base::PLATFORM_FILE_OK, result);
+ CHECK(BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &HTML5FileWriter::NotifyWritten, base::Unretained(this))));
+ }
+
+ void NotifyWritten() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DownloadsEventsListener::DownloadsNotificationSource notification_source;
+ notification_source.event_name = kHTML5FileWritten;
+ notification_source.profile = profile_;
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
+ content::Source<DownloadsEventsListener::DownloadsNotificationSource>(
+ &notification_source),
+ content::Details<std::string>(&filename_));
+ }
+
+ void TearDownURLRequestContext() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ url_request_context_->blob_storage_controller()->RemoveBlob(blob_url());
+ url_request_context_.reset();
+ CHECK(BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &HTML5FileWriter::NotifyURLRequestContextToreDown,
+ base::Unretained(this))));
+ }
+
+ void NotifyURLRequestContextToreDown() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DownloadsEventsListener::DownloadsNotificationSource notification_source;
+ notification_source.event_name = kURLRequestContextToreDown;
+ notification_source.profile = profile_;
+ std::string empty_args;
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
+ content::Source<DownloadsEventsListener::DownloadsNotificationSource>(
+ &notification_source),
+ content::Details<std::string>(&empty_args));
+ }
+
+ Profile* profile_;
+ std::string filename_;
+ std::string origin_;
+ std::string root_;
+ DownloadsEventsListener* events_listener_;
+ scoped_refptr<webkit_blob::BlobData> blob_data_;
+ std::string payload_;
+ scoped_ptr<TestURLRequestContext> url_request_context_;
+ fileapi::FileSystemContext* fs_;
+
+ DISALLOW_COPY_AND_ASSIGN(HTML5FileWriter);
+};
+
+const char HTML5FileWriter::kHTML5FileWritten[] = "html5_file_written";
+const char HTML5FileWriter::kURLRequestContextToreDown[] =
+ "url_request_context_tore_down";
+
+// TODO(benjhayden) Merge this with the other TestObservers.
+class JustInProgressDownloadObserver
+ : public content::DownloadTestObserverInProgress {
+ public:
+ JustInProgressDownloadObserver(
+ DownloadManager* download_manager, size_t wait_count)
+ : content::DownloadTestObserverInProgress(download_manager, wait_count) {
+ }
+
+ virtual ~JustInProgressDownloadObserver() {}
+
+ private:
+ virtual bool IsDownloadInFinalState(DownloadItem* item) OVERRIDE {
+ return item->GetState() == DownloadItem::IN_PROGRESS;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(JustInProgressDownloadObserver);
+};
+
+bool ItemIsInterrupted(DownloadItem* item) {
+ return item->GetState() == DownloadItem::INTERRUPTED;
+}
+
+content::DownloadInterruptReason InterruptReasonExtensionToContent(
+ api::InterruptReason error) {
+ switch (error) {
+ case api::INTERRUPT_REASON_NONE:
+ return content::DOWNLOAD_INTERRUPT_REASON_NONE;
+#define INTERRUPT_REASON(name, value) \
+ case api::INTERRUPT_REASON_##name: \
+ return content::DOWNLOAD_INTERRUPT_REASON_##name;
+#include "content/public/browser/download_interrupt_reason_values.h"
+#undef INTERRUPT_REASON
+ }
+ NOTREACHED();
+ return content::DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+api::InterruptReason InterruptReasonContentToExtension(
+ content::DownloadInterruptReason error) {
+ switch (error) {
+ case content::DOWNLOAD_INTERRUPT_REASON_NONE:
+ return api::INTERRUPT_REASON_NONE;
+#define INTERRUPT_REASON(name, value) \
+ case content::DOWNLOAD_INTERRUPT_REASON_##name: \
+ return api::INTERRUPT_REASON_##name;
+#include "content/public/browser/download_interrupt_reason_values.h"
+#undef INTERRUPT_REASON
+ }
+ NOTREACHED();
+ return api::INTERRUPT_REASON_NONE;
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Open) {
+ LoadExtension("downloads_split");
+ EXPECT_STREQ(errors::kInvalidId,
+ RunFunctionAndReturnError(
+ new DownloadsOpenFunction(),
+ "[-42]").c_str());
+
+ DownloadItem* download_item = CreateSlowTestDownload();
+ ASSERT_TRUE(download_item);
+ EXPECT_FALSE(download_item->GetOpened());
+ EXPECT_FALSE(download_item->GetOpenWhenComplete());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"application/octet-stream\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_item->GetURL().spec().c_str())));
+ EXPECT_STREQ(errors::kNotComplete,
+ RunFunctionAndReturnError(
+ new DownloadsOpenFunction(),
+ DownloadItemIdAsArgList(download_item)).c_str());
+
+ FinishPendingSlowDownloads();
+ EXPECT_FALSE(download_item->GetOpened());
+ EXPECT_TRUE(RunFunction(new DownloadsOpenFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_TRUE(download_item->GetOpened());
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_PauseResumeCancelErase) {
+ DownloadItem* download_item = CreateSlowTestDownload();
+ ASSERT_TRUE(download_item);
+
+ // Call pause(). It should succeed and the download should be paused on
+ // return.
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_TRUE(download_item->IsPaused());
+
+ // Calling pause() twice shouldn't be an error.
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_TRUE(download_item->IsPaused());
+
+ // Now try resuming this download. It should succeed.
+ EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_FALSE(download_item->IsPaused());
+
+ // Resume again. Resuming a download that wasn't paused is not an error.
+ EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_FALSE(download_item->IsPaused());
+
+ // Pause again.
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_TRUE(download_item->IsPaused());
+
+ // And now cancel.
+ EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_EQ(DownloadItem::CANCELLED, download_item->GetState());
+
+ // Cancel again. Shouldn't have any effect.
+ EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(),
+ DownloadItemIdAsArgList(download_item)));
+ EXPECT_EQ(DownloadItem::CANCELLED, download_item->GetState());
+
+ // Calling paused on a non-active download yields kInvalidId.
+ std::string error = RunFunctionAndReturnError(
+ new DownloadsPauseFunction(), DownloadItemIdAsArgList(download_item));
+ EXPECT_STREQ(errors::kNotInProgress, error.c_str());
+
+ // Calling resume on a non-active download yields kInvalidId
+ error = RunFunctionAndReturnError(
+ new DownloadsResumeFunction(), DownloadItemIdAsArgList(download_item));
+ EXPECT_STREQ(errors::kNotResumable, error.c_str());
+
+ // Calling paused on a non-existent download yields kInvalidId.
+ error = RunFunctionAndReturnError(
+ new DownloadsPauseFunction(), "[-42]");
+ EXPECT_STREQ(errors::kInvalidId, error.c_str());
+
+ // Calling resume on a non-existent download yields kInvalidId
+ error = RunFunctionAndReturnError(
+ new DownloadsResumeFunction(), "[-42]");
+ EXPECT_STREQ(errors::kInvalidId, error.c_str());
+
+ int id = download_item->GetId();
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsEraseFunction(),
+ base::StringPrintf("[{\"id\": %d}]", id)));
+ DownloadManager::DownloadVector items;
+ GetCurrentManager()->GetAllDownloads(&items);
+ EXPECT_EQ(0UL, items.size());
+ ASSERT_TRUE(result);
+ download_item = NULL;
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+ int element = -1;
+ ASSERT_TRUE(result_list->GetInteger(0, &element));
+ EXPECT_EQ(id, element);
+}
+
+scoped_refptr<UIThreadExtensionFunction> MockedGetFileIconFunction(
+ const base::FilePath& expected_path,
+ IconLoader::IconSize icon_size,
+ const std::string& response) {
+ scoped_refptr<DownloadsGetFileIconFunction> function(
+ new DownloadsGetFileIconFunction());
+ function->SetIconExtractorForTesting(new MockIconExtractorImpl(
+ expected_path, icon_size, response));
+ return function;
+}
+
+// Test downloads.getFileIcon() on in-progress, finished, cancelled and deleted
+// download items.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_FileIcon_Active) {
+ DownloadItem* download_item = CreateSlowTestDownload();
+ ASSERT_TRUE(download_item);
+ ASSERT_FALSE(download_item->GetTargetFilePath().empty());
+ std::string args32(base::StringPrintf("[%d, {\"size\": 32}]",
+ download_item->GetId()));
+ std::string result_string;
+
+ // Get the icon for the in-progress download. This call should succeed even
+ // if the file type isn't registered.
+ // Test whether the correct path is being pased into the icon extractor.
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"),
+ base::StringPrintf("[%d, {}]", download_item->GetId()), &result_string));
+
+ // Now try a 16x16 icon.
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ download_item->GetTargetFilePath(), IconLoader::SMALL, "foo"),
+ base::StringPrintf("[%d, {\"size\": 16}]", download_item->GetId()),
+ &result_string));
+
+ // Explicitly asking for 32x32 should give us a 32x32 icon.
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"),
+ args32, &result_string));
+
+ // Finish the download and try again.
+ FinishPendingSlowDownloads();
+ EXPECT_EQ(DownloadItem::COMPLETE, download_item->GetState());
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"),
+ args32, &result_string));
+
+ // Check the path passed to the icon extractor post-completion.
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"),
+ args32, &result_string));
+
+ // Now create another download.
+ download_item = CreateSlowTestDownload();
+ ASSERT_TRUE(download_item);
+ ASSERT_FALSE(download_item->GetTargetFilePath().empty());
+ args32 = base::StringPrintf("[%d, {\"size\": 32}]", download_item->GetId());
+
+ // Cancel the download. As long as the download has a target path, we should
+ // be able to query the file icon.
+ download_item->Cancel(true);
+ ASSERT_FALSE(download_item->GetTargetFilePath().empty());
+ // Let cleanup complete on the FILE thread.
+ content::RunAllPendingInMessageLoop(BrowserThread::FILE);
+ // Check the path passed to the icon extractor post-cancellation.
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ download_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"),
+ args32,
+ &result_string));
+
+ // Simulate an error during icon load by invoking the mock with an empty
+ // result string.
+ std::string error = RunFunctionAndReturnError(
+ MockedGetFileIconFunction(download_item->GetTargetFilePath(),
+ IconLoader::NORMAL,
+ std::string()),
+ args32);
+ EXPECT_STREQ(errors::kIconNotFound, error.c_str());
+
+ // Once the download item is deleted, we should return kInvalidId.
+ int id = download_item->GetId();
+ download_item->Remove();
+ download_item = NULL;
+ EXPECT_EQ(static_cast<DownloadItem*>(NULL),
+ GetCurrentManager()->GetDownload(id));
+ error = RunFunctionAndReturnError(new DownloadsGetFileIconFunction(), args32);
+ EXPECT_STREQ(errors::kInvalidId,
+ error.c_str());
+}
+
+// Test that we can acquire file icons for history downloads regardless of
+// whether they exist or not. If the file doesn't exist we should receive a
+// generic icon from the OS/toolkit that may or may not be specific to the file
+// type.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_FileIcon_History) {
+ const HistoryDownloadInfo kHistoryInfo[] = {
+ { FILE_PATH_LITERAL("real.txt"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS },
+ { FILE_PATH_LITERAL("fake.txt"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }
+ };
+ DownloadManager::DownloadVector all_downloads;
+ ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo),
+ &all_downloads));
+
+ base::FilePath real_path = all_downloads[0]->GetTargetFilePath();
+ base::FilePath fake_path = all_downloads[1]->GetTargetFilePath();
+
+ EXPECT_EQ(0, file_util::WriteFile(real_path, "", 0));
+ ASSERT_TRUE(base::PathExists(real_path));
+ ASSERT_FALSE(base::PathExists(fake_path));
+
+ for (DownloadManager::DownloadVector::iterator iter = all_downloads.begin();
+ iter != all_downloads.end();
+ ++iter) {
+ std::string result_string;
+ // Use a MockIconExtractorImpl to test if the correct path is being passed
+ // into the DownloadFileIconExtractor.
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ (*iter)->GetTargetFilePath(), IconLoader::NORMAL, "hello"),
+ base::StringPrintf("[%d, {\"size\": 32}]", (*iter)->GetId()),
+ &result_string));
+ EXPECT_STREQ("hello", result_string.c_str());
+ }
+}
+
+// Test passing the empty query to search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchEmptyQuery) {
+ ScopedCancellingItem item(CreateSlowTestDownload());
+ ASSERT_TRUE(item.get());
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+}
+
+// Test the |filenameRegex| parameter for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchFilenameRegex) {
+ const HistoryDownloadInfo kHistoryInfo[] = {
+ { FILE_PATH_LITERAL("foobar"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS },
+ { FILE_PATH_LITERAL("baz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }
+ };
+ DownloadManager::DownloadVector all_downloads;
+ ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo),
+ &all_downloads));
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{\"filenameRegex\": \"foobar\"}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+ base::DictionaryValue* item_value = NULL;
+ ASSERT_TRUE(result_list->GetDictionary(0, &item_value));
+ int item_id = -1;
+ ASSERT_TRUE(item_value->GetInteger("id", &item_id));
+ ASSERT_EQ(all_downloads[0]->GetId(), static_cast<uint32>(item_id));
+}
+
+// Test the |id| parameter for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchId) {
+ DownloadManager::DownloadVector items;
+ CreateSlowTestDownloads(2, &items);
+ ScopedItemVectorCanceller delete_items(&items);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), base::StringPrintf(
+ "[{\"id\": %u}]", items[0]->GetId())));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+ base::DictionaryValue* item_value = NULL;
+ ASSERT_TRUE(result_list->GetDictionary(0, &item_value));
+ int item_id = -1;
+ ASSERT_TRUE(item_value->GetInteger("id", &item_id));
+ ASSERT_EQ(items[0]->GetId(), static_cast<uint32>(item_id));
+}
+
+// Test specifying both the |id| and |filename| parameters for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchIdAndFilename) {
+ DownloadManager::DownloadVector items;
+ CreateSlowTestDownloads(2, &items);
+ ScopedItemVectorCanceller delete_items(&items);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(),
+ "[{\"id\": 0, \"filename\": \"foobar\"}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(0UL, result_list->GetSize());
+}
+
+// Test a single |orderBy| parameter for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchOrderBy) {
+ const HistoryDownloadInfo kHistoryInfo[] = {
+ { FILE_PATH_LITERAL("zzz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS },
+ { FILE_PATH_LITERAL("baz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }
+ };
+ DownloadManager::DownloadVector items;
+ ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo),
+ &items));
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{\"orderBy\": [\"filename\"]}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(2UL, result_list->GetSize());
+ base::DictionaryValue* item0_value = NULL;
+ base::DictionaryValue* item1_value = NULL;
+ ASSERT_TRUE(result_list->GetDictionary(0, &item0_value));
+ ASSERT_TRUE(result_list->GetDictionary(1, &item1_value));
+ std::string item0_name, item1_name;
+ ASSERT_TRUE(item0_value->GetString("filename", &item0_name));
+ ASSERT_TRUE(item1_value->GetString("filename", &item1_name));
+ ASSERT_GT(items[0]->GetTargetFilePath().value(),
+ items[1]->GetTargetFilePath().value());
+ ASSERT_LT(item0_name, item1_name);
+}
+
+// Test specifying an empty |orderBy| parameter for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchOrderByEmpty) {
+ const HistoryDownloadInfo kHistoryInfo[] = {
+ { FILE_PATH_LITERAL("zzz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS },
+ { FILE_PATH_LITERAL("baz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }
+ };
+ DownloadManager::DownloadVector items;
+ ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo),
+ &items));
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{\"orderBy\": []}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(2UL, result_list->GetSize());
+ base::DictionaryValue* item0_value = NULL;
+ base::DictionaryValue* item1_value = NULL;
+ ASSERT_TRUE(result_list->GetDictionary(0, &item0_value));
+ ASSERT_TRUE(result_list->GetDictionary(1, &item1_value));
+ std::string item0_name, item1_name;
+ ASSERT_TRUE(item0_value->GetString("filename", &item0_name));
+ ASSERT_TRUE(item1_value->GetString("filename", &item1_name));
+ ASSERT_GT(items[0]->GetTargetFilePath().value(),
+ items[1]->GetTargetFilePath().value());
+ ASSERT_GT(item0_name, item1_name);
+}
+
+// Test the |danger| option for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchDanger) {
+ const HistoryDownloadInfo kHistoryInfo[] = {
+ { FILE_PATH_LITERAL("zzz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT },
+ { FILE_PATH_LITERAL("baz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }
+ };
+ DownloadManager::DownloadVector items;
+ ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo),
+ &items));
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{\"danger\": \"content\"}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+}
+
+// Test the |state| option for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchState) {
+ DownloadManager::DownloadVector items;
+ CreateSlowTestDownloads(2, &items);
+ ScopedItemVectorCanceller delete_items(&items);
+
+ items[0]->Cancel(true);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{\"state\": \"in_progress\"}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+}
+
+// Test the |limit| option for search().
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchLimit) {
+ DownloadManager::DownloadVector items;
+ CreateSlowTestDownloads(2, &items);
+ ScopedItemVectorCanceller delete_items(&items);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{\"limit\": 1}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+}
+
+// Test invalid search parameters.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchInvalid) {
+ std::string error = RunFunctionAndReturnError(
+ new DownloadsSearchFunction(), "[{\"filenameRegex\": \"(\"}]");
+ EXPECT_STREQ(errors::kInvalidFilter,
+ error.c_str());
+ error = RunFunctionAndReturnError(
+ new DownloadsSearchFunction(), "[{\"orderBy\": [\"goat\"]}]");
+ EXPECT_STREQ(errors::kInvalidOrderBy,
+ error.c_str());
+ error = RunFunctionAndReturnError(
+ new DownloadsSearchFunction(), "[{\"limit\": -1}]");
+ EXPECT_STREQ(errors::kInvalidQueryLimit,
+ error.c_str());
+}
+
+// Test searching using multiple conditions through multiple downloads.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchPlural) {
+ const HistoryDownloadInfo kHistoryInfo[] = {
+ { FILE_PATH_LITERAL("aaa"),
+ DownloadItem::CANCELLED,
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS },
+ { FILE_PATH_LITERAL("zzz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT },
+ { FILE_PATH_LITERAL("baz"),
+ DownloadItem::COMPLETE,
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT },
+ };
+ DownloadManager::DownloadVector items;
+ ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo),
+ &items));
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{"
+ "\"state\": \"complete\", "
+ "\"danger\": \"content\", "
+ "\"orderBy\": [\"filename\"], "
+ "\"limit\": 1}]"));
+ ASSERT_TRUE(result.get());
+ base::ListValue* result_list = NULL;
+ ASSERT_TRUE(result->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+ base::DictionaryValue* item_value = NULL;
+ ASSERT_TRUE(result_list->GetDictionary(0, &item_value));
+ base::FilePath::StringType item_name;
+ ASSERT_TRUE(item_value->GetString("filename", &item_name));
+ ASSERT_EQ(items[2]->GetTargetFilePath().value(), item_name);
+}
+
+// Test that incognito downloads are only visible in incognito contexts, and
+// test that on-record downloads are visible in both incognito and on-record
+// contexts, for DownloadsSearchFunction, DownloadsPauseFunction,
+// DownloadsResumeFunction, and DownloadsCancelFunction.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_SearchPauseResumeCancelGetFileIconIncognito) {
+ scoped_ptr<base::Value> result_value;
+ base::ListValue* result_list = NULL;
+ base::DictionaryValue* result_dict = NULL;
+ base::FilePath::StringType filename;
+ bool is_incognito = false;
+ std::string error;
+ std::string on_item_arg;
+ std::string off_item_arg;
+ std::string result_string;
+
+ // Set up one on-record item and one off-record item.
+ // Set up the off-record item first because otherwise there are mysteriously 3
+ // items total instead of 2.
+ // TODO(benjhayden): Figure out where the third item comes from.
+ GoOffTheRecord();
+ DownloadItem* off_item = CreateSlowTestDownload();
+ ASSERT_TRUE(off_item);
+ off_item_arg = DownloadItemIdAsArgList(off_item);
+
+ GoOnTheRecord();
+ DownloadItem* on_item = CreateSlowTestDownload();
+ ASSERT_TRUE(on_item);
+ on_item_arg = DownloadItemIdAsArgList(on_item);
+ ASSERT_TRUE(on_item->GetTargetFilePath() != off_item->GetTargetFilePath());
+
+ // Extensions running in the incognito window should have access to both
+ // items because the Test extension is in spanning mode.
+ GoOffTheRecord();
+ result_value.reset(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{}]"));
+ ASSERT_TRUE(result_value.get());
+ ASSERT_TRUE(result_value->GetAsList(&result_list));
+ ASSERT_EQ(2UL, result_list->GetSize());
+ ASSERT_TRUE(result_list->GetDictionary(0, &result_dict));
+ ASSERT_TRUE(result_dict->GetString("filename", &filename));
+ ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito));
+ EXPECT_TRUE(on_item->GetTargetFilePath() == base::FilePath(filename));
+ EXPECT_FALSE(is_incognito);
+ ASSERT_TRUE(result_list->GetDictionary(1, &result_dict));
+ ASSERT_TRUE(result_dict->GetString("filename", &filename));
+ ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito));
+ EXPECT_TRUE(off_item->GetTargetFilePath() == base::FilePath(filename));
+ EXPECT_TRUE(is_incognito);
+
+ // Extensions running in the on-record window should have access only to the
+ // on-record item.
+ GoOnTheRecord();
+ result_value.reset(RunFunctionAndReturnResult(
+ new DownloadsSearchFunction(), "[{}]"));
+ ASSERT_TRUE(result_value.get());
+ ASSERT_TRUE(result_value->GetAsList(&result_list));
+ ASSERT_EQ(1UL, result_list->GetSize());
+ ASSERT_TRUE(result_list->GetDictionary(0, &result_dict));
+ ASSERT_TRUE(result_dict->GetString("filename", &filename));
+ EXPECT_TRUE(on_item->GetTargetFilePath() == base::FilePath(filename));
+ ASSERT_TRUE(result_dict->GetBoolean("incognito", &is_incognito));
+ EXPECT_FALSE(is_incognito);
+
+ // Pausing/Resuming the off-record item while on the record should return an
+ // error. Cancelling "non-existent" downloads is not an error.
+ error = RunFunctionAndReturnError(new DownloadsPauseFunction(), off_item_arg);
+ EXPECT_STREQ(errors::kInvalidId,
+ error.c_str());
+ error = RunFunctionAndReturnError(new DownloadsResumeFunction(),
+ off_item_arg);
+ EXPECT_STREQ(errors::kInvalidId,
+ error.c_str());
+ error = RunFunctionAndReturnError(
+ new DownloadsGetFileIconFunction(),
+ base::StringPrintf("[%d, {}]", off_item->GetId()));
+ EXPECT_STREQ(errors::kInvalidId,
+ error.c_str());
+
+ GoOffTheRecord();
+
+ // Do the FileIcon test for both the on- and off-items while off the record.
+ // NOTE(benjhayden): This does not include the FileIcon test from history,
+ // just active downloads. This shouldn't be a problem.
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ on_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"),
+ base::StringPrintf("[%d, {}]", on_item->GetId()), &result_string));
+ EXPECT_TRUE(RunFunctionAndReturnString(MockedGetFileIconFunction(
+ off_item->GetTargetFilePath(), IconLoader::NORMAL, "foo"),
+ base::StringPrintf("[%d, {}]", off_item->GetId()), &result_string));
+
+ // Do the pause/resume/cancel test for both the on- and off-items while off
+ // the record.
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg));
+ EXPECT_TRUE(on_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg));
+ EXPECT_TRUE(on_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), on_item_arg));
+ EXPECT_FALSE(on_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), on_item_arg));
+ EXPECT_FALSE(on_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), on_item_arg));
+ EXPECT_TRUE(on_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), on_item_arg));
+ EXPECT_EQ(DownloadItem::CANCELLED, on_item->GetState());
+ EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), on_item_arg));
+ EXPECT_EQ(DownloadItem::CANCELLED, on_item->GetState());
+ error = RunFunctionAndReturnError(new DownloadsPauseFunction(), on_item_arg);
+ EXPECT_STREQ(errors::kNotInProgress, error.c_str());
+ error = RunFunctionAndReturnError(new DownloadsResumeFunction(), on_item_arg);
+ EXPECT_STREQ(errors::kNotResumable, error.c_str());
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg));
+ EXPECT_TRUE(off_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg));
+ EXPECT_TRUE(off_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), off_item_arg));
+ EXPECT_FALSE(off_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsResumeFunction(), off_item_arg));
+ EXPECT_FALSE(off_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg));
+ EXPECT_TRUE(off_item->IsPaused());
+ EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), off_item_arg));
+ EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState());
+ EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), off_item_arg));
+ EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState());
+ error = RunFunctionAndReturnError(new DownloadsPauseFunction(), off_item_arg);
+ EXPECT_STREQ(errors::kNotInProgress, error.c_str());
+ error = RunFunctionAndReturnError(new DownloadsResumeFunction(),
+ off_item_arg);
+ EXPECT_STREQ(errors::kNotResumable, error.c_str());
+}
+
+// Test that we can start a download and that the correct sequence of events is
+// fired for it.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Basic) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+ GoOnTheRecord();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// Test that we can start a download from an incognito context, and that the
+// download knows that it's incognito.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Incognito) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ GoOffTheRecord();
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": true,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\":%d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\":%d,"
+ " \"state\": {"
+ " \"current\": \"complete\","
+ " \"previous\": \"in_progress\"}}]",
+ result_id)));
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+// This test is very flaky on Win Aura. http://crbug.com/248438
+#define MAYBE_DownloadExtensionTest_Download_UnsafeHeaders \
+ DISABLED_DownloadExtensionTest_Download_UnsafeHeaders
+#else
+#define MAYBE_DownloadExtensionTest_Download_UnsafeHeaders \
+ DownloadExtensionTest_Download_UnsafeHeaders
+#endif
+
+// Test that we disallow certain headers case-insensitively.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ MAYBE_DownloadExtensionTest_Download_UnsafeHeaders) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ GoOnTheRecord();
+
+ static const char* kUnsafeHeaders[] = {
+ "Accept-chArsEt",
+ "accept-eNcoding",
+ "coNNection",
+ "coNteNt-leNgth",
+ "cooKIE",
+ "cOOkie2",
+ "coNteNt-traNsfer-eNcodiNg",
+ "dAtE",
+ "ExpEcT",
+ "hOsT",
+ "kEEp-aLivE",
+ "rEfErEr",
+ "tE",
+ "trAilER",
+ "trANsfer-eNcodiNg",
+ "upGRAde",
+ "usER-agENt",
+ "viA",
+ "pRoxY-",
+ "sEc-",
+ "pRoxY-probably-not-evil",
+ "sEc-probably-not-evil",
+ "oRiGiN",
+ "Access-Control-Request-Headers",
+ "Access-Control-Request-Method",
+ };
+
+ for (size_t index = 0; index < arraysize(kUnsafeHeaders); ++index) {
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+ EXPECT_STREQ(errors::kInvalidHeader,
+ RunFunctionAndReturnError(new DownloadsDownloadFunction(),
+ base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"unsafe-header-%d.txt\","
+ " \"headers\": [{"
+ " \"name\": \"%s\","
+ " \"value\": \"unsafe\"}]}]",
+ download_url.c_str(),
+ static_cast<int>(index),
+ kUnsafeHeaders[index])).c_str());
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Subdirectory) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"sub/dir/ect/ory.txt\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("sub/dir/ect/ory.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// Test that invalid filenames are disallowed.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_InvalidFilename) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+ GoOnTheRecord();
+
+ EXPECT_STREQ(errors::kInvalidFilename,
+ RunFunctionAndReturnError(new DownloadsDownloadFunction(),
+ base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"../../../../../etc/passwd\"}]",
+ download_url.c_str())).c_str());
+}
+
+// Test that downloading invalid URLs immediately returns kInvalidURLError.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_InvalidURLs) {
+ LoadExtension("downloads_split");
+ GoOnTheRecord();
+
+ static const char* kInvalidURLs[] = {
+ "foo bar",
+ "../hello",
+ "/hello",
+ "http://",
+ "#frag",
+ "foo/bar.html#frag",
+ "google.com/",
+ };
+
+ for (size_t index = 0; index < arraysize(kInvalidURLs); ++index) {
+ EXPECT_STREQ(errors::kInvalidURL,
+ RunFunctionAndReturnError(new DownloadsDownloadFunction(),
+ base::StringPrintf(
+ "[{\"url\": \"%s\"}]", kInvalidURLs[index])).c_str())
+ << kInvalidURLs[index];
+ }
+
+ EXPECT_STREQ("net::ERR_ACCESS_DENIED", RunFunctionAndReturnError(
+ new DownloadsDownloadFunction(),
+ "[{\"url\": \"javascript:document.write(\\\"hello\\\");\"}]").c_str());
+ EXPECT_STREQ("net::ERR_ACCESS_DENIED", RunFunctionAndReturnError(
+ new DownloadsDownloadFunction(),
+ "[{\"url\": \"javascript:return false;\"}]").c_str());
+ EXPECT_STREQ("net::ERR_NOT_IMPLEMENTED", RunFunctionAndReturnError(
+ new DownloadsDownloadFunction(),
+ "[{\"url\": \"ftp://example.com/example.txt\"}]").c_str());
+}
+
+// TODO(benjhayden): Set up a test ftp server, add ftp://localhost* to
+// permissions, test downloading from ftp.
+
+// Valid URLs plus fragments are still valid URLs.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_URLFragment) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0#fragment").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// Valid data URLs are valid URLs.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_DataURL) {
+ LoadExtension("downloads_split");
+ std::string download_url = "data:text/plain,hello";
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"data.txt\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("data.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// Valid file URLs are valid URLs.
+#if defined(OS_WIN) && defined(USE_AURA)
+// Disabled due to crbug.com/175711
+#define MAYBE_DownloadExtensionTest_Download_File \
+ DISABLED_DownloadExtensionTest_Download_File
+#else
+#define MAYBE_DownloadExtensionTest_Download_File \
+ DownloadExtensionTest_Download_File
+#endif
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ MAYBE_DownloadExtensionTest_Download_File) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ std::string download_url = "file:///";
+#if defined(OS_WIN)
+ download_url += "C:/";
+#endif
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"file.txt\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/html\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("file.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// Test that auth-basic-succeed would fail if the resource requires the
+// Authorization header and chrome fails to propagate it back to the server.
+// This tests both that testserver.py does not succeed when it should fail as
+// well as how the downloads extension API exposes the failure to extensions.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_AuthBasic_Fail) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("auth-basic").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"auth-basic-fail.txt\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/html\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+}
+
+// Test that DownloadsDownloadFunction propagates |headers| to the URLRequest.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Headers) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("files/downloads/"
+ "a_zip_file.zip?expected_headers=Foo:bar&expected_headers=Qx:yo").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"headers-succeed.txt\","
+ " \"headers\": ["
+ " {\"name\": \"Foo\", \"value\": \"bar\"},"
+ " {\"name\": \"Qx\", \"value\":\"yo\"}]}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"application/octet-stream\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("headers-succeed.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// Test that headers-succeed would fail if the resource requires the headers and
+// chrome fails to propagate them back to the server. This tests both that
+// testserver.py does not succeed when it should fail as well as how the
+// downloads extension api exposes the failure to extensions.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Headers_Fail) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("files/downloads/"
+ "a_zip_file.zip?expected_headers=Foo:bar&expected_headers=Qx:yo").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"headers-fail.txt\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"bytesReceived\": 0,"
+ " \"mime\": \"\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+}
+
+// Test that DownloadsDownloadFunction propagates the Authorization header
+// correctly.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_AuthBasic) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("auth-basic").spec();
+ // This is just base64 of 'username:secret'.
+ static const char* kAuthorization = "dXNlcm5hbWU6c2VjcmV0";
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"auth-basic-succeed.txt\","
+ " \"headers\": [{"
+ " \"name\": \"Authorization\","
+ " \"value\": \"Basic %s\"}]}]",
+ download_url.c_str(), kAuthorization)));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/html\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]", result_id)));
+}
+
+// Test that DownloadsDownloadFunction propagates the |method| and |body|
+// parameters to the URLRequest.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Post) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("files/post/downloads/"
+ "a_zip_file.zip?expected_body=BODY").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"post-succeed.txt\","
+ " \"method\": \"POST\","
+ " \"body\": \"BODY\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"application/octet-stream\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("post-succeed.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// Test that downloadPostSuccess would fail if the resource requires the POST
+// method, and chrome fails to propagate the |method| parameter back to the
+// server. This tests both that testserver.py does not succeed when it should
+// fail, and this tests how the downloads extension api exposes the failure to
+// extensions.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Post_Get) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("files/post/downloads/"
+ "a_zip_file.zip?expected_body=BODY").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"body\": \"BODY\","
+ " \"filename\": \"post-get.txt\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"\","
+ " \"paused\": false,"
+ " \"id\": %d,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+}
+
+// Test that downloadPostSuccess would fail if the resource requires the POST
+// method, and chrome fails to propagate the |body| parameter back to the
+// server. This tests both that testserver.py does not succeed when it should
+// fail, and this tests how the downloads extension api exposes the failure to
+// extensions.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Post_NoBody) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("files/post/downloads/"
+ "a_zip_file.zip?expected_body=BODY").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"method\": \"POST\","
+ " \"filename\": \"post-nobody.txt\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"\","
+ " \"paused\": false,"
+ " \"id\": %d,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+}
+
+// Test that cancel()ing an in-progress download causes its state to transition
+// to interrupted, and test that that state transition is detectable by an
+// onChanged event listener. TODO(benjhayden): Test other sources of
+// interruptions such as server death.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_Cancel) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL(
+ "download-known-size").spec();
+ GoOnTheRecord();
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"application/octet-stream\","
+ " \"paused\": false,"
+ " \"id\": %d,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ item->Cancel(true);
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"error\": {\"current\":\"USER_CANCELED\"},"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"interrupted\"}}]",
+ result_id)));
+}
+
+// Test downloading filesystem: URLs.
+// NOTE: chrome disallows creating HTML5 FileSystem Files in incognito.
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_Download_FileSystemURL) {
+ static const char* kPayloadData = "on the record\ndata";
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ HTML5FileWriter html5_file_writer(
+ browser()->profile(),
+ "on_record.txt",
+ GetExtensionURL(),
+ events_listener(),
+ kPayloadData);
+ ASSERT_TRUE(html5_file_writer.WriteFile());
+
+ std::string download_url = "filesystem:" + GetExtensionURL() +
+ "temporary/on_record.txt";
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("on_record.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+ std::string disk_data;
+ EXPECT_TRUE(file_util::ReadFileToString(item->GetTargetFilePath(),
+ &disk_data));
+ EXPECT_STREQ(kPayloadData, disk_data.c_str());
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_NoChange) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ // Wait for the onCreated and onDeterminingFilename events.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_EQ("", error);
+
+ // The download should complete successfully.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_DangerousOverride) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("overridden.swf")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_EQ("", error);
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"danger\": {"
+ " \"previous\":\"safe\","
+ " \"current\":\"file\"}}]",
+ result_id)));
+
+ item->ValidateDangerousDownload();
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"danger\": {"
+ " \"previous\":\"file\","
+ " \"current\":\"accepted\"}}]",
+ result_id)));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+ EXPECT_EQ(downloads_directory().AppendASCII("overridden.swf"),
+ item->GetTargetFilePath());
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_ReferencesParentInvalid) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("sneaky/../../sneaky.txt")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_IllegalFilename) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("<")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
+ "[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
+ "[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_IllegalFilenameExtension) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL(
+ "My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}/foo")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
+ "[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
+ "[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_ReservedFilename) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("con.foo")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
+ "[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
+ "[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_CurDirInvalid) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL(".")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_ParentDirInvalid) {
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("..")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_AbsPathInvalid) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename. Absolute paths should be rejected.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ downloads_directory().Append(FILE_PATH_LITERAL("sneaky.txt")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_EmptyBasenameInvalid) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename. Empty basenames should be rejected.
+ std::string error;
+ ASSERT_FALSE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("foo/")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_Override) {
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ AddFilenameDeterminer();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ std::string error;
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_EQ("", error);
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("slow.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+
+ // Start downloading a file.
+ result.reset(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller2(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ // Also test that DetermineFilename allows (chrome) extensions to set
+ // filenames without (filename) extensions. (Don't ask about v8 extensions or
+ // python extensions or kernel extensions or firefox extensions...)
+ error = "";
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("foo")),
+ api::FILENAME_CONFLICT_ACTION_OVERWRITE,
+ &error));
+ EXPECT_EQ("", error);
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("foo").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+// TODO test precedence rules: install_time
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_RemoveFilenameDeterminer) {
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ GoOnTheRecord();
+ LoadExtension("downloads_split");
+ content::RenderProcessHost* host = AddFilenameDeterminer();
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ // Start downloading a file.
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Remove a determiner while waiting for it.
+ RemoveFilenameDeterminer(host);
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_IncognitoSplit) {
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ GoOnTheRecord();
+ AddFilenameDeterminer();
+
+ GoOffTheRecord();
+ AddFilenameDeterminer();
+
+ // Start an on-record download.
+ GoOnTheRecord();
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ // Wait for the onCreated and onDeterminingFilename events.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"incognito\": false,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename events.
+ std::string error;
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ current_browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("42.txt")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_EQ("", error);
+
+ // The download should complete successfully.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("42.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+
+ // Start an incognito download for comparison.
+ GoOffTheRecord();
+ result.reset(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller2(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": true,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ // On-Record renderers should not see events for off-record items.
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"incognito\": true,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ error = "";
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ current_browser()->profile(),
+ false,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("5.txt")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_EQ("", error);
+
+ // The download should complete successfully.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("5.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ DownloadExtensionTest_OnDeterminingFilename_IncognitoSpanning) {
+ LoadExtension("downloads_spanning");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ std::string download_url = test_server()->GetURL("slow?0").spec();
+
+ GoOnTheRecord();
+ AddFilenameDeterminer();
+
+ // There is a single extension renderer that sees both on-record and
+ // off-record events. The extension functions see the on-record profile with
+ // include_incognito=true.
+
+ // Start an on-record download.
+ GoOnTheRecord();
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ // Wait for the onCreated and onDeterminingFilename events.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"incognito\": false,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename events.
+ std::string error;
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ current_browser()->profile(),
+ true,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("42.txt")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_EQ("", error);
+
+ // The download should complete successfully.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("42.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+
+ // Start an incognito download for comparison.
+ GoOffTheRecord();
+ result.reset(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\"}]", download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller2(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": true,"
+ " \"id\": %d,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"incognito\": true,"
+ " \"filename\":\"slow.txt\"}]",
+ result_id)));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ // Respond to the onDeterminingFilename.
+ error = "";
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ current_browser()->profile(),
+ true,
+ GetExtensionId(),
+ result_id,
+ base::FilePath(FILE_PATH_LITERAL("42.txt")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error));
+ EXPECT_EQ("", error);
+
+ // The download should complete successfully.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("42 (1).txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
+}
+
+#if defined(OS_WIN)
+// This test is very flaky on Win XP and Aura. http://crbug.com/248438
+#define MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume \
+ DISABLED_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume
+#else
+#define MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume \
+ DownloadExtensionTest_OnDeterminingFilename_InterruptedResume
+#endif
+
+// Test download interruption while extensions determining filename. Should not
+// re-dispatch onDeterminingFilename.
+IN_PROC_BROWSER_TEST_F(
+ DownloadExtensionTest,
+ MAYBE_DownloadExtensionTest_OnDeterminingFilename_InterruptedResume) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ LoadExtension("downloads_split");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(test_server()->Start());
+ GoOnTheRecord();
+ content::RenderProcessHost* host = AddFilenameDeterminer();
+
+ // Start a download.
+ DownloadItem* item = NULL;
+ {
+ DownloadManager* manager = GetCurrentManager();
+ scoped_ptr<content::DownloadTestObserver> observer(
+ new JustInProgressDownloadObserver(manager, 1));
+ ASSERT_EQ(0, manager->InProgressCount());
+ // Tabs created just for a download are automatically closed, invalidating
+ // the download's WebContents. Downloads without WebContents cannot be
+ // resumed. http://crbug.com/225901
+ ui_test_utils::NavigateToURLWithDisposition(
+ current_browser(),
+ GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl),
+ CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_NONE);
+ observer->WaitForFinished();
+ EXPECT_EQ(1u, observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS));
+ DownloadManager::DownloadVector items;
+ manager->GetAllDownloads(&items);
+ for (DownloadManager::DownloadVector::iterator iter = items.begin();
+ iter != items.end(); ++iter) {
+ if ((*iter)->GetState() == DownloadItem::IN_PROGRESS) {
+ // There should be only one IN_PROGRESS item.
+ EXPECT_EQ(NULL, item);
+ item = *iter;
+ }
+ }
+ ASSERT_TRUE(item);
+ }
+ ScopedCancellingItem canceller(item);
+
+ // Wait for the onCreated and onDeterminingFilename event.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"id\": %d,"
+ " \"mime\": \"application/octet-stream\","
+ " \"paused\": false}]",
+ item->GetId())));
+ ASSERT_TRUE(WaitFor(
+ events::kOnDownloadDeterminingFilename,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"incognito\": false,"
+ " \"filename\":\"download-unknown-size\"}]",
+ item->GetId())));
+ ASSERT_TRUE(item->GetTargetFilePath().empty());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ ClearEvents();
+ ui_test_utils::NavigateToURLWithDisposition(
+ current_browser(),
+ GURL(URLRequestSlowDownloadJob::kErrorDownloadUrl),
+ NEW_BACKGROUND_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+
+ // Errors caught before filename determination are delayed until after
+ // filename determination.
+ std::string error;
+ ASSERT_TRUE(ExtensionDownloadsEventRouter::DetermineFilename(
+ current_browser()->profile(),
+ false,
+ GetExtensionId(),
+ item->GetId(),
+ base::FilePath(FILE_PATH_LITERAL("42.txt")),
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ &error)) << error;
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ item->GetId(),
+ GetFilename("42.txt").c_str())));
+
+ content::DownloadUpdatedObserver interrupted(item, base::Bind(
+ ItemIsInterrupted));
+ ASSERT_TRUE(interrupted.WaitForEvent());
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"error\":{\"current\":\"NETWORK_FAILED\"},"
+ " \"state\":{"
+ " \"previous\":\"in_progress\","
+ " \"current\":\"interrupted\"}}]",
+ item->GetId())));
+
+ ClearEvents();
+ // Downloads that are restarted on resumption trigger another download target
+ // determination.
+ RemoveFilenameDeterminer(host);
+ item->Resume();
+
+ // Errors caught before filename determination is complete are delayed until
+ // after filename determination so that, on resumption, filename determination
+ // does not need to be re-done. So, there will not be a second
+ // onDeterminingFilename event.
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"error\":{\"previous\":\"NETWORK_FAILED\"},"
+ " \"state\":{"
+ " \"previous\":\"interrupted\","
+ " \"current\":\"in_progress\"}}]",
+ item->GetId())));
+
+ ClearEvents();
+ FinishPendingSlowDownloads();
+
+ // The download should complete successfully.
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ item->GetId())));
+}
+
+// TODO(benjhayden) Figure out why DisableExtension() does not fire
+// OnListenerRemoved.
+
+// TODO(benjhayden) Test that the shelf is shown for download() both with and
+// without a WebContents.
+
+class DownloadsApiTest : public ExtensionApiTest {
+ public:
+ DownloadsApiTest() {}
+ virtual ~DownloadsApiTest() {}
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DownloadsApiTest);
+};
+
+
+IN_PROC_BROWSER_TEST_F(DownloadsApiTest, DownloadsApiTest) {
+ ASSERT_TRUE(RunExtensionTest("downloads")) << message_;
+}
+
+
+TEST(DownloadInterruptReasonEnumsSynced,
+ DownloadInterruptReasonEnumsSynced) {
+#define INTERRUPT_REASON(name, value) \
+ EXPECT_EQ(InterruptReasonContentToExtension( \
+ content::DOWNLOAD_INTERRUPT_REASON_##name), \
+ api::INTERRUPT_REASON_##name); \
+ EXPECT_EQ(InterruptReasonExtensionToContent( \
+ api::INTERRUPT_REASON_##name), \
+ content::DOWNLOAD_INTERRUPT_REASON_##name);
+#include "content/public/browser/download_interrupt_reason_values.h"
+#undef INTERRUPT_REASON
+}