From facee1f750d0853664b06535d3a5f2edd6e3dab8 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Fri, 17 Nov 2017 11:33:50 -0500 Subject: [PATCH] - Add cache awareness to CefURLRequest (issue #2283) - Properly set referer values on server requests (issue #2278) --- include/internal/cef_types.h | 19 +- libcef/browser/server_impl.cc | 13 +- libcef/common/request_impl.cc | 146 ++- tests/ceftests/server_unittest.cc | 1 + tests/ceftests/urlrequest_unittest.cc | 1596 +++++++++++++++++++------ 5 files changed, 1363 insertions(+), 412 deletions(-) diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index d5a1e22f9..6efd3c02e 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -1222,15 +1222,26 @@ typedef enum { UR_FLAG_NONE = 0, /// - // If set the cache will be skipped when handling the request. + // If set the cache will be skipped when handling the request. Setting this + // value is equivalent to specifying the "Cache-Control: no-cache" request + // header. Setting this value in combination with UR_FLAG_ONLY_FROM_CACHE will + // cause the request to fail. /// UR_FLAG_SKIP_CACHE = 1 << 0, + /// + // If set the request will fail if it cannot be served from the cache (or some + // equivalent local store). Setting this value is equivalent to specifying the + // "Cache-Control: only-if-cached" request header. Setting this value in + // combination with UR_FLAG_SKIP_CACHE will cause the request to fail. + /// + UR_FLAG_ONLY_FROM_CACHE = 1 << 1, + /// // If set user name, password, and cookies may be sent with the request, and // cookies may be saved from the response. /// - UR_FLAG_ALLOW_CACHED_CREDENTIALS = 1 << 1, + UR_FLAG_ALLOW_STORED_CREDENTIALS = 1 << 2, /// // If set upload progress events will be generated when a request has a body. @@ -1240,14 +1251,14 @@ typedef enum { /// // If set the CefURLRequestClient::OnDownloadData method will not be called. /// - UR_FLAG_NO_DOWNLOAD_DATA = 1 << 6, + UR_FLAG_NO_DOWNLOAD_DATA = 1 << 4, /// // If set 5XX redirect errors will be propagated to the observer instead of // automatically re-tried. This currently only applies for requests // originated in the browser process. /// - UR_FLAG_NO_RETRY_ON_5XX = 1 << 7, + UR_FLAG_NO_RETRY_ON_5XX = 1 << 5, } cef_urlrequest_flags_t; /// diff --git a/libcef/browser/server_impl.cc b/libcef/browser/server_impl.cc index e92264e7f..261710921 100644 --- a/libcef/browser/server_impl.cc +++ b/libcef/browser/server_impl.cc @@ -40,6 +40,8 @@ namespace { +const char kReferrerLowerCase[] = "referer"; + // Wrap a string in a unique_ptr to avoid extra copies. std::unique_ptr CreateUniqueString(const void* data, size_t data_size) { @@ -67,18 +69,27 @@ CefRefPtr CreateRequest(const std::string& address, post_data->AddElement(post_element); } + std::string referer; + CefRequest::HeaderMap header_map; if (!info.headers.empty()) { net::HttpServerRequestInfo::HeadersMap::const_iterator it = info.headers.begin(); for (; it != info.headers.end(); ++it) { - header_map.insert(std::make_pair(it->first, it->second)); + // Don't include Referer in the header map. + if (base::LowerCaseEqualsASCII(it->first, kReferrerLowerCase)) { + referer = it->second; + } else { + header_map.insert(std::make_pair(it->first, it->second)); + } } } CefRefPtr request = new CefRequestImpl(); request->Set((is_websocket ? "ws://" : "http://") + address + info.path, info.method, post_data, header_map); + if (!referer.empty()) + request->SetReferrer(referer, REFERRER_POLICY_DEFAULT); request->SetReadOnly(true); return request; } diff --git a/libcef/common/request_impl.cc b/libcef/common/request_impl.cc index a0a71218f..6e3aa0d72 100644 --- a/libcef/common/request_impl.cc +++ b/libcef/common/request_impl.cc @@ -2,6 +2,7 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. +#include #include #include #include @@ -44,8 +45,14 @@ namespace { const char kReferrerLowerCase[] = "referer"; const char kContentTypeLowerCase[] = "content-type"; +const char kCacheControlLowerCase[] = "cache-control"; +const char kCacheControlDirectiveNoCacheLowerCase[] = "no-cache"; +const char kCacheControlDirectiveOnlyIfCachedLowerCase[] = "only-if-cached"; const char kApplicationFormURLEncoded[] = "application/x-www-form-urlencoded"; +// Mask of values that configure the cache policy. +const int kURCachePolicyMask = (UR_FLAG_SKIP_CACHE | UR_FLAG_ONLY_FROM_CACHE); + // A subclass of net::UploadBytesElementReader that keeps the associated // UploadElement alive until the request completes. class BytesElementReader : public net::UploadBytesElementReader { @@ -100,6 +107,71 @@ std::string GetURLRequestReferrer(const GURL& referrer_url) { return referrer_url.spec(); } +// Returns the cef_urlrequest_flags_t policy specified by the Cache-Control +// request header directives, if any. The directives are case-insensitive and +// some have an optional argument. Multiple directives are comma-separated. +// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control +// for details. +int GetCacheControlHeaderPolicy(CefRequest::HeaderMap headerMap) { + std::string line; + + // Extract the Cache-Control header line. + { + CefRequest::HeaderMap::const_iterator it = headerMap.begin(); + for (; it != headerMap.end(); ++it) { + if (base::LowerCaseEqualsASCII(it->first.ToString(), + kCacheControlLowerCase)) { + line = it->second; + break; + } + } + } + + int flags = 0; + + if (!line.empty()) { + std::transform(line.begin(), line.end(), line.begin(), ::tolower); + + std::vector pieces = base::SplitStringPiece( + line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (const auto& piece : pieces) { + if (base::LowerCaseEqualsASCII(piece, + kCacheControlDirectiveNoCacheLowerCase)) { + flags |= UR_FLAG_SKIP_CACHE; + } else if (base::LowerCaseEqualsASCII( + piece, kCacheControlDirectiveOnlyIfCachedLowerCase)) { + flags |= UR_FLAG_ONLY_FROM_CACHE; + } + } + } + + return flags; +} + +// Convert cef_urlrequest_flags_t to blink::WebCachePolicy. +blink::WebCachePolicy GetWebCachePolicy(int ur_flags) { + if ((ur_flags & kURCachePolicyMask) == kURCachePolicyMask) { + return blink::WebCachePolicy::kBypassCacheLoadOnlyFromCache; + } else if (ur_flags & UR_FLAG_SKIP_CACHE) { + return blink::WebCachePolicy::kBypassingCache; + } else if (ur_flags & UR_FLAG_ONLY_FROM_CACHE) { + return blink::WebCachePolicy::kReturnCacheDataDontLoad; + } + return blink::WebCachePolicy::kUseProtocolCachePolicy; +} + +// Convert blink::WebCachePolicy to cef_urlrequest_flags_t. +int GetURCachePolicy(blink::WebCachePolicy web_policy) { + if (web_policy == blink::WebCachePolicy::kBypassCacheLoadOnlyFromCache) { + return kURCachePolicyMask; + } else if (web_policy == blink::WebCachePolicy::kBypassingCache) { + return UR_FLAG_SKIP_CACHE; + } else if (web_policy == blink::WebCachePolicy::kReturnCacheDataDontLoad) { + return UR_FLAG_ONLY_FROM_CACHE; + } + return 0; +} + blink::WebString FilePathStringToWebString( const base::FilePath::StringType& str) { #if defined(OS_POSIX) @@ -216,10 +288,9 @@ CefRefPtr CefRequest::Create() { CefRequestImpl::CefRequestImpl() : read_only_(false), track_changes_(false) { // Verify that our enum matches Chromium's values. - static_assert( - static_cast(REFERRER_POLICY_LAST_VALUE) == - static_cast(net::URLRequest::MAX_REFERRER_POLICY), - "enum mismatch"); + static_assert(static_cast(REFERRER_POLICY_LAST_VALUE) == + static_cast(net::URLRequest::MAX_REFERRER_POLICY), + "enum mismatch"); base::AutoLock lock_scope(lock_); Reset(); @@ -495,10 +566,9 @@ void CefRequestImpl::Set(const blink::WebURLRequest& request) { site_for_cookies_ = request.SiteForCookies(); - if (request.GetCachePolicy() == blink::WebCachePolicy::kBypassingCache) - flags_ |= UR_FLAG_SKIP_CACHE; + flags_ |= GetURCachePolicy(request.GetCachePolicy()); if (request.AllowStoredCredentials()) - flags_ |= UR_FLAG_ALLOW_CACHED_CREDENTIALS; + flags_ |= UR_FLAG_ALLOW_STORED_CREDENTIALS; if (request.ReportUploadProgress()) flags_ |= UR_FLAG_REPORT_UPLOAD_PROGRESS; } @@ -545,12 +615,16 @@ void CefRequestImpl::Get(blink::WebURLRequest& request, if (!site_for_cookies_.is_empty()) request.SetSiteForCookies(site_for_cookies_); - request.SetCachePolicy((flags_ & UR_FLAG_SKIP_CACHE) - ? blink::WebCachePolicy::kBypassingCache - : blink::WebCachePolicy::kUseProtocolCachePolicy); + int flags = flags_; + if (!(flags & kURCachePolicyMask)) { + // Only consider the Cache-Control directives when a cache policy is not + // explicitly set on the request. + flags |= GetCacheControlHeaderPolicy(headermap_); + } + request.SetCachePolicy(GetWebCachePolicy(flags)); SETBOOLFLAG(request, flags_, SetAllowStoredCredentials, - UR_FLAG_ALLOW_CACHED_CREDENTIALS); + UR_FLAG_ALLOW_STORED_CREDENTIALS); SETBOOLFLAG(request, flags_, SetReportUploadProgress, UR_FLAG_REPORT_UPLOAD_PROGRESS); } @@ -576,12 +650,14 @@ void CefRequestImpl::Get(const CefMsg_LoadRequest_Params& params, } } + CefRequest::HeaderMap headerMap; if (!params.headers.empty()) { for (net::HttpUtil::HeadersIterator i(params.headers.begin(), params.headers.end(), "\n"); i.GetNext();) { request.AddHTTPHeaderField(blink::WebString::FromUTF8(i.name()), blink::WebString::FromUTF8(i.values())); + headerMap.insert(std::make_pair(i.name(), i.values())); } } @@ -624,12 +700,16 @@ void CefRequestImpl::Get(const CefMsg_LoadRequest_Params& params, if (params.site_for_cookies.is_valid()) request.SetSiteForCookies(params.site_for_cookies); - request.SetCachePolicy((params.load_flags & UR_FLAG_SKIP_CACHE) - ? blink::WebCachePolicy::kBypassingCache - : blink::WebCachePolicy::kUseProtocolCachePolicy); + int flags = params.load_flags; + if (!(flags & kURCachePolicyMask)) { + // Only consider the Cache-Control directives when a cache policy is not + // explicitly set on the request. + flags |= GetCacheControlHeaderPolicy(headerMap); + } + request.SetCachePolicy(GetWebCachePolicy(flags)); SETBOOLFLAG(request, params.load_flags, SetAllowStoredCredentials, - UR_FLAG_ALLOW_CACHED_CREDENTIALS); + UR_FLAG_ALLOW_STORED_CREDENTIALS); SETBOOLFLAG(request, params.load_flags, SetReportUploadProgress, UR_FLAG_REPORT_UPLOAD_PROGRESS); } @@ -726,21 +806,31 @@ void CefRequestImpl::Get(net::URLFetcher& fetcher, if (!site_for_cookies_.is_empty()) fetcher.SetInitiator(url::Origin(site_for_cookies_)); - if (flags_ & UR_FLAG_NO_RETRY_ON_5XX) - fetcher.SetAutomaticallyRetryOn5xx(false); - - int load_flags = 0; - - if (flags_ & UR_FLAG_SKIP_CACHE) - load_flags |= net::LOAD_BYPASS_CACHE; - - if (!(flags_ & UR_FLAG_ALLOW_CACHED_CREDENTIALS)) { - load_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA; - load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; - load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; + int flags = flags_; + if (!(flags & kURCachePolicyMask)) { + // Only consider the Cache-Control directives when a cache policy is not + // explicitly set on the request. + flags |= GetCacheControlHeaderPolicy(headerMap); } - fetcher.SetLoadFlags(load_flags); + if (flags & UR_FLAG_NO_RETRY_ON_5XX) + fetcher.SetAutomaticallyRetryOn5xx(false); + + int net_flags = 0; + + if (flags & UR_FLAG_SKIP_CACHE) { + net_flags |= net::LOAD_BYPASS_CACHE; + } + if (flags & UR_FLAG_ONLY_FROM_CACHE) { + net_flags |= net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION; + } + + if (!(flags & UR_FLAG_ALLOW_STORED_CREDENTIALS)) { + net_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA | + net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES; + } + + fetcher.SetLoadFlags(net_flags); } void CefRequestImpl::SetReadOnly(bool read_only) { diff --git a/tests/ceftests/server_unittest.cc b/tests/ceftests/server_unittest.cc index c096c93d9..dd8a7b2b4 100644 --- a/tests/ceftests/server_unittest.cc +++ b/tests/ceftests/server_unittest.cc @@ -966,6 +966,7 @@ class StaticHttpRequestRunner : public HttpTestRunner::RequestRunner { CefRefPtr request = CreateTestServerRequest( path, "POST", "foo=bar&choo=too", "application/x-www-form-urlencoded", request_headers); + request->SetReferrer("http://tests/referer.html", REFERRER_POLICY_DEFAULT); HttpServerResponse response(HttpServerResponse::TYPE_CUSTOM); response.response_code = 202; diff --git a/tests/ceftests/urlrequest_unittest.cc b/tests/ceftests/urlrequest_unittest.cc index 699906576..ebd3ef8cc 100644 --- a/tests/ceftests/urlrequest_unittest.cc +++ b/tests/ceftests/urlrequest_unittest.cc @@ -2,11 +2,13 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. +#include #include #include #include "include/base/cef_bind.h" #include "include/cef_scheme.h" +#include "include/cef_server.h" #include "include/cef_task.h" #include "include/cef_urlrequest.h" #include "include/cef_waitable_event.h" @@ -33,11 +35,22 @@ namespace { // Unique values for URLRequest tests. const char* kRequestTestUrl = "http://tests/URLRequestTest.Test"; const char* kRequestTestMsg = "URLRequestTest.Test"; -const char* kRequestScheme = "urcustom"; -const char* kRequestHost = "test"; -const char* kRequestOrigin = "urcustom://test"; -const char* kRequestSendCookieName = "urcookie_send"; -const char* kRequestSaveCookieName = "urcookie_save"; + +// TEST DATA + +// Custom scheme handler backend. +const char kRequestSchemeCustom[] = "urcustom"; +const char kRequestHostCustom[] = "test"; + +// Server backend. +const char kRequestAddressServer[] = "127.0.0.1"; +const uint16 kRequestPortServer = 8099; +const char kRequestSchemeServer[] = "http"; + +const char kRequestSendCookieName[] = "urcookie_send"; +const char kRequestSaveCookieName[] = "urcookie_save"; + +const char kCacheControlHeader[] = "cache-control"; enum RequestTestMode { REQTEST_GET = 0, @@ -49,6 +62,14 @@ enum RequestTestMode { REQTEST_POST_FILE, REQTEST_POST_WITHPROGRESS, REQTEST_HEAD, + REQTEST_CACHE_WITH_CONTROL, + REQTEST_CACHE_WITHOUT_CONTROL, + REQTEST_CACHE_SKIP_FLAG, + REQTEST_CACHE_SKIP_HEADER, + REQTEST_CACHE_ONLY_FAILURE_FLAG, + REQTEST_CACHE_ONLY_FAILURE_HEADER, + REQTEST_CACHE_ONLY_SUCCESS_FLAG, + REQTEST_CACHE_ONLY_SUCCESS_HEADER, }; enum ContextTestMode { @@ -57,6 +78,7 @@ enum ContextTestMode { CONTEXT_ONDISK, }; +// Defines test expectations for a request. struct RequestRunSettings { RequestRunSettings() : expect_upload_progress(false), @@ -66,15 +88,28 @@ struct RequestRunSettings { expected_error_code(ERR_NONE), expect_send_cookie(false), expect_save_cookie(false), - expect_follow_redirect(true) {} + expect_follow_redirect(true), + expected_send_count(-1), + expected_receive_count(-1) {} + + // Set expectations for request failure. + void SetRequestFailureExpected(cef_errorcode_t error_code) { + expect_upload_progress = false; + expect_download_progress = false; + expect_download_data = false; + expected_status = UR_FAILED; + expected_error_code = error_code; + response = nullptr; + response_data.clear(); + } // Request that will be sent. CefRefPtr request; - // Response that will be returned by the scheme handler. + // Response that will be returned by the backend. CefRefPtr response; - // Optional response data that will be returned by the scheme handler. + // Optional response data that will be returned by the backend. std::string response_data; // If true upload progress notification will be expected. @@ -95,7 +130,7 @@ struct RequestRunSettings { // If true the request cookie should be sent to the server. bool expect_send_cookie; - // If true the response cookie should be save. + // If true the response cookie should be saved. bool expect_save_cookie; // If specified the test will begin with this redirect request and response. @@ -104,8 +139,142 @@ struct RequestRunSettings { // If true the redirect is expected to be followed. bool expect_follow_redirect; + + // The expected number of requests to send, or -1 if unspecified. + // Used only with the server backend. + int expected_send_count; + + // The expected number of requests to receive, or -1 if unspecified. + // Used only with the server backend. + int expected_receive_count; + + typedef base::Callback + NextRequestCallback; + + // If non-null this callback will be executed before subsequent requests are + // sent. + NextRequestCallback setup_next_request; }; +// Manages the map of request URL to test expectations. +class RequestDataMap { + public: + struct Entry { + enum Type { + TYPE_UNKNOWN, + TYPE_NORMAL, + TYPE_REDIRECT, + }; + + Entry(Type entry_type) : type(entry_type), settings(nullptr) {} + + Type type; + + // Used with TYPE_NORMAL. + // |settings| is not owned by this object. + RequestRunSettings* settings; + + // Used with TYPE_REDIRECT. + CefRefPtr redirect_request; + CefRefPtr redirect_response; + }; + + RequestDataMap() : owner_task_runner_(CefTaskRunner::GetForCurrentThread()) {} + + // Pass ownership to the specified |task_runner| thread. + // If |task_runner| is nullptr the test is considered destroyed. + void SetOwnerTaskRunner(CefRefPtr task_runner) { + EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); + owner_task_runner_ = task_runner; + } + + void AddSchemeHandler(RequestRunSettings* settings) { + EXPECT_TRUE(settings); + EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); + + const std::string& url = settings->request->GetURL(); + data_map_.insert(std::make_pair(url, settings)); + + if (settings->redirect_request) { + const std::string& redirect_url = settings->redirect_request->GetURL(); + redirect_data_map_.insert(std::make_pair( + redirect_url, std::make_pair(settings->redirect_request, + settings->redirect_response))); + } + } + + Entry Find(const std::string& url) { + EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); + + Entry entry(Entry::TYPE_UNKNOWN); + + // Try to find a test match. + { + DataMap::const_iterator it = data_map_.find(url); + if (it != data_map_.end()) { + entry.type = Entry::TYPE_NORMAL; + entry.settings = it->second; + return entry; + } + } + + // Try to find a redirect match. + { + RedirectDataMap::const_iterator it = redirect_data_map_.find(url); + if (it != redirect_data_map_.end()) { + entry.type = Entry::TYPE_REDIRECT; + entry.redirect_request = it->second.first; + entry.redirect_response = it->second.second; + return entry; + } + } + + // Unknown test. + ADD_FAILURE() << "url: " << url; + return entry; + } + + size_t size() const { return data_map_.size() + redirect_data_map_.size(); } + + private: + CefRefPtr owner_task_runner_; + + // The below members are only accessed on the |owner_task_runner_| thread. + + // RequestRunSettings pointer is not owned by this object. + typedef std::map DataMap; + DataMap data_map_; + + typedef std::map, CefRefPtr>> + RedirectDataMap; + RedirectDataMap redirect_data_map_; +}; + +std::string GetRequestScheme(bool server_backend) { + return server_backend ? kRequestSchemeServer : kRequestSchemeCustom; +} + +std::string GetRequestHost(bool server_backend, bool with_port) { + if (server_backend) { + if (with_port) { + std::stringstream ss; + ss << kRequestAddressServer << ":" << kRequestPortServer; + return ss.str(); + } else { + return kRequestAddressServer; + } + } else { + return kRequestHostCustom; + } +} + +std::string GetRequestOrigin(bool server_backend) { + return GetRequestScheme(server_backend) + "://" + + GetRequestHost(server_backend, true); +} + void SetUploadData(CefRefPtr request, const std::string& data) { CefRefPtr postData = CefPostData::Create(); CefRefPtr element = CefPostDataElement::Create(); @@ -139,7 +308,8 @@ void GetUploadData(CefRefPtr request, std::string& data) { } // Set a cookie so that we can test if it's sent with the request. -void SetTestCookie(CefRefPtr request_context) { +void SetTestCookie(CefRefPtr request_context, + bool server_backend) { EXPECT_TRUE(CefCurrentlyOn(TID_FILE)); class Callback : public CefSetCookieCallback { @@ -166,11 +336,11 @@ void SetTestCookie(CefRefPtr request_context) { CefCookie cookie; CefString(&cookie.name) = kRequestSendCookieName; CefString(&cookie.value) = "send-cookie-value"; - CefString(&cookie.domain) = kRequestHost; + CefString(&cookie.domain) = GetRequestHost(server_backend, false); CefString(&cookie.path) = "/"; cookie.has_expires = false; EXPECT_TRUE(request_context->GetDefaultCookieManager(NULL)->SetCookie( - kRequestOrigin, cookie, new Callback(event))); + GetRequestOrigin(server_backend), cookie, new Callback(event))); // Wait for the Callback. event->TimedWait(2000); @@ -180,7 +350,8 @@ void SetTestCookie(CefRefPtr request_context) { // Tests if the save cookie has been set. If set, it will be deleted at the same // time. void GetTestCookie(CefRefPtr request_context, - bool* cookie_exists) { + bool* cookie_exists, + bool server_backend) { EXPECT_TRUE(CefCurrentlyOn(TID_FILE)); class Visitor : public CefCookieVisitor { @@ -216,7 +387,7 @@ void GetTestCookie(CefRefPtr request_context, CefRefPtr cookie_manager = request_context->GetDefaultCookieManager(NULL); - cookie_manager->VisitUrlCookies(kRequestOrigin, true, + cookie_manager->VisitUrlCookies(GetRequestOrigin(server_backend), true, new Visitor(event, cookie_exists)); // Wait for the Visitor. @@ -224,55 +395,100 @@ void GetTestCookie(CefRefPtr request_context, EXPECT_TRUE(event->IsSignaled()); } +std::string GetHeaderValue(const CefRequest::HeaderMap& header_map, + const std::string& header_name_lower) { + CefRequest::HeaderMap::const_iterator it = header_map.begin(); + for (; it != header_map.end(); ++it) { + std::string name = it->first; + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + if (name == header_name_lower) + return it->second; + } + return std::string(); +} + +// Verify normal request expectations. +void VerifyNormalRequest(const RequestRunSettings* settings, + CefRefPtr request, + bool server_backend) { + // Shouldn't get here if we're not following redirects. + EXPECT_TRUE(settings->expect_follow_redirect); + + // Verify that the request was sent correctly. + TestRequestEqual(settings->request, request, true); + + CefRequest::HeaderMap headerMap; + request->GetHeaderMap(headerMap); + + // Check if the default headers were sent. + EXPECT_FALSE(GetHeaderValue(headerMap, "user-agent").empty()); + + // Verify that we get the value that was set via + // CefSettings.accept_language_list in CefTestSuite::GetSettings(). + EXPECT_STREQ(CEF_SETTINGS_ACCEPT_LANGUAGE, + GetHeaderValue(headerMap, "accept-language").c_str()); + + if (server_backend) { + EXPECT_FALSE(GetHeaderValue(headerMap, "accept-encoding").empty()); + EXPECT_STREQ(GetRequestHost(true, true).c_str(), + GetHeaderValue(headerMap, "host").c_str()); + } + + // Check if the request cookie was sent. + const std::string& cookie_value = GetHeaderValue(headerMap, "cookie"); + bool has_send_cookie = false; + if (!cookie_value.empty() && + cookie_value.find(kRequestSendCookieName) != std::string::npos) { + has_send_cookie = true; + } + + if (settings->expect_send_cookie) + EXPECT_TRUE(has_send_cookie); + else + EXPECT_FALSE(has_send_cookie); +} + +// Populate normal response contents. +void GetNormalResponse(const RequestRunSettings* settings, + CefRefPtr response) { + EXPECT_TRUE(settings->response); + if (!settings->response) + return; + + response->SetStatus(settings->response->GetStatus()); + response->SetStatusText(settings->response->GetStatusText()); + response->SetMimeType(settings->response->GetMimeType()); + + CefResponse::HeaderMap headerMap; + settings->response->GetHeaderMap(headerMap); + + if (settings->expect_save_cookie) { + std::stringstream ss; + ss << kRequestSaveCookieName << "=" + << "save-cookie-value"; + headerMap.insert(std::make_pair("Set-Cookie", ss.str())); + } + + response->SetHeaderMap(headerMap); +} + +// SCHEME HANDLER BACKEND + // Serves request responses. class RequestSchemeHandler : public CefResourceHandler { public: - explicit RequestSchemeHandler(const RequestRunSettings& settings) + explicit RequestSchemeHandler(RequestRunSettings* settings) : settings_(settings), offset_(0) {} bool ProcessRequest(CefRefPtr request, CefRefPtr callback) override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); - - // Shouldn't get here if we're not following redirects. - EXPECT_TRUE(settings_.expect_follow_redirect); - - // Verify that the request was sent correctly. - TestRequestEqual(settings_.request, request, true); + VerifyNormalRequest(settings_, request, false); // HEAD requests are identical to GET requests except no response data is // sent. - if (request->GetMethod() == "HEAD") - settings_.response_data.clear(); - - CefRequest::HeaderMap headerMap; - CefRequest::HeaderMap::iterator headerIter; - request->GetHeaderMap(headerMap); - - // Check if the default headers were sent. - headerIter = headerMap.find("User-Agent"); - EXPECT_TRUE(headerIter != headerMap.end() && !headerIter->second.empty()); - headerIter = headerMap.find("Accept-Language"); - EXPECT_TRUE(headerIter != headerMap.end() && !headerIter->second.empty()); - - // Verify that we get the value that was set via - // CefSettings.accept_language_list in CefTestSuite::GetSettings(). - EXPECT_STREQ(CEF_SETTINGS_ACCEPT_LANGUAGE, - headerIter->second.ToString().data()); - - // Check if the request cookie was sent. - bool has_send_cookie = false; - headerIter = headerMap.find("Cookie"); - if (headerIter != headerMap.end()) { - std::string cookie = headerIter->second; - if (cookie.find(kRequestSendCookieName) != std::string::npos) - has_send_cookie = true; - } - - if (settings_.expect_send_cookie) - EXPECT_TRUE(has_send_cookie); - else - EXPECT_FALSE(has_send_cookie); + if (request->GetMethod() != "HEAD") + response_data_ = settings_->response_data; // Continue immediately. callback->Continue(); @@ -283,24 +499,8 @@ class RequestSchemeHandler : public CefResourceHandler { int64& response_length, CefString& redirectUrl) override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); - - response->SetStatus(settings_.response->GetStatus()); - response->SetStatusText(settings_.response->GetStatusText()); - response->SetMimeType(settings_.response->GetMimeType()); - - CefResponse::HeaderMap headerMap; - settings_.response->GetHeaderMap(headerMap); - - if (settings_.expect_save_cookie) { - std::stringstream ss; - ss << kRequestSaveCookieName << "=" - << "save-cookie-value"; - headerMap.insert(std::make_pair("Set-Cookie", ss.str())); - } - - response->SetHeaderMap(headerMap); - - response_length = settings_.response_data.length(); + GetNormalResponse(settings_, response); + response_length = response_data_.length(); } bool ReadResponse(void* response_data_out, @@ -312,11 +512,11 @@ class RequestSchemeHandler : public CefResourceHandler { bool has_data = false; bytes_read = 0; - size_t size = settings_.response_data.length(); + size_t size = response_data_.length(); if (offset_ < size) { int transfer_size = std::min(bytes_to_read, static_cast(size - offset_)); - memcpy(response_data_out, settings_.response_data.c_str() + offset_, + memcpy(response_data_out, response_data_.c_str() + offset_, transfer_size); offset_ += transfer_size; @@ -330,7 +530,10 @@ class RequestSchemeHandler : public CefResourceHandler { void Cancel() override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } private: - RequestRunSettings settings_; + // |settings_| is not owned by this object. + RequestRunSettings* settings_; + + std::string response_data_; size_t offset_; IMPLEMENT_REFCOUNTING(RequestSchemeHandler); @@ -398,79 +601,368 @@ class RequestSchemeHandlerFactory : public CefSchemeHandlerFactory { const CefString& scheme_name, CefRefPtr request) override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); - std::string url = request->GetURL(); - - // Try to find a test match. - { - HandlerMap::const_iterator it = handler_map_.find(url); - if (it != handler_map_.end()) - return new RequestSchemeHandler(it->second); - } - - // Try to find a redirect match. - { - RedirectHandlerMap::const_iterator it = redirect_handler_map_.find(url); - if (it != redirect_handler_map_.end()) { - return new RequestRedirectSchemeHandler(it->second.first, - it->second.second); - } + RequestDataMap::Entry entry = data_map_.Find(request->GetURL()); + if (entry.type == RequestDataMap::Entry::TYPE_NORMAL) { + return new RequestSchemeHandler(entry.settings); + } else if (entry.type == RequestDataMap::Entry::TYPE_REDIRECT) { + return new RequestRedirectSchemeHandler(entry.redirect_request, + entry.redirect_response); } // Unknown test. ADD_FAILURE(); - return NULL; + return nullptr; } - void AddSchemeHandler(const RequestRunSettings& settings) { - // Verify that the scheme is correct. - std::string url = settings.request->GetURL(); - EXPECT_EQ((unsigned long)0, url.find(kRequestScheme)); - - handler_map_.insert(std::make_pair(url, settings)); + void SetOwnerTaskRunner(CefRefPtr task_runner) { + data_map_.SetOwnerTaskRunner(task_runner); } - void AddRedirectSchemeHandler(CefRefPtr redirect_request, - CefRefPtr redirect_response) { - // Verify that the scheme is correct. - std::string url = redirect_request->GetURL(); - EXPECT_EQ((unsigned long)0, url.find(kRequestScheme)); + void AddSchemeHandler(RequestRunSettings* settings) { + const std::string& scheme = GetRequestScheme(false); - redirect_handler_map_.insert(std::make_pair( - url, std::make_pair(redirect_request, redirect_response))); + // Verify that the scheme is correct. + const std::string& url = settings->request->GetURL(); + EXPECT_EQ(0U, url.find(scheme)); + + if (settings->redirect_request) { + // Verify that the scheme is correct. + const std::string& redirect_url = settings->redirect_request->GetURL(); + EXPECT_EQ(0U, redirect_url.find(scheme)); + } + + data_map_.AddSchemeHandler(settings); + } + + void Shutdown(const base::Closure& complete_callback) { + if (!CefCurrentlyOn(TID_IO)) { + CefPostTask(TID_IO, base::Bind(&RequestSchemeHandlerFactory::Shutdown, + this, complete_callback)); + return; + } + + data_map_.SetOwnerTaskRunner(nullptr); + complete_callback.Run(); } private: - typedef std::map HandlerMap; - HandlerMap handler_map_; - - typedef std::map, CefRefPtr>> - RedirectHandlerMap; - RedirectHandlerMap redirect_handler_map_; + RequestDataMap data_map_; IMPLEMENT_REFCOUNTING(RequestSchemeHandlerFactory); }; +// SERVER BACKEND + +// HTTP server handler. +class RequestServerHandler : public CefServerHandler { + public: + RequestServerHandler() + : initialized_(false), + expected_connection_ct_(-1), + actual_connection_ct_(0), + expected_http_request_ct_(-1), + actual_http_request_ct_(0) {} + + virtual ~RequestServerHandler() { RunCompleteCallback(false); } + + // Must be called before CreateServer(). + void AddSchemeHandler(RequestRunSettings* settings) { + EXPECT_FALSE(initialized_); + data_map_.AddSchemeHandler(settings); + } + + // Must be called before CreateServer(). + void SetExpectedRequestCount(int count) { + EXPECT_FALSE(initialized_); + expected_connection_ct_ = expected_http_request_ct_ = count; + } + + // |complete_callback| will be executed on the UI thread after the server is + // started. + void CreateServer(const base::Closure& complete_callback) { + EXPECT_UI_THREAD(); + + if (expected_connection_ct_ < 0) { + // Default to the assumption of one request per registered URL. + SetExpectedRequestCount(data_map_.size()); + } + + EXPECT_FALSE(initialized_); + initialized_ = true; + + EXPECT_TRUE(complete_callback_.is_null()); + complete_callback_ = complete_callback; + + CefServer::CreateServer(kRequestAddressServer, kRequestPortServer, 10, + this); + } + + // Results in a call to VerifyResults() and eventual execution of the + // |complete_callback| on the UI thread via RequestServerHandler destruction. + void ShutdownServer(const base::Closure& complete_callback) { + EXPECT_UI_THREAD(); + + EXPECT_TRUE(complete_callback_.is_null()); + complete_callback_ = complete_callback; + + EXPECT_TRUE(server_); + if (server_) + server_->Shutdown(); + } + + void OnServerCreated(CefRefPtr server) override { + EXPECT_TRUE(server); + EXPECT_TRUE(server->IsRunning()); + EXPECT_FALSE(server->HasConnection()); + + EXPECT_FALSE(got_server_created_); + got_server_created_.yes(); + + EXPECT_FALSE(server_); + server_ = server; + + EXPECT_FALSE(server_runner_); + server_runner_ = server_->GetTaskRunner(); + EXPECT_TRUE(server_runner_); + EXPECT_TRUE(server_runner_->BelongsToCurrentThread()); + + CefPostTask(TID_UI, base::Bind(&RequestServerHandler::RunCompleteCallback, + this, true)); + } + + void OnServerDestroyed(CefRefPtr server) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_FALSE(server->IsRunning()); + EXPECT_FALSE(server->HasConnection()); + + EXPECT_FALSE(got_server_destroyed_); + got_server_destroyed_.yes(); + + data_map_.SetOwnerTaskRunner(nullptr); + server_ = nullptr; + + VerifyResults(); + } + + void OnClientConnected(CefRefPtr server, + int connection_id) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_TRUE(server->HasConnection()); + EXPECT_TRUE(server->IsValidConnection(connection_id)); + + EXPECT_TRUE(connection_id_set_.find(connection_id) == + connection_id_set_.end()); + connection_id_set_.insert(connection_id); + + actual_connection_ct_++; + } + + void OnClientDisconnected(CefRefPtr server, + int connection_id) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_FALSE(server->IsValidConnection(connection_id)); + + ConnectionIdSet::iterator it = connection_id_set_.find(connection_id); + EXPECT_TRUE(it != connection_id_set_.end()); + connection_id_set_.erase(it); + } + + void OnHttpRequest(CefRefPtr server, + int connection_id, + const CefString& client_address, + CefRefPtr request) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_TRUE(VerifyConnection(connection_id)); + EXPECT_FALSE(client_address.empty()); + + // Log the requests for better error reporting. + request_log_ += request->GetMethod().ToString() + " " + + request->GetURL().ToString() + "\n"; + + HandleRequest(server, connection_id, request); + + actual_http_request_ct_++; + } + + void OnWebSocketRequest(CefRefPtr server, + int connection_id, + const CefString& client_address, + CefRefPtr request, + CefRefPtr callback) override { + NOTREACHED(); + } + + void OnWebSocketConnected(CefRefPtr server, + int connection_id) override { + NOTREACHED(); + } + + void OnWebSocketMessage(CefRefPtr server, + int connection_id, + const void* data, + size_t data_size) override { + NOTREACHED(); + } + + private: + bool RunningOnServerThread() { + return server_runner_ && server_runner_->BelongsToCurrentThread(); + } + + bool VerifyServer(CefRefPtr server) { + V_DECLARE(); + V_EXPECT_TRUE(RunningOnServerThread()); + V_EXPECT_TRUE(server); + V_EXPECT_TRUE(server_); + V_EXPECT_TRUE(server->GetAddress().ToString() == + server_->GetAddress().ToString()); + V_RETURN(); + } + + bool VerifyConnection(int connection_id) { + return connection_id_set_.find(connection_id) != connection_id_set_.end(); + } + + void VerifyResults() { + EXPECT_TRUE(RunningOnServerThread()); + + EXPECT_TRUE(got_server_created_); + EXPECT_TRUE(got_server_destroyed_); + EXPECT_TRUE(connection_id_set_.empty()); + EXPECT_EQ(expected_connection_ct_, actual_connection_ct_) << request_log_; + EXPECT_EQ(expected_http_request_ct_, actual_http_request_ct_) + << request_log_; + } + + void HandleRequest(CefRefPtr server, + int connection_id, + CefRefPtr request) { + RequestDataMap::Entry entry = data_map_.Find(request->GetURL()); + if (entry.type == RequestDataMap::Entry::TYPE_NORMAL) { + HandleNormalRequest(server, connection_id, request, entry.settings); + } else if (entry.type == RequestDataMap::Entry::TYPE_REDIRECT) { + HandleRedirectRequest(server, connection_id, request, + entry.redirect_request, entry.redirect_response); + } else { + // Unknown test. + ADD_FAILURE() << "url: " << request->GetURL().ToString(); + server->SendHttp500Response(connection_id, "Unknown test"); + } + } + + void HandleNormalRequest(CefRefPtr server, + int connection_id, + CefRefPtr request, + RequestRunSettings* settings) { + VerifyNormalRequest(settings, request, true); + + CefRefPtr response = CefResponse::Create(); + GetNormalResponse(settings, response); + + // HEAD requests are identical to GET requests except no response data is + // sent. + std::string response_data; + if (request->GetMethod() != "HEAD") + response_data = settings->response_data; + + SendResponse(server, connection_id, response, response_data); + } + + void HandleRedirectRequest(CefRefPtr server, + int connection_id, + CefRefPtr request, + CefRefPtr redirect_request, + CefRefPtr redirect_response) { + // Verify that the request was sent correctly. + TestRequestEqual(redirect_request, request, true); + + SendResponse(server, connection_id, redirect_response, std::string()); + } + + void SendResponse(CefRefPtr server, + int connection_id, + CefRefPtr response, + const std::string& response_data) { + int response_code = response->GetStatus(); + const CefString& content_type = response->GetMimeType(); + int64 content_length = static_cast(response_data.size()); + + CefResponse::HeaderMap extra_headers; + response->GetHeaderMap(extra_headers); + + server->SendHttpResponse(connection_id, response_code, content_type, + content_length, extra_headers); + + if (content_length != 0) { + server->SendRawData(connection_id, response_data.data(), + response_data.size()); + server->CloseConnection(connection_id); + } + + // The connection should be closed. + EXPECT_FALSE(server->IsValidConnection(connection_id)); + } + + void RunCompleteCallback(bool startup) { + EXPECT_UI_THREAD(); + + if (startup) { + // Transfer DataMap ownership to the server thread. + data_map_.SetOwnerTaskRunner(server_->GetTaskRunner()); + } + + EXPECT_FALSE(complete_callback_.is_null()); + complete_callback_.Run(); + complete_callback_.Reset(); + } + + RequestDataMap data_map_; + + CefRefPtr server_; + CefRefPtr server_runner_; + bool initialized_; + + // Only accessed on the UI thread. + base::Closure complete_callback_; + + // After initialization the below members are only accessed on the server + // thread. + + TrackCallback got_server_created_; + TrackCallback got_server_destroyed_; + + typedef std::set ConnectionIdSet; + ConnectionIdSet connection_id_set_; + + int expected_connection_ct_; + int actual_connection_ct_; + int expected_http_request_ct_; + int actual_http_request_ct_; + + std::string request_log_; + + IMPLEMENT_REFCOUNTING(RequestServerHandler); + DISALLOW_COPY_AND_ASSIGN(RequestServerHandler); +}; + +// URLREQUEST CLIENT + // Implementation of CefURLRequestClient that stores response information. class RequestClient : public CefURLRequestClient { public: - class Delegate { - public: - // Used to notify the handler when the request has completed. - virtual void OnRequestComplete(CefRefPtr client) = 0; + typedef base::Callback)> + RequestCompleteCallback; - protected: - virtual ~Delegate() {} - }; - - explicit RequestClient(Delegate* delegate) - : delegate_(delegate), + explicit RequestClient(const RequestCompleteCallback& complete_callback) + : complete_callback_(complete_callback), request_complete_ct_(0), upload_progress_ct_(0), download_progress_ct_(0), download_data_ct_(0), upload_total_(0), - download_total_(0) {} + download_total_(0) { + EXPECT_FALSE(complete_callback_.is_null()); + } void OnRequestComplete(CefRefPtr request) override { request_complete_ct_++; @@ -480,10 +972,11 @@ class RequestClient : public CefURLRequestClient { status_ = request->GetRequestStatus(); error_code_ = request->GetRequestError(); response_ = request->GetResponse(); - EXPECT_TRUE(response_.get()); - EXPECT_TRUE(response_->IsReadOnly()); + if (response_) { + EXPECT_TRUE(response_->IsReadOnly()); + } - delegate_->OnRequestComplete(this); + complete_callback_.Run(this); } void OnUploadProgress(CefRefPtr request, @@ -523,7 +1016,7 @@ class RequestClient : public CefURLRequestClient { } private: - Delegate* delegate_; + RequestCompleteCallback complete_callback_; public: int request_complete_ct_; @@ -543,27 +1036,19 @@ class RequestClient : public CefURLRequestClient { IMPLEMENT_REFCOUNTING(RequestClient); }; +// SHARED TEST RUNNER + // Executes the tests. class RequestTestRunner : public base::RefCountedThreadSafe { public: typedef base::Callback TestCallback; - // Delegate methods will be called on the same thread that constructed the - // RequestTestRunner object. - class Delegate { - public: - // Setup has completed. - virtual void OnRunnerSetupComplete() = 0; - - // Run has completed. - virtual void OnRunnerRunComplete() = 0; - - protected: - virtual ~Delegate() {} - }; - - RequestTestRunner(Delegate* delegate, bool is_browser_process) - : delegate_(delegate), is_browser_process_(is_browser_process) { + RequestTestRunner(bool is_browser_process, + bool is_server_backend, + bool run_in_browser_process) + : is_browser_process_(is_browser_process), + is_server_backend_(is_server_backend), + run_in_browser_process_(run_in_browser_process) { owner_task_runner_ = CefTaskRunner::GetForCurrentThread(); EXPECT_TRUE(owner_task_runner_.get()); EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); @@ -574,17 +1059,33 @@ class RequestTestRunner : public base::RefCountedThreadSafe { base::Bind(&RequestTestRunner::run_method, this)); // Register the test callbacks. - REGISTER_TEST(REQTEST_GET, SetupGetTest, GenericRunTest); - REGISTER_TEST(REQTEST_GET_NODATA, SetupGetNoDataTest, GenericRunTest); + REGISTER_TEST(REQTEST_GET, SetupGetTest, SingleRunTest); + REGISTER_TEST(REQTEST_GET_NODATA, SetupGetNoDataTest, SingleRunTest); REGISTER_TEST(REQTEST_GET_ALLOWCOOKIES, SetupGetAllowCookiesTest, - GenericRunTest); - REGISTER_TEST(REQTEST_GET_REDIRECT, SetupGetRedirectTest, GenericRunTest); - REGISTER_TEST(REQTEST_GET_REFERRER, SetupGetReferrerTest, GenericRunTest); - REGISTER_TEST(REQTEST_POST, SetupPostTest, GenericRunTest); - REGISTER_TEST(REQTEST_POST_FILE, SetupPostFileTest, GenericRunTest); + SingleRunTest); + REGISTER_TEST(REQTEST_GET_REDIRECT, SetupGetRedirectTest, SingleRunTest); + REGISTER_TEST(REQTEST_GET_REFERRER, SetupGetReferrerTest, SingleRunTest); + REGISTER_TEST(REQTEST_POST, SetupPostTest, SingleRunTest); + REGISTER_TEST(REQTEST_POST_FILE, SetupPostFileTest, SingleRunTest); REGISTER_TEST(REQTEST_POST_WITHPROGRESS, SetupPostWithProgressTest, - GenericRunTest); - REGISTER_TEST(REQTEST_HEAD, SetupHeadTest, GenericRunTest); + SingleRunTest); + REGISTER_TEST(REQTEST_HEAD, SetupHeadTest, SingleRunTest); + REGISTER_TEST(REQTEST_CACHE_WITH_CONTROL, SetupCacheWithControlTest, + MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_WITHOUT_CONTROL, SetupCacheWithoutControlTest, + MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_SKIP_FLAG, SetupCacheSkipFlagTest, + MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_SKIP_HEADER, SetupCacheSkipHeaderTest, + MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_ONLY_FAILURE_FLAG, + SetupCacheOnlyFailureFlagTest, MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_ONLY_FAILURE_HEADER, + SetupCacheOnlyFailureHeaderTest, MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_ONLY_SUCCESS_FLAG, + SetupCacheOnlySuccessFlagTest, MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_ONLY_SUCCESS_HEADER, + SetupCacheOnlySuccessHeaderTest, MultipleRunTest); } void Destroy() { @@ -602,15 +1103,17 @@ class RequestTestRunner : public base::RefCountedThreadSafe { } // Called in both the browser and render process to setup the test. - void SetupTest(RequestTestMode test_mode) { + void SetupTest(RequestTestMode test_mode, + const base::Closure& complete_callback) { EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); - const base::Closure& complete_callback = - base::Bind(&RequestTestRunner::SetupComplete, this); + const base::Closure& safe_complete_callback = base::Bind( + &RequestTestRunner::CompleteOnCorrectThread, this, complete_callback); + TestMap::const_iterator it = test_map_.find(test_mode); if (it != test_map_.end()) { it->second.setup.Run(base::Bind(&RequestTestRunner::SetupContinue, this, - complete_callback)); + safe_complete_callback)); } else { // Unknown test. ADD_FAILURE(); @@ -619,14 +1122,16 @@ class RequestTestRunner : public base::RefCountedThreadSafe { } // Called in either the browser or render process to run the test. - void RunTest(RequestTestMode test_mode) { + void RunTest(RequestTestMode test_mode, + const base::Closure& complete_callback) { EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); - const base::Closure& complete_callback = - base::Bind(&RequestTestRunner::RunComplete, this); + const base::Closure& safe_complete_callback = base::Bind( + &RequestTestRunner::CompleteOnCorrectThread, this, complete_callback); + TestMap::const_iterator it = test_map_.find(test_mode); if (it != test_map_.end()) { - it->second.run.Run(complete_callback); + it->second.run.Run(safe_complete_callback); } else { // Unknown test. ADD_FAILURE(); @@ -634,6 +1139,25 @@ class RequestTestRunner : public base::RefCountedThreadSafe { } } + // Called in both the browser and render process to shut down the test. + void ShutdownTest(const base::Closure& complete_callback) { + EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); + + const base::Closure& safe_complete_callback = base::Bind( + &RequestTestRunner::CompleteOnCorrectThread, this, complete_callback); + + if (!post_file_tmpdir_.IsEmpty()) { + EXPECT_TRUE(is_browser_process_); + CefPostTask(TID_FILE, + base::Bind(&RequestTestRunner::RunDeleteTempDirectory, this, + safe_complete_callback)); + return; + } + + // Continue with test shutdown. + RunShutdown(safe_complete_callback); + } + private: // Continued after |settings_| is populated for the test. void SetupContinue(const base::Closure& complete_callback) { @@ -643,15 +1167,24 @@ class RequestTestRunner : public base::RefCountedThreadSafe { return; } - if (is_browser_process_) - AddSchemeHandler(); + if (is_browser_process_) { + SetupTestBackend(complete_callback); + } else { + complete_callback.Run(); + } + } - complete_callback.Run(); + std::string GetTestURL(const std::string& name) { + // Avoid name duplication between tests running in different processes. + // Otherwise we'll get unexpected state leakage (cache hits) when running + // multiple tests. + return GetRequestOrigin(is_server_backend_) + "/" + + (run_in_browser_process_ ? "Browser" : "Renderer") + name; } void SetupGetTestShared() { settings_.request = CefRequest::Create(); - settings_.request->SetURL(MakeSchemeURL("GetTest.html")); + settings_.request->SetURL(GetTestURL("GetTest.html")); settings_.request->SetMethod("GET"); settings_.response = CefResponse::Create(); @@ -684,7 +1217,7 @@ class RequestTestRunner : public base::RefCountedThreadSafe { SetupGetTestShared(); // Send cookies. - settings_.request->SetFlags(UR_FLAG_ALLOW_CACHED_CREDENTIALS); + settings_.request->SetFlags(UR_FLAG_ALLOW_STORED_CREDENTIALS); settings_.expect_save_cookie = true; settings_.expect_send_cookie = true; @@ -698,7 +1231,7 @@ class RequestTestRunner : public base::RefCountedThreadSafe { // Add a redirect request. settings_.redirect_request = CefRequest::Create(); - settings_.redirect_request->SetURL(MakeSchemeURL("redirect.html")); + settings_.redirect_request->SetURL(GetTestURL("redirect.html")); settings_.redirect_request->SetMethod("GET"); settings_.redirect_response = CefResponse::Create(); @@ -715,7 +1248,7 @@ class RequestTestRunner : public base::RefCountedThreadSafe { void SetupGetReferrerTest(const base::Closure& complete_callback) { settings_.request = CefRequest::Create(); - settings_.request->SetURL(MakeSchemeURL("GetTest.html")); + settings_.request->SetURL(GetTestURL("GetTest.html")); settings_.request->SetMethod("GET"); // The referrer URL must be HTTP or HTTPS. This is enforced by @@ -735,7 +1268,7 @@ class RequestTestRunner : public base::RefCountedThreadSafe { void SetupPostTestShared() { settings_.request = CefRequest::Create(); - settings_.request->SetURL(MakeSchemeURL("PostTest.html")); + settings_.request->SetURL(GetTestURL("PostTest.html")); settings_.request->SetMethod("POST"); SetUploadData(settings_.request, "the_post_data"); @@ -757,7 +1290,7 @@ class RequestTestRunner : public base::RefCountedThreadSafe { EXPECT_TRUE(is_browser_process_); settings_.request = CefRequest::Create(); - settings_.request->SetURL(MakeSchemeURL("PostFileTest.html")); + settings_.request->SetURL(GetTestURL("PostFileTest.html")); settings_.request->SetMethod("POST"); settings_.response = CefResponse::Create(); @@ -801,7 +1334,7 @@ class RequestTestRunner : public base::RefCountedThreadSafe { void SetupHeadTest(const base::Closure& complete_callback) { settings_.request = CefRequest::Create(); - settings_.request->SetURL(MakeSchemeURL("HeadTest.html")); + settings_.request->SetURL(GetTestURL("HeadTest.html")); settings_.request->SetMethod("HEAD"); settings_.response = CefResponse::Create(); @@ -809,7 +1342,7 @@ class RequestTestRunner : public base::RefCountedThreadSafe { settings_.response->SetStatus(200); settings_.response->SetStatusText("OK"); - // The scheme handler will disregard this value when it returns the result. + // The backend will disregard this value when it returns the result. settings_.response_data = "HEAD TEST SUCCESS"; settings_.expect_download_progress = false; @@ -818,92 +1351,296 @@ class RequestTestRunner : public base::RefCountedThreadSafe { complete_callback.Run(); } - // Generic test runner. - void GenericRunTest(const base::Closure& complete_callback) { - class Test : public RequestClient::Delegate { - public: - Test(const RequestRunSettings& settings, - const base::Closure& complete_callback) - : settings_(settings), complete_callback_(complete_callback) { - EXPECT_FALSE(complete_callback_.is_null()); - } + void SetupCacheShared(const std::string& name, bool with_cache_control) { + // Start with the normal get test. + SetupGetTestShared(); - void OnRequestComplete(CefRefPtr client) override { - CefRefPtr expected_request; - CefRefPtr expected_response; + // Specify a unique URL. + settings_.request->SetURL(GetTestURL(name)); - if (settings_.redirect_request.get()) - expected_request = settings_.redirect_request; - else - expected_request = settings_.request; + if (with_cache_control) { + // Allow the page to be cached for 10 seconds. + CefResponse::HeaderMap headerMap; + headerMap.insert(std::make_pair(kCacheControlHeader, "max-age=10")); + settings_.response->SetHeaderMap(headerMap); + } + } - if (settings_.redirect_response.get() && - !settings_.expect_follow_redirect) { - // A redirect response was sent but the redirect is not expected to be - // followed. - expected_response = settings_.redirect_response; - } else { - expected_response = settings_.response; - } + void SetupCacheWithControlTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheWithControlTest.html", true); - TestRequestEqual(expected_request, client->request_, false); + // Send multiple requests. With the Cache-Control response header the 2nd+ + // should receive cached data. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 1; - EXPECT_EQ(settings_.expected_status, client->status_); - EXPECT_EQ(settings_.expected_error_code, client->error_code_); - TestResponseEqual(expected_response, client->response_, true); + complete_callback.Run(); + } - EXPECT_EQ(1, client->request_complete_ct_); + void SetupCacheWithoutControlTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheWithoutControlTest.html", false); - if (settings_.expect_upload_progress) { - EXPECT_LE(1, client->upload_progress_ct_); + // Send multiple requests. Without the Cache-Control response header all + // should be received. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 3; - std::string upload_data; - GetUploadData(expected_request, upload_data); - EXPECT_EQ(upload_data.size(), client->upload_total_); - } else { - EXPECT_EQ(0, client->upload_progress_ct_); - EXPECT_EQ((uint64)0, client->upload_total_); - } + complete_callback.Run(); + } - if (settings_.expect_download_progress) { - EXPECT_LE(1, client->download_progress_ct_); - EXPECT_EQ(settings_.response_data.size(), client->download_total_); - } else { - EXPECT_EQ(0, client->download_progress_ct_); - EXPECT_EQ((uint64)0, client->download_total_); - } + void SetupCacheSkipFlagTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheSkipFlagTest.html", true); - if (settings_.expect_download_data) { - EXPECT_LE(1, client->download_data_ct_); - EXPECT_STREQ(settings_.response_data.c_str(), - client->download_data_.c_str()); - } else { - EXPECT_EQ(0, client->download_data_ct_); - EXPECT_TRUE(client->download_data_.empty()); - } + // Skip the cache despite the the Cache-Control response header. + settings_.request->SetFlags(UR_FLAG_SKIP_CACHE); - complete_callback_.Run(); - complete_callback_.Reset(); - } + // Send multiple requests. All should be received. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 3; - private: - RequestRunSettings settings_; - base::Closure complete_callback_; - }; + complete_callback.Run(); + } + void SetupCacheSkipHeaderTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheSkipHeaderTest.html", true); + + // Skip the cache despite the the Cache-Control response header. + CefRequest::HeaderMap headerMap; + headerMap.insert(std::make_pair(kCacheControlHeader, "no-cache")); + settings_.request->SetHeaderMap(headerMap); + + // Send multiple requests. All should be received. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 3; + + complete_callback.Run(); + } + + void SetupCacheOnlyFailureFlagTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheOnlyFailureFlagTest.html", true); + + // Only handle from the cache. + settings_.request->SetFlags(UR_FLAG_ONLY_FROM_CACHE); + + // Send multiple requests. All should fail because there's no entry in the + // cache currently. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 0; + + // The request is expected to fail. + settings_.SetRequestFailureExpected(ERR_CACHE_MISS); + + complete_callback.Run(); + } + + void SetupCacheOnlyFailureHeaderTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheOnlyFailureFlagTest.html", true); + + // Only handle from the cache. + CefRequest::HeaderMap headerMap; + headerMap.insert(std::make_pair(kCacheControlHeader, "only-if-cached")); + settings_.request->SetHeaderMap(headerMap); + + // Send multiple requests. All should fail because there's no entry in the + // cache currently. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 0; + + // The request is expected to fail. + settings_.SetRequestFailureExpected(ERR_CACHE_MISS); + + complete_callback.Run(); + } + + void SetupCacheOnlySuccessFlagTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheOnlySuccessFlagTest.html", false); + + // Send multiple requests. The 1st request will be handled normally. The + // 2nd+ requests will be configured by SetupCacheOnlySuccessFlagNext to + // require cached data. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 1; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheOnlySuccessFlagNext, this); + + complete_callback.Run(); + } + + void SetupCacheOnlySuccessFlagNext(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheOnlySuccessFlagTest.html", false); + + // Only handle from the cache. + settings_.request->SetFlags(UR_FLAG_ONLY_FROM_CACHE); + + // The following requests will use the same setup, so no more callbacks + // are required. + settings_.setup_next_request.Reset(); + + complete_callback.Run(); + } + + void SetupCacheOnlySuccessHeaderTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheOnlySuccessHeaderTest.html", false); + + // Send multiple requests. The 1st request will be handled normally. The + // 2nd+ requests will be configured by SetupCacheOnlySuccessHeaderNext to + // require cached data. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 1; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheOnlySuccessHeaderNext, this); + + complete_callback.Run(); + } + + void SetupCacheOnlySuccessHeaderNext(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheOnlySuccessHeaderTest.html", false); + + // Only handle from the cache. + CefRequest::HeaderMap headerMap; + headerMap.insert(std::make_pair(kCacheControlHeader, "only-if-cached")); + settings_.request->SetHeaderMap(headerMap); + + // The following requests will use the same setup, so no more callbacks + // are required. + settings_.setup_next_request.Reset(); + + complete_callback.Run(); + } + + // Send a request. |complete_callback| will be executed on request completion. + void SendRequest( + const RequestClient::RequestCompleteCallback& complete_callback) { CefRefPtr request; - if (settings_.redirect_request.get()) + if (settings_.redirect_request) request = settings_.redirect_request; else request = settings_.request; EXPECT_TRUE(request.get()); - CefRefPtr client = - new RequestClient(new Test(settings_, complete_callback)); - + CefRefPtr client = new RequestClient(complete_callback); CefURLRequest::Create(request, client.get(), request_context_); } + // Verify a response. + void VerifyResponse(CefRefPtr client) { + CefRefPtr expected_request; + CefRefPtr expected_response; + + if (settings_.redirect_request) + expected_request = settings_.redirect_request; + else + expected_request = settings_.request; + + if (settings_.redirect_response && !settings_.expect_follow_redirect) { + // A redirect response was sent but the redirect is not expected to be + // followed. + expected_response = settings_.redirect_response; + } else { + expected_response = settings_.response; + } + + TestRequestEqual(expected_request, client->request_, false); + + EXPECT_EQ(settings_.expected_status, client->status_); + EXPECT_EQ(settings_.expected_error_code, client->error_code_); + if (expected_response && client->response_) + TestResponseEqual(expected_response, client->response_, true); + + EXPECT_EQ(1, client->request_complete_ct_); + + if (settings_.expect_upload_progress) { + EXPECT_LE(1, client->upload_progress_ct_); + + std::string upload_data; + GetUploadData(expected_request, upload_data); + EXPECT_EQ(upload_data.size(), client->upload_total_); + } else { + EXPECT_EQ(0, client->upload_progress_ct_); + EXPECT_EQ((uint64)0, client->upload_total_); + } + + if (settings_.expect_download_progress) { + EXPECT_LE(1, client->download_progress_ct_); + EXPECT_EQ(settings_.response_data.size(), client->download_total_); + } else { + EXPECT_EQ(0, client->download_progress_ct_); + EXPECT_EQ((uint64)0, client->download_total_); + } + + if (settings_.expect_download_data) { + EXPECT_LE(1, client->download_data_ct_); + EXPECT_STREQ(settings_.response_data.c_str(), + client->download_data_.c_str()); + } else { + EXPECT_EQ(0, client->download_data_ct_); + EXPECT_TRUE(client->download_data_.empty()); + } + } + + // Run a test with a single request. + void SingleRunTest(const base::Closure& complete_callback) { + SendRequest(base::Bind(&RequestTestRunner::SingleRunTestComplete, this, + complete_callback)); + } + + void SingleRunTestComplete(const base::Closure& complete_callback, + CefRefPtr completed_client) { + VerifyResponse(completed_client); + complete_callback.Run(); + } + + // Run a test with multiple requests. + void MultipleRunTest(const base::Closure& complete_callback) { + EXPECT_GT(settings_.expected_send_count, 0); + EXPECT_GE(settings_.expected_receive_count, 0); + MultipleRunTestContinue(complete_callback, 1); + } + + void MultipleRunTestContinue(const base::Closure& complete_callback, + int send_count) { + // Send the next request. + SendRequest(base::Bind(&RequestTestRunner::MultipleRunTestNext, this, + complete_callback, send_count)); + } + + void MultipleRunTestNext(const base::Closure& complete_callback, + int send_count, + CefRefPtr completed_client) { + // Verify the completed request. + VerifyResponse(completed_client); + + if (send_count == settings_.expected_send_count) { + // All requests complete. + complete_callback.Run(); + return; + } + + const int next_send_count = send_count + 1; + const base::Closure& continue_callback = + base::Bind(&RequestTestRunner::MultipleRunTestContinue, this, + complete_callback, next_send_count); + + if (!settings_.setup_next_request.is_null()) { + // Provide an opportunity to modify expectations before the next request. + // Copy the callback object in case |settings_.setup_next_request| is + // modified as a result of the call. + RequestRunSettings::NextRequestCallback next_callback = + settings_.setup_next_request; + next_callback.Run(next_send_count, continue_callback); + } else { + continue_callback.Run(); + } + } + // Register a test. Called in the constructor. void RegisterTest(RequestTestMode test_mode, TestCallback setup, @@ -912,91 +1649,117 @@ class RequestTestRunner : public base::RefCountedThreadSafe { test_map_.insert(std::make_pair(test_mode, entry)); } - void SetupComplete() { + void CompleteOnCorrectThread(const base::Closure& complete_callback) { if (!owner_task_runner_->BelongsToCurrentThread()) { owner_task_runner_->PostTask(CefCreateClosureTask( - base::Bind(&RequestTestRunner::SetupComplete, this))); + base::Bind(&RequestTestRunner::CompleteOnCorrectThread, this, + complete_callback))); return; } - delegate_->OnRunnerSetupComplete(); + complete_callback.Run(); } - // Destroy the current test. Called when the test is complete. - void RunComplete() { - if (!post_file_tmpdir_.IsEmpty()) { - EXPECT_TRUE(is_browser_process_); - CefPostTask( - TID_FILE, - base::Bind(&RequestTestRunner::RunCompleteDeleteTempDirectory, this)); - return; - } - - // Continue with test completion. - RunCompleteContinue(); - } - - void RunCompleteDeleteTempDirectory() { + void RunDeleteTempDirectory(const base::Closure& complete_callback) { EXPECT_TRUE(CefCurrentlyOn(TID_FILE)); EXPECT_TRUE(post_file_tmpdir_.Delete()); EXPECT_TRUE(post_file_tmpdir_.IsEmpty()); - // Continue with test completion. - RunCompleteContinue(); + // Continue with test shutdown. + RunShutdown(complete_callback); } - void RunCompleteContinue() { + void RunShutdown(const base::Closure& complete_callback) { if (!owner_task_runner_->BelongsToCurrentThread()) { - owner_task_runner_->PostTask(CefCreateClosureTask( - base::Bind(&RequestTestRunner::RunCompleteContinue, this))); + owner_task_runner_->PostTask(CefCreateClosureTask(base::Bind( + &RequestTestRunner::RunShutdown, this, complete_callback))); return; } - if (scheme_factory_.get()) { - EXPECT_TRUE(is_browser_process_); - - // Remove the factory registration. - request_context_->RegisterSchemeHandlerFactory(kRequestScheme, - kRequestHost, NULL); - scheme_factory_ = NULL; + if (is_browser_process_) { + ShutdownTestBackend(complete_callback); + } else { + complete_callback.Run(); } - - delegate_->OnRunnerRunComplete(); } - // Return an appropriate scheme URL for the specified |path|. - std::string MakeSchemeURL(const std::string& path) { - std::stringstream ss; - ss << kRequestOrigin << "/" << path; - return ss.str(); - } - - // Add a scheme handler for the current test. Called during test setup. - void AddSchemeHandler() { - // Scheme handlers are only registered in the browser process. + // Create the backend for the current test. Called during test setup. + void SetupTestBackend(const base::Closure& complete_callback) { + // Backends are only created in the browser process. EXPECT_TRUE(is_browser_process_); - if (!scheme_factory_.get()) { - // Add the factory registration. - scheme_factory_ = new RequestSchemeHandlerFactory(); - request_context_->RegisterSchemeHandlerFactory( - kRequestScheme, kRequestHost, scheme_factory_.get()); - } - EXPECT_TRUE(settings_.request.get()); - EXPECT_TRUE(settings_.response.get()); + EXPECT_TRUE(settings_.response.get() || + settings_.expected_status == UR_FAILED); - scheme_factory_->AddSchemeHandler(settings_); - - if (settings_.redirect_request.get()) { - scheme_factory_->AddRedirectSchemeHandler(settings_.redirect_request, - settings_.redirect_response); - } + if (is_server_backend_) + StartServer(complete_callback); + else + AddSchemeHandler(complete_callback); + } + + void StartServer(const base::Closure& complete_callback) { + EXPECT_FALSE(server_handler_); + + server_handler_ = new RequestServerHandler(); + + server_handler_->AddSchemeHandler(&settings_); + if (settings_.expected_receive_count >= 0) { + server_handler_->SetExpectedRequestCount( + settings_.expected_receive_count); + } + + server_handler_->CreateServer(complete_callback); + } + + void AddSchemeHandler(const base::Closure& complete_callback) { + EXPECT_FALSE(scheme_factory_); + + // Add the factory registration. + scheme_factory_ = new RequestSchemeHandlerFactory(); + request_context_->RegisterSchemeHandlerFactory(GetRequestScheme(false), + GetRequestHost(false, false), + scheme_factory_.get()); + + scheme_factory_->AddSchemeHandler(&settings_); + + // Any further calls will come from the IO thread. + scheme_factory_->SetOwnerTaskRunner(CefTaskRunner::GetForThread(TID_IO)); + + complete_callback.Run(); + } + + // Shutdown the backend for the current test. Called during test shutdown. + void ShutdownTestBackend(const base::Closure& complete_callback) { + // Backends are only created in the browser process. + EXPECT_TRUE(is_browser_process_); + if (is_server_backend_) + ShutdownServer(complete_callback); + else + RemoveSchemeHandler(complete_callback); + } + + void ShutdownServer(const base::Closure& complete_callback) { + EXPECT_TRUE(server_handler_); + + server_handler_->ShutdownServer(complete_callback); + server_handler_ = nullptr; + } + + void RemoveSchemeHandler(const base::Closure& complete_callback) { + EXPECT_TRUE(scheme_factory_); + + // Remove the factory registration. + request_context_->RegisterSchemeHandlerFactory( + GetRequestScheme(false), GetRequestHost(false, false), NULL); + scheme_factory_->Shutdown(complete_callback); + scheme_factory_ = nullptr; } - Delegate* delegate_; bool is_browser_process_; + bool is_server_backend_; + bool run_in_browser_process_; // Primary thread runner for the object that owns us. In the browser process // this will be the UI thread and in the renderer process this will be the @@ -1012,6 +1775,10 @@ class RequestTestRunner : public base::RefCountedThreadSafe { typedef std::map TestMap; TestMap test_map_; + // Server backend. + CefRefPtr server_handler_; + + // Scheme handler backend. std::string scheme_name_; CefRefPtr scheme_factory_; @@ -1021,9 +1788,9 @@ class RequestTestRunner : public base::RefCountedThreadSafe { RequestRunSettings settings_; }; -// Renderer side. -class RequestRendererTest : public ClientAppRenderer::Delegate, - public RequestTestRunner::Delegate { +// RENDERER-SIDE TEST HARNESS + +class RequestRendererTest : public ClientAppRenderer::Delegate { public: RequestRendererTest() {} @@ -1037,14 +1804,14 @@ class RequestRendererTest : public ClientAppRenderer::Delegate, app_ = app; browser_ = browser; - test_mode_ = - static_cast(message->GetArgumentList()->GetInt(0)); - - test_runner_ = new RequestTestRunner(this, false); + CefRefPtr args = message->GetArgumentList(); + test_mode_ = static_cast(args->GetInt(0)); + test_runner_ = new RequestTestRunner(false, args->GetBool(1), false); // Setup the test. This will create the objects that we test against but - // not register any scheme handlers (because we're in the render process). - test_runner_->SetupTest(test_mode_); + // not register any backend (because we're in the render process). + test_runner_->SetupTest( + test_mode_, base::Bind(&RequestRendererTest::OnSetupComplete, this)); return true; } @@ -1054,15 +1821,23 @@ class RequestRendererTest : public ClientAppRenderer::Delegate, } private: - void OnRunnerSetupComplete() override { + void OnSetupComplete() { EXPECT_TRUE(CefCurrentlyOn(TID_RENDERER)); // Run the test. - test_runner_->RunTest(test_mode_); + test_runner_->RunTest( + test_mode_, base::Bind(&RequestRendererTest::OnRunComplete, this)); } - // Return from the test. - void OnRunnerRunComplete() override { + void OnRunComplete() { + EXPECT_TRUE(CefCurrentlyOn(TID_RENDERER)); + + // Shutdown the test. + test_runner_->ShutdownTest( + base::Bind(&RequestRendererTest::OnShutdownComplete, this)); + } + + void OnShutdownComplete() { EXPECT_TRUE(CefCurrentlyOn(TID_RENDERER)); // Check if the test has failed. @@ -1087,17 +1862,19 @@ class RequestRendererTest : public ClientAppRenderer::Delegate, IMPLEMENT_REFCOUNTING(RequestRendererTest); }; -// Browser side. -class RequestTestHandler : public TestHandler, - public RequestTestRunner::Delegate { +// BROWSER-SIDE TEST HARNESS + +class RequestTestHandler : public TestHandler { public: RequestTestHandler(RequestTestMode test_mode, ContextTestMode context_mode, bool test_in_browser, + bool test_server_backend, const char* test_url) : test_mode_(test_mode), context_mode_(context_mode), test_in_browser_(test_in_browser), + test_server_backend_(test_server_backend), test_url_(test_url) {} void RunTest() override { @@ -1129,7 +1906,8 @@ class RequestTestHandler : public TestHandler, void PreSetupContinue() { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); - test_runner_ = new RequestTestRunner(this, true); + test_runner_ = + new RequestTestRunner(true, test_server_backend_, test_in_browser_); // Get or create the request context. if (context_mode_ == CONTEXT_GLOBAL) { @@ -1158,14 +1936,19 @@ class RequestTestHandler : public TestHandler, EXPECT_TRUE(request_context.get()); test_runner_->SetRequestContext(request_context); - // Set the schemes that are allowed to store cookies. - std::vector supported_schemes; - supported_schemes.push_back(kRequestScheme); + if (!test_server_backend_) { + // Set the schemes that are allowed to store cookies. + std::vector supported_schemes; + supported_schemes.push_back(GetRequestScheme(false)); - // Continue the test once supported schemes has been set. - request_context->GetDefaultCookieManager(NULL)->SetSupportedSchemes( - supported_schemes, new SupportedSchemesCompletionCallback(base::Bind( - &RequestTestHandler::PreSetupComplete, this))); + // Continue the test once supported schemes has been set. + request_context->GetDefaultCookieManager(NULL)->SetSupportedSchemes( + supported_schemes, + new SupportedSchemesCompletionCallback( + base::Bind(&RequestTestHandler::PreSetupComplete, this))); + } else { + PreSetupComplete(); + } } } @@ -1177,17 +1960,14 @@ class RequestTestHandler : public TestHandler, } // Setup the test. This will create the objects that we test against and - // register any scheme handlers. - test_runner_->SetupTest(test_mode_); + // register the backend. + test_runner_->SetupTest( + test_mode_, base::Bind(&RequestTestHandler::OnSetupComplete, this)); } // Browser process setup is complete. - void OnRunnerSetupComplete() override { + void OnSetupComplete() { // Start post-setup actions. - PostSetupStart(); - } - - void PostSetupStart() { CefPostTask(TID_FILE, base::Bind(&RequestTestHandler::PostSetupFileTasks, this)); } @@ -1196,7 +1976,7 @@ class RequestTestHandler : public TestHandler, EXPECT_TRUE(CefCurrentlyOn(TID_FILE)); // Don't use WaitableEvent on the UI thread. - SetTestCookie(test_runner_->GetRequestContext()); + SetTestCookie(test_runner_->GetRequestContext(), test_server_backend_); CefPostTask(TID_UI, base::Bind(&RequestTestHandler::PostSetupComplete, this)); @@ -1207,7 +1987,8 @@ class RequestTestHandler : public TestHandler, if (test_in_browser_) { // Run the test now. - test_runner_->RunTest(test_mode_); + test_runner_->RunTest( + test_mode_, base::Bind(&RequestTestHandler::OnRunComplete, this)); } else { EXPECT_TRUE(test_url_ != NULL); AddResource(test_url_, "TEST", "text/html"); @@ -1224,7 +2005,9 @@ class RequestTestHandler : public TestHandler, if (frame->IsMain()) { CefRefPtr test_message = CefProcessMessage::Create(kRequestTestMsg); - EXPECT_TRUE(test_message->GetArgumentList()->SetInt(0, test_mode_)); + CefRefPtr args = test_message->GetArgumentList(); + EXPECT_TRUE(args->SetInt(0, test_mode_)); + EXPECT_TRUE(args->SetBool(1, test_server_backend_)); // Send a message to the renderer process to run the test. EXPECT_TRUE(browser->SendProcessMessage(PID_RENDERER, test_message)); @@ -1246,15 +2029,13 @@ class RequestTestHandler : public TestHandler, got_success_.yes(); // Renderer process test is complete. - PostRunStart(); + OnRunComplete(); return true; } - // Browser process test is complete. - void OnRunnerRunComplete() override { PostRunStart(); } - - void PostRunStart() { + // Test run is complete. It ran in either the browser or render process. + void OnRunComplete() { CefPostTask(TID_FILE, base::Bind(&RequestTestHandler::PostRunFileTasks, this)); } @@ -1264,7 +2045,8 @@ class RequestTestHandler : public TestHandler, // Don't use WaitableEvent on the UI thread. bool has_save_cookie = false; - GetTestCookie(test_runner_->GetRequestContext(), &has_save_cookie); + GetTestCookie(test_runner_->GetRequestContext(), &has_save_cookie, + test_server_backend_); EXPECT_EQ(test_runner_->settings_.expect_save_cookie, has_save_cookie); CefPostTask(TID_UI, base::Bind(&RequestTestHandler::PostRunComplete, this)); @@ -1272,10 +2054,18 @@ class RequestTestHandler : public TestHandler, void PostRunComplete() { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); - DestroyTest(); + + // Shut down the browser side of the test. + test_runner_->ShutdownTest( + base::Bind(&RequestTestHandler::DestroyTest, this)); } void DestroyTest() override { + if (!test_in_browser_) { + EXPECT_TRUE(got_message_); + EXPECT_TRUE(got_success_); + } + TestHandler::DestroyTest(); // Need to call TestComplete() explicitly if testing in the browser and @@ -1359,6 +2149,7 @@ class RequestTestHandler : public TestHandler, RequestTestMode test_mode_; ContextTestMode context_mode_; bool test_in_browser_; + bool test_server_backend_; const char* test_url_; scoped_refptr test_runner_; @@ -1387,59 +2178,109 @@ void CreateURLRequestRendererTests(ClientAppRenderer::DelegateSet& delegates) { void RegisterURLRequestCustomSchemes( CefRawPtr registrar, std::vector& cookiable_schemes) { - registrar->AddCustomScheme(kRequestScheme, true, false, false, false, true, - false); - cookiable_schemes.push_back(kRequestScheme); + const std::string& scheme = GetRequestScheme(false); + registrar->AddCustomScheme(scheme, true, false, false, false, true, false); + cookiable_schemes.push_back(scheme); } // Helpers for defining URLRequest tests. -#define REQ_TEST_EX(name, test_mode, context_mode, test_in_browser, test_url) \ - TEST(URLRequestTest, name) { \ - CefRefPtr handler = new RequestTestHandler( \ - test_mode, context_mode, test_in_browser, test_url); \ - handler->ExecuteTest(); \ - if (!test_in_browser) { \ - EXPECT_TRUE(handler->got_message_); \ - EXPECT_TRUE(handler->got_success_); \ - } \ - ReleaseAndWaitForDestructor(handler); \ +#define REQ_TEST_EX(name, test_mode, context_mode, test_in_browser, \ + test_server_backend, test_url) \ + TEST(URLRequestTest, name) { \ + CefRefPtr handler = \ + new RequestTestHandler(test_mode, context_mode, test_in_browser, \ + test_server_backend, test_url); \ + handler->ExecuteTest(); \ + ReleaseAndWaitForDestructor(handler); \ } -#define REQ_TEST(name, test_mode, context_mode, test_in_browser) \ - REQ_TEST_EX(name, test_mode, context_mode, test_in_browser, kRequestTestUrl) +#define REQ_TEST(name, test_mode, context_mode, test_in_browser, \ + test_server_backend) \ + REQ_TEST_EX(name, test_mode, context_mode, test_in_browser, \ + test_server_backend, kRequestTestUrl) // Define the tests. -#define REQ_TEST_SET(suffix, context_mode) \ - REQ_TEST(BrowserGET##suffix, REQTEST_GET, context_mode, true); \ - REQ_TEST(BrowserGETNoData##suffix, REQTEST_GET_NODATA, context_mode, true); \ - REQ_TEST(BrowserGETAllowCookies##suffix, REQTEST_GET_ALLOWCOOKIES, \ - context_mode, true); \ - REQ_TEST(BrowserGETRedirect##suffix, REQTEST_GET_REDIRECT, context_mode, \ - true); \ - REQ_TEST(BrowserGETReferrer##suffix, REQTEST_GET_REFERRER, context_mode, \ - true); \ - REQ_TEST(BrowserPOST##suffix, REQTEST_POST, context_mode, true); \ - REQ_TEST(BrowserPOSTFile##suffix, REQTEST_POST_FILE, context_mode, true); \ - REQ_TEST(BrowserPOSTWithProgress##suffix, REQTEST_POST_WITHPROGRESS, \ - context_mode, true); \ - REQ_TEST(BrowserHEAD##suffix, REQTEST_HEAD, context_mode, true); \ - REQ_TEST(RendererGET##suffix, REQTEST_GET, context_mode, false); \ - REQ_TEST(RendererGETNoData##suffix, REQTEST_GET_NODATA, context_mode, \ - false); \ - REQ_TEST(RendererGETAllowCookies##suffix, REQTEST_GET_ALLOWCOOKIES, \ - context_mode, false); \ - REQ_TEST(RendererGETRedirect##suffix, REQTEST_GET_REDIRECT, context_mode, \ - false); \ - REQ_TEST(RendererGETReferrer##suffix, REQTEST_GET_REFERRER, context_mode, \ - false); \ - REQ_TEST(RendererPOST##suffix, REQTEST_POST, context_mode, false); \ - REQ_TEST(RendererPOSTWithProgress##suffix, REQTEST_POST_WITHPROGRESS, \ - context_mode, false); \ - REQ_TEST(RendererHEAD##suffix, REQTEST_HEAD, context_mode, false) +#define REQ_TEST_SET(suffix, context_mode, test_server_backend) \ + REQ_TEST(BrowserGET##suffix, REQTEST_GET, context_mode, true, \ + test_server_backend); \ + REQ_TEST(BrowserGETNoData##suffix, REQTEST_GET_NODATA, context_mode, true, \ + test_server_backend); \ + REQ_TEST(BrowserGETAllowCookies##suffix, REQTEST_GET_ALLOWCOOKIES, \ + context_mode, true, test_server_backend); \ + REQ_TEST(BrowserGETRedirect##suffix, REQTEST_GET_REDIRECT, context_mode, \ + true, test_server_backend); \ + REQ_TEST(BrowserGETReferrer##suffix, REQTEST_GET_REFERRER, context_mode, \ + true, test_server_backend); \ + REQ_TEST(BrowserPOST##suffix, REQTEST_POST, context_mode, true, \ + test_server_backend); \ + REQ_TEST(BrowserPOSTFile##suffix, REQTEST_POST_FILE, context_mode, true, \ + test_server_backend); \ + REQ_TEST(BrowserPOSTWithProgress##suffix, REQTEST_POST_WITHPROGRESS, \ + context_mode, true, test_server_backend); \ + REQ_TEST(BrowserHEAD##suffix, REQTEST_HEAD, context_mode, true, \ + test_server_backend); \ + REQ_TEST(RendererGET##suffix, REQTEST_GET, context_mode, false, \ + test_server_backend); \ + REQ_TEST(RendererGETNoData##suffix, REQTEST_GET_NODATA, context_mode, false, \ + test_server_backend); \ + REQ_TEST(RendererGETAllowCookies##suffix, REQTEST_GET_ALLOWCOOKIES, \ + context_mode, false, test_server_backend); \ + REQ_TEST(RendererGETRedirect##suffix, REQTEST_GET_REDIRECT, context_mode, \ + false, test_server_backend); \ + REQ_TEST(RendererGETReferrer##suffix, REQTEST_GET_REFERRER, context_mode, \ + false, test_server_backend); \ + REQ_TEST(RendererPOST##suffix, REQTEST_POST, context_mode, false, \ + test_server_backend); \ + REQ_TEST(RendererPOSTWithProgress##suffix, REQTEST_POST_WITHPROGRESS, \ + context_mode, false, test_server_backend); \ + REQ_TEST(RendererHEAD##suffix, REQTEST_HEAD, context_mode, false, \ + test_server_backend) -REQ_TEST_SET(ContextGlobal, CONTEXT_GLOBAL); -REQ_TEST_SET(ContextInMemory, CONTEXT_INMEMORY); -REQ_TEST_SET(ContextOnDisk, CONTEXT_ONDISK); +REQ_TEST_SET(ContextGlobalCustom, CONTEXT_GLOBAL, false); +REQ_TEST_SET(ContextInMemoryCustom, CONTEXT_INMEMORY, false); +REQ_TEST_SET(ContextOnDiskCustom, CONTEXT_ONDISK, false); +REQ_TEST_SET(ContextGlobalServer, CONTEXT_GLOBAL, true); +REQ_TEST_SET(ContextInMemoryServer, CONTEXT_INMEMORY, true); +REQ_TEST_SET(ContextOnDiskServer, CONTEXT_ONDISK, true); + +// Cache tests can only be run with the server backend. +#define REQ_TEST_CACHE_SET(suffix, context_mode) \ + REQ_TEST(BrowserGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \ + context_mode, true, true); \ + REQ_TEST(BrowserGETCacheWithoutControl##suffix, \ + REQTEST_CACHE_WITHOUT_CONTROL, context_mode, true, true); \ + REQ_TEST(BrowserGETCacheSkipFlag##suffix, REQTEST_CACHE_SKIP_FLAG, \ + context_mode, true, true); \ + REQ_TEST(BrowserGETCacheSkipHeader##suffix, REQTEST_CACHE_SKIP_HEADER, \ + context_mode, true, true); \ + REQ_TEST(BrowserGETCacheOnlyFailureFlag##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_FLAG, context_mode, true, true); \ + REQ_TEST(BrowserGETCacheOnlyFailureHeader##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_HEADER, context_mode, true, true); \ + REQ_TEST(BrowserGETCacheOnlySuccessFlag##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_FLAG, context_mode, true, true) \ + REQ_TEST(BrowserGETCacheOnlySuccessHeader##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_HEADER, context_mode, true, true) \ + REQ_TEST(RendererGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \ + context_mode, false, true); \ + REQ_TEST(RendererGETCacheWithoutControl##suffix, \ + REQTEST_CACHE_WITHOUT_CONTROL, context_mode, false, true); \ + REQ_TEST(RendererGETCacheSkipFlag##suffix, REQTEST_CACHE_SKIP_FLAG, \ + context_mode, false, true); \ + REQ_TEST(RendererGETCacheSkipHeader##suffix, REQTEST_CACHE_SKIP_HEADER, \ + context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlyFailureFlag##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_FLAG, context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlyFailureHeader##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_HEADER, context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlySuccessFlag##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_FLAG, context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlySuccessHeader##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_HEADER, context_mode, false, true) + +REQ_TEST_CACHE_SET(ContextGlobalServer, CONTEXT_GLOBAL); +REQ_TEST_CACHE_SET(ContextInMemoryServer, CONTEXT_INMEMORY); +REQ_TEST_CACHE_SET(ContextOnDiskServer, CONTEXT_ONDISK); namespace { @@ -1519,7 +2360,4 @@ class InvalidURLTestClient : public CefURLRequestClient { TEST(URLRequestTest, BrowserInvalidURL) { CefRefPtr client = new InvalidURLTestClient(); client->RunTest(); - - // Verify that there's only one reference to the client. - EXPECT_TRUE(client->HasOneRef()); }