// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. #include #include #include #include #include #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 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 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 request, bool& handle_request, CefRefPtr 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 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 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 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( stream_->Read(static_cast(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 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 request, CefRefPtr 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 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 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 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 request, bool& handle_request, CefRefPtr 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 request, CefRefPtr callback) override { EXPECT_TRUE(false); // Not reached. return false; } void GetResponseHeaders(CefRefPtr 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 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 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 incomplete_open_callback_; CefRefPtr 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 browser) override { EXPECT_UI_THREAD(); TestHandler::OnAfterCreated(browser); if (mode_ == ABORT_AFTER_CREATED) { SetSignalTestCompletionCount(1U); CloseBrowser(browser, false); } } void OnBeforeClose(CefRefPtr browser) override { EXPECT_UI_THREAD(); TestHandler::OnBeforeClose(browser); if (IsAborted()) { DestroyTest(); } } bool OnBeforeBrowse(CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 (!use_alloy_style_browser()) { // 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 GetResourceRequestHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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()); const std::string& url = request->GetURL(); if (IgnoreURL(url)) { return nullptr; } 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 GetCookieAccessFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) override { EXPECT_IO_THREAD(); if (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 GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 GetResourceResponseFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response, URLRequestStatus status, int64_t received_content_length) override { EXPECT_IO_THREAD(); if (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(GetResponseBody().length()), received_content_length); } on_resource_load_complete_ct_++; if (IsIncomplete()) { MaybeDestroyTest(false); } } void OnProtocolExecution(CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr 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 "Response"; } std::string GetRedirectBody() const { return "Redirect"; } 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 GetResource(int status_code, const CefString& status_text, const CefString& mime_type, CefResponse::HeaderMap header_map, const std::string& body) { CefRefPtr stream = CefStreamReader::CreateForData( const_cast(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 GetOKResource() { return GetResource(200, "OK", "text/html", CefResponse::HeaderMap(), GetResponseBody()); } CefRefPtr 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 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 request) { EXPECT_FALSE(request->IsReadOnly()); request->SetHeaderByName("X-Custom-Header", "value", false); } static std::string GetCustomHeader(CefRefPtr 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 request, CefRefPtr 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 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 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 incomplete_callback_; DISALLOW_COPY_AND_ASSIGN(BasicResponseTest); IMPLEMENT_REFCOUNTING(BasicResponseTest); }; } // namespace #define BASIC_TEST(name, test_mode, custom, unhandled) \ TEST(ResourceRequestHandlerTest, Basic##name) { \ CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr 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 (!use_alloy_style_browser() && 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 GetResourceRequestHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 GetCookieAccessFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) override { EXPECT_IO_THREAD(); if (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 GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 GetResourceResponseFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response, URLRequestStatus status, int64_t received_content_length) override { EXPECT_IO_THREAD(); if (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(GetMainResponseBody().length()), received_content_length); return; } else if (IsSubURL(request->GetURL())) { EXPECT_FALSE(frame->IsMain()); EXPECT_EQ(UR_SUCCESS, status); EXPECT_EQ(static_cast(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(GetResponseBody().length()), received_content_length); } on_resource_load_complete_ct_++; if (IsIncomplete()) { MaybeDestroyTest(false); } } void OnProtocolExecution(CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr 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 browser, CefRefPtr frame, int64_t query_id, const CefString& request, bool persistent, CefRefPtr 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 << ""; if (subframe_) { const std::string& url = GetSubURL(); html << ""; } else { const std::string& url = GetStartupURL(); html << ""; } html << "

Main

"; return html.str(); } std::string GetSubResponseBody() const { EXPECT_TRUE(subframe_); std::stringstream html; html << ""; const std::string& url = GetStartupURL(); html << ""; html << "

Sub

"; return html.str(); } std::string GetResponseBody() const { return "window.testQuery({request:'" + std::string(kSubresourceProcessMsg) + "'});"; } std::string GetRedirectBody() const { return "Redirect"; } 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 GetResource(int status_code, const CefString& status_text, const CefString& mime_type, CefResponse::HeaderMap header_map, const std::string& body) { CefRefPtr stream = CefStreamReader::CreateForData( const_cast(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 GetMainResource() { return GetResource(200, "OK", "text/html", CefResponse::HeaderMap(), GetMainResponseBody()); } CefRefPtr GetSubResource() { return GetResource(200, "OK", "text/html", CefResponse::HeaderMap(), GetSubResponseBody()); } CefRefPtr GetOKResource() { return GetResource(200, "OK", "text/javascript", CefResponse::HeaderMap(), GetResponseBody()); } CefRefPtr 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 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 request) { EXPECT_FALSE(request->IsReadOnly()); request->SetHeaderByName("X-Custom-Header", "value", false); } static std::string GetCustomHeader(CefRefPtr 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 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 request, CefRefPtr 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 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 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 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 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(); } else if (mode == HEADER) { resource_test_ = std::make_unique(); } else { resource_test_ = std::make_unique(); } } void RunTest() override { AddResource(kResourceTestHtml, GetHtml(), "text/html"); resource_request_handler_ = new ResourceRequestHandler(this); CefRefPtr request_context = CefRequestContext::GetGlobalContext(); if (via_request_context_handler_) { CefRefPtr 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 GetResourceRequestHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr 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 << ""; const std::string& url = resource_test_->start_url(); html << ""; html << "

Main

"; 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 browser, CefRefPtr frame, CefRefPtr request) { before_resource_load_ct_++; return false; } virtual CefRefPtr GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) { get_resource_handler_ct_++; const std::string& js_content = ""; CefRefPtr stream = CefStreamReader::CreateForData( const_cast(js_content.c_str()), js_content.size()); return new CefStreamResourceHandler(200, "OK", "text/javascript", CefResponse::HeaderMap(), stream); } virtual void OnResourceRedirect(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefString& new_url) { resource_redirect_ct_++; } bool OnResourceResponse(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 GetResourceResponseFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) { get_resource_response_filter_ct_++; return nullptr; } void OnResourceLoadComplete(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) { got_resource_.yes(); } virtual void OnRetryReceived(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) override { ResourceTest::OnResourceReceived(browser, frame, request, response); request->SetURL(redirect_url_); } void OnRetryReceived(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) override { ResourceTest::OnResourceReceived(browser, frame, request, response); request->SetHeaderMap(expected_headers_); } void OnRetryReceived(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) override { ResourceTest::OnResourceReceived(browser, frame, request, response); request->SetPostData(expected_post_); } void OnRetryReceived(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) override { ResourceTest::OnRetryReceived(browser, frame, request, response); CefRefPtr actual_post = request->GetPostData(); TestPostDataEqual(expected_post_, actual_post); } CefRefPtr expected_post_; }; class RequestContextHandler : public CefRequestContextHandler { public: explicit RequestContextHandler( CefRefPtr resource_request_handler) : resource_request_handler_(resource_request_handler) {} CefRefPtr GetResourceRequestHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, bool is_navigation, bool is_download, const CefString& request_initiator, bool& disable_default_handling) override { return resource_request_handler_; } private: CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) override { EXPECT_IO_THREAD(); if (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 GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 GetResourceResponseFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr 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 browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response, URLRequestStatus status, int64_t received_content_length) override { EXPECT_IO_THREAD(); if (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 resource_test_; CefRefPtr resource_request_handler_; IMPLEMENT_REFCOUNTING(RedirectResponseTest); }; } // namespace // Verify redirect with client handler. TEST(ResourceRequestHandlerTest, RedirectURLViaClient) { CefRefPtr handler = new RedirectResponseTest(RedirectResponseTest::URL, false); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Verify redirect + modified headers with client handler. TEST(ResourceRequestHandlerTest, RedirectHeaderViaClient) { CefRefPtr handler = new RedirectResponseTest(RedirectResponseTest::HEADER, false); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Verify redirect + modified post data with client handler. TEST(ResourceRequestHandlerTest, RedirectPostViaClient) { CefRefPtr handler = new RedirectResponseTest(RedirectResponseTest::POST, false); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Verify redirect with context handler. TEST(ResourceRequestHandlerTest, RedirectURLViaContext) { CefRefPtr handler = new RedirectResponseTest(RedirectResponseTest::URL, true); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Verify redirect + modified headers with context handler. TEST(ResourceRequestHandlerTest, RedirectHeaderViaContext) { CefRefPtr handler = new RedirectResponseTest(RedirectResponseTest::HEADER, true); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Verify redirect + modified post data with context handler. TEST(ResourceRequestHandlerTest, RedirectPostViaContext) { CefRefPtr 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, "Test", "text/html"); AddResource(kResourceTestHtml2, "Test2", "text/html"); CreateBrowser(kResourceTestHtml); SetTestTimeout(); } cef_return_value_t OnBeforeResourceLoad( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) override { EXPECT_IO_THREAD(); if (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 browser, CefRefPtr 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 browser, CefRefPtr 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 handler = new BeforeResourceLoadTest(BeforeResourceLoadTest::CANCEL); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } TEST(ResourceRequestHandlerTest, BeforeResourceLoadCancelAsync) { CefRefPtr handler = new BeforeResourceLoadTest(BeforeResourceLoadTest::CANCEL_ASYNC); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } TEST(ResourceRequestHandlerTest, BeforeResourceLoadCancelNav) { CefRefPtr handler = new BeforeResourceLoadTest(BeforeResourceLoadTest::CANCEL_NAV); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } TEST(ResourceRequestHandlerTest, BeforeResourceLoadContinue) { CefRefPtr handler = new BeforeResourceLoadTest(BeforeResourceLoadTest::CONTINUE); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } TEST(ResourceRequestHandlerTest, BeforeResourceLoadContinueAsync) { CefRefPtr 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[] = ""; const char kInputFooter[] = ""; // 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(std::ceil(static_cast(desired_min_size) / static_cast(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(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(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(data_in); char* data_out_ptr = static_cast(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(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 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 GetResourceResponseFilter( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response) override { EXPECT_IO_THREAD(); EXPECT_FALSE(got_resource_response_filter_); got_resource_response_filter_.yes(); return response_filter_; } void OnResourceLoadComplete(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response, URLRequestStatus status, int64_t received_content_length) override { EXPECT_IO_THREAD(); if (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 browser, CefRefPtr 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 frame) { class StringVisitor : public CefStringVisitor { public: using VisitorCallback = base::OnceCallback; 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 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 handler = new ResponseFilterTestHandler(new ResponseFilterPassThru(false)); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Pass through contents unchanged. Read limited input. TEST(ResourceRequestHandlerTest, FilterPassThruReadLimited) { CefRefPtr handler = new ResponseFilterTestHandler(new ResponseFilterPassThru(true)); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Find/replace contents such that we occasionally need more data. TEST(ResourceRequestHandlerTest, FilterNeedMore) { CefRefPtr handler = new ResponseFilterTestHandler(new ResponseFilterNeedMore()); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Error during filtering. TEST(ResourceRequestHandlerTest, FilterError) { CefRefPtr handler = new ResponseFilterTestHandler(new ResponseFilterError()); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Entry point for registering custom schemes. // Called from client_app_delegates.cc. void RegisterResourceRequestHandlerCustomSchemes( CefRawPtr registrar) { // Add a custom standard scheme. registrar->AddCustomScheme( "rrhcustom", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CORS_ENABLED); }