cef/tests/ceftests/download_unittest.cc
Marshall Greenblatt 4fbd247231 Add chrome runtime support for more callbacks and ceftests (see issue #2969)
This change adds support for:
- Protocol and request handling.
- Loading and navigation events.
- Display and focus events.
- Mouse/keyboard events.
- Popup browsers.
- Callbacks in the renderer process.
- Misc. functionality required for ceftests.

This change also adds a new CefBrowserProcessHandler::GetCookieableSchemes
callback for configuring global state that will be applied to all
CefCookieManagers by default. This global callback is currently required by the
chrome runtime because the primary ProfileImpl is created via
ChromeBrowserMainParts::PreMainMessageLoopRun (CreatePrimaryProfile) before
OnContextCreated can be called.

ProfileImpl will use the "C:\Users\[user]\AppData\Local\CEF\User Data\Default"
directory by default (on Windows). Cookies may persist in this directory when
running ceftests and may need to be manually deleted if those tests fail.

Remaining work includes:
- Support for client-created request contexts.
- Embedding the browser in a Views hierarchy (cefclient support).
- TryCloseBrowser and DoClose support.
- Most of the CefSettings configuration.
- DevTools protocol and window control (ShowDevTools, ExecuteDevToolsMethod).
- CEF-specific WebUI pages (about, license, webui-hosts).
- Context menu customization (CefContextMenuHandler).
- Auto resize (SetAutoResizeEnabled).
- Zoom settings (SetZoomLevel).
- File dialog runner (RunFileDialog).
- File and JS dialog handlers (CefDialogHandler, CefJSDialogHandler).
- Extension loading (LoadExtension, etc).
- Plugin loading (OnBeforePluginLoad).
- Widevine loading (CefRegisterWidevineCdm).
- PDF and print preview does not display.
- Crash reporting is untested.
- Mac: Web content loads but does not display.

The following ceftests are now passing when run with the
"--enable-chrome-runtime" command-line flag:

CorsTest.*
DisplayTest.*:-DisplayTest.AutoResize
DOMTest.*
DraggableRegionsTest.*
ImageTest.*
MessageRouterTest.*
NavigationTest.*
ParserTest.*
RequestContextTest.*Global*
RequestTest.*
ResourceManagerTest.*
ResourceRequestHandlerTest.*
ResponseTest.*
SchemeHandlerTest.*
ServerTest.*
StreamResourceHandlerTest.*
StreamTest.*
StringTest.*
TaskTest.*
TestServerTest.*
ThreadTest.*
URLRequestTest.*Global*
V8Test.*:-V8Test.OnUncaughtExceptionDevTools
ValuesTest.*
WaitableEventTest.*
XmlReaderTest.*
ZipReaderTest.*
2020-09-29 18:31:43 -04:00

533 lines
17 KiB
C++

// 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_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::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_USER_VISIBLE,
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)