diff --git a/include/capi/cef_resource_handler_capi.h b/include/capi/cef_resource_handler_capi.h index eb31980f9..5db8ca35e 100644 --- a/include/capi/cef_resource_handler_capi.h +++ b/include/capi/cef_resource_handler_capi.h @@ -78,7 +78,8 @@ typedef struct _cef_resource_handler_t { // (0) or the specified number of bytes have been read. Use the |response| // object to set the mime type, http status code and other optional header // values. To redirect the request to a new URL set |redirectUrl| to the new - // URL. + // URL. If an error occured while setting up the request you can call + // set_error() on |response| to indicate the error condition. /// void (CEF_CALLBACK *get_response_headers)( struct _cef_resource_handler_t* self, struct _cef_response_t* response, diff --git a/include/capi/cef_response_capi.h b/include/capi/cef_response_capi.h index e6143c842..0c25d7f87 100644 --- a/include/capi/cef_response_capi.h +++ b/include/capi/cef_response_capi.h @@ -60,6 +60,18 @@ typedef struct _cef_response_t { /// int (CEF_CALLBACK *is_read_only)(struct _cef_response_t* self); + /// + // Get the response error code. Returns ERR_NONE if there was no error. + /// + cef_errorcode_t (CEF_CALLBACK *get_error)(struct _cef_response_t* self); + + /// + // Set the response error code. This can be used by custom scheme handlers to + // return errors during initial request processing. + /// + void (CEF_CALLBACK *set_error)(struct _cef_response_t* self, + cef_errorcode_t error); + /// // Get the response status code. /// diff --git a/include/cef_resource_handler.h b/include/cef_resource_handler.h index 57c8b7fc0..19913c756 100644 --- a/include/cef_resource_handler.h +++ b/include/cef_resource_handler.h @@ -71,7 +71,8 @@ class CefResourceHandler : public virtual CefBase { // false or the specified number of bytes have been read. Use the |response| // object to set the mime type, http status code and other optional header // values. To redirect the request to a new URL set |redirectUrl| to the new - // URL. + // URL. If an error occured while setting up the request you can call + // SetError() on |response| to indicate the error condition. /// /*--cef()--*/ virtual void GetResponseHeaders(CefRefPtr response, diff --git a/include/cef_response.h b/include/cef_response.h index 32fbef1b0..6fcb4e236 100644 --- a/include/cef_response.h +++ b/include/cef_response.h @@ -62,6 +62,19 @@ class CefResponse : public virtual CefBase { /*--cef()--*/ virtual bool IsReadOnly() =0; + /// + // Get the response error code. Returns ERR_NONE if there was no error. + /// + /*--cef(default_retval=ERR_NONE)--*/ + virtual cef_errorcode_t GetError() = 0; + + /// + // Set the response error code. This can be used by custom scheme handlers + // to return errors during initial request processing. + /// + /*--cef()--*/ + virtual void SetError(cef_errorcode_t error) = 0; + /// // Get the response status code. /// diff --git a/libcef/browser/net/resource_request_job.cc b/libcef/browser/net/resource_request_job.cc index 3fbfabebe..b9f8f3d32 100644 --- a/libcef/browser/net/resource_request_job.cc +++ b/libcef/browser/net/resource_request_job.cc @@ -366,6 +366,17 @@ void CefResourceRequestJob::SendHeaders() { // Get header information from the handler. handler_->GetResponseHeaders(response_, remaining_bytes_, redirectUrl); receive_headers_end_ = base::TimeTicks::Now(); + + if (response_->GetError() != ERR_NONE) { + const URLRequestStatus& status = + URLRequestStatus::FromError(response_->GetError()); + if (status.status() == URLRequestStatus::CANCELED || + status.status() == URLRequestStatus::FAILED) { + NotifyStartError(status); + return; + } + } + if (!redirectUrl.empty()) { std::string redirectUrlStr = redirectUrl; redirect_url_ = GURL(redirectUrlStr); diff --git a/libcef/common/response_impl.cc b/libcef/common/response_impl.cc index 74f6f34cd..56ff9555a 100644 --- a/libcef/common/response_impl.cc +++ b/libcef/common/response_impl.cc @@ -35,7 +35,8 @@ CefRefPtr CefResponse::Create() { // CefResponseImpl ------------------------------------------------------------ CefResponseImpl::CefResponseImpl() - : status_code_(0), + : error_code_(ERR_NONE), + status_code_(0), read_only_(false) { } @@ -44,6 +45,17 @@ bool CefResponseImpl::IsReadOnly() { return read_only_; } +cef_errorcode_t CefResponseImpl::GetError() { + base::AutoLock lock_scope(lock_); + return error_code_; +} + +void CefResponseImpl::SetError(cef_errorcode_t error) { + base::AutoLock lock_scope(lock_); + CHECK_READONLY_RETURN_VOID(); + error_code_ = error; +} + int CefResponseImpl::GetStatus() { base::AutoLock lock_scope(lock_); return status_code_; diff --git a/libcef/common/response_impl.h b/libcef/common/response_impl.h index e8bc0c1ff..d211be8fd 100644 --- a/libcef/common/response_impl.h +++ b/libcef/common/response_impl.h @@ -26,6 +26,8 @@ class CefResponseImpl : public CefResponse { // CefResponse methods. bool IsReadOnly() override; + cef_errorcode_t GetError() override; + void SetError(cef_errorcode_t error) override; int GetStatus() override; void SetStatus(int status) override; CefString GetStatusText() override; @@ -45,6 +47,7 @@ class CefResponseImpl : public CefResponse { void SetReadOnly(bool read_only); protected: + cef_errorcode_t error_code_; int status_code_; CefString status_text_; CefString mime_type_; diff --git a/libcef_dll/cpptoc/response_cpptoc.cc b/libcef_dll/cpptoc/response_cpptoc.cc index d21c7628f..e93cd4560 100644 --- a/libcef_dll/cpptoc/response_cpptoc.cc +++ b/libcef_dll/cpptoc/response_cpptoc.cc @@ -45,6 +45,33 @@ int CEF_CALLBACK response_is_read_only(struct _cef_response_t* self) { return _retval; } +cef_errorcode_t CEF_CALLBACK response_get_error(struct _cef_response_t* self) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return ERR_NONE; + + // Execute + cef_errorcode_t _retval = CefResponseCppToC::Get(self)->GetError(); + + // Return type: simple + return _retval; +} + +void CEF_CALLBACK response_set_error(struct _cef_response_t* self, + cef_errorcode_t error) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + + // Execute + CefResponseCppToC::Get(self)->SetError( + error); +} + int CEF_CALLBACK response_get_status(struct _cef_response_t* self) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING @@ -209,6 +236,8 @@ void CEF_CALLBACK response_set_header_map(struct _cef_response_t* self, CefResponseCppToC::CefResponseCppToC() { GetStruct()->is_read_only = response_is_read_only; + GetStruct()->get_error = response_get_error; + GetStruct()->set_error = response_set_error; GetStruct()->get_status = response_get_status; GetStruct()->set_status = response_set_status; GetStruct()->get_status_text = response_get_status_text; diff --git a/libcef_dll/ctocpp/response_ctocpp.cc b/libcef_dll/ctocpp/response_ctocpp.cc index 3fd299fee..1945bb7da 100644 --- a/libcef_dll/ctocpp/response_ctocpp.cc +++ b/libcef_dll/ctocpp/response_ctocpp.cc @@ -43,6 +43,32 @@ bool CefResponseCToCpp::IsReadOnly() { return _retval?true:false; } +cef_errorcode_t CefResponseCToCpp::GetError() { + cef_response_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, get_error)) + return ERR_NONE; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + cef_errorcode_t _retval = _struct->get_error(_struct); + + // Return type: simple + return _retval; +} + +void CefResponseCToCpp::SetError(cef_errorcode_t error) { + cef_response_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, set_error)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + _struct->set_error(_struct, + error); +} + int CefResponseCToCpp::GetStatus() { cef_response_t* _struct = GetStruct(); if (CEF_MEMBER_MISSING(_struct, get_status)) diff --git a/libcef_dll/ctocpp/response_ctocpp.h b/libcef_dll/ctocpp/response_ctocpp.h index e2f2eeb75..6611007e9 100644 --- a/libcef_dll/ctocpp/response_ctocpp.h +++ b/libcef_dll/ctocpp/response_ctocpp.h @@ -31,6 +31,8 @@ class CefResponseCToCpp // CefResponse methods. bool IsReadOnly() OVERRIDE; + cef_errorcode_t GetError() OVERRIDE; + void SetError(cef_errorcode_t error) OVERRIDE; int GetStatus() OVERRIDE; void SetStatus(int status) OVERRIDE; CefString GetStatusText() OVERRIDE; diff --git a/tests/unittests/scheme_handler_unittest.cc b/tests/unittests/scheme_handler_unittest.cc index 7d321e2c0..38fa952e0 100644 --- a/tests/unittests/scheme_handler_unittest.cc +++ b/tests/unittests/scheme_handler_unittest.cc @@ -26,6 +26,8 @@ class TestResults { url.clear(); html.clear(); status_code = 0; + response_error_code = ERR_NONE; + expected_error_code = ERR_NONE; redirect_url.clear(); sub_url.clear(); sub_html.clear(); @@ -48,6 +50,11 @@ class TestResults { std::string html; int status_code; + // Error code set on the response. + cef_errorcode_t response_error_code; + // Error code expected in OnLoadError. + cef_errorcode_t expected_error_code; + // Used for testing redirects std::string redirect_url; @@ -165,6 +172,8 @@ class TestSchemeHandler : public TestHandler { const CefString& errorText, const CefString& failedUrl) override { test_results_->got_error.yes(); + // Check that the error code matches the expectation. + EXPECT_EQ(errorCode, test_results_->expected_error_code); DestroyTest(); } @@ -239,6 +248,10 @@ class ClientSchemeHandler : public CefResourceHandler { callback->Continue(); } return true; + } else if (test_results_->response_error_code != ERR_NONE) { + // Propagate the error code. + callback->Continue(); + return true; } // Response was canceled. @@ -268,6 +281,8 @@ class ClientSchemeHandler : public CefResourceHandler { } } else if (!test_results_->redirect_url.empty()) { redirectUrl = test_results_->redirect_url; + } else if (test_results_->response_error_code != ERR_NONE) { + response->SetError(test_results_->response_error_code); } else { response->SetStatus(test_results_->status_code); @@ -527,6 +542,7 @@ TEST(SchemeHandlerTest, Registration) { g_TestResults.got_request.reset(); g_TestResults.got_read.reset(); g_TestResults.got_output.reset(); + g_TestResults.expected_error_code = ERR_UNKNOWN_URL_SCHEME; handler->ExecuteTest(); EXPECT_TRUE(g_TestResults.got_error); @@ -540,10 +556,12 @@ TEST(SchemeHandlerTest, Registration) { WaitForIOThread(); g_TestResults.got_error.reset(); + g_TestResults.expected_error_code = ERR_NONE; handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_FALSE(g_TestResults.got_error); EXPECT_TRUE(g_TestResults.got_request); EXPECT_TRUE(g_TestResults.got_read); EXPECT_TRUE(g_TestResults.got_output); @@ -629,6 +647,25 @@ TEST(SchemeHandlerTest, CustomStandardErrorResponse) { ClearTestSchemes(); } +// Test that a custom standard scheme can return a CEF error code in the response. +TEST(SchemeHandlerTest, CustomStandardErrorCodeResponse) { + RegisterTestScheme("customstd", "test"); + g_TestResults.url = "customstd://test/run.html"; + g_TestResults.response_error_code = ERR_FILE_TOO_BIG; + g_TestResults.expected_error_code = ERR_FILE_TOO_BIG; + + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(g_TestResults.got_request); + EXPECT_FALSE(g_TestResults.got_read); + EXPECT_FALSE(g_TestResults.got_output); + EXPECT_TRUE(g_TestResults.got_error); + + ClearTestSchemes(); +} + // Test that a custom nonstandard scheme can return an error code. TEST(SchemeHandlerTest, CustomNonStandardErrorResponse) { RegisterTestScheme("customnonstd", std::string()); @@ -653,6 +690,7 @@ TEST(SchemeHandlerTest, CustomNonStandardErrorResponse) { TEST(SchemeHandlerTest, CustomStandardNameNotHandled) { RegisterTestScheme("customstd", "test"); g_TestResults.url = "customstd2://test/run.html"; + g_TestResults.expected_error_code = ERR_UNKNOWN_URL_SCHEME; CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); @@ -661,6 +699,7 @@ TEST(SchemeHandlerTest, CustomStandardNameNotHandled) { EXPECT_FALSE(g_TestResults.got_request); EXPECT_FALSE(g_TestResults.got_read); EXPECT_FALSE(g_TestResults.got_output); + EXPECT_TRUE(g_TestResults.got_error); ClearTestSchemes(); } @@ -670,6 +709,7 @@ TEST(SchemeHandlerTest, CustomStandardNameNotHandled) { TEST(SchemeHandlerTest, CustomNonStandardNameNotHandled) { RegisterTestScheme("customnonstd", std::string()); g_TestResults.url = "customnonstd2:some%20value"; + g_TestResults.expected_error_code = ERR_UNKNOWN_URL_SCHEME; CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); @@ -678,6 +718,7 @@ TEST(SchemeHandlerTest, CustomNonStandardNameNotHandled) { EXPECT_FALSE(g_TestResults.got_request); EXPECT_FALSE(g_TestResults.got_read); EXPECT_FALSE(g_TestResults.got_output); + EXPECT_TRUE(g_TestResults.got_error); ClearTestSchemes(); } @@ -687,6 +728,7 @@ TEST(SchemeHandlerTest, CustomNonStandardNameNotHandled) { TEST(SchemeHandlerTest, CustomStandardDomainNotHandled) { RegisterTestScheme("customstd", "test"); g_TestResults.url = "customstd://noexist/run.html"; + g_TestResults.expected_error_code = ERR_FAILED; CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); @@ -695,6 +737,7 @@ TEST(SchemeHandlerTest, CustomStandardDomainNotHandled) { EXPECT_FALSE(g_TestResults.got_request); EXPECT_FALSE(g_TestResults.got_read); EXPECT_FALSE(g_TestResults.got_output); + EXPECT_TRUE(g_TestResults.got_error); ClearTestSchemes(); }