diff options
Diffstat (limited to 'tests/ceftests/download_unittest.cc')
-rw-r--r-- | tests/ceftests/download_unittest.cc | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/tests/ceftests/download_unittest.cc b/tests/ceftests/download_unittest.cc new file mode 100644 index 00000000..c31c7049 --- /dev/null +++ b/tests/ceftests/download_unittest.cc @@ -0,0 +1,600 @@ +// Copyright (c) 2013 The Chromium Embedded Framework 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 <memory> + +#include "include/base/cef_callback.h" +#include "include/base/cef_callback_helpers.h" +#include "include/cef_scheme.h" +#include "include/wrapper/cef_closure_task.h" +#include "include/wrapper/cef_scoped_temp_dir.h" +#include "tests/ceftests/test_handler.h" +#include "tests/ceftests/test_util.h" +#include "tests/gtest/include/gtest/gtest.h" +#include "tests/shared/browser/file_util.h" + +namespace { + +const char kTestDomain[] = "test-download.com"; +const char kTestStartUrl[] = "http://test-download.com/test.html"; +const char kTestDownloadUrl[] = "http://test-download.com/download.txt"; +const char kTestNavUrl[] = "http://test-download-nav.com/nav.html"; +const char kTestFileName[] = "download_test.txt"; +const char kTestContentDisposition[] = + "attachment; filename=\"download_test.txt\""; +const char kTestMimeType[] = "text/plain"; +const char kTestContent[] = "Download test text"; + +using DelayCallback = base::OnceCallback<void(base::OnceClosure /*callback*/)>; + +class DownloadSchemeHandler : public CefResourceHandler { + public: + DownloadSchemeHandler(DelayCallback delay_callback, + TrackCallback* got_download_request) + : delay_callback_(std::move(delay_callback)), + got_download_request_(got_download_request), + should_delay_(false), + offset_(0) {} + + bool Open(CefRefPtr<CefRequest> request, + bool& handle_request, + CefRefPtr<CefCallback> callback) override { + EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO)); + + std::string url = request->GetURL(); + if (url == kTestDownloadUrl) { + got_download_request_->yes(); + content_ = kTestContent; + mime_type_ = kTestMimeType; + content_disposition_ = kTestContentDisposition; + should_delay_ = true; + } else { + EXPECT_TRUE(false); // Not reached. + + // Cancel immediately. + handle_request = true; + return false; + } + + // Continue immediately. + handle_request = true; + return true; + } + + void GetResponseHeaders(CefRefPtr<CefResponse> response, + int64& response_length, + CefString& redirectUrl) override { + response_length = content_.size(); + + response->SetStatus(200); + response->SetMimeType(mime_type_); + + if (!content_disposition_.empty()) { + CefResponse::HeaderMap headerMap; + response->GetHeaderMap(headerMap); + headerMap.insert( + std::make_pair("Content-Disposition", content_disposition_)); + response->SetHeaderMap(headerMap); + } + } + + bool Read(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr<CefResourceReadCallback> callback) override { + EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO)); + + bytes_read = 0; + + if (should_delay_ && !delay_callback_.is_null()) { + // Delay the download response a single time. + std::move(delay_callback_) + .Run(base::BindOnce(&DownloadSchemeHandler::ContinueRead, this, + data_out, bytes_to_read, callback)); + return true; + } + + return DoRead(data_out, bytes_to_read, bytes_read); + } + + void Cancel() override {} + + private: + void ContinueRead(void* data_out, + int bytes_to_read, + CefRefPtr<CefResourceReadCallback> callback) { + int bytes_read = 0; + DoRead(data_out, bytes_to_read, bytes_read); + callback->Continue(bytes_read); + } + + bool DoRead(void* data_out, int bytes_to_read, int& bytes_read) { + bool has_data = false; + size_t size = content_.size(); + if (offset_ < size) { + int transfer_size = + std::min(bytes_to_read, static_cast<int>(size - offset_)); + memcpy(data_out, content_.c_str() + offset_, transfer_size); + offset_ += transfer_size; + + bytes_read = transfer_size; + has_data = true; + } + + return has_data; + } + + DelayCallback delay_callback_; + TrackCallback* got_download_request_; + bool should_delay_; + std::string content_; + std::string mime_type_; + std::string content_disposition_; + size_t offset_; + CefRefPtr<CefResourceReadCallback> read_callback_; + + IMPLEMENT_REFCOUNTING(DownloadSchemeHandler); + DISALLOW_COPY_AND_ASSIGN(DownloadSchemeHandler); +}; + +using DelayCallbackVendor = base::RepeatingCallback<DelayCallback(void)>; + +class DownloadSchemeHandlerFactory : public CefSchemeHandlerFactory { + public: + DownloadSchemeHandlerFactory(const DelayCallbackVendor& delay_callback_vendor, + TrackCallback* got_download_request) + : delay_callback_vendor_(delay_callback_vendor), + got_download_request_(got_download_request) {} + + CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> browser, + CefRefPtr<CefFrame> frame, + const CefString& scheme_name, + CefRefPtr<CefRequest> request) override { + return new DownloadSchemeHandler(delay_callback_vendor_.is_null() + ? base::NullCallback() + : delay_callback_vendor_.Run(), + got_download_request_); + } + + private: + DelayCallbackVendor delay_callback_vendor_; + TrackCallback* got_download_request_; + + IMPLEMENT_REFCOUNTING(DownloadSchemeHandlerFactory); + DISALLOW_COPY_AND_ASSIGN(DownloadSchemeHandlerFactory); +}; + +class DownloadTestHandler : public TestHandler { + public: + enum TestMode { + PROGRAMMATIC, + NAVIGATED, + PENDING, + CLICKED, + CLICKED_INVALID, + CLICKED_BLOCKED, + }; + + DownloadTestHandler(TestMode test_mode, + TestRequestContextMode rc_mode, + const std::string& rc_cache_path) + : test_mode_(test_mode), + rc_mode_(rc_mode), + rc_cache_path_(rc_cache_path), + download_id_(0), + verified_results_(false) {} + + bool is_clicked() const { + return test_mode_ == CLICKED || test_mode_ == CLICKED_INVALID || + test_mode_ == CLICKED_BLOCKED; + } + + bool is_clicked_and_downloaded() const { return test_mode_ == CLICKED; } + + bool is_downloaded() const { + return test_mode_ == PROGRAMMATIC || test_mode_ == NAVIGATED || + is_clicked_and_downloaded(); + } + + void RunTest() override { + DelayCallbackVendor delay_callback_vendor; + if (test_mode_ == NAVIGATED || test_mode_ == PENDING) { + delay_callback_vendor = base::BindRepeating( + [](CefRefPtr<DownloadTestHandler> self) { + return base::BindOnce(&DownloadTestHandler::OnDelayCallback, self); + }, + CefRefPtr<DownloadTestHandler>(this)); + } + + CefRefPtr<CefSchemeHandlerFactory> scheme_factory = + new DownloadSchemeHandlerFactory(delay_callback_vendor, + &got_download_request_); + + CefRefPtr<CefRequestContext> request_context = + CreateTestRequestContext(rc_mode_, rc_cache_path_); + if (request_context) { + request_context->RegisterSchemeHandlerFactory("http", kTestDomain, + scheme_factory); + } else { + CefRegisterSchemeHandlerFactory("http", kTestDomain, scheme_factory); + } + + if (!is_clicked() || is_clicked_and_downloaded()) { + // Create a new temporary directory. + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + test_path_ = + client::file_util::JoinPath(temp_dir_.GetPath(), kTestFileName); + } + + if (test_mode_ == NAVIGATED) { + // Add the resource that we'll navigate to. + AddResource(kTestNavUrl, "<html><body>Navigated</body></html>", + "text/html"); + } + + if (is_clicked()) { + if (test_mode_ == CLICKED || test_mode_ == CLICKED_BLOCKED) { + download_url_ = kTestDownloadUrl; + } else if (test_mode_ == CLICKED_INVALID) { + download_url_ = "invalid:foo@example.com"; + } else { + EXPECT_TRUE(false); // Not reached. + } + AddResource(kTestStartUrl, + "<html><body><a href=\"" + download_url_ + + "\">CLICK ME</a></body></html>", + "text/html"); + } else { + download_url_ = kTestStartUrl; + AddResource(kTestStartUrl, "<html><body>Download Test</body></html>", + "text/html"); + } + + // Create the browser + CreateBrowser(kTestStartUrl, request_context); + + // Time out the test after a reasonable period of time. + SetTestTimeout(); + } + + void OnLoadEnd(CefRefPtr<CefBrowser> browser, + CefRefPtr<CefFrame> frame, + int httpStatusCode) override { + const std::string& url = frame->GetURL().ToString(); + if (url == kTestNavUrl) { + got_nav_load_.yes(); + ContinueNavigatedIfReady(); + return; + } + + if (is_clicked()) { + // Begin the download by clicking a link. + // ALT key will trigger download of custom protocol links. + SendClick(browser, + test_mode_ == CLICKED_INVALID ? EVENTFLAG_ALT_DOWN : 0); + + if (is_clicked() && !is_clicked_and_downloaded()) { + // Destroy the test after a bit because there will be no further + // callbacks. + CefPostDelayedTask( + TID_UI, base::BindOnce(&DownloadTestHandler::DestroyTest, this), + 200); + } + } else { + // Begin the download progammatically. + browser->GetHost()->StartDownload(kTestDownloadUrl); + } + } + + // Callback from the scheme handler when the download request is delayed. + void OnDelayCallback(base::OnceClosure callback) { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, base::BindOnce(&DownloadTestHandler::OnDelayCallback, + this, std::move(callback))); + return; + } + + got_delay_callback_.yes(); + + if (test_mode_ == NAVIGATED) { + delay_callback_ = std::move(callback); + ContinueNavigatedIfReady(); + } else if (test_mode_ == PENDING) { + ContinuePendingIfReady(); + } else { + EXPECT_TRUE(false); // Not reached. + } + } + + void ContinueNavigatedIfReady() { + EXPECT_EQ(test_mode_, NAVIGATED); + if (got_delay_callback_ && got_nav_load_) { + EXPECT_FALSE(delay_callback_.is_null()); + std::move(delay_callback_).Run(); + } + } + + void ContinuePendingIfReady() { + EXPECT_EQ(test_mode_, PENDING); + if (got_delay_callback_ && got_on_before_download_ && + got_on_download_updated_) { + // Destroy the test without waiting for the download to complete. + DestroyTest(); + } + } + + bool CanDownload(CefRefPtr<CefBrowser> browser, + const CefString& url, + const CefString& request_method) override { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_FALSE(got_can_download_); + EXPECT_FALSE(got_on_before_download_); + EXPECT_TRUE(is_clicked()); + + got_can_download_.yes(); + + EXPECT_TRUE(browser->IsSame(GetBrowser())); + EXPECT_STREQ(download_url_.c_str(), url.ToString().c_str()); + EXPECT_STREQ("GET", request_method.ToString().c_str()); + + return test_mode_ != CLICKED_BLOCKED; + } + + void OnBeforeDownload( + CefRefPtr<CefBrowser> browser, + CefRefPtr<CefDownloadItem> download_item, + const CefString& suggested_name, + CefRefPtr<CefBeforeDownloadCallback> callback) override { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_FALSE(got_on_before_download_); + + if (is_clicked()) { + EXPECT_TRUE(got_can_download_); + } else { + EXPECT_FALSE(got_can_download_); + } + + got_on_before_download_.yes(); + + EXPECT_TRUE(browser->IsSame(GetBrowser())); + EXPECT_STREQ(kTestFileName, suggested_name.ToString().c_str()); + EXPECT_TRUE(download_item.get()); + EXPECT_TRUE(callback.get()); + + download_id_ = download_item->GetId(); + EXPECT_LT(0U, download_id_); + + EXPECT_TRUE(download_item->IsValid()); + EXPECT_TRUE(download_item->IsInProgress()); + EXPECT_FALSE(download_item->IsComplete()); + EXPECT_FALSE(download_item->IsCanceled()); + EXPECT_EQ(static_cast<int64>(sizeof(kTestContent) - 1), + download_item->GetTotalBytes()); + EXPECT_EQ(0UL, download_item->GetFullPath().length()); + EXPECT_STREQ(kTestDownloadUrl, download_item->GetURL().ToString().c_str()); + EXPECT_EQ(0UL, download_item->GetSuggestedFileName().length()); + EXPECT_STREQ(kTestContentDisposition, + download_item->GetContentDisposition().ToString().c_str()); + EXPECT_STREQ(kTestMimeType, + download_item->GetMimeType().ToString().c_str()); + + callback->Continue(test_path_, false); + + if (test_mode_ == NAVIGATED) { + CefRefPtr<CefFrame> main_frame = browser->GetMainFrame(); + EXPECT_TRUE(main_frame->IsMain()); + main_frame->LoadURL(kTestNavUrl); + } else if (test_mode_ == PENDING) { + ContinuePendingIfReady(); + } + } + + void OnDownloadUpdated(CefRefPtr<CefBrowser> browser, + CefRefPtr<CefDownloadItem> download_item, + CefRefPtr<CefDownloadItemCallback> callback) override { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + + if (destroyed_) { + return; + } + + got_on_download_updated_.yes(); + + EXPECT_TRUE(browser->IsSame(GetBrowser())); + EXPECT_TRUE(download_item.get()); + EXPECT_TRUE(callback.get()); + + if (got_on_before_download_) { + EXPECT_EQ(download_id_, download_item->GetId()); + } + + EXPECT_LE(0LL, download_item->GetCurrentSpeed()); + EXPECT_LE(0, download_item->GetPercentComplete()); + + EXPECT_TRUE(download_item->IsValid()); + EXPECT_FALSE(download_item->IsCanceled()); + EXPECT_STREQ(kTestDownloadUrl, download_item->GetURL().ToString().c_str()); + EXPECT_STREQ(kTestContentDisposition, + download_item->GetContentDisposition().ToString().c_str()); + EXPECT_STREQ(kTestMimeType, + download_item->GetMimeType().ToString().c_str()); + + std::string full_path = download_item->GetFullPath(); + if (!full_path.empty()) { + got_full_path_.yes(); + EXPECT_STREQ(test_path_.c_str(), full_path.c_str()); + } + + if (download_item->IsComplete()) { + got_download_complete_.yes(); + + EXPECT_FALSE(download_item->IsInProgress()); + EXPECT_EQ(100, download_item->GetPercentComplete()); + EXPECT_EQ(static_cast<int64>(sizeof(kTestContent) - 1), + download_item->GetReceivedBytes()); + EXPECT_EQ(static_cast<int64>(sizeof(kTestContent) - 1), + download_item->GetTotalBytes()); + + DestroyTest(); + } else { + EXPECT_TRUE(download_item->IsInProgress()); + EXPECT_LE(0LL, download_item->GetReceivedBytes()); + } + + if (test_mode_ == PENDING) { + download_item_callback_ = callback; + ContinuePendingIfReady(); + } + } + + void VerifyResultsOnFileThread() { + EXPECT_TRUE(CefCurrentlyOn(TID_FILE_USER_VISIBLE)); + + if (test_mode_ != PENDING) { + // Verify the file contents. + std::string contents; + EXPECT_TRUE(client::file_util::ReadFileToString(test_path_, &contents)); + EXPECT_STREQ(kTestContent, contents.c_str()); + } + + EXPECT_TRUE(temp_dir_.Delete()); + EXPECT_TRUE(temp_dir_.IsEmpty()); + + CefPostTask(TID_UI, + base::BindOnce(&DownloadTestHandler::DestroyTest, this)); + } + + void DestroyTest() override { + if (!verified_results_ && !temp_dir_.IsEmpty()) { + // Avoid an endless failure loop. + verified_results_ = true; + // Clean up temp_dir_ on the FILE thread before destroying the test. + CefPostTask(TID_FILE_USER_VISIBLE, + base::BindOnce( + &DownloadTestHandler::VerifyResultsOnFileThread, this)); + return; + } + + destroyed_ = true; + + if (download_item_callback_) { + // Cancel the pending download to avoid leaking request objects. + download_item_callback_->Cancel(); + download_item_callback_ = nullptr; + } + + if (request_context_) { + request_context_->RegisterSchemeHandlerFactory("http", kTestDomain, + nullptr); + request_context_ = nullptr; + } else { + CefRegisterSchemeHandlerFactory("http", kTestDomain, nullptr); + } + + if (is_clicked()) { + EXPECT_TRUE(got_can_download_); + } else { + EXPECT_FALSE(got_can_download_); + } + + if (test_mode_ == CLICKED_INVALID) { + // The invalid protocol request is not handled. + EXPECT_FALSE(got_download_request_); + } else { + EXPECT_TRUE(got_download_request_); + } + + if (is_clicked() && !is_clicked_and_downloaded()) { + // The download never proceeds. + EXPECT_FALSE(got_on_before_download_); + EXPECT_FALSE(got_on_download_updated_); + } else { + EXPECT_TRUE(got_on_before_download_); + EXPECT_TRUE(got_on_download_updated_); + } + + if (test_mode_ == NAVIGATED) { + EXPECT_TRUE(got_nav_load_); + } else { + EXPECT_FALSE(got_nav_load_); + } + + if (!is_downloaded()) { + // The download never completes. + EXPECT_FALSE(got_download_complete_); + EXPECT_FALSE(got_full_path_); + } else { + EXPECT_TRUE(got_download_complete_); + EXPECT_TRUE(got_full_path_); + } + + TestHandler::DestroyTest(); + } + + private: + void SendClick(CefRefPtr<CefBrowser> browser, uint32_t modifiers) { + EXPECT_TRUE(is_clicked()); + CefMouseEvent mouse_event; + mouse_event.x = 20; + mouse_event.y = 20; + mouse_event.modifiers = modifiers; + SendMouseClickEvent(browser, mouse_event); + } + + const TestMode test_mode_; + const TestRequestContextMode rc_mode_; + const std::string rc_cache_path_; + + CefRefPtr<CefRequestContext> request_context_; + + // Used with NAVIGATED and PENDING test modes. + base::OnceClosure delay_callback_; + + // Used with PENDING test mode. + CefRefPtr<CefDownloadItemCallback> download_item_callback_; + + std::string download_url_; + CefScopedTempDir temp_dir_; + std::string test_path_; + uint32 download_id_; + bool verified_results_; + bool destroyed_ = false; + + TrackCallback got_download_request_; + TrackCallback got_can_download_; + TrackCallback got_on_before_download_; + TrackCallback got_on_download_updated_; + TrackCallback got_full_path_; + TrackCallback got_download_complete_; + TrackCallback got_delay_callback_; + TrackCallback got_nav_load_; + + IMPLEMENT_REFCOUNTING(DownloadTestHandler); +}; + +} // namespace + +#define DOWNLOAD_TEST_GROUP(test_name, test_mode) \ + RC_TEST_GROUP_ALL(DownloadTest, test_name, DownloadTestHandler, test_mode) + +// Test a programmatic download. +DOWNLOAD_TEST_GROUP(Programmatic, PROGRAMMATIC) + +// Test a clicked download. +DOWNLOAD_TEST_GROUP(Clicked, CLICKED) + +// Test a clicked download where the protocol is invalid and therefore rejected. +// There will be no resulting CefDownloadHandler callbacks. +DOWNLOAD_TEST_GROUP(ClickedInvalid, CLICKED_INVALID) + +// Test a clicked download where CanDownload returns false. +// There will be no resulting CefDownloadHandler callbacks. +DOWNLOAD_TEST_GROUP(ClickedBlocked, CLICKED_BLOCKED) + +// Test where the download completes after cross-origin navigation. +DOWNLOAD_TEST_GROUP(Navigated, NAVIGATED) + +// Test where the download is still pending when the browser is destroyed. +DOWNLOAD_TEST_GROUP(Pending, PENDING) |