cef/tests/ceftests/resource_request_handler_un...

4002 lines
132 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 <algorithm>
#include <cmath>
#include <memory>
#include <sstream>
#include <string>
#include "include/base/cef_callback.h"
#include "include/cef_request_context_handler.h"
#include "include/cef_scheme.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_stream_resource_handler.h"
#include "tests/ceftests/routing_test_handler.h"
#include "tests/ceftests/test_handler.h"
#include "tests/ceftests/test_util.h"
#include "tests/gtest/include/gtest/gtest.h"
namespace {
// Normal stream resource handler implementation that additionally verifies
// calls to Cancel.
// This also tests the CefStreamResourceHandler implementation.
class NormalResourceHandler : public CefStreamResourceHandler {
public:
NormalResourceHandler(int status_code,
const CefString& status_text,
const CefString& mime_type,
CefResponse::HeaderMap header_map,
CefRefPtr<CefStreamReader> stream,
base::OnceClosure destroy_callback)
: CefStreamResourceHandler(status_code,
status_text,
mime_type,
header_map,
stream),
destroy_callback_(std::move(destroy_callback)) {}
~NormalResourceHandler() override {
EXPECT_EQ(1, cancel_ct_);
std::move(destroy_callback_).Run();
}
void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private:
base::OnceClosure destroy_callback_;
int cancel_ct_ = 0;
};
// Normal stream resource handler implementation that additionally continues
// using the callback object and verifies calls to Cancel.
class CallbackResourceHandler : public CefResourceHandler {
public:
enum Mode {
DELAYED_OPEN,
DELAYED_READ,
IMMEDIATE_OPEN,
IMMEDIATE_READ,
DELAYED_ALL,
IMMEDIATE_ALL,
};
bool IsDelayedOpen() const {
return mode_ == DELAYED_OPEN || mode_ == DELAYED_ALL;
}
bool IsDelayedRead() const {
return mode_ == DELAYED_READ || mode_ == DELAYED_ALL;
}
bool IsImmediateOpen() const {
return mode_ == IMMEDIATE_OPEN || mode_ == IMMEDIATE_ALL;
}
bool IsImmediateRead() const {
return mode_ == IMMEDIATE_READ || mode_ == IMMEDIATE_ALL;
}
CallbackResourceHandler(Mode mode,
int status_code,
const CefString& status_text,
const CefString& mime_type,
CefResponse::HeaderMap header_map,
CefRefPtr<CefStreamReader> stream,
base::OnceClosure destroy_callback)
: mode_(mode),
status_code_(status_code),
status_text_(status_text),
mime_type_(mime_type),
header_map_(header_map),
stream_(stream),
destroy_callback_(std::move(destroy_callback)) {
DCHECK(!mime_type_.empty());
DCHECK(stream_.get());
}
~CallbackResourceHandler() override {
EXPECT_EQ(1, cancel_ct_);
std::move(destroy_callback_).Run();
}
bool Open(CefRefPtr<CefRequest> request,
bool& handle_request,
CefRefPtr<CefCallback> callback) override {
EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO));
if (IsDelayedOpen()) {
// Continue the request asynchronously by executing the callback.
CefPostTask(TID_FILE_USER_VISIBLE,
base::BindOnce(&CefCallback::Continue, callback));
handle_request = false;
return true;
} else if (IsImmediateOpen()) {
// Continue the request immediately be executing the callback.
callback->Continue();
handle_request = false;
return true;
}
// Continue the request immediately in the default manner.
handle_request = true;
return true;
}
void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64_t& response_length,
CefString& redirectUrl) override {
response->SetStatus(status_code_);
response->SetStatusText(status_text_);
response->SetMimeType(mime_type_);
if (!header_map_.empty()) {
response->SetHeaderMap(header_map_);
}
response_length = -1;
}
bool Read(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefResourceReadCallback> callback) override {
EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO));
EXPECT_GT(bytes_to_read, 0);
bytes_read = 0;
if (IsDelayedRead()) {
// Continue the request asynchronously by executing the callback.
CefPostTask(TID_FILE_USER_VISIBLE,
base::BindOnce(&CallbackResourceHandler::ContinueRead, this,
data_out, bytes_to_read, callback));
return true;
} else if (IsImmediateRead()) {
// Continue the request immediately be executing the callback.
ContinueRead(data_out, bytes_to_read, callback);
return true;
}
// Continue the request immediately in the default manner.
return DoRead(data_out, bytes_to_read, bytes_read);
}
void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private:
void ContinueRead(void* data_out,
int bytes_to_read,
CefRefPtr<CefResourceReadCallback> callback) {
EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO));
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) {
EXPECT_GT(bytes_to_read, 0);
// Read until the buffer is full or until Read() returns 0 to indicate no
// more data.
bytes_read = 0;
int read = 0;
do {
read = static_cast<int>(
stream_->Read(static_cast<char*>(data_out) + bytes_read, 1,
bytes_to_read - bytes_read));
bytes_read += read;
} while (read != 0 && bytes_read < bytes_to_read);
return (bytes_read > 0);
}
const Mode mode_;
const int status_code_;
const CefString status_text_;
const CefString mime_type_;
const CefResponse::HeaderMap header_map_;
const CefRefPtr<CefStreamReader> stream_;
base::OnceClosure destroy_callback_;
int cancel_ct_ = 0;
IMPLEMENT_REFCOUNTING(CallbackResourceHandler);
DISALLOW_COPY_AND_ASSIGN(CallbackResourceHandler);
};
// Resource handler implementation that never completes. Used to test
// destruction handling behavior for in-progress requests.
class IncompleteResourceHandlerOld : public CefResourceHandler {
public:
enum TestMode {
BLOCK_PROCESS_REQUEST,
BLOCK_READ_RESPONSE,
};
IncompleteResourceHandlerOld(TestMode test_mode,
const std::string& mime_type,
base::OnceClosure destroy_callback)
: test_mode_(test_mode),
mime_type_(mime_type),
destroy_callback_(std::move(destroy_callback)) {}
~IncompleteResourceHandlerOld() override {
EXPECT_EQ(1, process_request_ct_);
EXPECT_EQ(1, cancel_ct_);
if (test_mode_ == BLOCK_READ_RESPONSE) {
EXPECT_EQ(1, get_response_headers_ct_);
EXPECT_EQ(1, read_response_ct_);
} else {
EXPECT_EQ(0, get_response_headers_ct_);
EXPECT_EQ(0, read_response_ct_);
}
std::move(destroy_callback_).Run();
}
bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
process_request_ct_++;
if (test_mode_ == BLOCK_PROCESS_REQUEST) {
// Never release or execute this callback.
incomplete_callback_ = callback;
} else {
callback->Continue();
}
return true;
}
void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64_t& response_length,
CefString& redirectUrl) override {
EXPECT_IO_THREAD();
EXPECT_EQ(test_mode_, BLOCK_READ_RESPONSE);
get_response_headers_ct_++;
response->SetStatus(200);
response->SetStatusText("OK");
response->SetMimeType(mime_type_);
response_length = 100;
}
bool ReadResponse(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
EXPECT_EQ(test_mode_, BLOCK_READ_RESPONSE);
read_response_ct_++;
// Never release or execute this callback.
incomplete_callback_ = callback;
bytes_read = 0;
return true;
}
void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private:
const TestMode test_mode_;
const std::string mime_type_;
base::OnceClosure destroy_callback_;
int process_request_ct_ = 0;
int get_response_headers_ct_ = 0;
int read_response_ct_ = 0;
int cancel_ct_ = 0;
CefRefPtr<CefCallback> incomplete_callback_;
IMPLEMENT_REFCOUNTING(IncompleteResourceHandlerOld);
DISALLOW_COPY_AND_ASSIGN(IncompleteResourceHandlerOld);
};
class IncompleteResourceHandler : public CefResourceHandler {
public:
enum TestMode {
BLOCK_OPEN,
BLOCK_READ,
};
IncompleteResourceHandler(TestMode test_mode,
const std::string& mime_type,
base::OnceClosure destroy_callback)
: test_mode_(test_mode),
mime_type_(mime_type),
destroy_callback_(std::move(destroy_callback)) {}
~IncompleteResourceHandler() override {
EXPECT_EQ(1, open_ct_);
EXPECT_EQ(1, cancel_ct_);
if (test_mode_ == BLOCK_READ) {
EXPECT_EQ(1, get_response_headers_ct_);
EXPECT_EQ(1, read_ct_);
} else {
EXPECT_EQ(0, get_response_headers_ct_);
EXPECT_EQ(0, read_ct_);
}
std::move(destroy_callback_).Run();
}
bool Open(CefRefPtr<CefRequest> request,
bool& handle_request,
CefRefPtr<CefCallback> callback) override {
EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO));
open_ct_++;
if (test_mode_ == BLOCK_OPEN) {
// Never release or execute this callback.
incomplete_open_callback_ = callback;
} else {
// Continue immediately.
handle_request = true;
}
return true;
}
bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(false); // Not reached.
return false;
}
void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64_t& response_length,
CefString& redirectUrl) override {
EXPECT_IO_THREAD();
EXPECT_EQ(test_mode_, BLOCK_READ);
get_response_headers_ct_++;
response->SetStatus(200);
response->SetStatusText("OK");
response->SetMimeType(mime_type_);
response_length = 100;
}
bool Read(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefResourceReadCallback> callback) override {
EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO));
EXPECT_EQ(test_mode_, BLOCK_READ);
read_ct_++;
// Never release or execute this callback.
incomplete_read_callback_ = callback;
bytes_read = 0;
return true;
}
bool ReadResponse(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(false); // Not reached.
bytes_read = -2;
return false;
}
void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private:
const TestMode test_mode_;
const std::string mime_type_;
base::OnceClosure destroy_callback_;
int open_ct_ = 0;
int get_response_headers_ct_ = 0;
int read_ct_ = 0;
int cancel_ct_ = 0;
CefRefPtr<CefCallback> incomplete_open_callback_;
CefRefPtr<CefResourceReadCallback> incomplete_read_callback_;
IMPLEMENT_REFCOUNTING(IncompleteResourceHandler);
DISALLOW_COPY_AND_ASSIGN(IncompleteResourceHandler);
};
class BasicResponseTest : public TestHandler {
public:
enum TestMode {
// Normal load, nothing fancy.
LOAD,
// Close the browser in OnAfterCreated to verify destruction handling of
// uninitialized requests.
ABORT_AFTER_CREATED,
// Close the browser in OnBeforeBrowse to verify destruction handling of
// uninitialized requests.
ABORT_BEFORE_BROWSE,
// Don't continue from OnBeforeResourceLoad, then close the browser to
// verify destruction handling of in-progress requests.
INCOMPLETE_BEFORE_RESOURCE_LOAD,
// Modify the request (add headers) in OnBeforeResourceLoad.
MODIFY_BEFORE_RESOURCE_LOAD,
// Redirect the request (change the URL) in OnBeforeResourceLoad.
REDIRECT_BEFORE_RESOURCE_LOAD,
// Return a CefResourceHandler from GetResourceHandler that continues
// immediately by using the callback object instead of the return value.
IMMEDIATE_REQUEST_HANDLER_OPEN,
IMMEDIATE_REQUEST_HANDLER_READ,
IMMEDIATE_REQUEST_HANDLER_ALL,
// Return a CefResourceHandler from GetResourceHandler that continues with
// a delay by using the callback object.
DELAYED_REQUEST_HANDLER_OPEN,
DELAYED_REQUEST_HANDLER_READ,
DELAYED_REQUEST_HANDLER_ALL,
// Return a CefResourceHandler from GetResourceHandler that never completes,
// then close the browser to verify destruction handling of in-progress
// requests.
INCOMPLETE_REQUEST_HANDLER_OPEN,
INCOMPLETE_REQUEST_HANDLER_READ,
// Redirect the request using a CefResourceHandler returned from
// GetResourceHandler.
REDIRECT_REQUEST_HANDLER,
// Redirect the request (change the URL) an additional time in
// OnResourceRedirect after using a CefResourceHandler returned from
// GetResourceHandler for the first redirect.
REDIRECT_RESOURCE_REDIRECT,
// Redirect the request (change the URL) in OnResourceResponse.
REDIRECT_RESOURCE_RESPONSE,
// Restart the request (add headers) in OnResourceResponse.
RESTART_RESOURCE_RESPONSE,
};
// If |custom_scheme| is true all requests will use a custom scheme.
// If |unhandled| is true the final request (after any redirects) will be
// unhandled, meaning that default handling is disabled and GetResourceHandler
// returns null.
BasicResponseTest(TestMode mode, bool custom_scheme, bool unhandled)
: mode_(mode), custom_scheme_(custom_scheme), unhandled_(unhandled) {}
void RunTest() override {
CreateBrowser(GetStartupURL());
SetTestTimeout();
}
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
EXPECT_UI_THREAD();
TestHandler::OnAfterCreated(browser);
if (mode_ == ABORT_AFTER_CREATED) {
SetSignalTestCompletionCount(1U);
CloseBrowser(browser, false);
}
}
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
EXPECT_UI_THREAD();
TestHandler::OnBeforeClose(browser);
if (IsAborted()) {
DestroyTest();
}
}
bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool user_gesture,
bool is_redirect) override {
EXPECT_UI_THREAD();
if (browser_id_ == 0) {
// This is the first callback that provides a browser ID.
browser_id_ = browser->GetIdentifier();
EXPECT_GT(browser_id_, 0);
} else {
EXPECT_EQ(browser_id_, browser->GetIdentifier());
}
EXPECT_TRUE(frame->IsMain());
if (IsChromeRuntimeEnabled()) {
// With the Chrome runtime this is true on initial navigation via
// chrome::AddTabAt() and also true for clicked links.
EXPECT_TRUE(user_gesture);
} else {
EXPECT_FALSE(user_gesture);
}
if (on_before_browse_ct_ == 0 || mode_ == RESTART_RESOURCE_RESPONSE) {
EXPECT_FALSE(is_redirect) << on_before_browse_ct_;
} else {
EXPECT_TRUE(is_redirect) << on_before_browse_ct_;
}
on_before_browse_ct_++;
VerifyState(kOnBeforeBrowse, request, nullptr);
if (mode_ == ABORT_BEFORE_BROWSE) {
SetSignalTestCompletionCount(1U);
CloseBrowser(browser, false);
}
return false;
}
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool is_navigation,
bool is_download,
const CefString& request_initiator,
bool& disable_default_handling) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
if (request_id_ == 0U) {
// This is the first callback that provides a request ID.
request_id_ = request->GetIdentifier();
EXPECT_GT(request_id_, 0U);
}
VerifyState(kGetResourceRequestHandler, request, nullptr);
EXPECT_TRUE(is_navigation);
EXPECT_FALSE(is_download);
EXPECT_STREQ("null", request_initiator.ToString().c_str());
// Check expected default value.
if (custom_scheme_) {
// There is no default handling for custom schemes.
EXPECT_TRUE(disable_default_handling);
} else {
EXPECT_FALSE(disable_default_handling);
// If |unhandled_| is true then we don't want default handling of requests
// (e.g. attempts to resolve over the network).
disable_default_handling = unhandled_;
}
get_resource_request_handler_ct_++;
return this;
}
CefRefPtr<CefCookieAccessFilter> GetCookieAccessFilter(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
VerifyState(kGetCookieAccessFilter, request, nullptr);
get_cookie_access_filter_ct_++;
return nullptr;
}
cef_return_value_t OnBeforeResourceLoad(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() && request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return RV_CANCEL;
}
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
VerifyState(kOnBeforeResourceLoad, request, nullptr);
on_before_resource_load_ct_++;
if (mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD) {
incomplete_callback_ = callback;
// Close the browser asynchronously to complete the test.
CloseBrowserAsync();
return RV_CONTINUE_ASYNC;
}
if (mode_ == MODIFY_BEFORE_RESOURCE_LOAD) {
// Expect this data in the request for future callbacks.
SetCustomHeader(request);
} else if (mode_ == REDIRECT_BEFORE_RESOURCE_LOAD) {
// Redirect to this URL.
request->SetURL(GetURL(RESULT_HTML));
}
// Other continuation modes are tested by
// ResourceRequestHandlerTest.BeforeResourceLoad*.
return RV_CONTINUE;
}
CefRefPtr<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
VerifyState(kGetResourceHandler, request, nullptr);
get_resource_handler_ct_++;
if (IsIncompleteRequestHandler()) {
// Close the browser asynchronously to complete the test.
CloseBrowserAsync();
return GetIncompleteResource();
}
const std::string& url = request->GetURL();
if (url == GetURL(RESULT_HTML) && mode_ == RESTART_RESOURCE_RESPONSE) {
if (get_resource_handler_ct_ == 1) {
// First request that will be restarted after response.
return GetOKResource();
} else {
// Restarted request.
if (unhandled_) {
return nullptr;
}
return GetOKResource();
}
} else if (url == GetURL(RESULT_HTML)) {
if (unhandled_) {
return nullptr;
}
return GetOKResource();
} else if (url == GetURL(REDIRECT_HTML) &&
mode_ == REDIRECT_RESOURCE_RESPONSE) {
if (get_resource_handler_ct_ == 1) {
// First request that will be redirected after response.
return GetOKResource();
} else {
// Redirected request.
if (unhandled_) {
return nullptr;
}
return GetOKResource();
}
} else if (url == GetURL(REDIRECT_HTML) || url == GetURL(REDIRECT2_HTML)) {
std::string redirect_url;
if (mode_ == REDIRECT_REQUEST_HANDLER ||
mode_ == REDIRECT_RESOURCE_RESPONSE) {
EXPECT_STREQ(GetURL(REDIRECT_HTML), url.c_str());
redirect_url = GetURL(RESULT_HTML);
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
EXPECT_STREQ(GetURL(REDIRECT2_HTML), url.c_str());
redirect_url = GetURL(REDIRECT_HTML);
} else {
NOTREACHED();
}
return GetRedirectResource(redirect_url);
} else {
NOTREACHED();
return nullptr;
}
}
void OnResourceRedirect(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
CefString& new_url) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
VerifyState(kOnResourceRedirect, request, response);
if (mode_ == REDIRECT_REQUEST_HANDLER ||
mode_ == REDIRECT_RESOURCE_RESPONSE) {
// The URL redirected to from GetResourceHandler or OnResourceResponse.
EXPECT_STREQ(GetURL(RESULT_HTML), new_url.ToString().c_str());
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
if (on_resource_redirect_ct_ == 0) {
// The URL redirected to from GetResourceHandler.
EXPECT_STREQ(GetURL(REDIRECT_HTML), new_url.ToString().c_str());
// Redirect again.
new_url = GetURL(RESULT_HTML);
} else {
NOTREACHED();
}
}
on_resource_redirect_ct_++;
}
bool OnResourceResponse(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
VerifyState(kOnResourceResponse, request, response);
on_resource_response_ct_++;
if (on_resource_response_ct_ == 1) {
if (mode_ == REDIRECT_RESOURCE_RESPONSE) {
// Redirect the request to this URL.
request->SetURL(GetURL(RESULT_HTML));
return true;
} else if (mode_ == RESTART_RESOURCE_RESPONSE) {
// Restart the request loading this data.
SetCustomHeader(request);
return true;
}
}
return false;
}
CefRefPtr<CefResponseFilter> GetResourceResponseFilter(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
VerifyState(kGetResourceResponseFilter, request, response);
get_resource_response_filter_ct_++;
return nullptr;
}
void OnResourceLoadComplete(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
URLRequestStatus status,
int64_t received_content_length) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() && request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return;
}
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
VerifyState(kOnResourceLoadComplete, request, response);
if (unhandled_ || IsIncomplete() || (IsAborted() && status == UR_FAILED)) {
EXPECT_EQ(UR_FAILED, status);
EXPECT_EQ(0, received_content_length);
} else {
EXPECT_EQ(UR_SUCCESS, status);
EXPECT_EQ(static_cast<int64_t>(GetResponseBody().length()),
received_content_length);
}
on_resource_load_complete_ct_++;
if (IsIncomplete()) {
MaybeDestroyTest(false);
}
}
void OnProtocolExecution(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool& allow_os_execution) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
EXPECT_TRUE(custom_scheme_);
EXPECT_TRUE(unhandled_);
// Check expected default value.
EXPECT_FALSE(allow_os_execution);
VerifyState(kOnProtocolExecution, request, nullptr);
on_protocol_execution_ct_++;
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
EXPECT_UI_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame->IsMain());
if (unhandled_) {
if (IsRedirect()) {
EXPECT_EQ(httpStatusCode, 307);
} else {
EXPECT_EQ(httpStatusCode, 0);
}
} else {
EXPECT_EQ(httpStatusCode, 200);
}
on_load_end_ct_++;
TestHandler::OnLoadEnd(browser, frame, httpStatusCode);
DestroyTest();
}
void DestroyTest() override {
if (mode_ == RESTART_RESOURCE_RESPONSE) {
EXPECT_EQ(1, on_before_browse_ct_);
EXPECT_EQ(2, get_resource_request_handler_ct_);
EXPECT_EQ(2, get_cookie_access_filter_ct_);
EXPECT_EQ(2, on_before_resource_load_ct_);
EXPECT_EQ(2, get_resource_handler_ct_);
EXPECT_EQ(0, on_resource_redirect_ct_);
// Unhandled requests won't see a call to GetResourceResponseFilter or
// OnResourceResponse. In this case we're restarting from inside
// OnResourceResponse.
if (unhandled_) {
EXPECT_EQ(0, get_resource_response_filter_ct_);
EXPECT_EQ(1, on_resource_response_ct_);
} else {
EXPECT_EQ(1, get_resource_response_filter_ct_);
EXPECT_EQ(2, on_resource_response_ct_);
}
} else if (IsLoad()) {
EXPECT_EQ(1, on_before_browse_ct_);
EXPECT_EQ(1, get_resource_request_handler_ct_);
EXPECT_EQ(1, get_cookie_access_filter_ct_);
EXPECT_EQ(1, on_before_resource_load_ct_);
EXPECT_EQ(1, get_resource_handler_ct_);
EXPECT_EQ(0, on_resource_redirect_ct_);
// Unhandled requests won't see a call to GetResourceResponseFilter
// or OnResourceResponse.
if (unhandled_) {
EXPECT_EQ(0, get_resource_response_filter_ct_);
EXPECT_EQ(0, on_resource_response_ct_);
} else {
EXPECT_EQ(1, get_resource_response_filter_ct_);
EXPECT_EQ(1, on_resource_response_ct_);
}
} else if (IsRedirect()) {
EXPECT_EQ(2, on_before_browse_ct_);
EXPECT_EQ(2, get_resource_request_handler_ct_);
EXPECT_EQ(2, get_cookie_access_filter_ct_);
EXPECT_EQ(2, on_before_resource_load_ct_);
if (mode_ == REDIRECT_BEFORE_RESOURCE_LOAD) {
EXPECT_EQ(1, get_resource_handler_ct_);
} else {
EXPECT_EQ(2, get_resource_handler_ct_);
}
EXPECT_EQ(1, on_resource_redirect_ct_);
// Unhandled requests won't see a call to GetResourceResponseFilter.
if (unhandled_) {
EXPECT_EQ(0, get_resource_response_filter_ct_);
} else {
EXPECT_EQ(1, get_resource_response_filter_ct_);
}
// Unhandled requests won't see a call to OnResourceResponse.
if (mode_ == REDIRECT_RESOURCE_RESPONSE) {
// In this case we're redirecting from inside OnResourceResponse.
if (unhandled_) {
EXPECT_EQ(1, on_resource_response_ct_);
} else {
EXPECT_EQ(2, on_resource_response_ct_);
}
} else {
if (unhandled_) {
EXPECT_EQ(0, on_resource_response_ct_);
} else {
EXPECT_EQ(1, on_resource_response_ct_);
}
}
} else if (IsIncomplete()) {
EXPECT_EQ(1, on_before_browse_ct_);
EXPECT_EQ(1, get_resource_request_handler_ct_);
EXPECT_EQ(1, get_cookie_access_filter_ct_);
EXPECT_EQ(1, on_before_resource_load_ct_);
if (IsIncompleteRequestHandler()) {
EXPECT_EQ(1, get_resource_handler_ct_);
} else {
EXPECT_EQ(0, get_resource_handler_ct_);
}
EXPECT_EQ(0, on_resource_redirect_ct_);
if (mode_ == INCOMPLETE_REQUEST_HANDLER_READ) {
EXPECT_EQ(1, get_resource_response_filter_ct_);
EXPECT_EQ(1, on_resource_response_ct_);
} else {
EXPECT_EQ(0, get_resource_response_filter_ct_);
EXPECT_EQ(0, on_resource_response_ct_);
}
} else if (IsAborted()) {
EXPECT_EQ(1, on_before_browse_ct_);
// The callbacks executed may vary based on timing.
EXPECT_NEAR(0, get_resource_request_handler_ct_, 1);
EXPECT_NEAR(0, get_cookie_access_filter_ct_, 1);
EXPECT_NEAR(0, on_before_resource_load_ct_, 1);
EXPECT_NEAR(0, get_resource_handler_ct_, 1);
EXPECT_NEAR(0, get_resource_response_filter_ct_, 1);
EXPECT_NEAR(0, on_resource_response_ct_, 1);
EXPECT_EQ(0, on_resource_redirect_ct_);
} else {
NOTREACHED();
}
if (IsAborted()) {
// The callbacks executed may vary based on timing.
EXPECT_NEAR(0, on_load_end_ct_, 1);
EXPECT_NEAR(resource_handler_created_ct_, resource_handler_destroyed_ct_,
1);
EXPECT_NEAR(0, on_resource_load_complete_ct_, 1);
} else {
EXPECT_EQ(resource_handler_created_ct_, resource_handler_destroyed_ct_);
EXPECT_EQ(1, on_resource_load_complete_ct_);
}
if (IsIncomplete()) {
EXPECT_EQ(0, on_load_end_ct_);
} else if (!IsAborted()) {
EXPECT_EQ(1, on_load_end_ct_);
}
if (custom_scheme_ && unhandled_ && !(IsIncomplete() || IsAborted())) {
EXPECT_EQ(1, on_protocol_execution_ct_);
} else if (IsAborted()) {
// The callbacks executed may vary based on timing.
EXPECT_NEAR(0, on_protocol_execution_ct_, 1);
} else {
EXPECT_EQ(0, on_protocol_execution_ct_);
}
TestHandler::DestroyTest();
if (!AllowTestCompletionWhenAllBrowsersClose()) {
// Complete asynchronously so the call stack has a chance to unwind.
CefPostTask(TID_UI, base::BindOnce(
&BasicResponseTest::SignalTestCompletion, this));
}
}
private:
enum TestUrl {
RESULT_HTML,
REDIRECT_HTML,
REDIRECT2_HTML,
};
const char* GetURL(TestUrl url) const {
if (custom_scheme_) {
if (url == RESULT_HTML) {
return "rrhcustom://test.com/result.html";
}
if (url == REDIRECT_HTML) {
return "rrhcustom://test.com/redirect.html";
}
if (url == REDIRECT2_HTML) {
return "rrhcustom://test.com/redirect2.html";
}
} else {
if (url == RESULT_HTML) {
return "https://test.com/result.html";
}
if (url == REDIRECT_HTML) {
return "https://test.com/redirect.html";
}
if (url == REDIRECT2_HTML) {
return "https://test.com/redirect2.html";
}
}
NOTREACHED();
return "";
}
const char* GetStartupURL() const {
if (IsLoad() || IsIncomplete() || IsAborted()) {
return GetURL(RESULT_HTML);
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
return GetURL(REDIRECT2_HTML);
} else if (IsRedirect()) {
return GetURL(REDIRECT_HTML);
}
NOTREACHED();
return "";
}
std::string GetResponseBody() const {
return "<html><body>Response</body></html>";
}
std::string GetRedirectBody() const {
return "<html><body>Redirect</body></html>";
}
base::OnceClosure GetResourceDestroyCallback() {
resource_handler_created_ct_++;
return base::BindOnce(&BasicResponseTest::MaybeDestroyTest, this, true);
}
bool GetCallbackResourceHandlerMode(CallbackResourceHandler::Mode& mode) {
switch (mode_) {
case IMMEDIATE_REQUEST_HANDLER_OPEN:
mode = CallbackResourceHandler::IMMEDIATE_OPEN;
return true;
case IMMEDIATE_REQUEST_HANDLER_READ:
mode = CallbackResourceHandler::IMMEDIATE_READ;
return true;
case IMMEDIATE_REQUEST_HANDLER_ALL:
mode = CallbackResourceHandler::IMMEDIATE_ALL;
return true;
case DELAYED_REQUEST_HANDLER_OPEN:
mode = CallbackResourceHandler::DELAYED_OPEN;
return true;
case DELAYED_REQUEST_HANDLER_READ:
mode = CallbackResourceHandler::DELAYED_READ;
return true;
case DELAYED_REQUEST_HANDLER_ALL:
mode = CallbackResourceHandler::DELAYED_ALL;
return true;
default:
break;
}
return false;
}
CefRefPtr<CefResourceHandler> GetResource(int status_code,
const CefString& status_text,
const CefString& mime_type,
CefResponse::HeaderMap header_map,
const std::string& body) {
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size());
CallbackResourceHandler::Mode handler_mode;
if (GetCallbackResourceHandlerMode(handler_mode)) {
return new CallbackResourceHandler(handler_mode, status_code, status_text,
mime_type, header_map, stream,
GetResourceDestroyCallback());
}
return new NormalResourceHandler(status_code, status_text, mime_type,
header_map, stream,
GetResourceDestroyCallback());
}
CefRefPtr<CefResourceHandler> GetOKResource() {
return GetResource(200, "OK", "text/html", CefResponse::HeaderMap(),
GetResponseBody());
}
CefRefPtr<CefResourceHandler> GetRedirectResource(
const std::string& redirect_url) {
CefResponse::HeaderMap headerMap;
headerMap.insert(std::make_pair("Location", redirect_url));
return GetResource(307, "Temporary Redirect", "text/html", headerMap,
GetRedirectBody());
}
CefRefPtr<CefResourceHandler> GetIncompleteResource() {
if (TestOldResourceAPI()) {
return new IncompleteResourceHandlerOld(
mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN
? IncompleteResourceHandlerOld::BLOCK_PROCESS_REQUEST
: IncompleteResourceHandlerOld::BLOCK_READ_RESPONSE,
"text/html", GetResourceDestroyCallback());
}
return new IncompleteResourceHandler(
mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN
? IncompleteResourceHandler::BLOCK_OPEN
: IncompleteResourceHandler::BLOCK_READ,
"text/html", GetResourceDestroyCallback());
}
bool IsLoad() const {
return mode_ == LOAD || mode_ == MODIFY_BEFORE_RESOURCE_LOAD ||
mode_ == RESTART_RESOURCE_RESPONSE ||
mode_ == IMMEDIATE_REQUEST_HANDLER_OPEN ||
mode_ == IMMEDIATE_REQUEST_HANDLER_READ ||
mode_ == IMMEDIATE_REQUEST_HANDLER_ALL ||
mode_ == DELAYED_REQUEST_HANDLER_OPEN ||
mode_ == DELAYED_REQUEST_HANDLER_READ ||
mode_ == DELAYED_REQUEST_HANDLER_ALL;
}
bool IsIncompleteRequestHandler() const {
return mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN ||
mode_ == INCOMPLETE_REQUEST_HANDLER_READ;
}
bool IsIncomplete() const {
return mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
IsIncompleteRequestHandler();
}
bool IsAborted() const {
return mode_ == ABORT_AFTER_CREATED || mode_ == ABORT_BEFORE_BROWSE;
}
bool IsRedirect() const {
return mode_ == REDIRECT_BEFORE_RESOURCE_LOAD ||
mode_ == REDIRECT_REQUEST_HANDLER ||
mode_ == REDIRECT_RESOURCE_REDIRECT ||
mode_ == REDIRECT_RESOURCE_RESPONSE;
}
static void SetCustomHeader(CefRefPtr<CefRequest> request) {
EXPECT_FALSE(request->IsReadOnly());
request->SetHeaderByName("X-Custom-Header", "value", false);
}
static std::string GetCustomHeader(CefRefPtr<CefRequest> request) {
return request->GetHeaderByName("X-Custom-Header");
}
// Resource-related callbacks.
enum Callback {
kOnBeforeBrowse,
kGetResourceRequestHandler,
kGetCookieAccessFilter,
kOnBeforeResourceLoad,
kGetResourceHandler,
kOnResourceRedirect,
kOnResourceResponse,
kGetResourceResponseFilter,
kOnResourceLoadComplete,
kOnProtocolExecution,
};
bool ShouldHaveResponse(Callback callback) const {
return callback >= kOnResourceRedirect &&
callback <= kOnResourceLoadComplete;
}
bool ShouldHaveWritableRequest(Callback callback) const {
return callback == kOnBeforeResourceLoad || callback == kOnResourceResponse;
}
void VerifyState(Callback callback,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) const {
EXPECT_TRUE(request) << callback;
if (ShouldHaveResponse(callback)) {
EXPECT_TRUE(response) << callback;
EXPECT_TRUE(response->IsReadOnly()) << callback;
} else {
EXPECT_FALSE(response) << callback;
}
if (ShouldHaveWritableRequest(callback)) {
EXPECT_FALSE(request->IsReadOnly()) << callback;
} else {
EXPECT_TRUE(request->IsReadOnly()) << callback;
}
if (callback == kOnBeforeBrowse) {
// Browser-side navigation no longer exposes the actual request
// information.
EXPECT_EQ(0U, request->GetIdentifier()) << callback;
} else {
// All resource-related callbacks share the same request ID.
EXPECT_EQ(request_id_, request->GetIdentifier()) << callback;
}
if (IsLoad() || IsIncomplete() || IsAborted()) {
EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback;
EXPECT_STREQ(GetURL(RESULT_HTML), request->GetURL().ToString().c_str())
<< callback;
// Expect the header for all callbacks following the callback that
// initially sets it.
const std::string& custom_header = GetCustomHeader(request);
if ((mode_ == RESTART_RESOURCE_RESPONSE &&
on_resource_response_ct_ > 0) ||
(mode_ == MODIFY_BEFORE_RESOURCE_LOAD &&
on_before_resource_load_ct_ > 0)) {
EXPECT_STREQ("value", custom_header.c_str()) << callback;
} else {
EXPECT_STREQ("", custom_header.c_str()) << callback;
}
if (response) {
VerifyOKResponse(callback, response);
}
} else if (IsRedirect()) {
EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback;
if (on_before_browse_ct_ == 1) {
// Before the redirect.
EXPECT_STREQ(GetStartupURL(), request->GetURL().ToString().c_str())
<< callback;
} else if (on_before_browse_ct_ == 2) {
// After the redirect.
EXPECT_STREQ(GetURL(RESULT_HTML), request->GetURL().ToString().c_str())
<< callback;
} else {
NOTREACHED() << callback;
}
if (response) {
if (callback == kOnResourceRedirect) {
// Before the redirect.
VerifyRedirectResponse(callback, response);
} else {
// After the redirect.
VerifyOKResponse(callback, response);
}
}
} else {
NOTREACHED() << callback;
}
}
void VerifyOKResponse(Callback callback,
CefRefPtr<CefResponse> response) const {
const auto error_code = response->GetError();
// True for the first response in cases where we're redirecting/restarting
// from inside OnResourceResponse (e.g. the first response always succeeds).
const bool override_unhandled = unhandled_ &&
(mode_ == REDIRECT_RESOURCE_RESPONSE ||
mode_ == RESTART_RESOURCE_RESPONSE) &&
get_resource_handler_ct_ == 1;
// True for tests where the request will be incomplete and never receive a
// response.
const bool incomplete_unhandled =
(mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN ||
(IsAborted() && !custom_scheme_ && error_code != ERR_NONE));
if ((unhandled_ && !override_unhandled) || incomplete_unhandled) {
EXPECT_TRUE(ERR_ABORTED == error_code ||
ERR_UNKNOWN_URL_SCHEME == error_code)
<< callback << error_code;
EXPECT_EQ(0, response->GetStatus()) << callback;
EXPECT_STREQ("", response->GetStatusText().ToString().c_str())
<< callback;
EXPECT_STREQ("", response->GetURL().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
} else {
if ((mode_ == INCOMPLETE_REQUEST_HANDLER_READ || IsAborted()) &&
callback == kOnResourceLoadComplete &&
response->GetError() != ERR_NONE) {
// We got a response, but we also got aborted.
EXPECT_EQ(ERR_ABORTED, response->GetError()) << callback;
} else {
EXPECT_EQ(ERR_NONE, response->GetError()) << callback;
}
EXPECT_EQ(200, response->GetStatus()) << callback;
EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str())
<< callback;
EXPECT_STREQ("", response->GetURL().ToString().c_str()) << callback;
EXPECT_STREQ("text/html", response->GetMimeType().ToString().c_str())
<< callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
}
}
void VerifyRedirectResponse(Callback callback,
CefRefPtr<CefResponse> response) const {
EXPECT_EQ(ERR_NONE, response->GetError()) << callback;
EXPECT_EQ(307, response->GetStatus()) << callback;
const std::string& status_text = response->GetStatusText();
EXPECT_TRUE(status_text == "Internal Redirect" ||
status_text == "Temporary Redirect")
<< status_text << callback;
EXPECT_STREQ("", response->GetURL().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
}
void CloseBrowserAsync() {
EXPECT_TRUE(IsIncomplete());
SetSignalTestCompletionCount(1U);
CefPostDelayedTask(
TID_UI, base::BindOnce(&TestHandler::CloseBrowser, GetBrowser(), false),
100);
}
void MaybeDestroyTest(bool from_handler) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&BasicResponseTest::MaybeDestroyTest,
this, from_handler));
return;
}
if (from_handler) {
resource_handler_destroyed_ct_++;
}
bool destroy_test = false;
if (IsIncomplete()) {
// Destroy the test if we got OnResourceLoadComplete and either the
// resource handler will never complete or it was destroyed.
destroy_test =
on_resource_load_complete_ct_ > 0 &&
(!IsIncompleteRequestHandler() ||
resource_handler_destroyed_ct_ == resource_handler_created_ct_);
} else {
// Destroy the test if we got OnLoadEnd and the expected number of
// resource handlers were destroyed.
destroy_test = on_load_end_ct_ > 0 && resource_handler_destroyed_ct_ ==
resource_handler_created_ct_;
}
if (destroy_test) {
DestroyTest();
}
}
const TestMode mode_;
const bool custom_scheme_;
const bool unhandled_;
int browser_id_ = 0;
uint64_t request_id_ = 0U;
int resource_handler_created_ct_ = 0;
int on_before_browse_ct_ = 0;
int on_load_end_ct_ = 0;
int get_resource_request_handler_ct_ = 0;
int on_before_resource_load_ct_ = 0;
int get_cookie_access_filter_ct_ = 0;
int get_resource_handler_ct_ = 0;
int on_resource_redirect_ct_ = 0;
int on_resource_response_ct_ = 0;
int get_resource_response_filter_ct_ = 0;
int on_resource_load_complete_ct_ = 0;
int on_protocol_execution_ct_ = 0;
int resource_handler_destroyed_ct_ = 0;
// Used with INCOMPLETE_BEFORE_RESOURCE_LOAD.
CefRefPtr<CefCallback> incomplete_callback_;
DISALLOW_COPY_AND_ASSIGN(BasicResponseTest);
IMPLEMENT_REFCOUNTING(BasicResponseTest);
};
} // namespace
#define BASIC_TEST(name, test_mode, custom, unhandled) \
TEST(ResourceRequestHandlerTest, Basic##name) { \
CefRefPtr<BasicResponseTest> handler = new BasicResponseTest( \
BasicResponseTest::test_mode, custom, unhandled); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
}
#define BASIC_TEST_ALL_MODES(name, custom, unhandled) \
BASIC_TEST(name##Load, LOAD, custom, unhandled) \
BASIC_TEST(name##AbortAfterCreated, ABORT_AFTER_CREATED, custom, unhandled) \
BASIC_TEST(name##AbortBeforeBrowse, ABORT_BEFORE_BROWSE, custom, unhandled) \
BASIC_TEST(name##ModifyBeforeResourceLoad, MODIFY_BEFORE_RESOURCE_LOAD, \
custom, unhandled) \
BASIC_TEST(name##RedirectBeforeResourceLoad, REDIRECT_BEFORE_RESOURCE_LOAD, \
custom, unhandled) \
BASIC_TEST(name##RedirectRequestHandler, REDIRECT_REQUEST_HANDLER, custom, \
unhandled) \
BASIC_TEST(name##RedirectResourceRedirect, REDIRECT_RESOURCE_REDIRECT, \
custom, unhandled) \
BASIC_TEST(name##RedirectResourceResponse, REDIRECT_RESOURCE_RESPONSE, \
custom, unhandled) \
BASIC_TEST(name##RestartResourceResponse, RESTART_RESOURCE_RESPONSE, custom, \
unhandled)
// Tests only supported in handled mode.
#define BASIC_TEST_HANDLED_MODES(name, custom) \
BASIC_TEST(name##ImmediateRequestHandlerOpen, \
IMMEDIATE_REQUEST_HANDLER_OPEN, custom, false) \
BASIC_TEST(name##ImmediateRequestHandlerRead, \
IMMEDIATE_REQUEST_HANDLER_READ, custom, false) \
BASIC_TEST(name##ImmediateRequestHandlerAll, IMMEDIATE_REQUEST_HANDLER_ALL, \
custom, false) \
BASIC_TEST(name##DelayedRequestHandlerOpen, DELAYED_REQUEST_HANDLER_OPEN, \
custom, false) \
BASIC_TEST(name##DelayedRequestHandlerRead, DELAYED_REQUEST_HANDLER_READ, \
custom, false) \
BASIC_TEST(name##DelayedRequestHandlerAll, DELAYED_REQUEST_HANDLER_ALL, \
custom, false) \
BASIC_TEST(name##IncompleteBeforeResourceLoad, \
INCOMPLETE_BEFORE_RESOURCE_LOAD, custom, false) \
BASIC_TEST(name##IncompleteRequestHandlerOpen, \
INCOMPLETE_REQUEST_HANDLER_OPEN, custom, false) \
BASIC_TEST(name##IncompleteRequestHandlerRead, \
INCOMPLETE_REQUEST_HANDLER_READ, custom, false)
BASIC_TEST_ALL_MODES(StandardHandled, false, false)
BASIC_TEST_ALL_MODES(StandardUnhandled, false, true)
BASIC_TEST_ALL_MODES(CustomHandled, true, false)
BASIC_TEST_ALL_MODES(CustomUnhandled, true, true)
BASIC_TEST_HANDLED_MODES(StandardHandled, false)
BASIC_TEST_HANDLED_MODES(CustomHandled, true)
namespace {
const char kSubresourceProcessMsg[] = "SubresourceMsg";
class SubresourceResponseTest : public RoutingTestHandler {
public:
enum TestMode {
// Normal load, nothing fancy.
LOAD,
// Don't continue from OnBeforeResourceLoad, then close the browser to
// verify destruction handling of in-progress requests.
INCOMPLETE_BEFORE_RESOURCE_LOAD,
// Modify the request (add headers) in OnBeforeResourceLoad.
MODIFY_BEFORE_RESOURCE_LOAD,
// Redirect the request (change the URL) in OnBeforeResourceLoad.
REDIRECT_BEFORE_RESOURCE_LOAD,
// Return a CefResourceHandler from GetResourceHandler that continues
// immediately by using the callback object instead of the return value.
IMMEDIATE_REQUEST_HANDLER_OPEN,
IMMEDIATE_REQUEST_HANDLER_READ,
IMMEDIATE_REQUEST_HANDLER_ALL,
// Return a CefResourceHandler from GetResourceHandler that continues with
// a delay by using the callback object.
DELAYED_REQUEST_HANDLER_OPEN,
DELAYED_REQUEST_HANDLER_READ,
DELAYED_REQUEST_HANDLER_ALL,
// Return a CefResourceHandler from GetResourceHandler that never completes,
// then close the browser to verify destruction handling of in-progress
// requests.
INCOMPLETE_REQUEST_HANDLER_OPEN,
INCOMPLETE_REQUEST_HANDLER_READ,
// Redirect the request using a CefResourceHandler returned from
// GetResourceHandler.
REDIRECT_REQUEST_HANDLER,
// Redirect the request (change the URL) an additional time in
// OnResourceRedirect after using a CefResourceHandler returned from
// GetResourceHandler for the first redirect.
REDIRECT_RESOURCE_REDIRECT,
// Redirect the request (change the URL) in OnResourceResponse.
REDIRECT_RESOURCE_RESPONSE,
// Restart the request (add headers) in OnResourceResponse.
RESTART_RESOURCE_RESPONSE,
};
// If |custom_scheme| is true all requests will use a custom scheme.
// If |unhandled| is true the final request (after any redirects) will be
// unhandled, meaning that default handling is disabled and GetResourceHandler
// returns null.
// If |subframe| is true the resource will be loaded in an iframe.
SubresourceResponseTest(TestMode mode,
bool custom_scheme,
bool unhandled,
bool subframe)
: mode_(mode),
custom_scheme_(custom_scheme),
unhandled_(unhandled),
subframe_(subframe) {}
void RunTest() override {
CreateBrowser(GetMainURL());
SetTestTimeout();
}
bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool user_gesture,
bool is_redirect) override {
EXPECT_UI_THREAD();
if (browser_id_ == 0) {
// This is the first callback that provides a browser ID.
browser_id_ = browser->GetIdentifier();
EXPECT_GT(browser_id_, 0);
} else {
EXPECT_EQ(browser_id_, browser->GetIdentifier());
}
const std::string& url = request->GetURL();
if (IsMainURL(url)) {
EXPECT_TRUE(frame->IsMain());
} else if (IsSubURL(url)) {
EXPECT_FALSE(frame->IsMain());
EXPECT_TRUE(subframe_);
} else {
EXPECT_FALSE(true); // Not reached.
}
if (IsChromeRuntimeEnabled() && IsMainURL(url)) {
// With the Chrome runtime this is true on initial navigation via
// chrome::AddTabAt() and also true for clicked links.
EXPECT_TRUE(user_gesture);
} else {
EXPECT_FALSE(user_gesture);
}
EXPECT_FALSE(is_redirect);
on_before_browse_ct_++;
return false;
}
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool is_navigation,
bool is_download,
const CefString& request_initiator,
bool& disable_default_handling) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
const std::string& url = request->GetURL();
if (IgnoreURL(url)) {
return nullptr;
}
const bool is_main_url = IsMainURL(url);
const bool is_sub_url = IsSubURL(url);
if (is_main_url) {
EXPECT_TRUE(frame->IsMain());
} else if (is_sub_url) {
EXPECT_FALSE(frame->IsMain());
EXPECT_TRUE(subframe_);
}
if (is_main_url || is_sub_url) {
// Track the frame ID that we'll expect for resource callbacks.
// Do this here instead of OnBeforeBrowse because OnBeforeBrowse may
// return -4 (kInvalidFrameId) for the initial navigation.
if (frame_id_.empty()) {
if (subframe_) {
if (is_sub_url) {
frame_id_ = frame->GetIdentifier();
}
} else {
frame_id_ = frame->GetIdentifier();
}
}
return this;
}
VerifyFrame(kGetResourceRequestHandler, frame);
if (request_id_ == 0U) {
// This is the first callback that provides a request ID.
request_id_ = request->GetIdentifier();
EXPECT_GT(request_id_, 0U);
}
VerifyState(kGetResourceRequestHandler, request, nullptr);
EXPECT_FALSE(is_navigation);
EXPECT_FALSE(is_download);
EXPECT_STREQ(GetOrigin(), request_initiator.ToString().c_str());
// Check expected default value.
if (custom_scheme_) {
// There is no default handling for custom schemes.
EXPECT_TRUE(disable_default_handling);
} else {
EXPECT_FALSE(disable_default_handling);
// If |unhandled_| is true then we don't want default handling of requests
// (e.g. attempts to resolve over the network).
disable_default_handling = unhandled_;
}
get_resource_request_handler_ct_++;
return this;
}
CefRefPtr<CefCookieAccessFilter> GetCookieAccessFilter(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
const std::string& url = request->GetURL();
if (IgnoreURL(url)) {
return nullptr;
}
if (IsMainURL(url)) {
EXPECT_TRUE(frame->IsMain());
return nullptr;
} else if (IsSubURL(url)) {
EXPECT_FALSE(frame->IsMain());
EXPECT_TRUE(subframe_);
return nullptr;
}
VerifyFrame(kGetCookieAccessFilter, frame);
VerifyState(kGetCookieAccessFilter, request, nullptr);
get_cookie_access_filter_ct_++;
return nullptr;
}
cef_return_value_t OnBeforeResourceLoad(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() && request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return RV_CANCEL;
}
EXPECT_EQ(browser_id_, browser->GetIdentifier());
if (IsMainURL(request->GetURL())) {
EXPECT_TRUE(frame->IsMain());
return RV_CONTINUE;
} else if (IsSubURL(request->GetURL())) {
EXPECT_FALSE(frame->IsMain());
EXPECT_TRUE(subframe_);
return RV_CONTINUE;
}
VerifyFrame(kOnBeforeResourceLoad, frame);
VerifyState(kOnBeforeResourceLoad, request, nullptr);
on_before_resource_load_ct_++;
if (mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD) {
incomplete_callback_ = callback;
// Close the browser asynchronously to complete the test.
CloseBrowserAsync();
return RV_CONTINUE_ASYNC;
}
if (mode_ == MODIFY_BEFORE_RESOURCE_LOAD) {
// Expect this data in the request for future callbacks.
SetCustomHeader(request);
} else if (mode_ == REDIRECT_BEFORE_RESOURCE_LOAD) {
// Redirect to this URL.
request->SetURL(GetURL(RESULT_JS));
}
// Other continuation modes are tested by
// ResourceRequestHandlerTest.BeforeResourceLoad*.
return RV_CONTINUE;
}
CefRefPtr<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
if (IsMainURL(request->GetURL())) {
EXPECT_TRUE(frame->IsMain());
return GetMainResource();
} else if (IsSubURL(request->GetURL())) {
EXPECT_FALSE(frame->IsMain());
EXPECT_TRUE(subframe_);
return GetSubResource();
}
VerifyFrame(kGetResourceHandler, frame);
VerifyState(kGetResourceHandler, request, nullptr);
get_resource_handler_ct_++;
if (IsIncompleteRequestHandler()) {
// Close the browser asynchronously to complete the test.
CloseBrowserAsync();
return GetIncompleteResource();
}
const std::string& url = request->GetURL();
if (url == GetURL(RESULT_JS) && mode_ == RESTART_RESOURCE_RESPONSE) {
if (get_resource_handler_ct_ == 1) {
// First request that will be restarted after response.
return GetOKResource();
} else {
// Restarted request.
if (unhandled_) {
return nullptr;
}
return GetOKResource();
}
} else if (url == GetURL(RESULT_JS)) {
if (unhandled_) {
return nullptr;
}
return GetOKResource();
} else if (url == GetURL(REDIRECT_JS) &&
mode_ == REDIRECT_RESOURCE_RESPONSE) {
if (get_resource_handler_ct_ == 1) {
// First request that will be redirected after response.
return GetOKResource();
} else {
// Redirected request.
if (unhandled_) {
return nullptr;
}
return GetOKResource();
}
} else if (url == GetURL(REDIRECT_JS) || url == GetURL(REDIRECT2_JS)) {
std::string redirect_url;
if (mode_ == REDIRECT_REQUEST_HANDLER ||
mode_ == REDIRECT_RESOURCE_RESPONSE) {
EXPECT_STREQ(GetURL(REDIRECT_JS), url.c_str());
redirect_url = GetURL(RESULT_JS);
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
EXPECT_STREQ(GetURL(REDIRECT2_JS), url.c_str());
redirect_url = GetURL(REDIRECT_JS);
} else {
NOTREACHED();
}
return GetRedirectResource(redirect_url);
} else {
NOTREACHED();
return nullptr;
}
}
void OnResourceRedirect(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
CefString& new_url) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
if (IsMainURL(request->GetURL()) || IsSubURL(request->GetURL())) {
EXPECT_FALSE(true); // Not reached.
return;
}
VerifyFrame(kOnResourceRedirect, frame);
VerifyState(kOnResourceRedirect, request, response);
if (mode_ == REDIRECT_REQUEST_HANDLER ||
mode_ == REDIRECT_RESOURCE_RESPONSE) {
// The URL redirected to from GetResourceHandler or OnResourceResponse.
EXPECT_STREQ(GetURL(RESULT_JS), new_url.ToString().c_str());
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
if (on_resource_redirect_ct_ == 0) {
// The URL redirected to from GetResourceHandler.
EXPECT_STREQ(GetURL(REDIRECT_JS), new_url.ToString().c_str());
// Redirect again.
new_url = GetURL(RESULT_JS);
} else {
NOTREACHED();
}
}
on_resource_redirect_ct_++;
}
bool OnResourceResponse(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
if (IsMainURL(request->GetURL())) {
EXPECT_TRUE(frame->IsMain());
return false;
} else if (IsSubURL(request->GetURL())) {
EXPECT_FALSE(frame->IsMain());
EXPECT_TRUE(subframe_);
return false;
}
VerifyFrame(kOnResourceResponse, frame);
VerifyState(kOnResourceResponse, request, response);
on_resource_response_ct_++;
if (on_resource_response_ct_ == 1) {
if (mode_ == REDIRECT_RESOURCE_RESPONSE) {
// Redirect the request to this URL.
request->SetURL(GetURL(RESULT_JS));
return true;
} else if (mode_ == RESTART_RESOURCE_RESPONSE) {
// Restart the request loading this data.
SetCustomHeader(request);
return true;
}
}
return false;
}
CefRefPtr<CefResponseFilter> GetResourceResponseFilter(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
if (IsMainURL(request->GetURL())) {
EXPECT_TRUE(frame->IsMain());
return nullptr;
} else if (IsSubURL(request->GetURL())) {
EXPECT_FALSE(frame->IsMain());
EXPECT_TRUE(subframe_);
return nullptr;
}
VerifyFrame(kGetResourceResponseFilter, frame);
VerifyState(kGetResourceResponseFilter, request, response);
get_resource_response_filter_ct_++;
return nullptr;
}
void OnResourceLoadComplete(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
URLRequestStatus status,
int64_t received_content_length) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() && request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return;
}
EXPECT_EQ(browser_id_, browser->GetIdentifier());
if (IsMainURL(request->GetURL())) {
EXPECT_TRUE(frame->IsMain());
EXPECT_EQ(UR_SUCCESS, status);
EXPECT_EQ(static_cast<int64_t>(GetMainResponseBody().length()),
received_content_length);
return;
} else if (IsSubURL(request->GetURL())) {
EXPECT_FALSE(frame->IsMain());
EXPECT_EQ(UR_SUCCESS, status);
EXPECT_EQ(static_cast<int64_t>(GetSubResponseBody().length()),
received_content_length);
EXPECT_TRUE(subframe_);
return;
}
VerifyFrame(kOnResourceLoadComplete, frame);
VerifyState(kOnResourceLoadComplete, request, response);
if (unhandled_ || IsIncomplete()) {
EXPECT_EQ(UR_FAILED, status);
EXPECT_EQ(0, received_content_length);
} else {
EXPECT_EQ(UR_SUCCESS, status);
EXPECT_EQ(static_cast<int64_t>(GetResponseBody().length()),
received_content_length);
}
on_resource_load_complete_ct_++;
if (IsIncomplete()) {
MaybeDestroyTest(false);
}
}
void OnProtocolExecution(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool& allow_os_execution) override {
EXPECT_IO_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
if (IsMainURL(request->GetURL()) || IsSubURL(request->GetURL())) {
EXPECT_FALSE(true); // Not reached.
return;
}
VerifyFrame(kOnProtocolExecution, frame);
EXPECT_TRUE(custom_scheme_);
EXPECT_TRUE(unhandled_);
// Check expected default value.
EXPECT_FALSE(allow_os_execution);
VerifyState(kOnProtocolExecution, request, nullptr);
on_protocol_execution_ct_++;
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
EXPECT_UI_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_EQ(httpStatusCode, 200);
on_load_end_ct_++;
TestHandler::OnLoadEnd(browser, frame, httpStatusCode);
MaybeDestroyTest(false);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
EXPECT_UI_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
EXPECT_STREQ(kSubresourceProcessMsg, request.ToString().c_str());
VerifyFrame(kOnQuery, frame);
callback->Success("");
on_query_ct_++;
MaybeDestroyTest(false);
return true;
}
void DestroyTest() override {
// Only called for the main and/or sub frame load.
if (subframe_) {
EXPECT_EQ(2, on_before_browse_ct_);
} else {
EXPECT_EQ(1, on_before_browse_ct_);
}
if (mode_ == RESTART_RESOURCE_RESPONSE) {
EXPECT_EQ(2, get_resource_request_handler_ct_);
EXPECT_EQ(2, get_cookie_access_filter_ct_);
EXPECT_EQ(2, on_before_resource_load_ct_);
EXPECT_EQ(2, get_resource_handler_ct_);
EXPECT_EQ(0, on_resource_redirect_ct_);
// Unhandled requests won't see a call to GetResourceResponseFilter or
// OnResourceResponse. In this case we're restarting from inside
// OnResourceResponse.
if (unhandled_) {
EXPECT_EQ(0, get_resource_response_filter_ct_);
EXPECT_EQ(1, on_resource_response_ct_);
} else {
EXPECT_EQ(1, get_resource_response_filter_ct_);
EXPECT_EQ(2, on_resource_response_ct_);
}
} else if (IsLoad()) {
EXPECT_EQ(1, get_resource_request_handler_ct_);
EXPECT_EQ(1, get_cookie_access_filter_ct_);
EXPECT_EQ(1, on_before_resource_load_ct_);
EXPECT_EQ(1, get_resource_handler_ct_);
EXPECT_EQ(0, on_resource_redirect_ct_);
// Unhandled requests won't see a call to GetResourceResponseFilter or
// OnResourceResponse.
if (unhandled_) {
EXPECT_EQ(0, get_resource_response_filter_ct_);
EXPECT_EQ(0, on_resource_response_ct_);
} else {
EXPECT_EQ(1, get_resource_response_filter_ct_);
EXPECT_EQ(1, on_resource_response_ct_);
}
} else if (IsRedirect()) {
EXPECT_EQ(2, get_resource_request_handler_ct_);
EXPECT_EQ(2, get_cookie_access_filter_ct_);
EXPECT_EQ(2, on_before_resource_load_ct_);
if (mode_ == REDIRECT_BEFORE_RESOURCE_LOAD) {
EXPECT_EQ(1, get_resource_handler_ct_);
} else {
EXPECT_EQ(2, get_resource_handler_ct_);
}
EXPECT_EQ(1, on_resource_redirect_ct_);
// Unhandled requests won't see a call to GetResourceResponseFilter.
if (unhandled_) {
EXPECT_EQ(0, get_resource_response_filter_ct_);
} else {
EXPECT_EQ(1, get_resource_response_filter_ct_);
}
// Unhandled requests won't see a call to OnResourceResponse.
if (mode_ == REDIRECT_RESOURCE_RESPONSE) {
// In this case we're redirecting from inside OnResourceResponse.
if (unhandled_) {
EXPECT_EQ(1, on_resource_response_ct_);
} else {
EXPECT_EQ(2, on_resource_response_ct_);
}
} else {
if (unhandled_) {
EXPECT_EQ(0, on_resource_response_ct_);
} else {
EXPECT_EQ(1, on_resource_response_ct_);
}
}
} else if (IsIncomplete()) {
EXPECT_EQ(1, get_resource_request_handler_ct_);
EXPECT_EQ(1, get_cookie_access_filter_ct_);
EXPECT_EQ(1, on_before_resource_load_ct_);
if (IsIncompleteRequestHandler()) {
EXPECT_EQ(1, get_resource_handler_ct_);
} else {
EXPECT_EQ(0, get_resource_handler_ct_);
}
EXPECT_EQ(0, on_resource_redirect_ct_);
if (mode_ == INCOMPLETE_REQUEST_HANDLER_READ) {
EXPECT_EQ(1, get_resource_response_filter_ct_);
EXPECT_EQ(1, on_resource_response_ct_);
} else {
EXPECT_EQ(0, get_resource_response_filter_ct_);
EXPECT_EQ(0, on_resource_response_ct_);
}
} else {
NOTREACHED();
}
EXPECT_EQ(resource_handler_created_ct_, resource_handler_destroyed_ct_);
EXPECT_EQ(1, on_resource_load_complete_ct_);
// Only called for the main and/or sub frame load.
if (IsIncomplete()) {
EXPECT_EQ(0, on_load_end_ct_);
} else {
if (subframe_) {
EXPECT_EQ(2, on_load_end_ct_);
} else {
EXPECT_EQ(1, on_load_end_ct_);
}
}
if (unhandled_ || IsIncomplete()) {
EXPECT_EQ(0, on_query_ct_);
} else {
EXPECT_EQ(1, on_query_ct_);
}
if (custom_scheme_ && unhandled_ && !IsIncomplete()) {
EXPECT_EQ(1, on_protocol_execution_ct_);
} else {
EXPECT_EQ(0, on_protocol_execution_ct_);
}
TestHandler::DestroyTest();
if (!AllowTestCompletionWhenAllBrowsersClose()) {
// Complete asynchronously so the call stack has a chance to unwind.
CefPostTask(
TID_UI,
base::BindOnce(&SubresourceResponseTest::SignalTestCompletion, this));
}
}
private:
const char* GetMainURL() const {
if (custom_scheme_) {
return "rrhcustom://test.com/main.html";
} else {
return "https://test.com/main.html";
}
}
const char* GetSubURL() const {
if (custom_scheme_) {
return "rrhcustom://test.com/subframe.html";
} else {
return "https://test.com/subframe.html";
}
}
const char* GetOrigin() const {
if (custom_scheme_) {
return "rrhcustom://test.com";
} else {
return "https://test.com";
}
}
bool IsMainURL(const std::string& url) const { return url == GetMainURL(); }
bool IsSubURL(const std::string& url) const { return url == GetSubURL(); }
enum TestUrl {
RESULT_JS,
REDIRECT_JS,
REDIRECT2_JS,
};
const char* GetURL(TestUrl url) const {
if (custom_scheme_) {
if (url == RESULT_JS) {
return "rrhcustom://test.com/result.js";
}
if (url == REDIRECT_JS) {
return "rrhcustom://test.com/redirect.js";
}
if (url == REDIRECT2_JS) {
return "rrhcustom://test.com/redirect2.js";
}
} else {
if (url == RESULT_JS) {
return "https://test.com/result.js";
}
if (url == REDIRECT_JS) {
return "https://test.com/redirect.js";
}
if (url == REDIRECT2_JS) {
return "https://test.com/redirect2.js";
}
}
NOTREACHED();
return "";
}
const char* GetStartupURL() const {
if (IsLoad() || IsIncomplete()) {
return GetURL(RESULT_JS);
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
return GetURL(REDIRECT2_JS);
} else if (IsRedirect()) {
return GetURL(REDIRECT_JS);
}
NOTREACHED();
return "";
}
std::string GetMainResponseBody() const {
std::stringstream html;
html << "<html><head>";
if (subframe_) {
const std::string& url = GetSubURL();
html << "<iframe src=\"" << url << "\"></iframe>";
} else {
const std::string& url = GetStartupURL();
html << "<script type=\"text/javascript\" src=\"" << url
<< "\"></script>";
}
html << "</head><body><p>Main</p></body></html>";
return html.str();
}
std::string GetSubResponseBody() const {
EXPECT_TRUE(subframe_);
std::stringstream html;
html << "<html><head>";
const std::string& url = GetStartupURL();
html << "<script type=\"text/javascript\" src=\"" << url << "\"></script>";
html << "</head><body><p>Sub</p></body></html>";
return html.str();
}
std::string GetResponseBody() const {
return "window.testQuery({request:'" + std::string(kSubresourceProcessMsg) +
"'});";
}
std::string GetRedirectBody() const {
return "<html><body>Redirect</body></html>";
}
base::OnceClosure GetResourceDestroyCallback() {
resource_handler_created_ct_++;
return base::BindOnce(&SubresourceResponseTest::MaybeDestroyTest, this,
true);
}
bool GetCallbackResourceHandlerMode(CallbackResourceHandler::Mode& mode) {
switch (mode_) {
case IMMEDIATE_REQUEST_HANDLER_OPEN:
mode = CallbackResourceHandler::IMMEDIATE_OPEN;
return true;
case IMMEDIATE_REQUEST_HANDLER_READ:
mode = CallbackResourceHandler::IMMEDIATE_READ;
return true;
case IMMEDIATE_REQUEST_HANDLER_ALL:
mode = CallbackResourceHandler::IMMEDIATE_ALL;
return true;
case DELAYED_REQUEST_HANDLER_OPEN:
mode = CallbackResourceHandler::DELAYED_OPEN;
return true;
case DELAYED_REQUEST_HANDLER_READ:
mode = CallbackResourceHandler::DELAYED_READ;
return true;
case DELAYED_REQUEST_HANDLER_ALL:
mode = CallbackResourceHandler::DELAYED_ALL;
return true;
default:
break;
}
return false;
}
CefRefPtr<CefResourceHandler> GetResource(int status_code,
const CefString& status_text,
const CefString& mime_type,
CefResponse::HeaderMap header_map,
const std::string& body) {
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size());
CallbackResourceHandler::Mode handler_mode;
if (GetCallbackResourceHandlerMode(handler_mode)) {
return new CallbackResourceHandler(handler_mode, status_code, status_text,
mime_type, header_map, stream,
GetResourceDestroyCallback());
}
return new NormalResourceHandler(status_code, status_text, mime_type,
header_map, stream,
GetResourceDestroyCallback());
}
CefRefPtr<CefResourceHandler> GetMainResource() {
return GetResource(200, "OK", "text/html", CefResponse::HeaderMap(),
GetMainResponseBody());
}
CefRefPtr<CefResourceHandler> GetSubResource() {
return GetResource(200, "OK", "text/html", CefResponse::HeaderMap(),
GetSubResponseBody());
}
CefRefPtr<CefResourceHandler> GetOKResource() {
return GetResource(200, "OK", "text/javascript", CefResponse::HeaderMap(),
GetResponseBody());
}
CefRefPtr<CefResourceHandler> GetRedirectResource(
const std::string& redirect_url) {
CefResponse::HeaderMap headerMap;
headerMap.insert(std::make_pair("Location", redirect_url));
return GetResource(307, "Temporary Redirect", "text/javascript", headerMap,
GetRedirectBody());
}
CefRefPtr<CefResourceHandler> GetIncompleteResource() {
if (TestOldResourceAPI()) {
return new IncompleteResourceHandlerOld(
mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN
? IncompleteResourceHandlerOld::BLOCK_PROCESS_REQUEST
: IncompleteResourceHandlerOld::BLOCK_READ_RESPONSE,
"text/javascript", GetResourceDestroyCallback());
}
return new IncompleteResourceHandler(
mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN
? IncompleteResourceHandler::BLOCK_OPEN
: IncompleteResourceHandler::BLOCK_READ,
"text/javascript", GetResourceDestroyCallback());
}
bool IsLoad() const {
return mode_ == LOAD || mode_ == MODIFY_BEFORE_RESOURCE_LOAD ||
mode_ == RESTART_RESOURCE_RESPONSE ||
mode_ == IMMEDIATE_REQUEST_HANDLER_OPEN ||
mode_ == IMMEDIATE_REQUEST_HANDLER_READ ||
mode_ == IMMEDIATE_REQUEST_HANDLER_ALL ||
mode_ == DELAYED_REQUEST_HANDLER_OPEN ||
mode_ == DELAYED_REQUEST_HANDLER_READ ||
mode_ == DELAYED_REQUEST_HANDLER_ALL;
}
bool IsIncompleteRequestHandler() const {
return mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN ||
mode_ == INCOMPLETE_REQUEST_HANDLER_READ;
}
bool IsIncomplete() const {
return mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
IsIncompleteRequestHandler();
}
bool IsRedirect() const {
return mode_ == REDIRECT_BEFORE_RESOURCE_LOAD ||
mode_ == REDIRECT_REQUEST_HANDLER ||
mode_ == REDIRECT_RESOURCE_REDIRECT ||
mode_ == REDIRECT_RESOURCE_RESPONSE;
}
static void SetCustomHeader(CefRefPtr<CefRequest> request) {
EXPECT_FALSE(request->IsReadOnly());
request->SetHeaderByName("X-Custom-Header", "value", false);
}
static std::string GetCustomHeader(CefRefPtr<CefRequest> request) {
return request->GetHeaderByName("X-Custom-Header");
}
// Resource-related callbacks.
enum Callback {
kGetResourceRequestHandler,
kGetCookieAccessFilter,
kOnBeforeResourceLoad,
kGetResourceHandler,
kOnResourceRedirect,
kOnResourceResponse,
kGetResourceResponseFilter,
kOnResourceLoadComplete,
kOnProtocolExecution,
kOnQuery,
};
bool ShouldHaveResponse(Callback callback) const {
return callback >= kOnResourceRedirect &&
callback <= kOnResourceLoadComplete;
}
bool ShouldHaveWritableRequest(Callback callback) const {
return callback == kOnBeforeResourceLoad || callback == kOnResourceResponse;
}
void VerifyFrame(Callback callback, CefRefPtr<CefFrame> frame) const {
EXPECT_TRUE(frame);
EXPECT_NE(subframe_, frame->IsMain()) << callback;
EXPECT_STREQ(frame_id_.c_str(), frame->GetIdentifier().ToString().c_str())
<< callback;
}
void VerifyState(Callback callback,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) const {
EXPECT_TRUE(request) << callback;
if (ShouldHaveResponse(callback)) {
EXPECT_TRUE(response) << callback;
EXPECT_TRUE(response->IsReadOnly()) << callback;
} else {
EXPECT_FALSE(response) << callback;
}
if (ShouldHaveWritableRequest(callback)) {
EXPECT_FALSE(request->IsReadOnly()) << callback;
} else {
EXPECT_TRUE(request->IsReadOnly()) << callback;
}
// All resource-related callbacks share the same request ID.
EXPECT_EQ(request_id_, request->GetIdentifier()) << callback;
if (IsLoad() || IsIncomplete()) {
EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback;
EXPECT_STREQ(GetURL(RESULT_JS), request->GetURL().ToString().c_str())
<< callback;
// Expect the header for all callbacks following the callback that
// initially sets it.
const std::string& custom_header = GetCustomHeader(request);
if ((mode_ == RESTART_RESOURCE_RESPONSE &&
on_resource_response_ct_ > 0) ||
(mode_ == MODIFY_BEFORE_RESOURCE_LOAD &&
on_before_resource_load_ct_ > 0)) {
EXPECT_STREQ("value", custom_header.c_str()) << callback;
} else {
EXPECT_STREQ("", custom_header.c_str()) << callback;
}
if (response) {
VerifyOKResponse(callback, response);
}
} else if (IsRedirect()) {
EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback;
// Subresource loads don't get OnBeforeBrowse calls, so this check is a
// bit less exact then with main resource loads.
if (on_resource_redirect_ct_ == 0) {
// Before the redirect.
EXPECT_STREQ(GetStartupURL(), request->GetURL().ToString().c_str())
<< callback;
} else {
// After the redirect.
EXPECT_STREQ(GetURL(RESULT_JS), request->GetURL().ToString().c_str())
<< callback;
}
if (response) {
if (callback == kOnResourceRedirect) {
// Before the redirect.
VerifyRedirectResponse(callback, response);
} else {
// After the redirect.
VerifyOKResponse(callback, response);
}
}
} else {
NOTREACHED() << callback;
}
}
void VerifyOKResponse(Callback callback,
CefRefPtr<CefResponse> response) const {
// True for the first response in cases where we're redirecting/restarting
// from inside OnResourceResponse (e.g. the first response always succeeds).
const bool override_unhandled = unhandled_ &&
(mode_ == REDIRECT_RESOURCE_RESPONSE ||
mode_ == RESTART_RESOURCE_RESPONSE) &&
get_resource_handler_ct_ == 1;
// True for tests where the request will be incomplete and never receive a
// response.
const bool incomplete_unhandled =
(mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
mode_ == INCOMPLETE_REQUEST_HANDLER_OPEN);
if ((unhandled_ && !override_unhandled) || incomplete_unhandled) {
if (incomplete_unhandled) {
EXPECT_EQ(ERR_ABORTED, response->GetError()) << callback;
} else {
EXPECT_EQ(ERR_UNKNOWN_URL_SCHEME, response->GetError()) << callback;
}
EXPECT_EQ(0, response->GetStatus()) << callback;
EXPECT_STREQ("", response->GetStatusText().ToString().c_str())
<< callback;
EXPECT_STREQ("", response->GetURL().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
} else {
if (mode_ == INCOMPLETE_REQUEST_HANDLER_READ &&
callback == kOnResourceLoadComplete) {
// We got a response, but we also got aborted.
EXPECT_EQ(ERR_ABORTED, response->GetError()) << callback;
} else {
EXPECT_EQ(ERR_NONE, response->GetError()) << callback;
}
EXPECT_EQ(200, response->GetStatus()) << callback;
EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str())
<< callback;
EXPECT_STREQ("", response->GetURL().ToString().c_str()) << callback;
EXPECT_STREQ("text/javascript",
response->GetMimeType().ToString().c_str())
<< callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
}
}
void VerifyRedirectResponse(Callback callback,
CefRefPtr<CefResponse> response) const {
EXPECT_EQ(ERR_NONE, response->GetError()) << callback;
EXPECT_EQ(307, response->GetStatus()) << callback;
const std::string& status_text = response->GetStatusText();
EXPECT_TRUE(status_text == "Internal Redirect" ||
status_text == "Temporary Redirect")
<< status_text << callback;
EXPECT_STREQ("", response->GetURL().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
}
void CloseBrowserAsync() {
EXPECT_TRUE(IsIncomplete());
SetSignalTestCompletionCount(1U);
CefPostDelayedTask(
TID_UI, base::BindOnce(&TestHandler::CloseBrowser, GetBrowser(), false),
100);
}
void MaybeDestroyTest(bool from_handler) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI,
base::BindOnce(&SubresourceResponseTest::MaybeDestroyTest,
this, from_handler));
return;
}
if (from_handler) {
resource_handler_destroyed_ct_++;
}
bool destroy_test = false;
if (IsIncomplete()) {
// Destroy the test if we got OnResourceLoadComplete and either the
// resource handler will never complete or it was destroyed.
destroy_test =
on_resource_load_complete_ct_ > 0 &&
(!IsIncompleteRequestHandler() ||
resource_handler_destroyed_ct_ == resource_handler_created_ct_);
} else {
// Destroy the test if we got the expected number of OnLoadEnd and
// OnQuery, and the expected number of resource handlers were destroyed.
destroy_test =
on_load_end_ct_ > (subframe_ ? 1 : 0) &&
(on_query_ct_ > 0 || unhandled_) &&
resource_handler_destroyed_ct_ == resource_handler_created_ct_;
}
if (destroy_test) {
DestroyTest();
}
}
const TestMode mode_;
const bool custom_scheme_;
const bool unhandled_;
const bool subframe_;
int browser_id_ = 0;
std::string frame_id_;
uint64_t request_id_ = 0U;
int resource_handler_created_ct_ = 0;
int on_before_browse_ct_ = 0;
int on_load_end_ct_ = 0;
int on_query_ct_ = 0;
int get_resource_request_handler_ct_ = 0;
int get_cookie_access_filter_ct_ = 0;
int on_before_resource_load_ct_ = 0;
int get_resource_handler_ct_ = 0;
int on_resource_redirect_ct_ = 0;
int on_resource_response_ct_ = 0;
int get_resource_response_filter_ct_ = 0;
int on_resource_load_complete_ct_ = 0;
int on_protocol_execution_ct_ = 0;
int resource_handler_destroyed_ct_ = 0;
// Used with INCOMPLETE_BEFORE_RESOURCE_LOAD.
CefRefPtr<CefCallback> incomplete_callback_;
DISALLOW_COPY_AND_ASSIGN(SubresourceResponseTest);
IMPLEMENT_REFCOUNTING(SubresourceResponseTest);
};
} // namespace
#define SUBRESOURCE_TEST(name, test_mode, custom, unhandled, subframe) \
TEST(ResourceRequestHandlerTest, Subresource##name) { \
CefRefPtr<SubresourceResponseTest> handler = new SubresourceResponseTest( \
SubresourceResponseTest::test_mode, custom, unhandled, subframe); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
}
#define SUBRESOURCE_TEST_ALL_MODES(name, custom, unhandled, subframe) \
SUBRESOURCE_TEST(name##Load, LOAD, custom, unhandled, subframe) \
SUBRESOURCE_TEST(name##ModifyBeforeResourceLoad, \
MODIFY_BEFORE_RESOURCE_LOAD, custom, unhandled, subframe) \
SUBRESOURCE_TEST(name##RedirectBeforeResourceLoad, \
REDIRECT_BEFORE_RESOURCE_LOAD, custom, unhandled, subframe) \
SUBRESOURCE_TEST(name##RedirectRequestHandler, REDIRECT_REQUEST_HANDLER, \
custom, unhandled, subframe) \
SUBRESOURCE_TEST(name##RedirectResourceRedirect, REDIRECT_RESOURCE_REDIRECT, \
custom, unhandled, subframe) \
SUBRESOURCE_TEST(name##RedirectResourceResponse, REDIRECT_RESOURCE_RESPONSE, \
custom, unhandled, subframe) \
SUBRESOURCE_TEST(name##RestartResourceResponse, RESTART_RESOURCE_RESPONSE, \
custom, unhandled, subframe)
// Tests only supported in handled mode.
#define SUBRESOURCE_TEST_HANDLED_MODES(name, custom, subframe) \
SUBRESOURCE_TEST(name##ImmediateRequestHandlerOpen, \
IMMEDIATE_REQUEST_HANDLER_OPEN, custom, false, subframe) \
SUBRESOURCE_TEST(name##ImmediateRequestHandlerRead, \
IMMEDIATE_REQUEST_HANDLER_READ, custom, false, subframe) \
SUBRESOURCE_TEST(name##ImmediateRequestHandlerAll, \
IMMEDIATE_REQUEST_HANDLER_ALL, custom, false, subframe) \
SUBRESOURCE_TEST(name##DelayedRequestHandlerOpen, \
DELAYED_REQUEST_HANDLER_OPEN, custom, false, subframe) \
SUBRESOURCE_TEST(name##DelayedRequestHandlerRead, \
DELAYED_REQUEST_HANDLER_READ, custom, false, subframe) \
SUBRESOURCE_TEST(name##DelayedRequestHandlerAll, \
DELAYED_REQUEST_HANDLER_ALL, custom, false, subframe) \
SUBRESOURCE_TEST(name##IncompleteBeforeResourceLoad, \
INCOMPLETE_BEFORE_RESOURCE_LOAD, custom, false, subframe) \
SUBRESOURCE_TEST(name##IncompleteRequestHandlerOpen, \
INCOMPLETE_REQUEST_HANDLER_OPEN, custom, false, subframe) \
SUBRESOURCE_TEST(name##IncompleteRequestHandlerRead, \
INCOMPLETE_REQUEST_HANDLER_READ, custom, false, subframe)
SUBRESOURCE_TEST_ALL_MODES(StandardHandledMainFrame, false, false, false)
SUBRESOURCE_TEST_ALL_MODES(StandardUnhandledMainFrame, false, true, false)
SUBRESOURCE_TEST_ALL_MODES(CustomHandledMainFrame, true, false, false)
SUBRESOURCE_TEST_ALL_MODES(CustomUnhandledMainFrame, true, true, false)
SUBRESOURCE_TEST_ALL_MODES(StandardHandledSubFrame, false, false, true)
SUBRESOURCE_TEST_ALL_MODES(StandardUnhandledSubFrame, false, true, true)
SUBRESOURCE_TEST_ALL_MODES(CustomHandledSubFrame, true, false, true)
SUBRESOURCE_TEST_ALL_MODES(CustomUnhandledSubFrame, true, true, true)
SUBRESOURCE_TEST_HANDLED_MODES(StandardHandledMainFrame, false, false)
SUBRESOURCE_TEST_HANDLED_MODES(CustomHandledMainFrame, true, false)
SUBRESOURCE_TEST_HANDLED_MODES(StandardHandledSubFrame, false, true)
SUBRESOURCE_TEST_HANDLED_MODES(CustomHandledSubFrame, true, true)
namespace {
const char kResourceTestHtml[] = "https://test.com/resource.html";
class RedirectResponseTest : public TestHandler {
public:
enum TestMode {
URL,
HEADER,
POST,
};
RedirectResponseTest(TestMode mode, bool via_request_context_handler)
: via_request_context_handler_(via_request_context_handler) {
if (mode == URL) {
resource_test_ = std::make_unique<UrlResourceTest>();
} else if (mode == HEADER) {
resource_test_ = std::make_unique<HeaderResourceTest>();
} else {
resource_test_ = std::make_unique<PostResourceTest>();
}
}
void RunTest() override {
AddResource(kResourceTestHtml, GetHtml(), "text/html");
resource_request_handler_ = new ResourceRequestHandler(this);
CefRefPtr<CefRequestContext> request_context =
CefRequestContext::GetGlobalContext();
if (via_request_context_handler_) {
CefRefPtr<CefRequestContextHandler> request_context_handler =
new RequestContextHandler(resource_request_handler_.get());
CefRequestContextSettings settings;
request_context = CefRequestContext::CreateContext(
request_context, request_context_handler);
}
CreateBrowser(kResourceTestHtml, request_context);
SetTestTimeout();
}
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool is_navigation,
bool is_download,
const CefString& request_initiator,
bool& disable_default_handling) override {
if (via_request_context_handler_) {
// Use the handler returned by RequestContextHandler.
return nullptr;
}
return resource_request_handler_.get();
}
bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool user_gesture,
bool is_redirect) override {
EXPECT_UI_THREAD();
EXPECT_EQ(0, browser_id_);
browser_id_ = browser->GetIdentifier();
EXPECT_GT(browser_id_, 0);
// This method is only called for the main resource.
EXPECT_STREQ(kResourceTestHtml, request->GetURL().ToString().c_str());
// Browser-side navigation no longer exposes the actual request information.
EXPECT_EQ(0U, request->GetIdentifier());
return false;
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
EXPECT_UI_THREAD();
EXPECT_EQ(browser_id_, browser->GetIdentifier());
TestHandler::OnLoadEnd(browser, frame, httpStatusCode);
DestroyTest();
}
void DestroyTest() override {
resource_test_->CheckExpected();
resource_test_.reset(nullptr);
TestHandler::DestroyTest();
}
private:
std::string GetHtml() const {
std::stringstream html;
html << "<html><head>";
const std::string& url = resource_test_->start_url();
html << "<script type=\"text/javascript\" src=\"" << url << "\"></script>";
html << "</head><body><p>Main</p></body></html>";
return html.str();
}
class ResourceTest {
public:
explicit ResourceTest(const std::string& start_url,
size_t expected_resource_response_ct = 2U,
size_t expected_before_resource_load_ct = 1U,
size_t expected_resource_redirect_ct = 0U,
size_t expected_resource_load_complete_ct = 1U)
: start_url_(start_url),
expected_resource_response_ct_(expected_resource_response_ct),
expected_before_resource_load_ct_(expected_before_resource_load_ct),
expected_resource_redirect_ct_(expected_resource_redirect_ct),
expected_resource_load_complete_ct_(
expected_resource_load_complete_ct) {}
virtual ~ResourceTest() = default;
const std::string& start_url() const { return start_url_; }
virtual bool OnBeforeResourceLoad(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) {
before_resource_load_ct_++;
return false;
}
virtual CefRefPtr<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) {
get_resource_handler_ct_++;
const std::string& js_content = "<!-- -->";
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(js_content.c_str()), js_content.size());
return new CefStreamResourceHandler(200, "OK", "text/javascript",
CefResponse::HeaderMap(), stream);
}
virtual void OnResourceRedirect(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefString& new_url) {
resource_redirect_ct_++;
}
bool OnResourceResponse(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) {
EXPECT_TRUE(CheckUrl(request->GetURL()));
// Verify the response returned by GetResourceHandler.
EXPECT_EQ(200, response->GetStatus());
EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str());
EXPECT_STREQ("text/javascript",
response->GetMimeType().ToString().c_str());
if (resource_response_ct_++ == 0U) {
// Always redirect at least one time.
OnResourceReceived(browser, frame, request, response);
return true;
}
OnRetryReceived(browser, frame, request, response);
return (resource_response_ct_ < expected_resource_response_ct_);
}
CefRefPtr<CefResponseFilter> GetResourceResponseFilter(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) {
get_resource_response_filter_ct_++;
return nullptr;
}
void OnResourceLoadComplete(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
URLRequestStatus status,
int64_t received_content_length) {
EXPECT_TRUE(CheckUrl(request->GetURL()));
// Verify the response returned by GetResourceHandler.
EXPECT_EQ(200, response->GetStatus());
EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str());
EXPECT_STREQ("text/javascript",
response->GetMimeType().ToString().c_str());
resource_load_complete_ct_++;
}
virtual bool CheckUrl(const std::string& url) const {
return (url == start_url_);
}
virtual void CheckExpected() {
EXPECT_TRUE(got_resource_);
EXPECT_TRUE(got_resource_retry_);
EXPECT_EQ(expected_resource_response_ct_, resource_response_ct_);
EXPECT_EQ(expected_resource_response_ct_, get_resource_handler_ct_);
EXPECT_EQ(expected_resource_load_complete_ct_,
get_resource_response_filter_ct_);
EXPECT_EQ(expected_before_resource_load_ct_, before_resource_load_ct_);
EXPECT_EQ(expected_resource_redirect_ct_, resource_redirect_ct_);
EXPECT_EQ(expected_resource_load_complete_ct_,
resource_load_complete_ct_);
}
protected:
virtual void OnResourceReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) {
got_resource_.yes();
}
virtual void OnRetryReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) {
got_resource_retry_.yes();
}
private:
std::string start_url_;
size_t resource_response_ct_ = 0U;
size_t expected_resource_response_ct_;
size_t before_resource_load_ct_ = 0U;
size_t expected_before_resource_load_ct_;
size_t get_resource_handler_ct_ = 0U;
size_t resource_redirect_ct_ = 0U;
size_t expected_resource_redirect_ct_;
size_t get_resource_response_filter_ct_ = 0U;
size_t resource_load_complete_ct_ = 0U;
size_t expected_resource_load_complete_ct_;
TrackCallback got_resource_;
TrackCallback got_resource_retry_;
};
class UrlResourceTest : public ResourceTest {
public:
// With NetworkService we don't get an additional (unnecessary) redirect
// callback.
UrlResourceTest()
: ResourceTest("https://test.com/start_url.js", 2U, 2U, 1U) {
redirect_url_ = "https://test.com/redirect_url.js";
}
bool CheckUrl(const std::string& url) const override {
if (url == redirect_url_) {
return true;
}
return ResourceTest::CheckUrl(url);
}
void OnResourceRedirect(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefString& new_url) override {
ResourceTest::OnResourceRedirect(browser, frame, request, new_url);
const std::string& old_url = request->GetURL();
EXPECT_STREQ(start_url().c_str(), old_url.c_str());
EXPECT_STREQ(redirect_url_.c_str(), new_url.ToString().c_str());
}
private:
void OnResourceReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
ResourceTest::OnResourceReceived(browser, frame, request, response);
request->SetURL(redirect_url_);
}
void OnRetryReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
ResourceTest::OnRetryReceived(browser, frame, request, response);
const std::string& new_url = request->GetURL();
EXPECT_STREQ(redirect_url_.c_str(), new_url.c_str());
}
std::string redirect_url_;
};
class HeaderResourceTest : public ResourceTest {
public:
// With NetworkService we restart the request, so we get another call to
// OnBeforeResourceLoad.
HeaderResourceTest()
: ResourceTest("https://test.com/start_header.js", 2U, 2U) {
expected_headers_.insert(std::make_pair("Test-Key1", "Value1"));
expected_headers_.insert(std::make_pair("Test-Key2", "Value2"));
}
private:
void OnResourceReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
ResourceTest::OnResourceReceived(browser, frame, request, response);
request->SetHeaderMap(expected_headers_);
}
void OnRetryReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
ResourceTest::OnRetryReceived(browser, frame, request, response);
CefRequest::HeaderMap actual_headers;
request->GetHeaderMap(actual_headers);
TestMapEqual(expected_headers_, actual_headers, true);
}
CefRequest::HeaderMap expected_headers_;
};
class PostResourceTest : public ResourceTest {
public:
// With NetworkService we restart the request, so we get another call to
// OnBeforeResourceLoad.
PostResourceTest()
: ResourceTest("https://test.com/start_post.js", 2U, 2U) {
CefRefPtr<CefPostDataElement> elem = CefPostDataElement::Create();
const std::string data("Test Post Data");
elem->SetToBytes(data.size(), data.c_str());
expected_post_ = CefPostData::Create();
expected_post_->AddElement(elem);
}
private:
void OnResourceReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
ResourceTest::OnResourceReceived(browser, frame, request, response);
request->SetPostData(expected_post_);
}
void OnRetryReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
ResourceTest::OnRetryReceived(browser, frame, request, response);
CefRefPtr<CefPostData> actual_post = request->GetPostData();
TestPostDataEqual(expected_post_, actual_post);
}
CefRefPtr<CefPostData> expected_post_;
};
class RequestContextHandler : public CefRequestContextHandler {
public:
explicit RequestContextHandler(
CefRefPtr<CefResourceRequestHandler> resource_request_handler)
: resource_request_handler_(resource_request_handler) {}
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
bool is_navigation,
bool is_download,
const CefString& request_initiator,
bool& disable_default_handling) override {
return resource_request_handler_;
}
private:
CefRefPtr<CefResourceRequestHandler> resource_request_handler_;
IMPLEMENT_REFCOUNTING(RequestContextHandler);
DISALLOW_COPY_AND_ASSIGN(RequestContextHandler);
};
class ResourceRequestHandler : public CefResourceRequestHandler {
public:
explicit ResourceRequestHandler(RedirectResponseTest* test) : test_(test) {}
cef_return_value_t OnBeforeResourceLoad(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() &&
request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return RV_CANCEL;
}
EXPECT_EQ(test_->browser_id_, browser->GetIdentifier());
if (request->GetURL() == kResourceTestHtml) {
// All loads of the main resource should keep the same request id.
EXPECT_EQ(0U, main_request_id_);
main_request_id_ = request->GetIdentifier();
EXPECT_GT(main_request_id_, 0U);
return RV_CONTINUE;
}
// All redirects of the sub-resource should keep the same request id.
if (sub_request_id_ == 0U) {
sub_request_id_ = request->GetIdentifier();
EXPECT_GT(sub_request_id_, 0U);
} else {
EXPECT_EQ(sub_request_id_, request->GetIdentifier());
}
return test_->resource_test_->OnBeforeResourceLoad(browser, frame,
request)
? RV_CANCEL
: RV_CONTINUE;
}
CefRefPtr<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request) override {
EXPECT_IO_THREAD();
EXPECT_EQ(test_->browser_id_, browser->GetIdentifier());
if (request->GetURL() == kResourceTestHtml) {
EXPECT_EQ(main_request_id_, request->GetIdentifier());
return test_->GetResourceHandler(browser, frame, request);
}
EXPECT_EQ(sub_request_id_, request->GetIdentifier());
return test_->resource_test_->GetResourceHandler(browser, frame, request);
}
void OnResourceRedirect(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
CefString& new_url) override {
EXPECT_IO_THREAD();
EXPECT_EQ(test_->browser_id_, browser->GetIdentifier());
EXPECT_EQ(sub_request_id_, request->GetIdentifier());
test_->resource_test_->OnResourceRedirect(browser, frame, request,
new_url);
}
bool OnResourceResponse(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
EXPECT_IO_THREAD();
EXPECT_TRUE(browser.get());
EXPECT_EQ(test_->browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame.get());
EXPECT_TRUE(frame->IsMain());
if (request->GetURL() == kResourceTestHtml) {
EXPECT_EQ(main_request_id_, request->GetIdentifier());
return false;
}
EXPECT_EQ(sub_request_id_, request->GetIdentifier());
return test_->resource_test_->OnResourceResponse(browser, frame, request,
response);
}
CefRefPtr<CefResponseFilter> GetResourceResponseFilter(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
EXPECT_IO_THREAD();
EXPECT_TRUE(browser.get());
EXPECT_EQ(test_->browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame.get());
EXPECT_TRUE(frame->IsMain());
if (request->GetURL() == kResourceTestHtml) {
EXPECT_EQ(main_request_id_, request->GetIdentifier());
return nullptr;
}
return test_->resource_test_->GetResourceResponseFilter(
browser, frame, request, response);
}
void OnResourceLoadComplete(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
URLRequestStatus status,
int64_t received_content_length) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() &&
request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return;
}
EXPECT_TRUE(browser.get());
EXPECT_EQ(test_->browser_id_, browser->GetIdentifier());
EXPECT_TRUE(frame.get());
EXPECT_TRUE(frame->IsMain());
if (request->GetURL() == kResourceTestHtml) {
EXPECT_EQ(main_request_id_, request->GetIdentifier());
return;
}
EXPECT_EQ(sub_request_id_, request->GetIdentifier());
test_->resource_test_->OnResourceLoadComplete(
browser, frame, request, response, status, received_content_length);
}
private:
RedirectResponseTest* const test_;
uint64_t main_request_id_ = 0U;
uint64_t sub_request_id_ = 0U;
IMPLEMENT_REFCOUNTING(ResourceRequestHandler);
DISALLOW_COPY_AND_ASSIGN(ResourceRequestHandler);
};
const bool via_request_context_handler_;
int browser_id_ = 0;
std::unique_ptr<ResourceTest> resource_test_;
CefRefPtr<ResourceRequestHandler> resource_request_handler_;
IMPLEMENT_REFCOUNTING(RedirectResponseTest);
};
} // namespace
// Verify redirect with client handler.
TEST(ResourceRequestHandlerTest, RedirectURLViaClient) {
CefRefPtr<RedirectResponseTest> handler =
new RedirectResponseTest(RedirectResponseTest::URL, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Verify redirect + modified headers with client handler.
TEST(ResourceRequestHandlerTest, RedirectHeaderViaClient) {
CefRefPtr<RedirectResponseTest> handler =
new RedirectResponseTest(RedirectResponseTest::HEADER, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Verify redirect + modified post data with client handler.
TEST(ResourceRequestHandlerTest, RedirectPostViaClient) {
CefRefPtr<RedirectResponseTest> handler =
new RedirectResponseTest(RedirectResponseTest::POST, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Verify redirect with context handler.
TEST(ResourceRequestHandlerTest, RedirectURLViaContext) {
CefRefPtr<RedirectResponseTest> handler =
new RedirectResponseTest(RedirectResponseTest::URL, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Verify redirect + modified headers with context handler.
TEST(ResourceRequestHandlerTest, RedirectHeaderViaContext) {
CefRefPtr<RedirectResponseTest> handler =
new RedirectResponseTest(RedirectResponseTest::HEADER, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Verify redirect + modified post data with context handler.
TEST(ResourceRequestHandlerTest, RedirectPostViaContext) {
CefRefPtr<RedirectResponseTest> handler =
new RedirectResponseTest(RedirectResponseTest::POST, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
const char kResourceTestHtml2[] = "https://test.com/resource2.html";
class BeforeResourceLoadTest : public TestHandler {
public:
enum TestMode {
CANCEL,
CANCEL_ASYNC,
CANCEL_NAV,
CONTINUE,
CONTINUE_ASYNC,
};
explicit BeforeResourceLoadTest(TestMode mode) : test_mode_(mode) {}
void RunTest() override {
AddResource(kResourceTestHtml, "<html><body>Test</body></html>",
"text/html");
AddResource(kResourceTestHtml2, "<html><body>Test2</body></html>",
"text/html");
CreateBrowser(kResourceTestHtml);
SetTestTimeout();
}
cef_return_value_t OnBeforeResourceLoad(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() && request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return RV_CANCEL;
}
// Allow the 2nd navigation to continue.
const std::string& url = request->GetURL();
if (url == kResourceTestHtml2) {
got_before_resource_load2_.yes();
EXPECT_EQ(CANCEL_NAV, test_mode_);
return RV_CONTINUE;
}
EXPECT_FALSE(got_before_resource_load_);
got_before_resource_load_.yes();
if (test_mode_ == CANCEL) {
// Cancel immediately.
return RV_CANCEL;
} else if (test_mode_ == CONTINUE) {
// Continue immediately.
return RV_CONTINUE;
} else {
if (test_mode_ == CANCEL_NAV) {
// Cancel the request by navigating to a new URL.
browser->GetMainFrame()->LoadURL(kResourceTestHtml2);
} else if (test_mode_ == CONTINUE_ASYNC) {
// Continue asynchronously.
CefPostTask(TID_UI,
base::BindOnce(&CefCallback::Continue, callback.get()));
} else {
// Cancel asynchronously.
CefPostTask(TID_UI,
base::BindOnce(&CefCallback::Cancel, callback.get()));
}
return RV_CONTINUE_ASYNC;
}
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
EXPECT_UI_THREAD();
EXPECT_FALSE(got_load_end_);
got_load_end_.yes();
const std::string& url = frame->GetURL();
if (test_mode_ == CANCEL_NAV) {
EXPECT_STREQ(kResourceTestHtml2, url.data());
} else {
EXPECT_STREQ(kResourceTestHtml, url.data());
}
TestHandler::OnLoadEnd(browser, frame, httpStatusCode);
DestroyTest();
}
void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) override {
EXPECT_UI_THREAD();
EXPECT_FALSE(got_load_error_);
got_load_error_.yes();
const std::string& url = failedUrl;
EXPECT_STREQ(kResourceTestHtml, url.data());
TestHandler::OnLoadError(browser, frame, errorCode, errorText, failedUrl);
if (test_mode_ != CANCEL_NAV) {
DestroyTest();
}
}
void DestroyTest() override {
EXPECT_TRUE(got_before_resource_load_);
if (test_mode_ == CANCEL_NAV) {
EXPECT_TRUE(got_before_resource_load2_);
} else {
EXPECT_FALSE(got_before_resource_load2_);
}
if (test_mode_ == CONTINUE || test_mode_ == CONTINUE_ASYNC) {
EXPECT_TRUE(got_load_end_);
EXPECT_FALSE(got_load_error_);
} else if (test_mode_ == CANCEL || test_mode_ == CANCEL_ASYNC) {
EXPECT_FALSE(got_load_end_);
EXPECT_TRUE(got_load_error_);
}
TestHandler::DestroyTest();
}
private:
const TestMode test_mode_;
TrackCallback got_before_resource_load_;
TrackCallback got_before_resource_load2_;
TrackCallback got_load_end_;
TrackCallback got_load_error_;
IMPLEMENT_REFCOUNTING(BeforeResourceLoadTest);
};
} // namespace
TEST(ResourceRequestHandlerTest, BeforeResourceLoadCancel) {
CefRefPtr<BeforeResourceLoadTest> handler =
new BeforeResourceLoadTest(BeforeResourceLoadTest::CANCEL);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
TEST(ResourceRequestHandlerTest, BeforeResourceLoadCancelAsync) {
CefRefPtr<BeforeResourceLoadTest> handler =
new BeforeResourceLoadTest(BeforeResourceLoadTest::CANCEL_ASYNC);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
TEST(ResourceRequestHandlerTest, BeforeResourceLoadCancelNav) {
CefRefPtr<BeforeResourceLoadTest> handler =
new BeforeResourceLoadTest(BeforeResourceLoadTest::CANCEL_NAV);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
TEST(ResourceRequestHandlerTest, BeforeResourceLoadContinue) {
CefRefPtr<BeforeResourceLoadTest> handler =
new BeforeResourceLoadTest(BeforeResourceLoadTest::CONTINUE);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
TEST(ResourceRequestHandlerTest, BeforeResourceLoadContinueAsync) {
CefRefPtr<BeforeResourceLoadTest> handler =
new BeforeResourceLoadTest(BeforeResourceLoadTest::CONTINUE_ASYNC);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
// For response filtering we need to test:
// - Passing through content unchanged.
// - Not reading all of the input buffer.
// - Needing more input and getting it.
// - Needing more input and not getting it.
// - Filter error.
const char kResponseFilterTestUrl[] = "https://test.com/response_filter.html";
size_t GetResponseBufferSize() {
// Match the default |capacity_num_bytes| value from
// mojo::Core::CreateDataPipe.
return 64 * 1024; // 64kb
}
const char kInputHeader[] = "<html><head></head><body>";
const char kInputFooter[] = "</body></html>";
// Repeat |content| the minimum number of times necessary to satisfy
// |desired_min_size|. If |calculated_repeat_ct| is non-nullptr it will be set
// to the number of times that |content| was repeated.
std::string CreateInput(const std::string& content,
size_t desired_min_size,
size_t* calculated_repeat_ct = nullptr) {
const size_t header_footer_size =
sizeof(kInputHeader) + sizeof(kInputFooter) - 2;
EXPECT_GE(desired_min_size, header_footer_size + content.size());
desired_min_size -= header_footer_size;
size_t repeat_ct =
static_cast<size_t>(std::ceil(static_cast<double>(desired_min_size) /
static_cast<double>(content.size())));
if (calculated_repeat_ct) {
*calculated_repeat_ct = repeat_ct;
}
std::string result;
result.reserve(header_footer_size + (content.size() * repeat_ct));
result = kInputHeader;
while (repeat_ct--) {
result += content;
}
result += kInputFooter;
return result;
}
std::string CreateOutput(const std::string& content, size_t repeat_ct) {
const size_t header_footer_size =
sizeof(kInputHeader) + sizeof(kInputFooter) - 2;
std::string result;
result.reserve(header_footer_size + (content.size() * repeat_ct));
result = kInputHeader;
while (repeat_ct--) {
result += content;
}
result += kInputFooter;
return result;
}
// Base class for test filters.
class ResponseFilterTestBase : public CefResponseFilter {
public:
ResponseFilterTestBase() = default;
bool InitFilter() override {
EXPECT_FALSE(got_init_filter_);
got_init_filter_.yes();
return true;
}
FilterStatus Filter(void* data_in,
size_t data_in_size,
size_t& data_in_read,
void* data_out,
size_t data_out_size,
size_t& data_out_written) override {
if (data_in_size == 0U) {
EXPECT_FALSE(data_in);
} else {
EXPECT_TRUE(data_in);
}
EXPECT_EQ(data_in_read, 0U);
EXPECT_TRUE(data_out);
EXPECT_GT(data_out_size, 0U);
EXPECT_EQ(data_out_written, 0U);
filter_count_++;
return RESPONSE_FILTER_ERROR;
}
// Returns the input that will be fed into the filter.
virtual std::string GetInput() = 0;
// Verify the output from the filter.
virtual void VerifyOutput(cef_urlrequest_status_t status,
int64_t received_content_length,
const std::string& received_content) {
EXPECT_TRUE(got_init_filter_);
EXPECT_GT(filter_count_, 0U);
}
virtual void VerifyStatusCode(int httpStatusCode) const {
EXPECT_TRUE(httpStatusCode == 0 || httpStatusCode == 200) << httpStatusCode;
}
protected:
TrackCallback got_init_filter_;
size_t filter_count_ = 0U;
IMPLEMENT_REFCOUNTING(ResponseFilterTestBase);
};
// Pass through the contents unchanged.
class ResponseFilterPassThru : public ResponseFilterTestBase {
public:
explicit ResponseFilterPassThru(bool limit_read) : limit_read_(limit_read) {}
FilterStatus Filter(void* data_in,
size_t data_in_size,
size_t& data_in_read,
void* data_out,
size_t data_out_size,
size_t& data_out_written) override {
ResponseFilterTestBase::Filter(data_in, data_in_size, data_in_read,
data_out, data_out_size, data_out_written);
if (limit_read_) {
// Read at most 1k bytes.
data_in_read = std::min(data_in_size, static_cast<size_t>(1024U));
} else {
// Read all available bytes.
data_in_read = data_in_size;
}
data_out_written = std::min(data_in_read, data_out_size);
memcpy(data_out, data_in, data_out_written);
return RESPONSE_FILTER_DONE;
}
std::string GetInput() override {
input_ = CreateInput("FOOBAR ", GetResponseBufferSize() * 2U + 1);
return input_;
}
void VerifyOutput(cef_urlrequest_status_t status,
int64_t received_content_length,
const std::string& received_content) override {
ResponseFilterTestBase::VerifyOutput(status, received_content_length,
received_content);
if (limit_read_) {
// Expected to read 2 full buffers of GetResponseBufferSize() at 1kb
// increments and one partial buffer.
EXPECT_EQ(2U * (GetResponseBufferSize() / 1024) + 1U, filter_count_);
} else {
// Expected to read 2 full buffers of GetResponseBufferSize() and one
// partial buffer.
EXPECT_EQ(3U, filter_count_);
}
EXPECT_STREQ(input_.c_str(), received_content.c_str());
// Input size and content size should match.
EXPECT_EQ(input_.size(), static_cast<size_t>(received_content_length));
EXPECT_EQ(input_.size(), received_content.size());
}
private:
std::string input_;
bool limit_read_;
};
const char kFindString[] = "REPLACE_THIS_STRING";
const char kReplaceString[] = "This is the replaced string!";
// Helper for passing params to Write().
#define WRITE_PARAMS data_out_ptr, data_out_size, data_out_written
// Replace all instances of |kFindString| with |kReplaceString|.
// This implementation is similar to the example in
// tests/shared/response_filter_test.cc.
class ResponseFilterNeedMore : public ResponseFilterTestBase {
public:
ResponseFilterNeedMore() = default;
FilterStatus Filter(void* data_in,
size_t data_in_size,
size_t& data_in_read,
void* data_out,
size_t data_out_size,
size_t& data_out_written) override {
ResponseFilterTestBase::Filter(data_in, data_in_size, data_in_read,
data_out, data_out_size, data_out_written);
// All data will be read.
data_in_read = data_in_size;
const size_t find_size = sizeof(kFindString) - 1;
const char* data_in_ptr = static_cast<char*>(data_in);
char* data_out_ptr = static_cast<char*>(data_out);
// Reset the overflow.
std::string old_overflow;
if (!overflow_.empty()) {
old_overflow = overflow_;
overflow_.clear();
}
const size_t likely_out_size =
data_in_size + replace_overflow_size_ + old_overflow.size();
if (data_out_size < likely_out_size) {
// We'll likely need to use the overflow buffer. Size it appropriately.
overflow_.reserve(likely_out_size - data_out_size);
}
if (!old_overflow.empty()) {
// Write the overflow from last time.
Write(old_overflow.c_str(), old_overflow.size(), WRITE_PARAMS);
}
// Evaluate each character in the input buffer. Track how many characters in
// a row match kFindString. If kFindString is completely matched then write
// kReplaceString. Otherwise, write the input characters as-is.
for (size_t i = 0U; i < data_in_size; ++i) {
if (data_in_ptr[i] == kFindString[find_match_offset_]) {
// Matched the next character in the find string.
if (++find_match_offset_ == find_size) {
// Complete match of the find string. Write the replace string.
Write(kReplaceString, sizeof(kReplaceString) - 1, WRITE_PARAMS);
// Start over looking for a match.
find_match_offset_ = 0;
}
continue;
}
// Character did not match the find string.
if (find_match_offset_ > 0) {
// Write the portion of the find string that has matched so far.
Write(kFindString, find_match_offset_, WRITE_PARAMS);
// Start over looking for a match.
find_match_offset_ = 0;
}
// Write the current character.
Write(&data_in_ptr[i], 1, WRITE_PARAMS);
}
// If a match is currently in-progress and input was provided then we need
// more data. Otherwise, we're done.
return find_match_offset_ > 0 && data_in_size > 0
? RESPONSE_FILTER_NEED_MORE_DATA
: RESPONSE_FILTER_DONE;
}
std::string GetInput() override {
const std::string& input =
CreateInput(std::string(kFindString) + " ",
GetResponseBufferSize() * 2U + 1, &repeat_ct_);
input_size_ = input.size();
const size_t find_size = sizeof(kFindString) - 1;
const size_t replace_size = sizeof(kReplaceString) - 1;
// Determine a reasonable amount of space for find/replace overflow.
if (replace_size > find_size) {
replace_overflow_size_ = (replace_size - find_size) * repeat_ct_;
}
return input;
}
void VerifyOutput(cef_urlrequest_status_t status,
int64_t received_content_length,
const std::string& received_content) override {
ResponseFilterTestBase::VerifyOutput(status, received_content_length,
received_content);
const std::string& output =
CreateOutput(std::string(kReplaceString) + " ", repeat_ct_);
EXPECT_STREQ(output.c_str(), received_content.c_str());
// Pre-filter content length should be the original input size.
EXPECT_EQ(input_size_, static_cast<size_t>(received_content_length));
// Filtered content length should be the output size.
EXPECT_EQ(output.size(), received_content.size());
// Expected to read 2 full buffers of GetResponseBufferSize() and one
// partial buffer, and then one additional call to drain the overflow.
EXPECT_EQ(4U, filter_count_);
}
private:
inline void Write(const char* str,
size_t str_size,
char*& data_out_ptr,
size_t data_out_size,
size_t& data_out_written) {
// Number of bytes remaining in the output buffer.
const size_t remaining_space = data_out_size - data_out_written;
// Maximum number of bytes we can write into the output buffer.
const size_t max_write = std::min(str_size, remaining_space);
// Write the maximum portion that fits in the output buffer.
if (max_write == 1) {
// Small optimization for single character writes.
*data_out_ptr = str[0];
data_out_ptr += 1;
data_out_written += 1;
} else if (max_write > 1) {
memcpy(data_out_ptr, str, max_write);
data_out_ptr += max_write;
data_out_written += max_write;
}
if (max_write < str_size) {
// Need to write more bytes than will fit in the output buffer. Store the
// remainder in the overflow buffer.
overflow_ += std::string(str + max_write, str_size - max_write);
}
}
// The portion of the find string that is currently matching.
size_t find_match_offset_ = 0U;
// The likely amount of overflow.
size_t replace_overflow_size_ = 0U;
// Overflow from the output buffer.
std::string overflow_;
// The original input size.
size_t input_size_ = 0U;
// The number of times the find string was repeated.
size_t repeat_ct_ = 0U;
};
// Return a filter error.
class ResponseFilterError : public ResponseFilterTestBase {
public:
ResponseFilterError() = default;
FilterStatus Filter(void* data_in,
size_t data_in_size,
size_t& data_in_read,
void* data_out,
size_t data_out_size,
size_t& data_out_written) override {
ResponseFilterTestBase::Filter(data_in, data_in_size, data_in_read,
data_out, data_out_size, data_out_written);
return RESPONSE_FILTER_ERROR;
}
std::string GetInput() override {
return kInputHeader + std::string("ERROR") + kInputFooter;
}
void VerifyOutput(cef_urlrequest_status_t status,
int64_t received_content_length,
const std::string& received_content) override {
ResponseFilterTestBase::VerifyOutput(status, received_content_length,
received_content);
EXPECT_EQ(UR_FAILED, status);
// Expect empty content.
EXPECT_STREQ("", received_content.c_str());
EXPECT_EQ(0U, received_content_length);
// Expect to only be called one time.
EXPECT_EQ(filter_count_, 1U);
}
void VerifyStatusCode(int httpStatusCode) const override {
EXPECT_EQ(ERR_CONTENT_DECODING_FAILED, httpStatusCode);
}
};
class ResponseFilterTestHandler : public TestHandler {
public:
explicit ResponseFilterTestHandler(
CefRefPtr<ResponseFilterTestBase> response_filter)
: response_filter_(response_filter) {}
void RunTest() override {
const std::string& resource = response_filter_->GetInput();
AddResource(kResponseFilterTestUrl, resource, "text/html");
// Create the browser.
CreateBrowser(kResponseFilterTestUrl);
// Time out the test after a reasonable period of time.
SetTestTimeout();
}
CefRefPtr<CefResponseFilter> GetResourceResponseFilter(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) override {
EXPECT_IO_THREAD();
EXPECT_FALSE(got_resource_response_filter_);
got_resource_response_filter_.yes();
return response_filter_;
}
void OnResourceLoadComplete(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response,
URLRequestStatus status,
int64_t received_content_length) override {
EXPECT_IO_THREAD();
if (IsChromeRuntimeEnabled() && request->GetResourceType() == RT_FAVICON) {
// Ignore favicon requests.
return;
}
EXPECT_FALSE(got_resource_load_complete_);
got_resource_load_complete_.yes();
status_ = status;
received_content_length_ = received_content_length;
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
EXPECT_FALSE(got_load_end_);
got_load_end_.yes();
response_filter_->VerifyStatusCode(httpStatusCode);
GetOutputContent(frame);
}
private:
// Retrieve the output content using a StringVisitor. This effectively
// serializes the DOM from the renderer process so any comparison to the
// filter output is somewhat error-prone.
void GetOutputContent(CefRefPtr<CefFrame> frame) {
class StringVisitor : public CefStringVisitor {
public:
using VisitorCallback =
base::OnceCallback<void(const std::string& /*received_content*/)>;
explicit StringVisitor(VisitorCallback callback)
: callback_(std::move(callback)) {}
void Visit(const CefString& string) override {
std::move(callback_).Run(string);
}
private:
VisitorCallback callback_;
IMPLEMENT_REFCOUNTING(StringVisitor);
};
frame->GetSource(new StringVisitor(
base::BindOnce(&ResponseFilterTestHandler::VerifyOutput, this)));
}
void VerifyOutput(const std::string& received_content) {
response_filter_->VerifyOutput(status_, received_content_length_,
received_content);
response_filter_ = nullptr;
DestroyTest();
}
void DestroyTest() override {
EXPECT_TRUE(got_resource_response_filter_);
EXPECT_TRUE(got_resource_load_complete_);
EXPECT_TRUE(got_load_end_);
TestHandler::DestroyTest();
}
CefRefPtr<ResponseFilterTestBase> response_filter_;
TrackCallback got_resource_response_filter_;
TrackCallback got_resource_load_complete_;
TrackCallback got_load_end_;
URLRequestStatus status_;
int64_t received_content_length_;
IMPLEMENT_REFCOUNTING(ResponseFilterTestHandler);
};
} // namespace
// Pass through contents unchanged. Read all available input.
TEST(ResourceRequestHandlerTest, FilterPassThruReadAll) {
CefRefPtr<ResponseFilterTestHandler> handler =
new ResponseFilterTestHandler(new ResponseFilterPassThru(false));
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Pass through contents unchanged. Read limited input.
TEST(ResourceRequestHandlerTest, FilterPassThruReadLimited) {
CefRefPtr<ResponseFilterTestHandler> handler =
new ResponseFilterTestHandler(new ResponseFilterPassThru(true));
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Find/replace contents such that we occasionally need more data.
TEST(ResourceRequestHandlerTest, FilterNeedMore) {
CefRefPtr<ResponseFilterTestHandler> handler =
new ResponseFilterTestHandler(new ResponseFilterNeedMore());
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Error during filtering.
TEST(ResourceRequestHandlerTest, FilterError) {
CefRefPtr<ResponseFilterTestHandler> handler =
new ResponseFilterTestHandler(new ResponseFilterError());
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Entry point for registering custom schemes.
// Called from client_app_delegates.cc.
void RegisterResourceRequestHandlerCustomSchemes(
CefRawPtr<CefSchemeRegistrar> registrar) {
// Add a custom standard scheme.
registrar->AddCustomScheme(
"rrhcustom", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CORS_ENABLED);
}