Index: chrome/browser/extensions/api/downloads/downloads_api_unittest.cc |
diff --git a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc b/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc |
index 6cf4d3e7f9931c3a17481499005a42f754640b98..e5cd52cc87cfff460fd89d91e95506892b547279 100644 |
--- a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc |
+++ b/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc |
@@ -5,28 +5,51 @@ |
#include <algorithm> |
#include "base/file_util.h" |
+#include "base/json/json_reader.h" |
+#include "base/json/json_writer.h" |
+#include "base/message_loop.h" |
#include "base/scoped_temp_dir.h" |
+#include "base/stl_util.h" |
#include "base/stringprintf.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_observer.h" |
#include "chrome/browser/extensions/api/downloads/downloads_api.h" |
+#include "chrome/browser/extensions/extension_apitest.h" |
+#include "chrome/browser/extensions/extension_event_names.h" |
#include "chrome/browser/extensions/extension_function_test_utils.h" |
#include "chrome/browser/net/url_request_mock_util.h" |
#include "chrome/browser/prefs/pref_service.h" |
#include "chrome/browser/profiles/profile.h" |
#include "chrome/browser/ui/browser.h" |
+#include "chrome/browser/ui/tab_contents/tab_contents.h" |
+#include "chrome/common/chrome_notification_types.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/download_persistent_store_info.h" |
+#include "content/public/browser/notification_service.h" |
+#include "content/public/browser/web_contents.h" |
+#include "content/public/common/page_transition_types.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 "ui/gfx/codec/png_codec.h" |
+#include "webkit/blob/blob_data.h" |
+#include "webkit/blob/blob_storage_controller.h" |
+#include "webkit/blob/blob_url_request_job.h" |
+#include "webkit/fileapi/file_system_context.h" |
+#include "webkit/fileapi/file_system_operation_interface.h" |
+#include "webkit/fileapi/file_system_url.h" |
using content::BrowserContext; |
using content::BrowserThread; |
@@ -44,7 +67,175 @@ struct DownloadIdComparator { |
} |
}; |
-class DownloadExtensionTest : public InProcessBrowserTest { |
+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_); |
+ } |
+ |
+ 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 Equals(const Event& other) { |
+ if ((profile_ != other.profile_) || |
+ (event_name_ != other.event_name_)) |
+ return false; |
+ if ((event_name_ == extension_event_names::kOnDownloadCreated || |
+ event_name_ == extension_event_names::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.HasNext(); 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_ == extension_event_names::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; |
+ |
+ void Observe(int type, const content::NotificationSource& source, |
+ const content::NotificationDetails& details) { |
+ 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() && |
+ waiting_for_->Equals(*new_event)) { |
+ waiting_ = false; |
+ 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)->Equals(*waiting_for_.get())) |
+ return true; |
+ } |
+ waiting_ = true; |
+ ui_test_utils::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: |
+ void TimedOut() { |
+ if (!waiting_for_.get()) |
+ return; |
+ MessageLoopForUI::current()->Quit(); |
+ } |
+ |
+ 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 { |
@@ -61,7 +252,15 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
content::DownloadDangerType danger_type; |
}; |
- virtual Browser* current_browser() { return browser(); } |
+ 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_); |
+ } |
+ |
+ Browser* current_browser() { return current_browser_; } |
// InProcessBrowserTest |
virtual void SetUpOnMainThread() OVERRIDE { |
@@ -69,14 +268,91 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
BrowserThread::IO, FROM_HERE, |
base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); |
InProcessBrowserTest::SetUpOnMainThread(); |
+ GoOnTheRecord(); |
CreateAndSetDownloadsDirectory(); |
current_browser()->profile()->GetPrefs()->SetBoolean( |
prefs::kPromptForDownload, false); |
- GetDownloadManager()->RemoveAllDownloads(); |
+ GetOnRecordManager()->RemoveAllDownloads(); |
+ events_listener_.reset(new DownloadsEventsListener()); |
+ } |
+ |
+ void GoOnTheRecord() { current_browser_ = browser(); } |
+ |
+ void GoOffTheRecord() { |
+ if (!incognito_browser_) { |
+ incognito_browser_ = CreateIncognitoBrowser(); |
+ GetOffRecordManager()->RemoveAllDownloads(); |
+ } |
+ 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, int expected_error, |
+ const std::string& on_created_event) { |
+ if (!WaitFor(extension_event_names::kOnDownloadCreated, on_created_event)) |
+ return false; |
+ // The item may or may not be interrupted before the onCreated event fires. |
+ if (item->IsInterrupted()) { |
+ scoped_ptr<base::Value> args(base::JSONReader::Read(on_created_event)); |
+ base::ListValue* args_list = NULL; |
+ base::DictionaryValue* args_dict = NULL; |
+ if (!args->GetAsList(&args_list) || |
+ !args_list->GetDictionary(0, &args_dict)) |
+ return false; |
+ args_dict->SetString("state", "interrupted"); |
+ args_dict->SetInteger("error", expected_error); |
+ std::string created_error; |
+ base::JSONWriter::Write(args_list, &created_error); |
+ // This is not waiting for a different event, it's refining the |
+ // expectations on the onCreated event that was just caught. Specifically, |
+ // if a DownloadItem is already interrupted by the time the onCreated |
+ // event fires, then the onCreated event should already describe the |
+ // error. |
+ return WaitFor(extension_event_names::kOnDownloadCreated, created_error); |
+ } else { |
+ return WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"error\": {\"current\": %d}," |
+ " \"state\": {" |
+ " \"previous\": \"in_progress\"," |
+ " \"current\": \"interrupted\"}}]", |
+ item->GetId(), |
+ expected_error)); |
+ } |
+ } |
+ |
+ std::string GetExtensionURL() { |
+ return extension_->url().spec(); |
+ } |
+ |
+ 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; |
} |
- virtual DownloadManager* GetDownloadManager() { |
- return BrowserContext::GetDownloadManager(current_browser()->profile()); |
+ 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| |
@@ -101,8 +377,8 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
false); // opened |
entries.push_back(entry); |
} |
- GetDownloadManager()->OnPersistentStoreQueryComplete(&entries); |
- GetDownloadManager()->GetAllDownloads(FilePath(), items); |
+ GetOnRecordManager()->OnPersistentStoreQueryComplete(&entries); |
+ GetOnRecordManager()->GetAllDownloads(FilePath(), items); |
EXPECT_EQ(count, items->size()); |
if (count != items->size()) |
return false; |
@@ -136,7 +412,7 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
// We don't expect a select file dialog. |
ASSERT_FALSE(observer->select_file_dialog_seen()); |
} |
- GetDownloadManager()->GetAllDownloads(FilePath(), items); |
+ GetCurrentManager()->GetAllDownloads(FilePath(), items); |
ASSERT_EQ(count, items->size()); |
} |
@@ -144,7 +420,7 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
scoped_ptr<DownloadTestObserver> observer( |
CreateInProgressDownloadObserver(1)); |
GURL slow_download_url(URLRequestSlowDownloadJob::kUnknownSizeUrl); |
- DownloadManager* manager = GetDownloadManager(); |
+ DownloadManager* manager = GetCurrentManager(); |
EXPECT_EQ(0, manager->InProgressCount()); |
if (manager->InProgressCount() != 0) |
@@ -188,15 +464,22 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
DownloadTestObserver* CreateDownloadObserver(size_t download_count) { |
return new DownloadTestObserverTerminal( |
- GetDownloadManager(), download_count, true, |
+ GetCurrentManager(), download_count, true, |
DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); |
} |
DownloadTestObserver* CreateInProgressDownloadObserver( |
size_t download_count) { |
- return new DownloadTestObserverInProgress(GetDownloadManager(), |
- download_count, |
- true); |
+ return new DownloadTestObserverInProgress( |
+ GetCurrentManager(), download_count, true); |
+ } |
+ |
+ 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() { |
@@ -207,25 +490,18 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
// 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. |
- |
- bool RunFunction(UIThreadExtensionFunction* function, |
- const std::string& args) { |
- // extension_function_test_utils::RunFunction() does not take |
- // ownership of |function|. |
- scoped_refptr<ExtensionFunction> function_owner(function); |
- return extension_function_test_utils::RunFunction( |
- function, args, browser(), GetFlags()); |
- } |
+ // on-record profile to match real-life behavior. |
base::Value* RunFunctionAndReturnResult(UIThreadExtensionFunction* function, |
const std::string& args) { |
+ SetUpExtensionFunction(function); |
return extension_function_test_utils::RunFunctionAndReturnResult( |
function, args, browser(), GetFlags()); |
} |
std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function, |
const std::string& args) { |
+ SetUpExtensionFunction(function); |
return extension_function_test_utils::RunFunctionAndReturnError( |
function, args, browser(), GetFlags()); |
} |
@@ -233,6 +509,7 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
bool RunFunctionAndReturnString(UIThreadExtensionFunction* function, |
const std::string& args, |
std::string* result_string) { |
+ SetUpExtensionFunction(function); |
scoped_ptr<base::Value> result(RunFunctionAndReturnResult(function, args)); |
EXPECT_TRUE(result.get()); |
return result.get() && result->GetAsString(result_string); |
@@ -269,7 +546,20 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
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. |
+ TabContents* tab = current_browser()->AddSelectedTabWithURL( |
+ extension_->GetResourceURL("empty.html"), |
+ content::PAGE_TRANSITION_LINK); |
+ function->set_extension(extension_); |
+ function->SetRenderViewHost(tab->web_contents()->GetRenderViewHost()); |
+ } |
+ } |
+ |
void CreateAndSetDownloadsDirectory() { |
ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir()); |
current_browser()->profile()->GetPrefs()->SetFilePath( |
@@ -278,26 +568,12 @@ class DownloadExtensionTest : public InProcessBrowserTest { |
} |
ScopedTempDir downloads_directory_; |
-}; |
- |
-class DownloadExtensionTestIncognito : public DownloadExtensionTest { |
- public: |
- virtual Browser* current_browser() OVERRIDE { return current_browser_; } |
- |
- virtual void SetUpOnMainThread() OVERRIDE { |
- GoOnTheRecord(); |
- DownloadExtensionTest::SetUpOnMainThread(); |
- incognito_browser_ = CreateIncognitoBrowser(); |
- GoOffTheRecord(); |
- GetDownloadManager()->RemoveAllDownloads(); |
- } |
- |
- void GoOnTheRecord() { current_browser_ = browser(); } |
- void GoOffTheRecord() { current_browser_ = incognito_browser_; } |
- |
- private: |
+ const extensions::Extension* extension_; |
Browser* incognito_browser_; |
Browser* current_browser_; |
+ scoped_ptr<DownloadsEventsListener> events_listener_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DownloadExtensionTest); |
}; |
class MockIconExtractorImpl : public DownloadFileIconExtractor { |
@@ -376,7 +652,195 @@ class ScopedItemVectorCanceller { |
DISALLOW_COPY_AND_ASSIGN(ScopedItemVectorCanceller); |
}; |
-} // namespace |
+class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { |
+ public: |
+ explicit TestProtocolHandler( |
+ webkit_blob::BlobStorageController* blob_storage_controller) |
+ : blob_storage_controller_(blob_storage_controller) {} |
+ |
+ virtual ~TestProtocolHandler() {} |
+ |
+ virtual net::URLRequestJob* MaybeCreateJob( |
+ net::URLRequest* request) const OVERRIDE { |
+ return new webkit_blob::BlobURLRequestJob( |
+ request, |
+ blob_storage_controller_->GetBlobDataFromUrl(request->url()), |
+ base::MessageLoopProxy::current()); |
+ } |
+ |
+ private: |
+ webkit_blob::BlobStorageController* const blob_storage_controller_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestProtocolHandler); |
+}; |
+ |
+class TestURLRequestContext : public net::URLRequestContext { |
+ public: |
+ TestURLRequestContext() |
+ : blob_storage_controller_(new webkit_blob::BlobStorageController) { |
+ // Job factory owns the protocol handler. |
+ job_factory_.SetProtocolHandler( |
+ "blob", new TestProtocolHandler(blob_storage_controller_.get())); |
+ set_job_factory(&job_factory_); |
+ } |
+ |
+ virtual ~TestURLRequestContext() {} |
+ |
+ webkit_blob::BlobStorageController* blob_storage_controller() const { |
+ return blob_storage_controller_.get(); |
+ } |
+ |
+ private: |
+ net::URLRequestJobFactory job_factory_; |
+ scoped_ptr<webkit_blob::BlobStorageController> blob_storage_controller_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestURLRequestContext); |
+}; |
+ |
+// TODO(benjhayden): Comment. |
+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::GetFileSystemContext(profile_)) { |
+ 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, ""); |
+ } |
+ |
+ bool WriteFile() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ fs_->OpenFileSystem( |
+ GURL(origin_), |
+ fileapi::kFileSystemTypeTemporary, |
+ kCreateFileSystem, |
+ 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; |
+ static const bool kCreateFileSystem = 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::FileSystemOperationInterface* operation() { |
+ return fs_->CreateFileSystemOperation(fileapi::FileSystemURL(GURL(root_))); |
+ } |
+ |
+ void CreateFile() { |
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ operation()->CreateFile(fileapi::FileSystemURL(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()); |
+ url_request_context_->blob_storage_controller()->AddFinishedBlob( |
+ blob_url(), blob_data_); |
+ operation()->Write( |
+ url_request_context_.get(), |
+ fileapi::FileSystemURL(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>( |
+ ¬ification_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>( |
+ ¬ification_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"; |
+ |
+} // namespace |
IN_PROC_BROWSER_TEST_F( |
DownloadExtensionTest, DownloadExtensionTest_PauseResumeCancel) { |
@@ -431,8 +895,17 @@ IN_PROC_BROWSER_TEST_F( |
EXPECT_STREQ(download_extension_errors::kInvalidOperationError, |
error.c_str()); |
- // Calling pause()/resume()/cancel() with invalid download Ids is |
- // tested in the API test (DownloadsApiTest). |
+ // Calling paused on a non-existent download yields kInvalidOperationError. |
+ error = RunFunctionAndReturnError( |
+ new DownloadsPauseFunction(), "[-42]"); |
+ EXPECT_STREQ(download_extension_errors::kInvalidOperationError, |
+ error.c_str()); |
+ |
+ // Calling resume on a non-existent download yields kInvalidOperationError |
+ error = RunFunctionAndReturnError( |
+ new DownloadsResumeFunction(), "[-42]"); |
+ EXPECT_STREQ(download_extension_errors::kInvalidOperationError, |
+ error.c_str()); |
} |
// Test downloads.getFileIcon() on in-progress, finished, cancelled and deleted |
@@ -528,9 +1001,6 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
error = RunFunctionAndReturnError(new DownloadsGetFileIconFunction(), args); |
EXPECT_STREQ(download_extension_errors::kInvalidOperationError, |
error.c_str()); |
- |
- // Asking for icons of other (invalid) sizes is tested in the API test |
- // (DownloadsApiTest). |
} |
// Test that we can acquire file icons for history downloads regardless of |
@@ -586,6 +1056,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
// The temporary files should be cleaned up when the ScopedTempDir is removed. |
} |
+// Test passing the empty query to search(). |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchEmptyQuery) { |
ScopedCancellingItem item(CreateSlowTestDownload()); |
@@ -599,6 +1070,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ASSERT_EQ(1UL, result_list->GetSize()); |
} |
+// Test the |filenameRegex| parameter for search(). |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchFilenameRegex) { |
const HistoryDownloadInfo kHistoryInfo[] = { |
@@ -626,6 +1098,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ASSERT_EQ(0, item_id); |
} |
+// Test the |id| parameter for search(). |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchId) { |
DownloadManager::DownloadVector items; |
CreateSlowTestDownloads(2, &items); |
@@ -644,6 +1117,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchId) { |
ASSERT_EQ(0, item_id); |
} |
+// Test specifying both the |id| and |filename| parameters for search(). |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchIdAndFilename) { |
DownloadManager::DownloadVector items; |
@@ -651,13 +1125,15 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ScopedItemVectorCanceller delete_items(&items); |
scoped_ptr<base::Value> result(RunFunctionAndReturnResult( |
- new DownloadsSearchFunction(), "[{\"id\": 0,\"filename\": \"foobar\"}]")); |
+ 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[] = { |
@@ -689,6 +1165,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
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[] = { |
@@ -720,6 +1197,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ASSERT_GT(item0_name, item1_name); |
} |
+// Test the |danger| option for search(). |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchDanger) { |
const HistoryDownloadInfo kHistoryInfo[] = { |
@@ -742,6 +1220,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ASSERT_EQ(1UL, result_list->GetSize()); |
} |
+// Test the |state| option for search(). |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchState) { |
DownloadManager::DownloadVector items; |
@@ -758,6 +1237,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ASSERT_EQ(1UL, result_list->GetSize()); |
} |
+// Test the |limit| option for search(). |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchLimit) { |
DownloadManager::DownloadVector items; |
@@ -772,6 +1252,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ASSERT_EQ(1UL, result_list->GetSize()); |
} |
+// Test invalid search parameters. |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchInvalid) { |
std::string error = RunFunctionAndReturnError( |
@@ -796,6 +1277,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
error.c_str()); |
} |
+// Test searching using multiple conditions through multiple downloads. |
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
DownloadExtensionTest_SearchPlural) { |
const HistoryDownloadInfo kHistoryInfo[] = { |
@@ -830,7 +1312,11 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
ASSERT_EQ(items[2]->GetFullPath().value(), item_name); |
} |
-IN_PROC_BROWSER_TEST_F(DownloadExtensionTestIncognito, |
+// 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; |
@@ -843,22 +1329,25 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTestIncognito, |
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); |
+ ASSERT_TRUE(off_item->IsOtr()); |
+ off_item_arg = DownloadItemIdAsArgList(off_item); |
GoOnTheRecord(); |
DownloadItem* on_item = CreateSlowTestDownload(); |
ASSERT_TRUE(on_item); |
ASSERT_FALSE(on_item->IsOtr()); |
on_item_arg = DownloadItemIdAsArgList(on_item); |
- |
- GoOffTheRecord(); |
- DownloadItem* off_item = CreateSlowTestDownload(); |
- ASSERT_TRUE(off_item); |
- ASSERT_TRUE(off_item->IsOtr()); |
ASSERT_TRUE(on_item->GetFullPath() != off_item->GetFullPath()); |
- off_item_arg = DownloadItemIdAsArgList(off_item); |
// 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()); |
@@ -904,9 +1393,6 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTestIncognito, |
EXPECT_STREQ(download_extension_errors::kInvalidOperationError, |
error.c_str()); |
- // TODO(benjhayden): Test incognito_split_mode() extension. |
- // TODO(benjhayden): Test download(), onCreated, onChanged, onErased. |
- |
GoOffTheRecord(); |
// Do the FileIcon test for both the on- and off-items while off the record. |
@@ -964,3 +1450,709 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTestIncognito, |
EXPECT_STREQ(download_extension_errors::kInvalidOperationError, |
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"); |
+ CHECK(StartTestServer()); |
+ std::string download_url = test_server()->GetURL("slow?0").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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"filename\": \"%s\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"text/plain\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", |
+ GetFilename("slow.txt.crdownload").c_str(), |
+ download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"filename\": {" |
+ " \"previous\": \"%s\"," |
+ " \"current\": \"%s\"}," |
+ " \"state\": {" |
+ " \"previous\": \"in_progress\"," |
+ " \"current\": \"complete\"}}]", |
+ result_id, |
+ GetFilename("slow.txt.crdownload").c_str(), |
+ GetFilename("slow.txt").c_str()))); |
+} |
+ |
+// 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"); |
+ CHECK(StartTestServer()); |
+ GoOffTheRecord(); |
+ std::string download_url = test_server()->GetURL("slow?0").spec(); |
+ |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"filename\": \"%s\"," |
+ " \"incognito\": true," |
+ " \"mime\": \"text/plain\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", |
+ GetFilename("slow.txt.crdownload").c_str(), |
+ download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\":%d," |
+ " \"filename\": {" |
+ " \"previous\": \"%s\"," |
+ " \"current\": \"%s\"}," |
+ " \"state\": {" |
+ " \"current\": \"complete\"," |
+ " \"previous\": \"in_progress\"}}]", |
+ result_id, |
+ GetFilename("slow.txt.crdownload").c_str(), |
+ GetFilename("slow.txt").c_str()))); |
+} |
+ |
+// Test that we disallow certain headers case-insensitively. |
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
+ DownloadExtensionTest_Download_UnsafeHeaders) { |
+ LoadExtension("downloads_split"); |
+ CHECK(StartTestServer()); |
+ 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(download_extension_errors::kGenericError, |
+ 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()); |
+ } |
+} |
+ |
+// Test that subdirectories (slashes) are disallowed in filenames. |
+// TODO(benjhayden) Update this when subdirectories are supported. |
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
+ DownloadExtensionTest_Download_Subdirectory) { |
+ LoadExtension("downloads_split"); |
+ CHECK(StartTestServer()); |
+ std::string download_url = test_server()->GetURL("slow?0").spec(); |
+ GoOnTheRecord(); |
+ |
+ EXPECT_STREQ(download_extension_errors::kGenericError, |
+ RunFunctionAndReturnError(new DownloadsDownloadFunction(), |
+ base::StringPrintf( |
+ "[{\"url\": \"%s\"," |
+ " \"filename\": \"sub/dir/ect/ory.txt\"}]", |
+ download_url.c_str())).c_str()); |
+} |
+ |
+// Test that invalid filenames are disallowed. |
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
+ DownloadExtensionTest_Download_InvalidFilename) { |
+ LoadExtension("downloads_split"); |
+ CHECK(StartTestServer()); |
+ std::string download_url = test_server()->GetURL("slow?0").spec(); |
+ GoOnTheRecord(); |
+ |
+ EXPECT_STREQ(download_extension_errors::kGenericError, |
+ 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", |
+ "google.com/", |
+ "http://", |
+ "#frag", |
+ "foo/bar.html#frag", |
+ "javascript:document.write(\\\"hello\\\");", |
+ "javascript:return false;", |
+ "ftp://example.com/example.txt", |
+ }; |
+ |
+ for (size_t index = 0; index < arraysize(kInvalidURLs); ++index) { |
+ EXPECT_STREQ(download_extension_errors::kInvalidURLError, |
+ RunFunctionAndReturnError(new DownloadsDownloadFunction(), |
+ base::StringPrintf( |
+ "[{\"url\": \"%s\"}]", kInvalidURLs[index])).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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"filename\": \"%s\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"text/plain\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", |
+ GetFilename("slow.txt.crdownload").c_str(), |
+ download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"filename\": {" |
+ " \"previous\": \"%s\"," |
+ " \"current\": \"%s\"}," |
+ " \"state\": {" |
+ " \"previous\": \"in_progress\"," |
+ " \"current\": \"complete\"}}]", |
+ result_id, |
+ GetFilename("slow.txt.crdownload").c_str(), |
+ GetFilename("slow.txt").c_str()))); |
+} |
+ |
+// Valid data URLs are valid URLs. |
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
+ DownloadExtensionTest_Download_DataURL) { |
+ LoadExtension("downloads_split"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"filename\": \"%s\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"text/plain\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", |
+ GetFilename("data.txt.crdownload").c_str(), |
+ download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"filename\": {" |
+ " \"previous\": \"%s\"," |
+ " \"current\": \"%s\"}," |
+ " \"state\": {" |
+ " \"previous\": \"in_progress\"," |
+ " \"current\": \"complete\"}}]", |
+ result_id, |
+ GetFilename("data.txt.crdownload").c_str(), |
+ GetFilename("data.txt").c_str()))); |
+} |
+ |
+// Valid file URLs are valid URLs. |
+IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, |
+ DownloadExtensionTest_Download_File) { |
+ GoOnTheRecord(); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"filename\": \"%s\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"text/html\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", |
+ GetFilename("file.txt.crdownload").c_str(), |
+ download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"filename\": {" |
+ " \"previous\": \"%s\"," |
+ " \"current\": \"%s\"}," |
+ " \"state\": {" |
+ " \"previous\": \"in_progress\"," |
+ " \"current\": \"complete\"}}]", |
+ result_id, |
+ GetFilename("file.txt.crdownload").c_str(), |
+ GetFilename("file.txt").c_str()))); |
+} |
+ |
+// 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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitForInterruption(item, 30, 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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"application/octet-stream\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", |
+ download_url.c_str()))); |
+ std::string incomplete_filename = GetFilename( |
+ "headers-succeed.txt.crdownload"); |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"filename\": {" |
+ " \"previous\": \"%s\"," |
+ " \"current\": \"%s\"}," |
+ " \"state\": {" |
+ " \"previous\": \"in_progress\"," |
+ " \"current\": \"complete\"}}]", |
+ result_id, |
+ incomplete_filename.c_str(), |
+ GetFilename("headers-succeed.txt").c_str()))); |
+} |
+ |
+// 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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitForInterruption(item, 33, 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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"text/html\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"application/octet-stream\"," |
+ " \"paused\": false," |
+ " \"bytesReceived\": 164," |
+ " \"url\": \"%s\"}]", download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitForInterruption(item, 33, 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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitForInterruption(item, 33, 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"); |
+ CHECK(StartTestServer()); |
+ 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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::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(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"error\": {\"current\": 40}," |
+ " \"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()->GetActiveDownloadItem(result_id); |
+ if (!item) item = GetCurrentManager()->GetDownloadItem(result_id); |
+ ASSERT_TRUE(item); |
+ ScopedCancellingItem canceller(item); |
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); |
+ |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadCreated, |
+ base::StringPrintf("[{\"danger\": \"safe\"," |
+ " \"filename\": \"%s\"," |
+ " \"incognito\": false," |
+ " \"mime\": \"text/plain\"," |
+ " \"paused\": false," |
+ " \"url\": \"%s\"}]", |
+ GetFilename("on_record.txt.crdownload").c_str(), |
+ download_url.c_str()))); |
+ ASSERT_TRUE(WaitFor(extension_event_names::kOnDownloadChanged, |
+ base::StringPrintf("[{\"id\": %d," |
+ " \"filename\": {" |
+ " \"previous\": \"%s\"," |
+ " \"current\": \"%s\"}," |
+ " \"state\": {" |
+ " \"previous\": \"in_progress\"," |
+ " \"current\": \"complete\"}}]", |
+ result_id, |
+ GetFilename("on_record.txt.crdownload").c_str(), |
+ GetFilename("on_record.txt").c_str()))); |
+ std::string disk_data; |
+ EXPECT_TRUE(file_util::ReadFileToString(item->GetFullPath(), &disk_data)); |
+ EXPECT_STREQ(kPayloadData, disk_data.c_str()); |
+} |