diff --git a/cef.gyp b/cef.gyp index 58d0a84a7..c42b98144 100644 --- a/cef.gyp +++ b/cef.gyp @@ -247,6 +247,7 @@ 'tests/unittests/dialog_unittest.cc', 'tests/unittests/display_unittest.cc', 'tests/unittests/dom_unittest.cc', + 'tests/unittests/download_unittest.cc', 'tests/unittests/geolocation_unittest.cc', 'tests/unittests/jsdialog_unittest.cc', 'tests/unittests/navigation_unittest.cc', diff --git a/include/capi/cef_browser_capi.h b/include/capi/cef_browser_capi.h index b10c64c89..02dd9136b 100644 --- a/include/capi/cef_browser_capi.h +++ b/include/capi/cef_browser_capi.h @@ -298,6 +298,12 @@ typedef struct _cef_browser_host_t { const cef_string_t* default_file_name, cef_string_list_t accept_types, struct _cef_run_file_dialog_callback_t* callback); + /// + // Download the file at |url| using cef_download_handler_t. + /// + void (CEF_CALLBACK *start_download)(struct _cef_browser_host_t* self, + const cef_string_t* url); + /// // Returns true (1) if window rendering is disabled. /// diff --git a/include/cef_browser.h b/include/cef_browser.h index c19e5f502..e3fbf03a1 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -339,6 +339,12 @@ class CefBrowserHost : public virtual CefBase { const std::vector& accept_types, CefRefPtr callback) =0; + /// + // Download the file at |url| using CefDownloadHandler. + /// + /*--cef()--*/ + virtual void StartDownload(const CefString& url) =0; + /// // Returns true if window rendering is disabled. /// diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index eb4abaed3..50a56700d 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -29,6 +29,8 @@ #include "base/bind_helpers.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/download_url_parameters.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" @@ -555,6 +557,35 @@ void CefBrowserHostImpl::RunFileDialog( base::Bind(&CefRunFileDialogCallbackWrapper::Callback, wrapper)); } +void CefBrowserHostImpl::StartDownload(const CefString& url) { + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK(CEF_UIT, + base::Bind(&CefBrowserHostImpl::StartDownload, this, url)); + return; + } + + GURL gurl = GURL(url.ToString()); + if (gurl.is_empty() || !gurl.is_valid()) + return; + + if (!web_contents()) + return; + + CefBrowserContext* context = _Context->browser_context(); + if (!context) + return; + + scoped_refptr manager = + content::BrowserContext::GetDownloadManager(context); + if (!manager) + return; + + scoped_ptr params; + params.reset( + content::DownloadUrlParameters::FromWebContents(web_contents(), gurl)); + manager->DownloadUrl(params.Pass()); +} + bool CefBrowserHostImpl::IsWindowRenderingDisabled() { return IsWindowRenderingDisabled(window_info_); } diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 56a08ad26..1f3cb5039 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -124,6 +124,7 @@ class CefBrowserHostImpl : public CefBrowserHost, const CefString& default_file_name, const std::vector& accept_types, CefRefPtr callback) OVERRIDE; + virtual void StartDownload(const CefString& url) OVERRIDE; virtual bool IsWindowRenderingDisabled() OVERRIDE; virtual void WasResized() OVERRIDE; virtual void Invalidate(const CefRect& dirtyRect, diff --git a/libcef_dll/cpptoc/browser_host_cpptoc.cc b/libcef_dll/cpptoc/browser_host_cpptoc.cc index 4e914fc53..d0ec3a06a 100644 --- a/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -262,6 +262,23 @@ void CEF_CALLBACK browser_host_run_file_dialog(struct _cef_browser_host_t* self, CefRunFileDialogCallbackCToCpp::Wrap(callback)); } +void CEF_CALLBACK browser_host_start_download(struct _cef_browser_host_t* self, + const cef_string_t* url) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: url; type: string_byref_const + DCHECK(url); + if (!url) + return; + + // Execute + CefBrowserHostCppToC::Get(self)->StartDownload( + CefString(url)); +} + int CEF_CALLBACK browser_host_is_window_rendering_disabled( struct _cef_browser_host_t* self) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING @@ -449,6 +466,7 @@ CefBrowserHostCppToC::CefBrowserHostCppToC(CefBrowserHost* cls) struct_.struct_.get_zoom_level = browser_host_get_zoom_level; struct_.struct_.set_zoom_level = browser_host_set_zoom_level; struct_.struct_.run_file_dialog = browser_host_run_file_dialog; + struct_.struct_.start_download = browser_host_start_download; struct_.struct_.is_window_rendering_disabled = browser_host_is_window_rendering_disabled; struct_.struct_.was_resized = browser_host_was_resized; diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.cc b/libcef_dll/ctocpp/browser_host_ctocpp.cc index 63f1849a1..e638580a5 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.cc +++ b/libcef_dll/ctocpp/browser_host_ctocpp.cc @@ -215,6 +215,22 @@ void CefBrowserHostCToCpp::RunFileDialog(FileDialogMode mode, cef_string_list_free(accept_typesList); } +void CefBrowserHostCToCpp::StartDownload(const CefString& url) { + if (CEF_MEMBER_MISSING(struct_, start_download)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: url; type: string_byref_const + DCHECK(!url.empty()); + if (url.empty()) + return; + + // Execute + struct_->start_download(struct_, + url.GetStruct()); +} + bool CefBrowserHostCToCpp::IsWindowRenderingDisabled() { if (CEF_MEMBER_MISSING(struct_, is_window_rendering_disabled)) return false; diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.h b/libcef_dll/ctocpp/browser_host_ctocpp.h index 2b0c9cb85..5514e1479 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.h +++ b/libcef_dll/ctocpp/browser_host_ctocpp.h @@ -51,6 +51,7 @@ class CefBrowserHostCToCpp const CefString& default_file_name, const std::vector& accept_types, CefRefPtr callback) OVERRIDE; + virtual void StartDownload(const CefString& url) OVERRIDE; virtual bool IsWindowRenderingDisabled() OVERRIDE; virtual void WasResized() OVERRIDE; virtual void Invalidate(const CefRect& dirtyRect, diff --git a/tests/unittests/download_unittest.cc b/tests/unittests/download_unittest.cc new file mode 100644 index 000000000..ad5930da4 --- /dev/null +++ b/tests/unittests/download_unittest.cc @@ -0,0 +1,265 @@ +// 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 "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "tests/unittests/test_handler.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kTestDomain[] = "test-download"; +const char kTestEntryUrl[] = "http://test-download/test.html"; +const char kTestDownloadUrl[] = "http://test-download/download.txt"; +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"; + +class DownloadSchemeHandler : public CefResourceHandler { + public: + explicit DownloadSchemeHandler(TrackCallback* got_download_request) + : got_download_request_(got_download_request), + offset_(0) {} + + virtual bool ProcessRequest(CefRefPtr request, + CefRefPtr callback) + OVERRIDE { + std::string url = request->GetURL(); + if (url == kTestEntryUrl) { + content_ = "Download Test"; + mime_type_ = "text/html"; + } else if (url == kTestDownloadUrl) { + got_download_request_->yes(); + content_ = kTestContent; + mime_type_ = kTestMimeType; + content_disposition_ = kTestContentDisposition; + } else { + EXPECT_TRUE(false); // Not reached. + return false; + } + + callback->Continue(); + return true; + } + + virtual void GetResponseHeaders(CefRefPtr 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); + } + } + + virtual bool ReadResponse(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) + OVERRIDE { + bool has_data = false; + bytes_read = 0; + + size_t size = content_.size(); + if (offset_ < size) { + int transfer_size = + std::min(bytes_to_read, static_cast(size - offset_)); + memcpy(data_out, content_.c_str() + offset_, transfer_size); + offset_ += transfer_size; + + bytes_read = transfer_size; + has_data = true; + } + + return has_data; + } + + virtual void Cancel() OVERRIDE { + } + + private: + TrackCallback* got_download_request_; + std::string content_; + std::string mime_type_; + std::string content_disposition_; + size_t offset_; + + IMPLEMENT_REFCOUNTING(SchemeHandler); +}; + +class DownloadSchemeHandlerFactory : public CefSchemeHandlerFactory { + public: + explicit DownloadSchemeHandlerFactory(TrackCallback* got_download_request) + : got_download_request_(got_download_request) {} + + virtual CefRefPtr Create( + CefRefPtr browser, + CefRefPtr frame, + const CefString& scheme_name, + CefRefPtr request) OVERRIDE { + return new DownloadSchemeHandler(got_download_request_); + } + + private: + TrackCallback* got_download_request_; + + IMPLEMENT_REFCOUNTING(SchemeHandlerFactory); +}; + +class DownloadTestHandler : public TestHandler { + public: + DownloadTestHandler() {} + + virtual void RunTest() OVERRIDE { + CefRegisterSchemeHandlerFactory("http", kTestDomain, + new DownloadSchemeHandlerFactory(&got_download_request_)); + + // Create a new temporary directory. + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + test_path_ = temp_dir_.path().AppendASCII(kTestFileName); + + // Create the browser + CreateBrowser(kTestEntryUrl); + } + + virtual void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) OVERRIDE { + EXPECT_STREQ(kTestEntryUrl, frame->GetURL().ToString().c_str()); + + // Begin the download. + browser->GetHost()->StartDownload(kTestDownloadUrl); + } + + virtual void OnBeforeDownload( + CefRefPtr browser, + CefRefPtr download_item, + const CefString& suggested_name, + CefRefPtr callback) OVERRIDE { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_FALSE(got_on_before_download_); + EXPECT_FALSE(got_on_download_updated_); + + 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(0, download_id_); + + EXPECT_TRUE(download_item->IsValid()); + EXPECT_TRUE(download_item->IsInProgress()); + EXPECT_FALSE(download_item->IsComplete()); + EXPECT_FALSE(download_item->IsCanceled()); + EXPECT_EQ(0LL, download_item->GetCurrentSpeed()); + EXPECT_EQ(0, download_item->GetPercentComplete()); + EXPECT_EQ(static_cast(sizeof(kTestContent)-1), + download_item->GetTotalBytes()); + EXPECT_EQ(0LL, download_item->GetReceivedBytes()); + EXPECT_EQ(0L, download_item->GetFullPath().length()); + EXPECT_STREQ(kTestDownloadUrl, download_item->GetURL().ToString().c_str()); + EXPECT_EQ(0L, 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_.value(), false); + } + + virtual void OnDownloadUpdated( + CefRefPtr browser, + CefRefPtr download_item, + CefRefPtr callback) OVERRIDE { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_TRUE(got_on_before_download_); + + got_on_download_updated_.yes(); + + EXPECT_TRUE(browser->IsSame(GetBrowser())); + EXPECT_TRUE(download_item.get()); + EXPECT_TRUE(callback.get()); + + EXPECT_EQ(download_id_, download_item->GetId()); + + EXPECT_TRUE(download_item->IsValid()); + EXPECT_FALSE(download_item->IsCanceled()); + EXPECT_LT(0LL, download_item->GetCurrentSpeed()); + EXPECT_LT(0, download_item->GetPercentComplete()); + EXPECT_EQ(static_cast(sizeof(kTestContent)-1), + download_item->GetTotalBytes()); + 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(CefString(test_path_.value()).ToString().c_str(), + full_path.c_str()); + } + + if (download_item->IsComplete()) { + EXPECT_FALSE(download_item->IsInProgress()); + EXPECT_EQ(100, download_item->GetPercentComplete()); + EXPECT_EQ(static_cast(sizeof(kTestContent)-1), + download_item->GetReceivedBytes()); + + DestroyTest(); + } else { + EXPECT_TRUE(download_item->IsInProgress()); + EXPECT_LT(0LL, download_item->GetReceivedBytes()); + } + } + + virtual void DestroyTest() OVERRIDE { + CefRegisterSchemeHandlerFactory("http", kTestDomain, NULL); + + EXPECT_TRUE(got_download_request_); + EXPECT_TRUE(got_on_before_download_); + EXPECT_TRUE(got_on_download_updated_); + EXPECT_TRUE(got_full_path_); + + // Verify the file contents. + std::string contents; + EXPECT_TRUE(file_util::ReadFileToString(test_path_, &contents)); + EXPECT_STREQ(kTestContent, contents.c_str()); + + EXPECT_TRUE(temp_dir_.Delete()); + + TestHandler::DestroyTest(); + } + + private: + base::ScopedTempDir temp_dir_; + FilePath test_path_; + int download_id_; + + TrackCallback got_download_request_; + TrackCallback got_on_before_download_; + TrackCallback got_on_download_updated_; + TrackCallback got_full_path_; +}; + +} // namespace + +// Verify that downloads work. +TEST(DownloadTest, Download) { + CefRefPtr handler = new DownloadTestHandler(); + handler->ExecuteTest(); +} diff --git a/tests/unittests/test_handler.h b/tests/unittests/test_handler.h index 8b6f580e3..adec312b7 100644 --- a/tests/unittests/test_handler.h +++ b/tests/unittests/test_handler.h @@ -33,6 +33,7 @@ class TrackCallback { class TestHandler : public CefClient, public CefDialogHandler, public CefDisplayHandler, + public CefDownloadHandler, public CefGeolocationHandler, public CefJSDialogHandler, public CefLifeSpanHandler, @@ -52,6 +53,9 @@ class TestHandler : public CefClient, virtual CefRefPtr GetDisplayHandler() OVERRIDE { return this; } + virtual CefRefPtr GetDownloadHandler() OVERRIDE { + return this; + } virtual CefRefPtr GetGeolocationHandler() OVERRIDE { return this; } @@ -68,6 +72,13 @@ class TestHandler : public CefClient, return this; } + // CefDownloadHandler methods + virtual void OnBeforeDownload( + CefRefPtr browser, + CefRefPtr download_item, + const CefString& suggested_name, + CefRefPtr callback) OVERRIDE {} + // CefLifeSpanHandler methods virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE; virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE;