| // 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 "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"; |
| |
| typedef base::Callback<void(const base::Closure& /*callback*/)> DelayCallback; |
| |
| class DownloadSchemeHandler : public CefResourceHandler { |
| public: |
| DownloadSchemeHandler(const DelayCallback& delay_callback, |
| TrackCallback* got_download_request) |
| : delay_callback_(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. |
| delay_callback_.Run(base::Bind(&DownloadSchemeHandler::ContinueRead, this, |
| data_out, bytes_to_read, callback)); |
| delay_callback_.Reset(); |
| 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); |
| }; |
| |
| class DownloadSchemeHandlerFactory : public CefSchemeHandlerFactory { |
| public: |
| DownloadSchemeHandlerFactory(const DelayCallback& delay_callback, |
| TrackCallback* got_download_request) |
| : delay_callback_(delay_callback), |
| 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_, got_download_request_); |
| } |
| |
| private: |
| DelayCallback delay_callback_; |
| TrackCallback* got_download_request_; |
| |
| IMPLEMENT_REFCOUNTING(DownloadSchemeHandlerFactory); |
| DISALLOW_COPY_AND_ASSIGN(DownloadSchemeHandlerFactory); |
| }; |
| |
| class DownloadTestHandler : public TestHandler { |
| public: |
| enum TestMode { |
| PROGAMMATIC, |
| NAVIGATED, |
| PENDING, |
| CLICKED, |
| CLICKED_REJECTED, |
| }; |
| |
| 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_REJECTED; |
| } |
| |
| void RunTest() override { |
| DelayCallback delay_callback; |
| if (test_mode_ == NAVIGATED || test_mode_ == PENDING) |
| delay_callback = base::Bind(&DownloadTestHandler::OnDelayCallback, this); |
| |
| CefRefPtr<CefSchemeHandlerFactory> scheme_factory = |
| new DownloadSchemeHandlerFactory(delay_callback, |
| &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 (test_mode_ != CLICKED_REJECTED) { |
| // 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()) { |
| std::string url; |
| if (test_mode_ == CLICKED) { |
| url = kTestDownloadUrl; |
| } else if (test_mode_ == CLICKED_REJECTED) { |
| url = "invalid:foo@example.com"; |
| } else { |
| EXPECT_TRUE(false); // Not reached. |
| } |
| AddResource( |
| kTestStartUrl, |
| "<html><body><a href=\"" + url + "\">CLICK ME</a></body></html>", |
| "text/html"); |
| } else { |
| 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_REJECTED ? EVENTFLAG_ALT_DOWN : 0); |
| |
| if (test_mode_ == CLICKED_REJECTED) { |
| // Destroy the test after a bit because there will be no further |
| // callbacks. |
| CefPostDelayedTask( |
| TID_UI, base::Bind(&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(const base::Closure& callback) { |
| if (!CefCurrentlyOn(TID_UI)) { |
| CefPostTask(TID_UI, base::Bind(&DownloadTestHandler::OnDelayCallback, |
| this, callback)); |
| return; |
| } |
| |
| got_delay_callback_.yes(); |
| |
| if (test_mode_ == NAVIGATED) { |
| delay_callback_ = 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()); |
| delay_callback_.Run(); |
| delay_callback_.Reset(); |
| } |
| } |
| |
| 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(); |
| } |
| } |
| |
| 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_); |
| |
| 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)); |
| |
| 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::Bind(&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, |
| base::Bind(&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 (test_mode_ == CLICKED_REJECTED) { |
| EXPECT_FALSE(got_download_request_); |
| EXPECT_FALSE(got_on_before_download_); |
| EXPECT_FALSE(got_on_download_updated_); |
| } else { |
| EXPECT_TRUE(got_download_request_); |
| 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 (test_mode_ == PENDING || test_mode_ == CLICKED_REJECTED) { |
| 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; |
| browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, false, 1); |
| browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, true, 1); |
| } |
| |
| 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::Closure delay_callback_; |
| |
| // Used with PENDING test mode. |
| CefRefPtr<CefDownloadItemCallback> download_item_callback_; |
| |
| CefScopedTempDir temp_dir_; |
| std::string test_path_; |
| uint32 download_id_; |
| bool verified_results_; |
| bool destroyed_ = false; |
| |
| TrackCallback got_download_request_; |
| 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, PROGAMMATIC) |
| |
| // 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(ClickedRejected, CLICKED_REJECTED) |
| |
| // 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) |