Fix issues with request callbacks during browser shutdown (see issue #2622).

The behavior has changed as follows with NetworkService enabled:
- All pending and in-progress requests will now be aborted when the CEF context
  or associated browser is destroyed. The OnResourceLoadComplete callback will
  now also be called in this case for in-progress requests that have a handler.
- The CefResourceHandler::Cancel method will now always be called when resource
  handling is complete, irrespective of whether handling completed successfully.
- Request callbacks that arrive after the OnBeforeClose callback for the
  associated browser (which may happen for in-progress requests that are aborted
  on browser destruction) will now always have a non-nullptr CefBrowser
  parameter.
- Allow empty parameters to CefRequest and CefResponse methods where it makes
  sense (e.g. resetting default response state, or clearing a referrer value).
- Fixed a reference loop that was keeping CefResourceHandler objects from being
  destroyed if they were holding a callback reference (from ProcessRequest,
  ReadResponse, etc.) during CEF context or associated browser destruction.
- Fixed an issue where the main frame was not detached on browser destruction
  which could cause a crash due to RFH use-after-free (see issue #2498).

To test: All unit tests pass as expected.
This commit is contained in:
Marshall Greenblatt
2019-05-31 12:50:12 +03:00
parent fd80e5c653
commit fa5268fa2d
21 changed files with 1458 additions and 369 deletions

View File

@ -33,7 +33,7 @@
// by hand. See the translator.README.txt file in the tools directory for // by hand. See the translator.README.txt file in the tools directory for
// more information. // more information.
// //
// $hash=9756fec933d13ecb320b0ec8c3bd8c9fcddfef47$ // $hash=85d9f30e93e1c3759213074cc5876f848cf4b012$
// //
#ifndef CEF_INCLUDE_CAPI_CEF_LIFE_SPAN_HANDLER_CAPI_H_ #ifndef CEF_INCLUDE_CAPI_CEF_LIFE_SPAN_HANDLER_CAPI_H_
@ -202,9 +202,13 @@ typedef struct _cef_life_span_handler_t {
/// ///
// Called just before a browser is destroyed. Release all references to the // Called just before a browser is destroyed. Release all references to the
// browser object and do not attempt to execute any functions on the browser // browser object and do not attempt to execute any functions on the browser
// object after this callback returns. This callback will be the last // object (other than GetIdentifier or IsSame) after this callback returns.
// notification that references |browser|. See do_close() documentation for // This callback will be the last notification that references |browser| on
// additional usage information. // the UI thread. Any in-progress network requests associated with |browser|
// will be aborted when the browser is destroyed, and
// cef_resource_request_handler_t callbacks related to those requests may
// still arrive on the IO thread after this function is called. See do_close()
// documentation for additional usage information.
/// ///
void(CEF_CALLBACK* on_before_close)(struct _cef_life_span_handler_t* self, void(CEF_CALLBACK* on_before_close)(struct _cef_life_span_handler_t* self,
struct _cef_browser_t* browser); struct _cef_browser_t* browser);

View File

@ -33,7 +33,7 @@
// by hand. See the translator.README.txt file in the tools directory for // by hand. See the translator.README.txt file in the tools directory for
// more information. // more information.
// //
// $hash=8d92ad3e0836dffa44a4b35a6b277e963af8144c$ // $hash=af8ddd4d2d19e5b64d0a40778cb3c62fd5f1d8c9$
// //
#ifndef CEF_INCLUDE_CAPI_CEF_RESOURCE_REQUEST_HANDLER_CAPI_H_ #ifndef CEF_INCLUDE_CAPI_CEF_RESOURCE_REQUEST_HANDLER_CAPI_H_
@ -172,7 +172,14 @@ typedef struct _cef_resource_request_handler_t {
// and |response| represent the request and response respectively and cannot // and |response| represent the request and response respectively and cannot
// be modified in this callback. |status| indicates the load completion // be modified in this callback. |status| indicates the load completion
// status. |received_content_length| is the number of response bytes actually // status. |received_content_length| is the number of response bytes actually
// read. // read. This function will be called for all requests, including requests
// that are aborted due to CEF shutdown or destruction of the associated
// browser. In cases where the associated browser is destroyed this callback
// may arrive after the cef_life_span_handler_t::OnBeforeClose callback for
// that browser. The cef_frame_t::IsValid function can be used to test for
// this situation, and care should be taken not to call |browser| or |frame|
// functions that modify state (like LoadURL, SendProcessMessage, etc.) if the
// frame is invalid.
/// ///
void(CEF_CALLBACK* on_resource_load_complete)( void(CEF_CALLBACK* on_resource_load_complete)(
struct _cef_resource_request_handler_t* self, struct _cef_resource_request_handler_t* self,

View File

@ -195,9 +195,13 @@ class CefLifeSpanHandler : public virtual CefBaseRefCounted {
/// ///
// Called just before a browser is destroyed. Release all references to the // Called just before a browser is destroyed. Release all references to the
// browser object and do not attempt to execute any methods on the browser // browser object and do not attempt to execute any methods on the browser
// object after this callback returns. This callback will be the last // object (other than GetIdentifier or IsSame) after this callback returns.
// notification that references |browser|. See DoClose() documentation for // This callback will be the last notification that references |browser| on
// additional usage information. // the UI thread. Any in-progress network requests associated with |browser|
// will be aborted when the browser is destroyed, and
// CefResourceRequestHandler callbacks related to those requests may still
// arrive on the IO thread after this method is called. See DoClose()
// documentation for additional usage information.
/// ///
/*--cef()--*/ /*--cef()--*/
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) {} virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) {}

View File

@ -99,7 +99,7 @@ class CefRequest : public virtual CefBaseRefCounted {
// fully qualified with an HTTP or HTTPS scheme component. Any username, // fully qualified with an HTTP or HTTPS scheme component. Any username,
// password or ref component will be removed. // password or ref component will be removed.
/// ///
/*--cef()--*/ /*--cef(optional_param=referrer_url)--*/
virtual void SetReferrer(const CefString& referrer_url, virtual void SetReferrer(const CefString& referrer_url,
ReferrerPolicy policy) = 0; ReferrerPolicy policy) = 0;
@ -154,7 +154,7 @@ class CefRequest : public virtual CefBaseRefCounted {
// existing values will not be overwritten. The Referer value cannot be set // existing values will not be overwritten. The Referer value cannot be set
// using this method. // using this method.
/// ///
/*--cef()--*/ /*--cef(optional_param=value)--*/
virtual void SetHeaderByName(const CefString& name, virtual void SetHeaderByName(const CefString& name,
const CefString& value, const CefString& value,
bool overwrite) = 0; bool overwrite) = 0;
@ -193,7 +193,7 @@ class CefRequest : public virtual CefBaseRefCounted {
// Set the URL to the first party for cookies used in combination with // Set the URL to the first party for cookies used in combination with
// CefURLRequest. // CefURLRequest.
/// ///
/*--cef()--*/ /*--cef(optional_param=url)--*/
virtual void SetFirstPartyForCookies(const CefString& url) = 0; virtual void SetFirstPartyForCookies(const CefString& url) = 0;
/// ///

View File

@ -174,6 +174,13 @@ class CefResourceRequestHandler : public virtual CefBaseRefCounted {
// |response| represent the request and response respectively and cannot be // |response| represent the request and response respectively and cannot be
// modified in this callback. |status| indicates the load completion status. // modified in this callback. |status| indicates the load completion status.
// |received_content_length| is the number of response bytes actually read. // |received_content_length| is the number of response bytes actually read.
// This method will be called for all requests, including requests that are
// aborted due to CEF shutdown or destruction of the associated browser. In
// cases where the associated browser is destroyed this callback may arrive
// after the CefLifeSpanHandler::OnBeforeClose callback for that browser. The
// CefFrame::IsValid method can be used to test for this situation, and care
// should be taken not to call |browser| or |frame| methods that modify state
// (like LoadURL, SendProcessMessage, etc.) if the frame is invalid.
/// ///
/*--cef(optional_param=browser,optional_param=frame)--*/ /*--cef(optional_param=browser,optional_param=frame)--*/
virtual void OnResourceLoadComplete(CefRefPtr<CefBrowser> browser, virtual void OnResourceLoadComplete(CefRefPtr<CefBrowser> browser,

View File

@ -96,7 +96,7 @@ class CefResponse : public virtual CefBaseRefCounted {
/// ///
// Set the response status text. // Set the response status text.
/// ///
/*--cef()--*/ /*--cef(optional_param=statusText)--*/
virtual void SetStatusText(const CefString& statusText) = 0; virtual void SetStatusText(const CefString& statusText) = 0;
/// ///
@ -108,7 +108,7 @@ class CefResponse : public virtual CefBaseRefCounted {
/// ///
// Set the response mime type. // Set the response mime type.
/// ///
/*--cef()--*/ /*--cef(optional_param=mimeType)--*/
virtual void SetMimeType(const CefString& mimeType) = 0; virtual void SetMimeType(const CefString& mimeType) = 0;
/// ///
@ -120,7 +120,7 @@ class CefResponse : public virtual CefBaseRefCounted {
/// ///
// Set the response charset. // Set the response charset.
/// ///
/*--cef()--*/ /*--cef(optional_param=charset)--*/
virtual void SetCharset(const CefString& charset) = 0; virtual void SetCharset(const CefString& charset) = 0;
/// ///
@ -150,7 +150,7 @@ class CefResponse : public virtual CefBaseRefCounted {
/// ///
// Set the resolved URL after redirects or changed as a result of HSTS. // Set the resolved URL after redirects or changed as a result of HSTS.
/// ///
/*--cef()--*/ /*--cef(optional_param=url)--*/
virtual void SetURL(const CefString& url) = 0; virtual void SetURL(const CefString& url) = 0;
}; };

View File

@ -496,6 +496,7 @@ class CefBrowserHostImpl : public CefBrowserHost,
void AddObserver(Observer* observer); void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer); void RemoveObserver(Observer* observer);
bool HasObserver(Observer* observer) const; bool HasObserver(Observer* observer) const;
bool StartAudioMirroring(); bool StartAudioMirroring();
bool StopAudioMirroring(); bool StopAudioMirroring();

View File

@ -353,6 +353,17 @@ void CefBrowserInfo::RemoveAllFrames() {
frame_id_map_.clear(); frame_id_map_.clear();
frame_tree_node_id_map_.clear(); frame_tree_node_id_map_.clear();
// Explicitly Detach main frames.
for (auto& info : frame_info_set_) {
if (info->frame_ && info->is_main_frame_)
info->frame_->Detach();
}
if (main_frame_) {
main_frame_->Detach();
main_frame_ = nullptr;
}
// And finally delete the frame info. // And finally delete the frame info.
frame_info_set_.clear(); frame_info_set_.clear();
} }

View File

@ -565,6 +565,21 @@ bool CefContext::ValidateCachePath(const base::FilePath& cache_path) {
return true; return true;
} }
void CefContext::AddObserver(Observer* observer) {
CEF_REQUIRE_UIT();
observers_.AddObserver(observer);
}
void CefContext::RemoveObserver(Observer* observer) {
CEF_REQUIRE_UIT();
observers_.RemoveObserver(observer);
}
bool CefContext::HasObserver(Observer* observer) const {
CEF_REQUIRE_UIT();
return observers_.HasObserver(observer);
}
void CefContext::OnContextInitialized() { void CefContext::OnContextInitialized() {
CEF_REQUIRE_UIT(); CEF_REQUIRE_UIT();
@ -591,6 +606,9 @@ void CefContext::FinishShutdownOnUIThread(
browser_info_manager_->DestroyAllBrowsers(); browser_info_manager_->DestroyAllBrowsers();
for (auto& observer : observers_)
observer.OnContextDestroyed();
if (trace_subscriber_.get()) if (trace_subscriber_.get())
trace_subscriber_.reset(NULL); trace_subscriber_.reset(NULL);

View File

@ -12,6 +12,7 @@
#include "include/cef_app.h" #include "include/cef_app.h"
#include "base/observer_list.h"
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
#include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkColor.h"
@ -36,6 +37,17 @@ class CefContext {
public: public:
typedef std::list<CefRefPtr<CefBrowserHostImpl>> BrowserList; typedef std::list<CefRefPtr<CefBrowserHostImpl>> BrowserList;
// Interface to implement for observers that wish to be informed of changes
// to the context. All methods will be called on the UI thread.
class Observer {
public:
// Called before the context is destroyed.
virtual void OnContextDestroyed() = 0;
protected:
virtual ~Observer() {}
};
CefContext(); CefContext();
~CefContext(); ~CefContext();
@ -80,6 +92,13 @@ class CefContext {
// Verify that |cache_path| is valid and create it if necessary. // Verify that |cache_path| is valid and create it if necessary.
bool ValidateCachePath(const base::FilePath& cache_path); bool ValidateCachePath(const base::FilePath& cache_path);
// Manage observer objects. The observer must either outlive this object or
// remove itself before destruction. These methods can only be called on the
// UI thread.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
bool HasObserver(Observer* observer) const;
private: private:
void OnContextInitialized(); void OnContextInitialized();
@ -104,6 +123,9 @@ class CefContext {
std::unique_ptr<service_manager::MainParams> sm_main_params_; std::unique_ptr<service_manager::MainParams> sm_main_params_;
std::unique_ptr<CefTraceSubscriber> trace_subscriber_; std::unique_ptr<CefTraceSubscriber> trace_subscriber_;
std::unique_ptr<CefBrowserInfoManager> browser_info_manager_; std::unique_ptr<CefBrowserInfoManager> browser_info_manager_;
// Observers that want to be notified of changes to this object.
base::ObserverList<Observer>::Unchecked observers_;
}; };
// Helper macro that returns true if the global context is in a valid state. // Helper macro that returns true if the global context is in a valid state.

View File

@ -113,15 +113,21 @@ class CefBrowserURLRequest::Context
request_context_impl->GetBrowserContext(); request_context_impl->GetBrowserContext();
DCHECK(browser_context); DCHECK(browser_context);
content::RenderFrameHost* rfh = nullptr; scoped_refptr<net_service::URLLoaderFactoryGetter> loader_factory_getter;
if (frame) { if (frame) {
// The request will be associated with this frame/browser. // The request will be associated with this frame/browser if it's valid,
rfh = static_cast<CefFrameHostImpl*>(frame.get())->GetRenderFrameHost(); // otherwise the request will be canceled.
content::RenderFrameHost* rfh =
static_cast<CefFrameHostImpl*>(frame.get())->GetRenderFrameHost();
if (rfh) {
loader_factory_getter =
net_service::URLLoaderFactoryGetter::Create(rfh, browser_context);
}
} else {
loader_factory_getter =
net_service::URLLoaderFactoryGetter::Create(nullptr, browser_context);
} }
auto loader_factory_getter =
net_service::URLLoaderFactoryGetter::Create(rfh, browser_context);
task_runner->PostTask( task_runner->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce( base::BindOnce(
@ -138,6 +144,12 @@ class CefBrowserURLRequest::Context
if (!url_request_) if (!url_request_)
return; return;
if (!loader_factory_getter) {
// Cancel the request immediately.
Cancel();
return;
}
DCHECK_EQ(status_, UR_IO_PENDING); DCHECK_EQ(status_, UR_IO_PENDING);
loader_factory_getter_ = loader_factory_getter; loader_factory_getter_ = loader_factory_getter;

View File

@ -24,7 +24,9 @@ class SkipCallbackWrapper : public CefResourceSkipCallback {
~SkipCallbackWrapper() override { ~SkipCallbackWrapper() override {
if (!callback_.is_null()) { if (!callback_.is_null()) {
std::move(callback_).Run(net::ERR_FAILED); // Make sure it executes on the correct thread.
work_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), net::ERR_FAILED));
} }
} }
@ -32,7 +34,7 @@ class SkipCallbackWrapper : public CefResourceSkipCallback {
if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) { if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) {
work_thread_task_runner_->PostTask( work_thread_task_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::Bind(&SkipCallbackWrapper::Continue, this, bytes_skipped)); base::BindOnce(&SkipCallbackWrapper::Continue, this, bytes_skipped));
return; return;
} }
if (!callback_.is_null()) { if (!callback_.is_null()) {
@ -59,7 +61,9 @@ class ReadCallbackWrapper : public CefResourceReadCallback {
~ReadCallbackWrapper() override { ~ReadCallbackWrapper() override {
if (!callback_.is_null()) { if (!callback_.is_null()) {
std::move(callback_).Run(net::ERR_FAILED); // Make sure it executes on the correct thread.
work_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), net::ERR_FAILED));
} }
} }
@ -67,7 +71,7 @@ class ReadCallbackWrapper : public CefResourceReadCallback {
if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) { if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) {
work_thread_task_runner_->PostTask( work_thread_task_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::Bind(&ReadCallbackWrapper::Continue, this, bytes_read)); base::BindOnce(&ReadCallbackWrapper::Continue, this, bytes_read));
return; return;
} }
if (!callback_.is_null()) { if (!callback_.is_null()) {
@ -86,47 +90,89 @@ class ReadCallbackWrapper : public CefResourceReadCallback {
DISALLOW_COPY_AND_ASSIGN(ReadCallbackWrapper); DISALLOW_COPY_AND_ASSIGN(ReadCallbackWrapper);
}; };
// Helper for accessing a CefResourceHandler without creating reference loops.
class HandlerProvider : public base::RefCountedThreadSafe<HandlerProvider> {
public:
explicit HandlerProvider(CefRefPtr<CefResourceHandler> handler)
: handler_(handler) {
DCHECK(handler_);
}
virtual ~HandlerProvider() {
// Detach should have been called.
DCHECK(!handler_);
}
CefRefPtr<CefResourceHandler> handler() const {
base::AutoLock lock_scope(lock_);
return handler_;
}
void Detach() {
base::AutoLock lock_scope(lock_);
if (!handler_)
return;
// Execute on the expected thread.
CEF_POST_TASK(CEF_IOT,
base::BindOnce(&CefResourceHandler::Cancel, handler_));
handler_ = nullptr;
}
private:
mutable base::Lock lock_;
CefRefPtr<CefResourceHandler> handler_;
};
class ReadResponseCallbackWrapper : public CefCallback { class ReadResponseCallbackWrapper : public CefCallback {
public: public:
~ReadResponseCallbackWrapper() override { ~ReadResponseCallbackWrapper() override {
if (callback_) { if (callback_) {
// This will post to the correct thread if necessary.
callback_->Continue(net::ERR_FAILED); callback_->Continue(net::ERR_FAILED);
} }
} }
void Continue() override { void Continue() override {
CEF_POST_TASK(CEF_IOT, CEF_POST_TASK(CEF_IOT,
base::Bind(&ReadResponseCallbackWrapper::DoRead, this)); base::BindOnce(&ReadResponseCallbackWrapper::DoRead, this));
} }
void Cancel() override { void Cancel() override {
CEF_POST_TASK(CEF_IOT, CEF_POST_TASK(CEF_IOT,
base::Bind(&ReadResponseCallbackWrapper::DoCancel, this)); base::BindOnce(&ReadResponseCallbackWrapper::DoCancel, this));
} }
static void ReadResponse(CefRefPtr<CefResourceHandler> handler, static void ReadResponse(scoped_refptr<HandlerProvider> handler_provider,
net::IOBuffer* dest, net::IOBuffer* dest,
int length, int length,
CefRefPtr<ReadCallbackWrapper> callback) { CefRefPtr<ReadCallbackWrapper> callback) {
CEF_POST_TASK( CEF_POST_TASK(
CEF_IOT, base::Bind(ReadResponseCallbackWrapper::ReadResponseOnIOThread, CEF_IOT,
handler, base::Unretained(dest), length, callback)); base::BindOnce(ReadResponseCallbackWrapper::ReadResponseOnIOThread,
handler_provider, base::Unretained(dest), length,
callback));
} }
private: private:
ReadResponseCallbackWrapper(CefRefPtr<CefResourceHandler> handler, ReadResponseCallbackWrapper(scoped_refptr<HandlerProvider> handler_provider,
net::IOBuffer* dest, net::IOBuffer* dest,
int length, int length,
CefRefPtr<ReadCallbackWrapper> callback) CefRefPtr<ReadCallbackWrapper> callback)
: handler_(handler), dest_(dest), length_(length), callback_(callback) {} : handler_provider_(handler_provider),
dest_(dest),
length_(length),
callback_(callback) {}
static void ReadResponseOnIOThread(CefRefPtr<CefResourceHandler> handler, static void ReadResponseOnIOThread(
net::IOBuffer* dest, scoped_refptr<HandlerProvider> handler_provider,
int length, net::IOBuffer* dest,
CefRefPtr<ReadCallbackWrapper> callback) { int length,
CefRefPtr<ReadCallbackWrapper> callback) {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
CefRefPtr<ReadResponseCallbackWrapper> callbackWrapper = CefRefPtr<ReadResponseCallbackWrapper> callbackWrapper =
new ReadResponseCallbackWrapper(handler, dest, length, callback); new ReadResponseCallbackWrapper(handler_provider, dest, length,
callback);
callbackWrapper->DoRead(); callbackWrapper->DoRead();
} }
@ -135,9 +181,15 @@ class ReadResponseCallbackWrapper : public CefCallback {
if (!callback_) if (!callback_)
return; return;
auto handler = handler_provider_->handler();
if (!handler) {
DoCancel();
return;
}
int bytes_read = 0; int bytes_read = 0;
bool result = bool result =
handler_->ReadResponse(dest_->data(), length_, bytes_read, this); handler->ReadResponse(dest_->data(), length_, bytes_read, this);
if (result) { if (result) {
if (bytes_read > 0) { if (bytes_read > 0) {
// Continue immediately. // Continue immediately.
@ -160,7 +212,7 @@ class ReadResponseCallbackWrapper : public CefCallback {
} }
} }
CefRefPtr<CefResourceHandler> handler_; scoped_refptr<HandlerProvider> handler_provider_;
net::IOBuffer* const dest_; net::IOBuffer* const dest_;
int length_; int length_;
CefRefPtr<ReadCallbackWrapper> callback_; CefRefPtr<ReadCallbackWrapper> callback_;
@ -171,15 +223,23 @@ class ReadResponseCallbackWrapper : public CefCallback {
class InputStreamWrapper : public InputStream { class InputStreamWrapper : public InputStream {
public: public:
explicit InputStreamWrapper(CefRefPtr<CefResourceHandler> handler) explicit InputStreamWrapper(scoped_refptr<HandlerProvider> handler_provider)
: handler_(handler) {} : handler_provider_(handler_provider) {}
~InputStreamWrapper() override {}
~InputStreamWrapper() override { Cancel(); }
// InputStream methods: // InputStream methods:
bool Skip(int64_t n, int64_t* bytes_skipped, SkipCallback callback) override { bool Skip(int64_t n, int64_t* bytes_skipped, SkipCallback callback) override {
auto handler = handler_provider_->handler();
if (!handler) {
// Cancel immediately.
*bytes_skipped = net::ERR_FAILED;
return false;
}
CefRefPtr<SkipCallbackWrapper> callbackWrapper = CefRefPtr<SkipCallbackWrapper> callbackWrapper =
new SkipCallbackWrapper(std::move(callback)); new SkipCallbackWrapper(std::move(callback));
bool result = handler_->Skip(n, *bytes_skipped, callbackWrapper.get()); bool result = handler->Skip(n, *bytes_skipped, callbackWrapper.get());
if (result) { if (result) {
if (*bytes_skipped > 0) { if (*bytes_skipped > 0) {
// Continue immediately. // Continue immediately.
@ -196,10 +256,17 @@ class InputStreamWrapper : public InputStream {
int length, int length,
int* bytes_read, int* bytes_read,
ReadCallback callback) override { ReadCallback callback) override {
auto handler = handler_provider_->handler();
if (!handler) {
// Cancel immediately.
*bytes_read = net::ERR_FAILED;
return false;
}
CefRefPtr<ReadCallbackWrapper> callbackWrapper = CefRefPtr<ReadCallbackWrapper> callbackWrapper =
new ReadCallbackWrapper(std::move(callback)); new ReadCallbackWrapper(std::move(callback));
bool result = handler_->Read(dest->data(), length, *bytes_read, bool result =
callbackWrapper.get()); handler->Read(dest->data(), length, *bytes_read, callbackWrapper.get());
if (result) { if (result) {
if (*bytes_read > 0) { if (*bytes_read > 0) {
// Continue immediately. // Continue immediately.
@ -210,7 +277,7 @@ class InputStreamWrapper : public InputStream {
if (*bytes_read == -1) { if (*bytes_read == -1) {
// Call ReadResponse on the IO thread. // Call ReadResponse on the IO thread.
ReadResponseCallbackWrapper::ReadResponse(handler_, dest, length, ReadResponseCallbackWrapper::ReadResponse(handler_provider_, dest, length,
callbackWrapper); callbackWrapper);
*bytes_read = 0; *bytes_read = 0;
return true; return true;
@ -220,8 +287,13 @@ class InputStreamWrapper : public InputStream {
return false; return false;
} }
void Cancel() {
// Triggers a call to Cancel on the handler.
handler_provider_->Detach();
}
private: private:
CefRefPtr<CefResourceHandler> handler_; scoped_refptr<HandlerProvider> handler_provider_;
DISALLOW_COPY_AND_ASSIGN(InputStreamWrapper); DISALLOW_COPY_AND_ASSIGN(InputStreamWrapper);
}; };
@ -236,14 +308,18 @@ class OpenCallbackWrapper : public CefCallback {
~OpenCallbackWrapper() override { ~OpenCallbackWrapper() override {
if (!callback_.is_null()) { if (!callback_.is_null()) {
Execute(std::move(callback_), std::move(stream_), false); // Make sure it executes on the correct thread.
work_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&OpenCallbackWrapper::Execute, std::move(callback_),
std::move(stream_), false));
} }
} }
void Continue() override { void Continue() override {
if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) { if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) {
work_thread_task_runner_->PostTask( work_thread_task_runner_->PostTask(
FROM_HERE, base::Bind(&OpenCallbackWrapper::Continue, this)); FROM_HERE, base::BindOnce(&OpenCallbackWrapper::Continue, this));
return; return;
} }
if (!callback_.is_null()) { if (!callback_.is_null()) {
@ -254,7 +330,7 @@ class OpenCallbackWrapper : public CefCallback {
void Cancel() override { void Cancel() override {
if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) { if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) {
work_thread_task_runner_->PostTask( work_thread_task_runner_->PostTask(
FROM_HERE, base::Bind(&OpenCallbackWrapper::Cancel, this)); FROM_HERE, base::BindOnce(&OpenCallbackWrapper::Cancel, this));
return; return;
} }
if (!callback_.is_null()) { if (!callback_.is_null()) {
@ -279,10 +355,16 @@ class OpenCallbackWrapper : public CefCallback {
}; };
void CallProcessRequestOnIOThread( void CallProcessRequestOnIOThread(
CefRefPtr<CefResourceHandler> handler, scoped_refptr<HandlerProvider> handler_provider,
CefRefPtr<CefRequestImpl> request, CefRefPtr<CefRequestImpl> request,
CefRefPtr<OpenCallbackWrapper> callbackWrapper) { CefRefPtr<OpenCallbackWrapper> callbackWrapper) {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
auto handler = handler_provider->handler();
if (!handler) {
callbackWrapper->Cancel();
return;
}
if (!handler->ProcessRequest(request.get(), callbackWrapper.get())) { if (!handler->ProcessRequest(request.get(), callbackWrapper.get())) {
callbackWrapper->Cancel(); callbackWrapper->Cancel();
} }
@ -292,10 +374,13 @@ class ResourceResponseWrapper : public ResourceResponse {
public: public:
ResourceResponseWrapper(const RequestId request_id, ResourceResponseWrapper(const RequestId request_id,
CefRefPtr<CefResourceHandler> handler) CefRefPtr<CefResourceHandler> handler)
: request_id_(request_id), handler_(handler) { : request_id_(request_id),
DCHECK(handler_); handler_provider_(new HandlerProvider(handler)) {}
~ResourceResponseWrapper() override {
// Triggers a call to Cancel on the handler.
handler_provider_->Detach();
} }
~ResourceResponseWrapper() override {}
// ResourceResponse methods: // ResourceResponse methods:
bool OpenInputStream(const RequestId& request_id, bool OpenInputStream(const RequestId& request_id,
@ -303,16 +388,23 @@ class ResourceResponseWrapper : public ResourceResponse {
OpenCallback callback) override { OpenCallback callback) override {
DCHECK_EQ(request_id, request_id_); DCHECK_EQ(request_id, request_id_);
auto handler = handler_provider_->handler();
if (!handler) {
// Cancel immediately.
return false;
}
// May be recreated on redirect. // May be recreated on redirect.
request_ = new CefRequestImpl(); request_ = new CefRequestImpl();
request_->Set(&request, request_id.hash()); request_->Set(&request, request_id.hash());
request_->SetReadOnly(true); request_->SetReadOnly(true);
CefRefPtr<OpenCallbackWrapper> callbackWrapper = new OpenCallbackWrapper( CefRefPtr<OpenCallbackWrapper> callbackWrapper = new OpenCallbackWrapper(
std::move(callback), std::make_unique<InputStreamWrapper>(handler_)); std::move(callback),
std::make_unique<InputStreamWrapper>(handler_provider_));
bool handle_request = false; bool handle_request = false;
bool result = bool result =
handler_->Open(request_.get(), handle_request, callbackWrapper.get()); handler->Open(request_.get(), handle_request, callbackWrapper.get());
if (result) { if (result) {
if (handle_request) { if (handle_request) {
// Continue immediately. // Continue immediately.
@ -328,8 +420,9 @@ class ResourceResponseWrapper : public ResourceResponse {
} }
// Call ProcessRequest on the IO thread. // Call ProcessRequest on the IO thread.
CEF_POST_TASK(CEF_IOT, base::Bind(CallProcessRequestOnIOThread, handler_, CEF_POST_TASK(
request_, callbackWrapper)); CEF_IOT, base::BindOnce(CallProcessRequestOnIOThread, handler_provider_,
request_, callbackWrapper));
return true; return true;
} }
@ -343,10 +436,17 @@ class ResourceResponseWrapper : public ResourceResponse {
DCHECK_EQ(request_id, request_id_); DCHECK_EQ(request_id, request_id_);
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
auto handler = handler_provider_->handler();
if (!handler) {
// Cancel immediately.
*status_code = net::ERR_FAILED;
return;
}
CefRefPtr<CefResponse> response = CefResponse::Create(); CefRefPtr<CefResponse> response = CefResponse::Create();
int64_t response_length = -1; int64_t response_length = -1;
CefString redirect_url; CefString redirect_url;
handler_->GetResponseHeaders(response, response_length, redirect_url); handler->GetResponseHeaders(response, response_length, redirect_url);
const auto error_code = response->GetError(); const auto error_code = response->GetError();
if (error_code != ERR_NONE) { if (error_code != ERR_NONE) {
@ -390,7 +490,7 @@ class ResourceResponseWrapper : public ResourceResponse {
const RequestId request_id_; const RequestId request_id_;
CefRefPtr<CefRequestImpl> request_; CefRefPtr<CefRequestImpl> request_;
CefRefPtr<CefResourceHandler> handler_; scoped_refptr<HandlerProvider> handler_provider_;
DISALLOW_COPY_AND_ASSIGN(ResourceResponseWrapper); DISALLOW_COPY_AND_ASSIGN(ResourceResponseWrapper);
}; };

View File

@ -7,6 +7,7 @@
#include "libcef/browser/browser_host_impl.h" #include "libcef/browser/browser_host_impl.h"
#include "libcef/browser/browser_platform_delegate.h" #include "libcef/browser/browser_platform_delegate.h"
#include "libcef/browser/content_browser_client.h" #include "libcef/browser/content_browser_client.h"
#include "libcef/browser/context.h"
#include "libcef/browser/net_service/cookie_helper.h" #include "libcef/browser/net_service/cookie_helper.h"
#include "libcef/browser/net_service/proxy_url_loader_factory.h" #include "libcef/browser/net_service/proxy_url_loader_factory.h"
#include "libcef/browser/net_service/resource_handler_wrapper.h" #include "libcef/browser/net_service/resource_handler_wrapper.h"
@ -54,7 +55,7 @@ class RequestCallbackWrapper : public CefRequestCallback {
if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) { if (!work_thread_task_runner_->RunsTasksInCurrentSequence()) {
work_thread_task_runner_->PostTask( work_thread_task_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::Bind(&RequestCallbackWrapper::Continue, this, allow)); base::BindOnce(&RequestCallbackWrapper::Continue, this, allow));
return; return;
} }
if (!callback_.is_null()) { if (!callback_.is_null()) {
@ -139,6 +140,12 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
callback_(std::move(callback)), callback_(std::move(callback)),
cancel_callback_(std::move(cancel_callback)) {} cancel_callback_(std::move(cancel_callback)) {}
~PendingRequest() {
if (cancel_callback_) {
std::move(cancel_callback_).Run(net::ERR_ABORTED);
}
}
void Run(InterceptedRequestHandlerWrapper* self) { void Run(InterceptedRequestHandlerWrapper* self) {
self->OnBeforeRequest(id_, request_, request_was_redirected_, self->OnBeforeRequest(id_, request_, request_was_redirected_,
std::move(callback_), std::move(cancel_callback_)); std::move(callback_), std::move(cancel_callback_));
@ -151,9 +158,12 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CancelRequestCallback cancel_callback_; CancelRequestCallback cancel_callback_;
}; };
class BrowserObserver : public CefBrowserHostImpl::Observer { // Observer to receive notification of CEF context or associated browser
// destruction. Only one of the *Destroyed() methods will be called.
class DestructionObserver : public CefBrowserHostImpl::Observer,
public CefContext::Observer {
public: public:
BrowserObserver() = default; DestructionObserver() = default;
void SetWrapper(base::WeakPtr<InterceptedRequestHandlerWrapper> wrapper) { void SetWrapper(base::WeakPtr<InterceptedRequestHandlerWrapper> wrapper) {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
@ -163,35 +173,57 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
void OnBrowserDestroyed(CefBrowserHostImpl* browser) override { void OnBrowserDestroyed(CefBrowserHostImpl* browser) override {
CEF_REQUIRE_UIT(); CEF_REQUIRE_UIT();
browser->RemoveObserver(this); browser->RemoveObserver(this);
CEF_POST_TASK( NotifyOnDestroyed();
CEF_IOT, }
base::BindOnce(&InterceptedRequestHandlerWrapper::OnBrowserDestroyed,
wrapper_)); void OnContextDestroyed() override {
CEF_REQUIRE_UIT();
CefContext::Get()->RemoveObserver(this);
NotifyOnDestroyed();
} }
private: private:
void NotifyOnDestroyed() {
// It's not safe to test the WeakPtr on the UI thread, so we'll just post
// a task which will be ignored if the WeakPtr is invalid.
CEF_POST_TASK(CEF_IOT, base::BindOnce(
&InterceptedRequestHandlerWrapper::OnDestroyed,
wrapper_));
}
base::WeakPtr<InterceptedRequestHandlerWrapper> wrapper_; base::WeakPtr<InterceptedRequestHandlerWrapper> wrapper_;
DISALLOW_COPY_AND_ASSIGN(BrowserObserver); DISALLOW_COPY_AND_ASSIGN(DestructionObserver);
}; };
InterceptedRequestHandlerWrapper() : weak_ptr_factory_(this) {} InterceptedRequestHandlerWrapper() : weak_ptr_factory_(this) {}
~InterceptedRequestHandlerWrapper() override { ~InterceptedRequestHandlerWrapper() override {
if (browser_observer_) { // There should be no pending or in-progress requests during destruction.
DCHECK(pending_requests_.empty());
DCHECK(request_map_.empty());
if (destruction_observer_) {
destruction_observer_->SetWrapper(nullptr);
CEF_POST_TASK( CEF_POST_TASK(
CEF_UIT, base::BindOnce(&InterceptedRequestHandlerWrapper:: CEF_UIT, base::BindOnce(&InterceptedRequestHandlerWrapper::
RemoveBrowserObserverOnUIThread, RemoveDestructionObserverOnUIThread,
browser_info_, std::move(browser_observer_))); browser_ ? browser_->browser_info() : nullptr,
std::move(destruction_observer_)));
} }
} }
static void RemoveBrowserObserverOnUIThread( static void RemoveDestructionObserverOnUIThread(
scoped_refptr<CefBrowserInfo> browser_info, scoped_refptr<CefBrowserInfo> browser_info,
std::unique_ptr<BrowserObserver> observer) { std::unique_ptr<DestructionObserver> observer) {
// The browser may already have been destroyed when this method is executed. // Verify that the browser or context is still valid before attempting to
auto browser = browser_info->browser(); // remove the observer.
if (browser) if (browser_info) {
browser->RemoveObserver(observer.get()); auto browser = browser_info->browser();
if (browser)
browser->RemoveObserver(observer.get());
} else if (CONTEXT_STATE_VALID()) {
CefContext::Get()->RemoveObserver(observer.get());
}
} }
void Initialize(content::BrowserContext* browser_context, void Initialize(content::BrowserContext* browser_context,
@ -211,14 +243,20 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
static_cast<CefResourceContext*>(browser_context->GetResourceContext()); static_cast<CefResourceContext*>(browser_context->GetResourceContext());
DCHECK(resource_context_); DCHECK(resource_context_);
// Don't hold a RefPtr to the CefBrowserHostImpl. // We register to be notified of CEF context or browser destruction so that
if (browser) { // we can stop accepting new requests and cancel pending/in-progress
browser_info_ = browser->browser_info(); // requests in a timely manner (e.g. before we start asserting about leaked
// objects during CEF shutdown).
destruction_observer_.reset(new DestructionObserver());
browser_observer_.reset(new BrowserObserver()); if (browser) {
browser->AddObserver(browser_observer_.get()); // These references will be released in OnDestroyed().
browser_ = browser;
frame_ = frame;
browser->AddObserver(destruction_observer_.get());
} else {
CefContext::Get()->AddObserver(destruction_observer_.get());
} }
frame_ = frame;
render_process_id_ = render_process_id; render_process_id_ = render_process_id;
render_frame_id_ = render_frame_id; render_frame_id_ = render_frame_id;
@ -245,10 +283,21 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
initialized_ = true; initialized_ = true;
if (browser_observer_) { // Check that the CEF context or associated browser was not destroyed
browser_observer_->SetWrapper(weak_ptr_factory_.GetWeakPtr()); // between the calls to Initialize and SetInitialized, in which case
// we won't get an OnDestroyed callback from DestructionObserver.
if (browser_) {
if (!browser_->browser_info()->browser()) {
OnDestroyed();
return;
}
} else if (!CONTEXT_STATE_VALID()) {
OnDestroyed();
return;
} }
destruction_observer_->SetWrapper(weak_ptr_factory_.GetWeakPtr());
// Continue any pending requests. // Continue any pending requests.
if (!pending_requests_.empty()) { if (!pending_requests_.empty()) {
for (const auto& request : pending_requests_) for (const auto& request : pending_requests_)
@ -265,6 +314,12 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CancelRequestCallback cancel_callback) override { CancelRequestCallback cancel_callback) override {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
if (shutting_down_) {
// Abort immediately.
std::move(cancel_callback).Run(net::ERR_ABORTED);
return;
}
if (!initialized_) { if (!initialized_) {
// Queue requests until we're initialized. // Queue requests until we're initialized.
pending_requests_.push_back(std::make_unique<PendingRequest>( pending_requests_.push_back(std::make_unique<PendingRequest>(
@ -305,8 +360,8 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
std::move(cancel_callback)); std::move(cancel_callback));
if (handler) { if (handler) {
state->cookie_filter_ = handler->GetCookieAccessFilter( state->cookie_filter_ =
GetBrowser(), frame_, requestPtr.get()); handler->GetCookieAccessFilter(browser_, frame_, requestPtr.get());
} }
auto exec_callback = auto exec_callback =
@ -366,7 +421,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CefCookie cef_cookie; CefCookie cef_cookie;
if (net_service::MakeCefCookie(cookie, cef_cookie)) { if (net_service::MakeCefCookie(cookie, cef_cookie)) {
*allow = state->cookie_filter_->CanSendCookie( *allow = state->cookie_filter_->CanSendCookie(
GetBrowser(), frame_, state->pending_request_.get(), cef_cookie); browser_, frame_, state->pending_request_.get(), cef_cookie);
} }
} }
@ -413,7 +468,11 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
RequestState* state = GetState(id); RequestState* state = GetState(id);
DCHECK(state); if (!state) {
// The request may have been canceled during destruction.
return;
}
// Must have a handler and/or scheme factory. // Must have a handler and/or scheme factory.
DCHECK(state->handler_ || state->scheme_factory_); DCHECK(state->handler_ || state->scheme_factory_);
DCHECK(state->pending_request_); DCHECK(state->pending_request_);
@ -431,8 +490,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
base::Passed(std::move(callback)))); base::Passed(std::move(callback))));
cef_return_value_t retval = state->handler_->OnBeforeResourceLoad( cef_return_value_t retval = state->handler_->OnBeforeResourceLoad(
GetBrowser(), frame_, state->pending_request_.get(), browser_, frame_, state->pending_request_.get(), callbackPtr.get());
callbackPtr.get());
if (retval != RV_CONTINUE_ASYNC) { if (retval != RV_CONTINUE_ASYNC) {
// Continue or cancel the request immediately. // Continue or cancel the request immediately.
callbackPtr->Continue(retval == RV_CONTINUE); callbackPtr->Continue(retval == RV_CONTINUE);
@ -493,18 +551,17 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
} }
CefRefPtr<CefResourceHandler> resource_handler; CefRefPtr<CefResourceHandler> resource_handler;
CefRefPtr<CefBrowser> browser = GetBrowser();
if (state->handler_) { if (state->handler_) {
// Does the client want to handle the request? // Does the client want to handle the request?
resource_handler = state->handler_->GetResourceHandler( resource_handler = state->handler_->GetResourceHandler(
browser, frame_, state->pending_request_.get()); browser_, frame_, state->pending_request_.get());
} }
if (!resource_handler && state->scheme_factory_) { if (!resource_handler && state->scheme_factory_) {
// Does the scheme factory want to handle the request? // Does the scheme factory want to handle the request?
resource_handler = resource_handler = state->scheme_factory_->Create(
state->scheme_factory_->Create(browser, frame_, request->url.scheme(), browser_, frame_, request->url.scheme(),
state->pending_request_.get()); state->pending_request_.get());
} }
std::unique_ptr<ResourceResponse> resource_response; std::unique_ptr<ResourceResponse> resource_response;
@ -526,7 +583,11 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
RequestState* state = GetState(id); RequestState* state = GetState(id);
DCHECK(state); if (!state) {
// The request may have been canceled during destruction.
return;
}
if (!state->handler_) if (!state->handler_)
return; return;
@ -549,7 +610,11 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
RequestState* state = GetState(id); RequestState* state = GetState(id);
DCHECK(state); if (!state) {
// The request may have been canceled during destruction.
return;
}
if (!state->handler_) { if (!state->handler_) {
// Cookies may come from a scheme handler. // Cookies may come from a scheme handler.
MaybeSaveCookies( MaybeSaveCookies(
@ -581,7 +646,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CefString newUrl = redirect_info.new_url.spec(); CefString newUrl = redirect_info.new_url.spec();
CefString oldUrl = newUrl; CefString oldUrl = newUrl;
bool url_changed = false; bool url_changed = false;
state->handler_->OnResourceRedirect(GetBrowser(), frame_, state->handler_->OnResourceRedirect(browser_, frame_,
state->pending_request_.get(), state->pending_request_.get(),
state->pending_response_.get(), newUrl); state->pending_response_.get(), newUrl);
if (newUrl != oldUrl) { if (newUrl != oldUrl) {
@ -619,7 +684,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
auto response_mode = ResponseMode::CONTINUE; auto response_mode = ResponseMode::CONTINUE;
GURL new_url; GURL new_url;
if (state->handler_->OnResourceResponse(GetBrowser(), frame_, if (state->handler_->OnResourceResponse(browser_, frame_,
state->pending_request_.get(), state->pending_request_.get(),
state->pending_response_.get())) { state->pending_response_.get())) {
// The request may have been modified. // The request may have been modified.
@ -703,7 +768,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CefCookie cef_cookie; CefCookie cef_cookie;
if (net_service::MakeCefCookie(cookie, cef_cookie)) { if (net_service::MakeCefCookie(cookie, cef_cookie)) {
*allow = state->cookie_filter_->CanSaveCookie( *allow = state->cookie_filter_->CanSaveCookie(
GetBrowser(), frame_, state->pending_request_.get(), browser_, frame_, state->pending_request_.get(),
state->pending_response_.get(), cef_cookie); state->pending_response_.get(), cef_cookie);
} }
} }
@ -723,11 +788,14 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
RequestState* state = GetState(id); RequestState* state = GetState(id);
DCHECK(state); if (!state) {
// The request may have been canceled during destruction.
return body;
}
if (state->handler_) { if (state->handler_) {
auto filter = state->handler_->GetResourceResponseFilter( auto filter = state->handler_->GetResourceResponseFilter(
GetBrowser(), frame_, state->pending_request_.get(), browser_, frame_, state->pending_request_.get(),
state->pending_response_.get()); state->pending_response_.get());
if (filter) { if (filter) {
return CreateResponseFilterHandler( return CreateResponseFilterHandler(
@ -763,7 +831,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
RequestState* state = GetState(id); RequestState* state = GetState(id);
if (!state) { if (!state) {
// The request may have been canceled. // The request may have been canceled during destruction.
return; return;
} }
@ -779,28 +847,13 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
if (state->handler_ && !ignore_result) { if (state->handler_ && !ignore_result) {
DCHECK(state->pending_request_); DCHECK(state->pending_request_);
if (!state->pending_response_) { CallHandlerOnComplete(state, status);
// If the request failed there may not be a response object yet.
state->pending_response_ = new CefResponseImpl();
} else {
state->pending_response_->SetReadOnly(false);
}
state->pending_response_->SetError(
static_cast<cef_errorcode_t>(status.error_code));
state->pending_response_->SetReadOnly(true);
CefRefPtr<CefBrowser> browser = GetBrowser();
state->handler_->OnResourceLoadComplete(
browser, frame_, state->pending_request_.get(),
state->pending_response_.get(),
status.error_code == 0 ? UR_SUCCESS : UR_FAILED,
status.encoded_body_length);
if (status.error_code != 0 && is_external_) { if (status.error_code != 0 && is_external_) {
bool allow_os_execution = false; bool allow_os_execution = false;
state->handler_->OnProtocolExecution( state->handler_->OnProtocolExecution(browser_, frame_,
browser, frame_, state->pending_request_.get(), allow_os_execution); state->pending_request_.get(),
allow_os_execution);
if (allow_os_execution) { if (allow_os_execution) {
CefBrowserPlatformDelegate::HandleExternalProtocol(request.url); CefBrowserPlatformDelegate::HandleExternalProtocol(request.url);
} }
@ -811,6 +864,34 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
} }
private: private:
void CallHandlerOnComplete(RequestState* state,
const network::URLLoaderCompletionStatus& status) {
if (!state->handler_ || !state->pending_request_)
return;
// The request object may be currently flagged as writable in cases where we
// abort a request that is waiting on a pending callack.
if (!state->pending_request_->IsReadOnly()) {
state->pending_request_->SetReadOnly(true);
}
if (!state->pending_response_) {
// If the request failed there may not be a response object yet.
state->pending_response_ = new CefResponseImpl();
} else {
state->pending_response_->SetReadOnly(false);
}
state->pending_response_->SetError(
static_cast<cef_errorcode_t>(status.error_code));
state->pending_response_->SetReadOnly(true);
state->handler_->OnResourceLoadComplete(
browser_, frame_, state->pending_request_.get(),
state->pending_response_.get(),
status.error_code == 0 ? UR_SUCCESS : UR_FAILED,
status.encoded_body_length);
}
// Returns the handler, if any, that should be used for this request. // Returns the handler, if any, that should be used for this request.
CefRefPtr<CefResourceRequestHandler> GetHandler( CefRefPtr<CefResourceRequestHandler> GetHandler(
const RequestId& id, const RequestId& id,
@ -821,10 +902,9 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
const int64 request_id = id.hash(); const int64 request_id = id.hash();
CefRefPtr<CefBrowser> browser = GetBrowser(); if (browser_) {
if (browser) {
// Maybe the browser's client wants to handle it? // Maybe the browser's client wants to handle it?
CefRefPtr<CefClient> client = browser->GetHost()->GetClient(); CefRefPtr<CefClient> client = browser_->GetHost()->GetClient();
if (client) { if (client) {
CefRefPtr<CefRequestHandler> request_handler = CefRefPtr<CefRequestHandler> request_handler =
client->GetRequestHandler(); client->GetRequestHandler();
@ -832,7 +912,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
requestPtr = MakeRequest(request, request_id, true); requestPtr = MakeRequest(request, request_id, true);
handler = request_handler->GetResourceRequestHandler( handler = request_handler->GetResourceRequestHandler(
browser, frame_, requestPtr.get(), is_navigation_, is_download_, browser_, frame_, requestPtr.get(), is_navigation_, is_download_,
request_initiator_, *intercept_only); request_initiator_, *intercept_only);
} }
} }
@ -849,7 +929,7 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
requestPtr = MakeRequest(request, request_id, true); requestPtr = MakeRequest(request, request_id, true);
handler = context_handler->GetResourceRequestHandler( handler = context_handler->GetResourceRequestHandler(
browser, frame_, requestPtr.get(), is_navigation_, is_download_, browser_, frame_, requestPtr.get(), is_navigation_, is_download_,
request_initiator_, *intercept_only); request_initiator_, *intercept_only);
} }
} }
@ -880,29 +960,52 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
request_map_.erase(it); request_map_.erase(it);
} }
CefRefPtr<CefBrowser> GetBrowser() const { // Stop accepting new requests and cancel pending/in-flight requests when the
if (browser_info_) // CEF context or associated browser is destroyed.
return browser_info_->browser().get(); void OnDestroyed() {
return nullptr;
}
// Called when the associated browser, if any, is destroyed.
void OnBrowserDestroyed() {
CEF_REQUIRE_IOT(); CEF_REQUIRE_IOT();
DCHECK(initialized_); DCHECK(initialized_);
DCHECK(browser_observer_); DCHECK(destruction_observer_);
browser_observer_.reset(); destruction_observer_.reset();
// Cancel any pending requests. // Stop accepting new requests.
while (!request_map_.empty()) { shutting_down_ = true;
auto cancel_callback =
std::move(request_map_.begin()->second->cancel_callback_); // Stop the delivery of pending callbacks.
if (cancel_callback) { weak_ptr_factory_.InvalidateWeakPtrs();
// Results in a call to RemoveState().
std::move(cancel_callback).Run(net::ERR_ABORTED); // Take ownership of any pending requests.
} else { PendingRequests pending_requests;
RemoveState(request_map_.begin()->first); pending_requests.swap(pending_requests_);
// Take ownership of any in-progress requests.
RequestMap request_map;
request_map.swap(request_map_);
// Notify handlers for in-progress requests.
for (const auto& pair : request_map) {
CallHandlerOnComplete(
pair.second.get(),
network::URLLoaderCompletionStatus(net::ERR_ABORTED));
}
if (browser_) {
// Clear objects that reference the browser.
browser_ = nullptr;
frame_ = nullptr;
}
// Execute cancel callbacks and delete pending and in-progress requests.
// This may result in the request being torn down sooner, or it may be
// ignored if the request is already in the process of being torn down. When
// the last callback is executed it may result in |this| being deleted.
pending_requests.clear();
for (auto& pair : request_map) {
auto state = std::move(pair.second);
if (state->cancel_callback_) {
std::move(state->cancel_callback_).Run(net::ERR_ABORTED);
} }
} }
} }
@ -921,11 +1024,12 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
} }
bool initialized_ = false; bool initialized_ = false;
bool shutting_down_ = false;
// Only accessed on the UI thread. // Only accessed on the UI thread.
content::BrowserContext* browser_context_ = nullptr; content::BrowserContext* browser_context_ = nullptr;
scoped_refptr<CefBrowserInfo> browser_info_; CefRefPtr<CefBrowserHostImpl> browser_;
CefRefPtr<CefFrame> frame_; CefRefPtr<CefFrame> frame_;
CefResourceContext* resource_context_ = nullptr; CefResourceContext* resource_context_ = nullptr;
int render_process_id_ = 0; int render_process_id_ = 0;
@ -946,8 +1050,8 @@ class InterceptedRequestHandlerWrapper : public InterceptedRequestHandler {
using PendingRequests = std::vector<std::unique_ptr<PendingRequest>>; using PendingRequests = std::vector<std::unique_ptr<PendingRequest>>;
PendingRequests pending_requests_; PendingRequests pending_requests_;
// Used to recieve notification of browser destruction. // Used to receive destruction notification.
std::unique_ptr<BrowserObserver> browser_observer_; std::unique_ptr<DestructionObserver> destruction_observer_;
base::WeakPtrFactory<InterceptedRequestHandlerWrapper> weak_ptr_factory_; base::WeakPtrFactory<InterceptedRequestHandlerWrapper> weak_ptr_factory_;
@ -961,8 +1065,12 @@ void InitOnUIThread(
const network::ResourceRequest& request) { const network::ResourceRequest& request) {
CEF_REQUIRE_UIT(); CEF_REQUIRE_UIT();
// May return nullptr if the WebContents was destroyed while this callback was
// in-flight.
content::WebContents* web_contents = web_contents_getter.Run(); content::WebContents* web_contents = web_contents_getter.Run();
DCHECK(web_contents); if (!web_contents) {
return;
}
content::BrowserContext* browser_context = web_contents->GetBrowserContext(); content::BrowserContext* browser_context = web_contents->GetBrowserContext();
DCHECK(browser_context); DCHECK(browser_context);

View File

@ -34,12 +34,16 @@ using OnInputStreamOpenedCallback =
class OpenInputStreamWrapper class OpenInputStreamWrapper
: public base::RefCountedThreadSafe<OpenInputStreamWrapper> { : public base::RefCountedThreadSafe<OpenInputStreamWrapper> {
public: public:
static void Open( static base::OnceClosure Open(
std::unique_ptr<StreamReaderURLLoader::Delegate> delegate, std::unique_ptr<StreamReaderURLLoader::Delegate> delegate,
scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner, scoped_refptr<base::SequencedTaskRunner> work_thread_task_runner,
const RequestId& request_id, const RequestId& request_id,
const network::ResourceRequest& request, const network::ResourceRequest& request,
OnInputStreamOpenedCallback callback) { OnInputStreamOpenedCallback callback) WARN_UNUSED_RESULT {
scoped_refptr<OpenInputStreamWrapper> wrapper = new OpenInputStreamWrapper(
std::move(delegate), base::ThreadTaskRunnerHandle::Get(),
std::move(callback));
work_thread_task_runner->PostTask( work_thread_task_runner->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce(OpenInputStreamWrapper::OpenOnWorkThread, base::BindOnce(OpenInputStreamWrapper::OpenOnWorkThread,
@ -47,8 +51,9 @@ class OpenInputStreamWrapper
// while the callback is executing on the background // while the callback is executing on the background
// thread. The delegate will be "returned" to the loader // thread. The delegate will be "returned" to the loader
// once the InputStream open attempt is completed. // once the InputStream open attempt is completed.
std::move(delegate), base::ThreadTaskRunnerHandle::Get(), wrapper, request_id, request));
request_id, request, std::move(callback)));
return wrapper->GetCancelCallback();
} }
private: private:
@ -63,20 +68,26 @@ class OpenInputStreamWrapper
callback_(std::move(callback)) {} callback_(std::move(callback)) {}
virtual ~OpenInputStreamWrapper() {} virtual ~OpenInputStreamWrapper() {}
static void OpenOnWorkThread( static void OpenOnWorkThread(scoped_refptr<OpenInputStreamWrapper> wrapper,
std::unique_ptr<StreamReaderURLLoader::Delegate> delegate, const RequestId& request_id,
scoped_refptr<base::SingleThreadTaskRunner> job_thread_task_runner, const network::ResourceRequest& request) {
const RequestId& request_id,
const network::ResourceRequest& request,
OnInputStreamOpenedCallback callback) {
DCHECK(!CEF_CURRENTLY_ON_IOT()); DCHECK(!CEF_CURRENTLY_ON_IOT());
DCHECK(!CEF_CURRENTLY_ON_UIT()); DCHECK(!CEF_CURRENTLY_ON_UIT());
scoped_refptr<OpenInputStreamWrapper> wrapper = new OpenInputStreamWrapper(
std::move(delegate), job_thread_task_runner, std::move(callback));
wrapper->Open(request_id, request); wrapper->Open(request_id, request);
} }
base::OnceClosure GetCancelCallback() {
return base::BindOnce(&OpenInputStreamWrapper::Cancel,
base::WrapRefCounted(this));
}
void Cancel() {
DCHECK(job_thread_task_runner_->RunsTasksInCurrentSequence());
delegate_.reset();
callback_.Reset();
}
void Open(const RequestId& request_id, void Open(const RequestId& request_id,
const network::ResourceRequest& request) { const network::ResourceRequest& request) {
if (!delegate_->OpenInputStream( if (!delegate_->OpenInputStream(
@ -96,7 +107,7 @@ class OpenInputStreamWrapper
return; return;
} }
DCHECK(!callback_.is_null()); // May be null if we were canceled.
if (callback_.is_null()) if (callback_.is_null())
return; return;
@ -470,7 +481,12 @@ StreamReaderURLLoader::StreamReaderURLLoader(
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}); base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()});
} }
StreamReaderURLLoader::~StreamReaderURLLoader() {} StreamReaderURLLoader::~StreamReaderURLLoader() {
if (open_cancel_callback_) {
// Release the Delegate held by OpenInputStreamWrapper.
std::move(open_cancel_callback_).Run();
}
}
void StreamReaderURLLoader::Start() { void StreamReaderURLLoader::Start() {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(thread_checker_.CalledOnValidThread());
@ -503,7 +519,7 @@ void StreamReaderURLLoader::ContinueWithRequestHeaders(
request_.headers = *headers; request_.headers = *headers;
} }
OpenInputStreamWrapper::Open( open_cancel_callback_ = OpenInputStreamWrapper::Open(
// This is intentional - the loader could be deleted while // This is intentional - the loader could be deleted while
// the callback is executing on the background thread. The // the callback is executing on the background thread. The
// delegate will be "returned" to the loader once the // delegate will be "returned" to the loader once the
@ -536,6 +552,7 @@ void StreamReaderURLLoader::OnInputStreamOpened(
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(returned_delegate); DCHECK(returned_delegate);
response_delegate_ = std::move(returned_delegate); response_delegate_ = std::move(returned_delegate);
open_cancel_callback_.Reset();
if (!input_stream) { if (!input_stream) {
bool restarted = false; bool restarted = false;

View File

@ -225,6 +225,8 @@ class StreamReaderURLLoader : public network::mojom::URLLoader {
scoped_refptr<base::SequencedTaskRunner> stream_work_task_runner_; scoped_refptr<base::SequencedTaskRunner> stream_work_task_runner_;
base::OnceClosure open_cancel_callback_;
base::WeakPtrFactory<StreamReaderURLLoader> weak_factory_; base::WeakPtrFactory<StreamReaderURLLoader> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(StreamReaderURLLoader); DISALLOW_COPY_AND_ASSIGN(StreamReaderURLLoader);

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory // implementations. See the translator.README.txt file in the tools directory
// for more information. // for more information.
// //
// $hash=fc10026d8f3e77868d19807ea625f9449678da60$ // $hash=65f76c903a9c9f245ffe2b3275472d7d3d464f93$
// //
#include "libcef_dll/cpptoc/request_cpptoc.h" #include "libcef_dll/cpptoc/request_cpptoc.h"
@ -116,10 +116,7 @@ void CEF_CALLBACK request_set_referrer(struct _cef_request_t* self,
DCHECK(self); DCHECK(self);
if (!self) if (!self)
return; return;
// Verify param: referrer_url; type: string_byref_const // Unverified params: referrer_url
DCHECK(referrer_url);
if (!referrer_url)
return;
// Execute // Execute
CefRequestCppToC::Get(self)->SetReferrer(CefString(referrer_url), policy); CefRequestCppToC::Get(self)->SetReferrer(CefString(referrer_url), policy);
@ -265,10 +262,7 @@ void CEF_CALLBACK request_set_header_by_name(struct _cef_request_t* self,
DCHECK(name); DCHECK(name);
if (!name) if (!name)
return; return;
// Verify param: value; type: string_byref_const // Unverified params: value
DCHECK(value);
if (!value)
return;
// Execute // Execute
CefRequestCppToC::Get(self)->SetHeaderByName( CefRequestCppToC::Get(self)->SetHeaderByName(
@ -357,10 +351,7 @@ request_set_first_party_for_cookies(struct _cef_request_t* self,
DCHECK(self); DCHECK(self);
if (!self) if (!self)
return; return;
// Verify param: url; type: string_byref_const // Unverified params: url
DCHECK(url);
if (!url)
return;
// Execute // Execute
CefRequestCppToC::Get(self)->SetFirstPartyForCookies(CefString(url)); CefRequestCppToC::Get(self)->SetFirstPartyForCookies(CefString(url));

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory // implementations. See the translator.README.txt file in the tools directory
// for more information. // for more information.
// //
// $hash=4573a140180f11230e688f73e8c09503f9123c3d$ // $hash=5c6b14610ef7bcadf5e4936a262c660896a32526$
// //
#include "libcef_dll/cpptoc/response_cpptoc.h" #include "libcef_dll/cpptoc/response_cpptoc.h"
@ -119,10 +119,7 @@ void CEF_CALLBACK response_set_status_text(struct _cef_response_t* self,
DCHECK(self); DCHECK(self);
if (!self) if (!self)
return; return;
// Verify param: statusText; type: string_byref_const // Unverified params: statusText
DCHECK(statusText);
if (!statusText)
return;
// Execute // Execute
CefResponseCppToC::Get(self)->SetStatusText(CefString(statusText)); CefResponseCppToC::Get(self)->SetStatusText(CefString(statusText));
@ -150,10 +147,7 @@ void CEF_CALLBACK response_set_mime_type(struct _cef_response_t* self,
DCHECK(self); DCHECK(self);
if (!self) if (!self)
return; return;
// Verify param: mimeType; type: string_byref_const // Unverified params: mimeType
DCHECK(mimeType);
if (!mimeType)
return;
// Execute // Execute
CefResponseCppToC::Get(self)->SetMimeType(CefString(mimeType)); CefResponseCppToC::Get(self)->SetMimeType(CefString(mimeType));
@ -181,10 +175,7 @@ void CEF_CALLBACK response_set_charset(struct _cef_response_t* self,
DCHECK(self); DCHECK(self);
if (!self) if (!self)
return; return;
// Verify param: charset; type: string_byref_const // Unverified params: charset
DCHECK(charset);
if (!charset)
return;
// Execute // Execute
CefResponseCppToC::Get(self)->SetCharset(CefString(charset)); CefResponseCppToC::Get(self)->SetCharset(CefString(charset));
@ -275,10 +266,7 @@ void CEF_CALLBACK response_set_url(struct _cef_response_t* self,
DCHECK(self); DCHECK(self);
if (!self) if (!self)
return; return;
// Verify param: url; type: string_byref_const // Unverified params: url
DCHECK(url);
if (!url)
return;
// Execute // Execute
CefResponseCppToC::Get(self)->SetURL(CefString(url)); CefResponseCppToC::Get(self)->SetURL(CefString(url));

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory // implementations. See the translator.README.txt file in the tools directory
// for more information. // for more information.
// //
// $hash=8f57157cf8f05f46b5ae96335584b5e9df02b200$ // $hash=30da50026aa5654b04de874040b194dcc87a7e30$
// //
#include "libcef_dll/ctocpp/request_ctocpp.h" #include "libcef_dll/ctocpp/request_ctocpp.h"
@ -118,10 +118,7 @@ void CefRequestCToCpp::SetReferrer(const CefString& referrer_url,
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: referrer_url; type: string_byref_const // Unverified params: referrer_url
DCHECK(!referrer_url.empty());
if (referrer_url.empty())
return;
// Execute // Execute
_struct->set_referrer(_struct, referrer_url.GetStruct(), policy); _struct->set_referrer(_struct, referrer_url.GetStruct(), policy);
@ -274,10 +271,7 @@ void CefRequestCToCpp::SetHeaderByName(const CefString& name,
DCHECK(!name.empty()); DCHECK(!name.empty());
if (name.empty()) if (name.empty())
return; return;
// Verify param: value; type: string_byref_const // Unverified params: value
DCHECK(!value.empty());
if (value.empty())
return;
// Execute // Execute
_struct->set_header_by_name(_struct, name.GetStruct(), value.GetStruct(), _struct->set_header_by_name(_struct, name.GetStruct(), value.GetStruct(),
@ -369,10 +363,7 @@ void CefRequestCToCpp::SetFirstPartyForCookies(const CefString& url) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: url; type: string_byref_const // Unverified params: url
DCHECK(!url.empty());
if (url.empty())
return;
// Execute // Execute
_struct->set_first_party_for_cookies(_struct, url.GetStruct()); _struct->set_first_party_for_cookies(_struct, url.GetStruct());

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory // implementations. See the translator.README.txt file in the tools directory
// for more information. // for more information.
// //
// $hash=781ea7e9b24023e8bdcc80ed93b27eb7ef80aa69$ // $hash=0c197661e7e3e905b90bec127ff7b1ff23240fa2$
// //
#include "libcef_dll/ctocpp/response_ctocpp.h" #include "libcef_dll/ctocpp/response_ctocpp.h"
@ -118,10 +118,7 @@ void CefResponseCToCpp::SetStatusText(const CefString& statusText) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: statusText; type: string_byref_const // Unverified params: statusText
DCHECK(!statusText.empty());
if (statusText.empty())
return;
// Execute // Execute
_struct->set_status_text(_struct, statusText.GetStruct()); _struct->set_status_text(_struct, statusText.GetStruct());
@ -151,10 +148,7 @@ void CefResponseCToCpp::SetMimeType(const CefString& mimeType) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: mimeType; type: string_byref_const // Unverified params: mimeType
DCHECK(!mimeType.empty());
if (mimeType.empty())
return;
// Execute // Execute
_struct->set_mime_type(_struct, mimeType.GetStruct()); _struct->set_mime_type(_struct, mimeType.GetStruct());
@ -184,10 +178,7 @@ void CefResponseCToCpp::SetCharset(const CefString& charset) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: charset; type: string_byref_const // Unverified params: charset
DCHECK(!charset.empty());
if (charset.empty())
return;
// Execute // Execute
_struct->set_charset(_struct, charset.GetStruct()); _struct->set_charset(_struct, charset.GetStruct());
@ -286,10 +277,7 @@ NO_SANITIZE("cfi-icall") void CefResponseCToCpp::SetURL(const CefString& url) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: url; type: string_byref_const // Unverified params: url
DCHECK(!url.empty());
if (url.empty())
return;
// Execute // Execute
_struct->set_url(_struct, url.GetStruct()); _struct->set_url(_struct, url.GetStruct());

View File

@ -20,18 +20,165 @@
namespace { namespace {
// Normal stream resource handler implementation that additionally verifies
// calls to Cancel.
class NormalResourceHandler : public CefStreamResourceHandler {
public:
NormalResourceHandler(int status_code,
const CefString& status_text,
const CefString& mime_type,
CefResponse::HeaderMap header_map,
CefRefPtr<CefStreamReader> stream,
const base::Closure& destroy_callback)
: CefStreamResourceHandler(status_code,
status_text,
mime_type,
header_map,
stream),
destroy_callback_(destroy_callback) {}
~NormalResourceHandler() override {
if (IsNetworkServiceEnabled())
EXPECT_EQ(1, cancel_ct_);
else
EXPECT_EQ(0, cancel_ct_);
destroy_callback_.Run();
}
void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private:
const base::Closure destroy_callback_;
int cancel_ct_ = 0;
DISALLOW_COPY_AND_ASSIGN(NormalResourceHandler);
};
// Resource handler implementation that never completes. Used to test
// destruction handling behavior for in-progress requests.
class IncompleteResourceHandler : public CefResourceHandler {
public:
enum TestMode {
BLOCK_PROCESS_REQUEST,
BLOCK_READ_RESPONSE,
};
IncompleteResourceHandler(TestMode test_mode,
const std::string& mime_type,
const base::Closure& destroy_callback)
: test_mode_(test_mode),
mime_type_(mime_type),
destroy_callback_(destroy_callback) {}
~IncompleteResourceHandler() override {
EXPECT_EQ(1, process_request_ct_);
if (IsNetworkServiceEnabled())
EXPECT_EQ(1, cancel_ct_);
else
EXPECT_EQ(0, 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_);
}
destroy_callback_.Run();
}
bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
process_request_ct_++;
if (test_mode_ == BLOCK_PROCESS_REQUEST) {
// Never release or execute this callback.
incomplete_callback_ = callback;
} else {
callback->Continue();
}
return true;
}
void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64& response_length,
CefString& redirectUrl) override {
EXPECT_IO_THREAD();
EXPECT_EQ(test_mode_, BLOCK_READ_RESPONSE);
get_response_headers_ct_++;
response->SetStatus(200);
response->SetStatusText("OK");
response->SetMimeType(mime_type_);
response_length = 100;
}
bool ReadResponse(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
EXPECT_EQ(test_mode_, BLOCK_READ_RESPONSE);
read_response_ct_++;
// Never release or execute this callback.
incomplete_callback_ = callback;
bytes_read = 0;
return true;
}
void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private:
const TestMode test_mode_;
const std::string mime_type_;
const base::Closure destroy_callback_;
int process_request_ct_ = 0;
int get_response_headers_ct_ = 0;
int read_response_ct_ = 0;
int cancel_ct_ = 0;
CefRefPtr<CefCallback> incomplete_callback_;
IMPLEMENT_REFCOUNTING(IncompleteResourceHandler);
DISALLOW_COPY_AND_ASSIGN(IncompleteResourceHandler);
};
class BasicResponseTest : public TestHandler { class BasicResponseTest : public TestHandler {
public: public:
enum TestMode { enum TestMode {
// Normal load, nothing fancy. // Normal load, nothing fancy.
LOAD, 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 the request (add headers) in OnBeforeResourceLoad.
MODIFY_BEFORE_RESOURCE_LOAD, MODIFY_BEFORE_RESOURCE_LOAD,
// Redirect the request (change the URL) in OnBeforeResourceLoad. // Redirect the request (change the URL) in OnBeforeResourceLoad.
REDIRECT_BEFORE_RESOURCE_LOAD, REDIRECT_BEFORE_RESOURCE_LOAD,
// Return a CefResourceHandler from GetResourceHandler that never completes,
// then close the browser to verify destruction handling of in-progress
// requests.
INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST,
INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE,
// Redirect the request using a CefResourceHandler returned from // Redirect the request using a CefResourceHandler returned from
// GetResourceHandler. // GetResourceHandler.
REDIRECT_REQUEST_HANDLER, REDIRECT_REQUEST_HANDLER,
@ -157,6 +304,14 @@ class BasicResponseTest : public TestHandler {
on_before_resource_load_ct_++; 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) { if (mode_ == MODIFY_BEFORE_RESOURCE_LOAD) {
// Expect this data in the request for future callbacks. // Expect this data in the request for future callbacks.
SetCustomHeader(request); SetCustomHeader(request);
@ -182,6 +337,12 @@ class BasicResponseTest : public TestHandler {
get_resource_handler_ct_++; get_resource_handler_ct_++;
if (IsIncompleteRequestHandler()) {
// Close the browser asynchronously to complete the test.
CloseBrowserAsync();
return GetIncompleteResource();
}
const std::string& url = request->GetURL(); const std::string& url = request->GetURL();
if (url == GetURL(RESULT_HTML) && mode_ == RESTART_RESOURCE_RESPONSE) { if (url == GetURL(RESULT_HTML) && mode_ == RESTART_RESOURCE_RESPONSE) {
if (get_resource_handler_ct_ == 1) { if (get_resource_handler_ct_ == 1) {
@ -314,7 +475,7 @@ class BasicResponseTest : public TestHandler {
VerifyState(kOnResourceLoadComplete, request, response); VerifyState(kOnResourceLoadComplete, request, response);
if (unhandled_) { if (unhandled_ || IsIncomplete()) {
EXPECT_EQ(UR_FAILED, status); EXPECT_EQ(UR_FAILED, status);
EXPECT_EQ(0, received_content_length); EXPECT_EQ(0, received_content_length);
} else { } else {
@ -324,6 +485,10 @@ class BasicResponseTest : public TestHandler {
} }
on_resource_load_complete_ct_++; on_resource_load_complete_ct_++;
if (IsIncomplete()) {
MaybeDestroyTest(false);
}
} }
void OnProtocolExecution(CefRefPtr<CefBrowser> browser, void OnProtocolExecution(CefRefPtr<CefBrowser> browser,
@ -474,19 +639,51 @@ class BasicResponseTest : public TestHandler {
else else
EXPECT_EQ(1, on_resource_response_ct_); EXPECT_EQ(1, on_resource_response_ct_);
} }
} else if (IsIncomplete()) {
EXPECT_EQ(1, on_before_browse_ct_);
if (IsNetworkServiceEnabled()) {
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_RESPONSE) {
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 { } else {
NOTREACHED(); NOTREACHED();
} }
EXPECT_EQ(resource_handler_created_ct_, resource_handler_destroyed_ct_);
EXPECT_EQ(1, on_resource_load_complete_ct_); EXPECT_EQ(1, on_resource_load_complete_ct_);
EXPECT_EQ(1, on_load_end_ct_);
if (custom_scheme_ && unhandled_) if (IsIncomplete())
EXPECT_EQ(0, on_load_end_ct_);
else
EXPECT_EQ(1, on_load_end_ct_);
if (custom_scheme_ && unhandled_ && !IsIncomplete())
EXPECT_EQ(1, on_protocol_execution_ct_); EXPECT_EQ(1, on_protocol_execution_ct_);
else else
EXPECT_EQ(0, on_protocol_execution_ct_); EXPECT_EQ(0, on_protocol_execution_ct_);
TestHandler::DestroyTest(); TestHandler::DestroyTest();
if (!SignalCompletionWhenAllBrowsersClose()) {
// Complete asynchronously so the call stack has a chance to unwind.
CefPostTask(TID_UI, base::Bind(&BasicResponseTest::TestComplete, this));
}
} }
private: private:
@ -518,7 +715,7 @@ class BasicResponseTest : public TestHandler {
} }
const char* GetStartupURL() const { const char* GetStartupURL() const {
if (IsLoad()) { if (IsLoad() || IsIncomplete()) {
return GetURL(RESULT_HTML); return GetURL(RESULT_HTML);
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) { } else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
return GetURL(REDIRECT2_HTML); return GetURL(REDIRECT2_HTML);
@ -537,17 +734,23 @@ class BasicResponseTest : public TestHandler {
return "<html><body>Redirect</body></html>"; return "<html><body>Redirect</body></html>";
} }
CefRefPtr<CefResourceHandler> GetOKResource() const { base::Closure GetResourceDestroyCallback() {
resource_handler_created_ct_++;
return base::Bind(&BasicResponseTest::MaybeDestroyTest, this, true);
}
CefRefPtr<CefResourceHandler> GetOKResource() {
const std::string& body = GetResponseBody(); const std::string& body = GetResponseBody();
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData( CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size()); const_cast<char*>(body.c_str()), body.size());
return new CefStreamResourceHandler(200, "OK", "text/html", return new NormalResourceHandler(200, "OK", "text/html",
CefResponse::HeaderMap(), stream); CefResponse::HeaderMap(), stream,
GetResourceDestroyCallback());
} }
CefRefPtr<CefResourceHandler> GetRedirectResource( CefRefPtr<CefResourceHandler> GetRedirectResource(
const std::string& redirect_url) const { const std::string& redirect_url) {
const std::string& body = GetRedirectBody(); const std::string& body = GetRedirectBody();
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData( CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size()); const_cast<char*>(body.c_str()), body.size());
@ -555,8 +758,17 @@ class BasicResponseTest : public TestHandler {
CefResponse::HeaderMap headerMap; CefResponse::HeaderMap headerMap;
headerMap.insert(std::make_pair("Location", redirect_url)); headerMap.insert(std::make_pair("Location", redirect_url));
return new CefStreamResourceHandler(307, "Temporary Redirect", "text/html", return new NormalResourceHandler(307, "Temporary Redirect", "text/html",
headerMap, stream); headerMap, stream,
GetResourceDestroyCallback());
}
CefRefPtr<CefResourceHandler> GetIncompleteResource() {
return new IncompleteResourceHandler(
mode_ == INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST
? IncompleteResourceHandler::BLOCK_PROCESS_REQUEST
: IncompleteResourceHandler::BLOCK_READ_RESPONSE,
"text/html", GetResourceDestroyCallback());
} }
bool IsLoad() const { bool IsLoad() const {
@ -564,6 +776,16 @@ class BasicResponseTest : public TestHandler {
mode_ == RESTART_RESOURCE_RESPONSE; mode_ == RESTART_RESOURCE_RESPONSE;
} }
bool IsIncompleteRequestHandler() const {
return mode_ == INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST ||
mode_ == INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE;
}
bool IsIncomplete() const {
return mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
IsIncompleteRequestHandler();
}
bool IsRedirect() const { bool IsRedirect() const {
return mode_ == REDIRECT_BEFORE_RESOURCE_LOAD || return mode_ == REDIRECT_BEFORE_RESOURCE_LOAD ||
mode_ == REDIRECT_REQUEST_HANDLER || mode_ == REDIRECT_REQUEST_HANDLER ||
@ -630,7 +852,7 @@ class BasicResponseTest : public TestHandler {
EXPECT_EQ(request_id_, request->GetIdentifier()) << callback; EXPECT_EQ(request_id_, request->GetIdentifier()) << callback;
} }
if (IsLoad()) { if (IsLoad() || IsIncomplete()) {
EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback; EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback;
EXPECT_STREQ(GetURL(RESULT_HTML), request->GetURL().ToString().c_str()) EXPECT_STREQ(GetURL(RESULT_HTML), request->GetURL().ToString().c_str())
<< callback; << callback;
@ -686,8 +908,18 @@ class BasicResponseTest : public TestHandler {
mode_ == RESTART_RESOURCE_RESPONSE) && mode_ == RESTART_RESOURCE_RESPONSE) &&
get_resource_handler_ct_ == 1; get_resource_handler_ct_ == 1;
if (unhandled_ && !override_unhandled) { // True for tests where the request will be incomplete and never receive a
EXPECT_EQ(ERR_UNKNOWN_URL_SCHEME, response->GetError()) << callback; // response.
const bool incomplete_unhandled =
(mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
mode_ == INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST);
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_EQ(0, response->GetStatus()) << callback;
EXPECT_STREQ("", response->GetStatusText().ToString().c_str()) EXPECT_STREQ("", response->GetStatusText().ToString().c_str())
<< callback; << callback;
@ -695,7 +927,13 @@ class BasicResponseTest : public TestHandler {
EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback; EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback; EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
} else { } else {
EXPECT_EQ(ERR_NONE, response->GetError()) << callback; if (mode_ == INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE &&
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_EQ(200, response->GetStatus()) << callback;
EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str()) EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str())
<< callback; << callback;
@ -719,6 +957,45 @@ class BasicResponseTest : public TestHandler {
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback; EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
} }
void CloseBrowserAsync() {
EXPECT_TRUE(IsIncomplete());
SetSignalCompletionWhenAllBrowsersClose(false);
CefPostDelayedTask(
TID_UI, base::Bind(&TestHandler::CloseBrowser, GetBrowser(), false),
100);
}
void MaybeDestroyTest(bool from_handler) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(&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 TestMode mode_;
const bool custom_scheme_; const bool custom_scheme_;
const bool unhandled_; const bool unhandled_;
@ -726,6 +1003,8 @@ class BasicResponseTest : public TestHandler {
int browser_id_ = 0; int browser_id_ = 0;
uint64 request_id_ = 0U; uint64 request_id_ = 0U;
int resource_handler_created_ct_ = 0;
int on_before_browse_ct_ = 0; int on_before_browse_ct_ = 0;
int on_load_end_ct_ = 0; int on_load_end_ct_ = 0;
@ -738,6 +1017,10 @@ class BasicResponseTest : public TestHandler {
int get_resource_response_filter_ct_ = 0; int get_resource_response_filter_ct_ = 0;
int on_resource_load_complete_ct_ = 0; int on_resource_load_complete_ct_ = 0;
int on_protocol_execution_ct_ = 0; int on_protocol_execution_ct_ = 0;
int resource_handler_destroyed_ct_ = 0;
// Used with INCOMPLETE_BEFORE_RESOURCE_LOAD.
CefRefPtr<CefRequestCallback> incomplete_callback_;
DISALLOW_COPY_AND_ASSIGN(BasicResponseTest); DISALLOW_COPY_AND_ASSIGN(BasicResponseTest);
IMPLEMENT_REFCOUNTING(BasicResponseTest); IMPLEMENT_REFCOUNTING(BasicResponseTest);
@ -746,10 +1029,21 @@ class BasicResponseTest : public TestHandler {
bool IsTestSupported(BasicResponseTest::TestMode test_mode, bool IsTestSupported(BasicResponseTest::TestMode test_mode,
bool custom_scheme, bool custom_scheme,
bool unhandled) { bool unhandled) {
if (!IsNetworkServiceEnabled() && (custom_scheme || unhandled)) { if (!IsNetworkServiceEnabled()) {
// The old network implementation does not support the same functionality if (custom_scheme || unhandled) {
// for custom schemes and unhandled requests. // The old network implementation does not support the same functionality
return false; // for custom schemes and unhandled requests.
return false;
}
if (test_mode == BasicResponseTest::INCOMPLETE_BEFORE_RESOURCE_LOAD ||
test_mode ==
BasicResponseTest::INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST ||
test_mode ==
BasicResponseTest::INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE) {
// The old network implementation does not support the same behavior
// for canceling incomplete requests.
return false;
}
} }
return true; return true;
} }
@ -782,11 +1076,23 @@ bool IsTestSupported(BasicResponseTest::TestMode test_mode,
BASIC_TEST(name##RestartResourceResponse, RESTART_RESOURCE_RESPONSE, custom, \ BASIC_TEST(name##RestartResourceResponse, RESTART_RESOURCE_RESPONSE, custom, \
unhandled) unhandled)
// Tests only supported in handled mode.
#define BASIC_TEST_HANDLED_MODES(name, custom) \
BASIC_TEST(name##IncompleteBeforeResourceLoad, \
INCOMPLETE_BEFORE_RESOURCE_LOAD, custom, false) \
BASIC_TEST(name##IncompleteRequestHandlerProcessRequest, \
INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST, custom, false) \
BASIC_TEST(name##IncompleteRequestHandlerReadResponse, \
INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE, custom, false)
BASIC_TEST_ALL_MODES(StandardHandled, false, false) BASIC_TEST_ALL_MODES(StandardHandled, false, false)
BASIC_TEST_ALL_MODES(StandardUnhandled, false, true) BASIC_TEST_ALL_MODES(StandardUnhandled, false, true)
BASIC_TEST_ALL_MODES(CustomHandled, true, false) BASIC_TEST_ALL_MODES(CustomHandled, true, false)
BASIC_TEST_ALL_MODES(CustomUnhandled, true, true) BASIC_TEST_ALL_MODES(CustomUnhandled, true, true)
BASIC_TEST_HANDLED_MODES(StandardHandled, false)
BASIC_TEST_HANDLED_MODES(CustomHandled, true)
namespace { namespace {
const char kSubresourceProcessMsg[] = "SubresourceMsg"; const char kSubresourceProcessMsg[] = "SubresourceMsg";
@ -797,12 +1103,22 @@ class SubresourceResponseTest : public RoutingTestHandler {
// Normal load, nothing fancy. // Normal load, nothing fancy.
LOAD, 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 the request (add headers) in OnBeforeResourceLoad.
MODIFY_BEFORE_RESOURCE_LOAD, MODIFY_BEFORE_RESOURCE_LOAD,
// Redirect the request (change the URL) in OnBeforeResourceLoad. // Redirect the request (change the URL) in OnBeforeResourceLoad.
REDIRECT_BEFORE_RESOURCE_LOAD, REDIRECT_BEFORE_RESOURCE_LOAD,
// Return a CefResourceHandler from GetResourceHandler that never completes,
// then close the browser to verify destruction handling of in-progress
// requests.
INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST,
INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE,
// Redirect the request using a CefResourceHandler returned from // Redirect the request using a CefResourceHandler returned from
// GetResourceHandler. // GetResourceHandler.
REDIRECT_REQUEST_HANDLER, REDIRECT_REQUEST_HANDLER,
@ -979,6 +1295,14 @@ class SubresourceResponseTest : public RoutingTestHandler {
on_before_resource_load_ct_++; 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) { if (mode_ == MODIFY_BEFORE_RESOURCE_LOAD) {
// Expect this data in the request for future callbacks. // Expect this data in the request for future callbacks.
SetCustomHeader(request); SetCustomHeader(request);
@ -1014,6 +1338,12 @@ class SubresourceResponseTest : public RoutingTestHandler {
get_resource_handler_ct_++; get_resource_handler_ct_++;
if (IsIncompleteRequestHandler()) {
// Close the browser asynchronously to complete the test.
CloseBrowserAsync();
return GetIncompleteResource();
}
const std::string& url = request->GetURL(); const std::string& url = request->GetURL();
if (url == GetURL(RESULT_JS) && mode_ == RESTART_RESOURCE_RESPONSE) { if (url == GetURL(RESULT_JS) && mode_ == RESTART_RESOURCE_RESPONSE) {
if (get_resource_handler_ct_ == 1) { if (get_resource_handler_ct_ == 1) {
@ -1188,7 +1518,7 @@ class SubresourceResponseTest : public RoutingTestHandler {
VerifyState(kOnResourceLoadComplete, request, response); VerifyState(kOnResourceLoadComplete, request, response);
if (unhandled_) { if (unhandled_ || IsIncomplete()) {
EXPECT_EQ(UR_FAILED, status); EXPECT_EQ(UR_FAILED, status);
EXPECT_EQ(0, received_content_length); EXPECT_EQ(0, received_content_length);
} else { } else {
@ -1198,6 +1528,10 @@ class SubresourceResponseTest : public RoutingTestHandler {
} }
on_resource_load_complete_ct_++; on_resource_load_complete_ct_++;
if (IsIncomplete()) {
MaybeDestroyTest(false);
}
} }
void OnProtocolExecution(CefRefPtr<CefBrowser> browser, void OnProtocolExecution(CefRefPtr<CefBrowser> browser,
@ -1235,7 +1569,7 @@ class SubresourceResponseTest : public RoutingTestHandler {
on_load_end_ct_++; on_load_end_ct_++;
TestHandler::OnLoadEnd(browser, frame, httpStatusCode); TestHandler::OnLoadEnd(browser, frame, httpStatusCode);
MaybeDestroyTest(); MaybeDestroyTest(false);
} }
bool OnQuery(CefRefPtr<CefBrowser> browser, bool OnQuery(CefRefPtr<CefBrowser> browser,
@ -1254,18 +1588,11 @@ class SubresourceResponseTest : public RoutingTestHandler {
callback->Success(""); callback->Success("");
on_query_ct_++; on_query_ct_++;
MaybeDestroyTest(); MaybeDestroyTest(false);
return true; return true;
} }
void MaybeDestroyTest() {
if (on_load_end_ct_ > (subframe_ ? 1 : 0) &&
(on_query_ct_ > 0 || unhandled_)) {
DestroyTest();
}
}
void DestroyTest() override { void DestroyTest() override {
if (!IsNetworkServiceEnabled()) { if (!IsNetworkServiceEnabled()) {
// Called once for each other callback. // Called once for each other callback.
@ -1380,29 +1707,61 @@ class SubresourceResponseTest : public RoutingTestHandler {
else else
EXPECT_EQ(1, on_resource_response_ct_); EXPECT_EQ(1, on_resource_response_ct_);
} }
} else if (IsIncomplete()) {
if (IsNetworkServiceEnabled()) {
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_RESPONSE) {
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 { } else {
NOTREACHED(); NOTREACHED();
} }
EXPECT_EQ(resource_handler_created_ct_, resource_handler_destroyed_ct_);
EXPECT_EQ(1, on_resource_load_complete_ct_); EXPECT_EQ(1, on_resource_load_complete_ct_);
// Only called for the main and/or sub frame load. // Only called for the main and/or sub frame load.
if (subframe_) if (IsIncomplete()) {
EXPECT_EQ(2, on_load_end_ct_); EXPECT_EQ(0, on_load_end_ct_);
else } else {
EXPECT_EQ(1, on_load_end_ct_); if (subframe_)
EXPECT_EQ(2, on_load_end_ct_);
else
EXPECT_EQ(1, on_load_end_ct_);
}
if (unhandled_) if (unhandled_ || IsIncomplete())
EXPECT_EQ(0, on_query_ct_); EXPECT_EQ(0, on_query_ct_);
else else
EXPECT_EQ(1, on_query_ct_); EXPECT_EQ(1, on_query_ct_);
if (custom_scheme_ && unhandled_) if (custom_scheme_ && unhandled_ && !IsIncomplete())
EXPECT_EQ(1, on_protocol_execution_ct_); EXPECT_EQ(1, on_protocol_execution_ct_);
else else
EXPECT_EQ(0, on_protocol_execution_ct_); EXPECT_EQ(0, on_protocol_execution_ct_);
TestHandler::DestroyTest(); TestHandler::DestroyTest();
if (!SignalCompletionWhenAllBrowsersClose()) {
// Complete asynchronously so the call stack has a chance to unwind.
CefPostTask(TID_UI,
base::Bind(&SubresourceResponseTest::TestComplete, this));
}
} }
private: private:
@ -1461,7 +1820,7 @@ class SubresourceResponseTest : public RoutingTestHandler {
} }
const char* GetStartupURL() const { const char* GetStartupURL() const {
if (IsLoad()) { if (IsLoad() || IsIncomplete()) {
return GetURL(RESULT_JS); return GetURL(RESULT_JS);
} else if (mode_ == REDIRECT_RESOURCE_REDIRECT) { } else if (mode_ == REDIRECT_RESOURCE_REDIRECT) {
return GetURL(REDIRECT2_JS); return GetURL(REDIRECT2_JS);
@ -1511,35 +1870,43 @@ class SubresourceResponseTest : public RoutingTestHandler {
return "<html><body>Redirect</body></html>"; return "<html><body>Redirect</body></html>";
} }
CefRefPtr<CefResourceHandler> GetMainResource() const { base::Closure GetResourceDestroyCallback() {
resource_handler_created_ct_++;
return base::Bind(&SubresourceResponseTest::MaybeDestroyTest, this, true);
}
CefRefPtr<CefResourceHandler> GetMainResource() {
const std::string& body = GetMainResponseBody(); const std::string& body = GetMainResponseBody();
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData( CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size()); const_cast<char*>(body.c_str()), body.size());
return new CefStreamResourceHandler(200, "OK", "text/html", return new NormalResourceHandler(200, "OK", "text/html",
CefResponse::HeaderMap(), stream); CefResponse::HeaderMap(), stream,
GetResourceDestroyCallback());
} }
CefRefPtr<CefResourceHandler> GetSubResource() const { CefRefPtr<CefResourceHandler> GetSubResource() {
const std::string& body = GetSubResponseBody(); const std::string& body = GetSubResponseBody();
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData( CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size()); const_cast<char*>(body.c_str()), body.size());
return new CefStreamResourceHandler(200, "OK", "text/html", return new NormalResourceHandler(200, "OK", "text/html",
CefResponse::HeaderMap(), stream); CefResponse::HeaderMap(), stream,
GetResourceDestroyCallback());
} }
CefRefPtr<CefResourceHandler> GetOKResource() const { CefRefPtr<CefResourceHandler> GetOKResource() {
const std::string& body = GetResponseBody(); const std::string& body = GetResponseBody();
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData( CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size()); const_cast<char*>(body.c_str()), body.size());
return new CefStreamResourceHandler(200, "OK", "text/javascript", return new NormalResourceHandler(200, "OK", "text/javascript",
CefResponse::HeaderMap(), stream); CefResponse::HeaderMap(), stream,
GetResourceDestroyCallback());
} }
CefRefPtr<CefResourceHandler> GetRedirectResource( CefRefPtr<CefResourceHandler> GetRedirectResource(
const std::string& redirect_url) const { const std::string& redirect_url) {
const std::string& body = GetRedirectBody(); const std::string& body = GetRedirectBody();
CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData( CefRefPtr<CefStreamReader> stream = CefStreamReader::CreateForData(
const_cast<char*>(body.c_str()), body.size()); const_cast<char*>(body.c_str()), body.size());
@ -1547,8 +1914,17 @@ class SubresourceResponseTest : public RoutingTestHandler {
CefResponse::HeaderMap headerMap; CefResponse::HeaderMap headerMap;
headerMap.insert(std::make_pair("Location", redirect_url)); headerMap.insert(std::make_pair("Location", redirect_url));
return new CefStreamResourceHandler(307, "Temporary Redirect", return new NormalResourceHandler(307, "Temporary Redirect",
"text/javascript", headerMap, stream); "text/javascript", headerMap, stream,
GetResourceDestroyCallback());
}
CefRefPtr<CefResourceHandler> GetIncompleteResource() {
return new IncompleteResourceHandler(
mode_ == INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST
? IncompleteResourceHandler::BLOCK_PROCESS_REQUEST
: IncompleteResourceHandler::BLOCK_READ_RESPONSE,
"text/javascript", GetResourceDestroyCallback());
} }
bool IsLoad() const { bool IsLoad() const {
@ -1556,6 +1932,16 @@ class SubresourceResponseTest : public RoutingTestHandler {
mode_ == RESTART_RESOURCE_RESPONSE; mode_ == RESTART_RESOURCE_RESPONSE;
} }
bool IsIncompleteRequestHandler() const {
return mode_ == INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST ||
mode_ == INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE;
}
bool IsIncomplete() const {
return mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
IsIncompleteRequestHandler();
}
bool IsRedirect() const { bool IsRedirect() const {
return mode_ == REDIRECT_BEFORE_RESOURCE_LOAD || return mode_ == REDIRECT_BEFORE_RESOURCE_LOAD ||
mode_ == REDIRECT_REQUEST_HANDLER || mode_ == REDIRECT_REQUEST_HANDLER ||
@ -1633,7 +2019,7 @@ class SubresourceResponseTest : public RoutingTestHandler {
EXPECT_EQ(request_id_, request->GetIdentifier()) << callback; EXPECT_EQ(request_id_, request->GetIdentifier()) << callback;
} }
if (IsLoad()) { if (IsLoad() || IsIncomplete()) {
EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback; EXPECT_STREQ("GET", request->GetMethod().ToString().c_str()) << callback;
EXPECT_STREQ(GetURL(RESULT_JS), request->GetURL().ToString().c_str()) EXPECT_STREQ(GetURL(RESULT_JS), request->GetURL().ToString().c_str())
<< callback; << callback;
@ -1697,8 +2083,18 @@ class SubresourceResponseTest : public RoutingTestHandler {
mode_ == RESTART_RESOURCE_RESPONSE) && mode_ == RESTART_RESOURCE_RESPONSE) &&
get_resource_handler_ct_ == 1; get_resource_handler_ct_ == 1;
if (unhandled_ && !override_unhandled) { // True for tests where the request will be incomplete and never receive a
EXPECT_EQ(ERR_UNKNOWN_URL_SCHEME, response->GetError()) << callback; // response.
const bool incomplete_unhandled =
(mode_ == INCOMPLETE_BEFORE_RESOURCE_LOAD ||
mode_ == INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST);
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_EQ(0, response->GetStatus()) << callback;
EXPECT_STREQ("", response->GetStatusText().ToString().c_str()) EXPECT_STREQ("", response->GetStatusText().ToString().c_str())
<< callback; << callback;
@ -1706,7 +2102,13 @@ class SubresourceResponseTest : public RoutingTestHandler {
EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback; EXPECT_STREQ("", response->GetMimeType().ToString().c_str()) << callback;
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback; EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
} else { } else {
EXPECT_EQ(ERR_NONE, response->GetError()) << callback; if (mode_ == INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE &&
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_EQ(200, response->GetStatus()) << callback;
EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str()) EXPECT_STREQ("OK", response->GetStatusText().ToString().c_str())
<< callback; << callback;
@ -1731,6 +2133,47 @@ class SubresourceResponseTest : public RoutingTestHandler {
EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback; EXPECT_STREQ("", response->GetCharset().ToString().c_str()) << callback;
} }
void CloseBrowserAsync() {
EXPECT_TRUE(IsIncomplete());
SetSignalCompletionWhenAllBrowsersClose(false);
CefPostDelayedTask(
TID_UI, base::Bind(&TestHandler::CloseBrowser, GetBrowser(), false),
100);
}
void MaybeDestroyTest(bool from_handler) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(&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 TestMode mode_;
const bool custom_scheme_; const bool custom_scheme_;
const bool unhandled_; const bool unhandled_;
@ -1740,6 +2183,8 @@ class SubresourceResponseTest : public RoutingTestHandler {
int64 frame_id_ = 0; int64 frame_id_ = 0;
uint64 request_id_ = 0U; uint64 request_id_ = 0U;
int resource_handler_created_ct_ = 0;
int on_before_browse_ct_ = 0; int on_before_browse_ct_ = 0;
int on_load_end_ct_ = 0; int on_load_end_ct_ = 0;
int on_query_ct_ = 0; int on_query_ct_ = 0;
@ -1753,6 +2198,10 @@ class SubresourceResponseTest : public RoutingTestHandler {
int get_resource_response_filter_ct_ = 0; int get_resource_response_filter_ct_ = 0;
int on_resource_load_complete_ct_ = 0; int on_resource_load_complete_ct_ = 0;
int on_protocol_execution_ct_ = 0; int on_protocol_execution_ct_ = 0;
int resource_handler_destroyed_ct_ = 0;
// Used with INCOMPLETE_BEFORE_RESOURCE_LOAD.
CefRefPtr<CefRequestCallback> incomplete_callback_;
DISALLOW_COPY_AND_ASSIGN(SubresourceResponseTest); DISALLOW_COPY_AND_ASSIGN(SubresourceResponseTest);
IMPLEMENT_REFCOUNTING(SubresourceResponseTest); IMPLEMENT_REFCOUNTING(SubresourceResponseTest);
@ -1762,10 +2211,21 @@ bool IsTestSupported(SubresourceResponseTest::TestMode test_mode,
bool custom_scheme, bool custom_scheme,
bool unhandled, bool unhandled,
bool subframe) { bool subframe) {
if (!IsNetworkServiceEnabled() && (custom_scheme || unhandled)) { if (!IsNetworkServiceEnabled()) {
// The old network implementation does not support the same functionality if (custom_scheme || unhandled) {
// for custom schemes and unhandled requests. // The old network implementation does not support the same functionality
return false; // for custom schemes and unhandled requests.
return false;
}
if (test_mode == SubresourceResponseTest::INCOMPLETE_BEFORE_RESOURCE_LOAD ||
test_mode == SubresourceResponseTest::
INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST ||
test_mode ==
SubresourceResponseTest::INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE) {
// The old network implementation does not support the same behavior
// for canceling incomplete requests.
return false;
}
} }
return true; return true;
} }
@ -1799,6 +2259,17 @@ bool IsTestSupported(SubresourceResponseTest::TestMode test_mode,
SUBRESOURCE_TEST(name##RestartResourceResponse, RESTART_RESOURCE_RESPONSE, \ SUBRESOURCE_TEST(name##RestartResourceResponse, RESTART_RESOURCE_RESPONSE, \
custom, unhandled, subframe) custom, unhandled, subframe)
// Tests only supported in handled mode.
#define SUBRESOURCE_TEST_HANDLED_MODES(name, custom, subframe) \
SUBRESOURCE_TEST(name##IncompleteBeforeResourceLoad, \
INCOMPLETE_BEFORE_RESOURCE_LOAD, custom, false, subframe) \
SUBRESOURCE_TEST(name##IncompleteRequestHandlerProcessRequest, \
INCOMPLETE_REQUEST_HANDLER_PROCESS_REQUEST, custom, false, \
subframe) \
SUBRESOURCE_TEST(name##IncompleteRequestHandlerReadResponse, \
INCOMPLETE_REQUEST_HANDLER_READ_RESPONSE, custom, false, \
subframe)
SUBRESOURCE_TEST_ALL_MODES(StandardHandledMainFrame, false, false, false) SUBRESOURCE_TEST_ALL_MODES(StandardHandledMainFrame, false, false, false)
SUBRESOURCE_TEST_ALL_MODES(StandardUnhandledMainFrame, false, true, false) SUBRESOURCE_TEST_ALL_MODES(StandardUnhandledMainFrame, false, true, false)
SUBRESOURCE_TEST_ALL_MODES(CustomHandledMainFrame, true, false, false) SUBRESOURCE_TEST_ALL_MODES(CustomHandledMainFrame, true, false, false)
@ -1809,6 +2280,12 @@ SUBRESOURCE_TEST_ALL_MODES(StandardUnhandledSubFrame, false, true, true)
SUBRESOURCE_TEST_ALL_MODES(CustomHandledSubFrame, true, false, true) SUBRESOURCE_TEST_ALL_MODES(CustomHandledSubFrame, true, false, true)
SUBRESOURCE_TEST_ALL_MODES(CustomUnhandledSubFrame, true, true, 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 { namespace {
const char kResourceTestHtml[] = "http://test.com/resource.html"; const char kResourceTestHtml[] = "http://test.com/resource.html";

View File

@ -34,8 +34,9 @@ using client::ClientAppRenderer;
namespace { namespace {
// Unique values for URLRequest tests. // Unique values for URLRequest tests.
const char* kRequestTestUrl = "http://tests/URLRequestTest.Test"; const char kRequestTestUrl[] = "http://tests/URLRequestTest.Test";
const char* kRequestTestMsg = "URLRequestTest.Test"; const char kRequestTestMsg[] = "URLRequestTest.Test";
const char kIncompleteRequestTestMsg[] = "URLRequestTest.IncompleteRequestTest";
// TEST DATA // TEST DATA
@ -53,6 +54,9 @@ const char kRequestSaveCookieName[] = "urcookie_save";
const char kCacheControlHeader[] = "cache-control"; const char kCacheControlHeader[] = "cache-control";
// Used with incomplete tests for data that should not be sent.
const char kIncompleteDoNotSendData[] = "DO NOT SEND";
enum RequestTestMode { enum RequestTestMode {
REQTEST_GET = 0, REQTEST_GET = 0,
REQTEST_GET_NODATA, REQTEST_GET_NODATA,
@ -75,6 +79,8 @@ enum RequestTestMode {
REQTEST_CACHE_ONLY_SUCCESS_HEADER, REQTEST_CACHE_ONLY_SUCCESS_HEADER,
REQTEST_CACHE_DISABLE_FLAG, REQTEST_CACHE_DISABLE_FLAG,
REQTEST_CACHE_DISABLE_HEADER, REQTEST_CACHE_DISABLE_HEADER,
REQTEST_INCOMPLETE_PROCESS_REQUEST,
REQTEST_INCOMPLETE_READ_RESPONSE,
}; };
enum ContextTestMode { enum ContextTestMode {
@ -85,18 +91,7 @@ enum ContextTestMode {
// Defines test expectations for a request. // Defines test expectations for a request.
struct RequestRunSettings { struct RequestRunSettings {
RequestRunSettings() RequestRunSettings() {}
: expect_upload_progress(false),
expect_download_progress(true),
expect_download_data(true),
expected_status(UR_SUCCESS),
expected_error_code(ERR_NONE),
expect_send_cookie(false),
expect_save_cookie(false),
expect_follow_redirect(true),
expect_response_was_cached(false),
expected_send_count(-1),
expected_receive_count(-1) {}
// Set expectations for request failure. // Set expectations for request failure.
void SetRequestFailureExpected(cef_errorcode_t error_code) { void SetRequestFailureExpected(cef_errorcode_t error_code) {
@ -118,44 +113,52 @@ struct RequestRunSettings {
// Optional response data that will be returned by the backend. // Optional response data that will be returned by the backend.
std::string response_data; std::string response_data;
// Create an incomplete request to test shutdown behavior.
enum IncompleteType {
INCOMPLETE_NONE,
INCOMPLETE_PROCESS_REQUEST,
INCOMPLETE_READ_RESPONSE,
};
IncompleteType incomplete_type = INCOMPLETE_NONE;
// If true upload progress notification will be expected. // If true upload progress notification will be expected.
bool expect_upload_progress; bool expect_upload_progress = false;
// If true download progress notification will be expected. // If true download progress notification will be expected.
bool expect_download_progress; bool expect_download_progress = true;
// If true download data will be expected. // If true download data will be expected.
bool expect_download_data; bool expect_download_data = true;
// Expected status value. // Expected status value.
CefURLRequest::Status expected_status; CefURLRequest::Status expected_status = UR_SUCCESS;
// Expected error code value. // Expected error code value.
CefURLRequest::ErrorCode expected_error_code; CefURLRequest::ErrorCode expected_error_code = ERR_NONE;
// If true the request cookie should be sent to the server. // If true the request cookie should be sent to the server.
bool expect_send_cookie; bool expect_send_cookie = false;
// If true the response cookie should be saved. // If true the response cookie should be saved.
bool expect_save_cookie; bool expect_save_cookie = false;
// If specified the test will begin with this redirect request and response. // If specified the test will begin with this redirect request and response.
CefRefPtr<CefRequest> redirect_request; CefRefPtr<CefRequest> redirect_request;
CefRefPtr<CefResponse> redirect_response; CefRefPtr<CefResponse> redirect_response;
// If true the redirect is expected to be followed. // If true the redirect is expected to be followed.
bool expect_follow_redirect; bool expect_follow_redirect = true;
// If true the response is expected to be served from cache. // If true the response is expected to be served from cache.
bool expect_response_was_cached; bool expect_response_was_cached = false;
// The expected number of requests to send, or -1 if unspecified. // The expected number of requests to send, or -1 if unspecified.
// Used only with the server backend. // Used only with the server backend.
int expected_send_count; int expected_send_count = -1;
// The expected number of requests to receive, or -1 if unspecified. // The expected number of requests to receive, or -1 if unspecified.
// Used only with the server backend. // Used only with the server backend.
int expected_receive_count; int expected_receive_count = -1;
typedef base::Callback<void(int /* next_send_count */, typedef base::Callback<void(int /* next_send_count */,
const base::Closure& /* complete_callback */)> const base::Closure& /* complete_callback */)>
@ -472,12 +475,20 @@ void GetNormalResponse(const RequestRunSettings* settings,
// Serves request responses. // Serves request responses.
class RequestSchemeHandler : public CefResourceHandler { class RequestSchemeHandler : public CefResourceHandler {
public: public:
explicit RequestSchemeHandler(RequestRunSettings* settings) RequestSchemeHandler(RequestRunSettings* settings,
: settings_(settings), offset_(0) {} const base::Closure& destroy_callback)
: settings_(settings), destroy_callback_(destroy_callback) {}
~RequestSchemeHandler() override {
if (IsNetworkServiceEnabled())
EXPECT_EQ(1, cancel_ct_);
destroy_callback_.Run();
}
bool ProcessRequest(CefRefPtr<CefRequest> request, bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override { CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO)); EXPECT_IO_THREAD();
VerifyNormalRequest(settings_, request, false); VerifyNormalRequest(settings_, request, false);
// HEAD requests are identical to GET requests except no response data is // HEAD requests are identical to GET requests except no response data is
@ -493,7 +504,7 @@ class RequestSchemeHandler : public CefResourceHandler {
void GetResponseHeaders(CefRefPtr<CefResponse> response, void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64& response_length, int64& response_length,
CefString& redirectUrl) override { CefString& redirectUrl) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO)); EXPECT_IO_THREAD();
GetNormalResponse(settings_, response); GetNormalResponse(settings_, response);
response_length = response_data_.length(); response_length = response_data_.length();
} }
@ -502,7 +513,7 @@ class RequestSchemeHandler : public CefResourceHandler {
int bytes_to_read, int bytes_to_read,
int& bytes_read, int& bytes_read,
CefRefPtr<CefCallback> callback) override { CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO)); EXPECT_IO_THREAD();
bool has_data = false; bool has_data = false;
bytes_read = 0; bytes_read = 0;
@ -522,14 +533,20 @@ class RequestSchemeHandler : public CefResourceHandler {
return has_data; return has_data;
} }
void Cancel() override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private: private:
// |settings_| is not owned by this object. // |settings_| is not owned by this object.
RequestRunSettings* settings_; RequestRunSettings* settings_;
base::Closure destroy_callback_;
std::string response_data_; std::string response_data_;
size_t offset_; size_t offset_ = 0;
int cancel_ct_ = 0;
IMPLEMENT_REFCOUNTING(RequestSchemeHandler); IMPLEMENT_REFCOUNTING(RequestSchemeHandler);
}; };
@ -537,13 +554,23 @@ class RequestSchemeHandler : public CefResourceHandler {
// Serves redirect request responses. // Serves redirect request responses.
class RequestRedirectSchemeHandler : public CefResourceHandler { class RequestRedirectSchemeHandler : public CefResourceHandler {
public: public:
explicit RequestRedirectSchemeHandler(CefRefPtr<CefRequest> request, RequestRedirectSchemeHandler(CefRefPtr<CefRequest> request,
CefRefPtr<CefResponse> response) CefRefPtr<CefResponse> response,
: request_(request), response_(response) {} const base::Closure& destroy_callback)
: request_(request),
response_(response),
destroy_callback_(destroy_callback) {}
~RequestRedirectSchemeHandler() override {
if (IsNetworkServiceEnabled())
EXPECT_EQ(1, cancel_ct_);
destroy_callback_.Run();
}
bool ProcessRequest(CefRefPtr<CefRequest> request, bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override { CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO)); EXPECT_IO_THREAD();
// Verify that the request was sent correctly. // Verify that the request was sent correctly.
TestRequestEqual(request_, request, true); TestRequestEqual(request_, request, true);
@ -556,7 +583,7 @@ class RequestRedirectSchemeHandler : public CefResourceHandler {
void GetResponseHeaders(CefRefPtr<CefResponse> response, void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64& response_length, int64& response_length,
CefString& redirectUrl) override { CefString& redirectUrl) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO)); EXPECT_IO_THREAD();
response->SetStatus(response_->GetStatus()); response->SetStatus(response_->GetStatus());
response->SetStatusText(response_->GetStatusText()); response->SetStatusText(response_->GetStatusText());
@ -573,20 +600,128 @@ class RequestRedirectSchemeHandler : public CefResourceHandler {
int bytes_to_read, int bytes_to_read,
int& bytes_read, int& bytes_read,
CefRefPtr<CefCallback> callback) override { CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO)); EXPECT_IO_THREAD();
NOTREACHED(); NOTREACHED();
return false; return false;
} }
void Cancel() override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } void Cancel() override {
EXPECT_IO_THREAD();
cancel_ct_++;
}
private: private:
CefRefPtr<CefRequest> request_; CefRefPtr<CefRequest> request_;
CefRefPtr<CefResponse> response_; CefRefPtr<CefResponse> response_;
base::Closure destroy_callback_;
int cancel_ct_ = 0;
IMPLEMENT_REFCOUNTING(RequestRedirectSchemeHandler); IMPLEMENT_REFCOUNTING(RequestRedirectSchemeHandler);
}; };
// Resource handler implementation that never completes. Used to test
// destruction handling behavior for in-progress requests.
class IncompleteSchemeHandler : public CefResourceHandler {
public:
IncompleteSchemeHandler(RequestRunSettings* settings,
const base::Closure& destroy_callback)
: settings_(settings), destroy_callback_(destroy_callback) {
EXPECT_NE(settings_->incomplete_type, RequestRunSettings::INCOMPLETE_NONE);
}
~IncompleteSchemeHandler() override {
EXPECT_EQ(1, process_request_ct_);
if (IsNetworkServiceEnabled())
EXPECT_EQ(1, cancel_ct_);
else
EXPECT_EQ(0, cancel_ct_);
if (settings_->incomplete_type ==
RequestRunSettings::INCOMPLETE_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_);
}
destroy_callback_.Run();
}
bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
process_request_ct_++;
if (settings_->incomplete_type ==
RequestRunSettings::INCOMPLETE_PROCESS_REQUEST) {
// Never release or execute this callback.
incomplete_callback_ = callback;
} else {
callback->Continue();
}
return true;
}
void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64& response_length,
CefString& redirectUrl) override {
EXPECT_IO_THREAD();
EXPECT_EQ(settings_->incomplete_type,
RequestRunSettings::INCOMPLETE_READ_RESPONSE);
get_response_headers_ct_++;
response->SetStatus(settings_->response->GetStatus());
response->SetStatusText(settings_->response->GetStatusText());
response->SetMimeType(settings_->response->GetMimeType());
CefResponse::HeaderMap headerMap;
settings_->response->GetHeaderMap(headerMap);
settings_->response->SetHeaderMap(headerMap);
response_length = static_cast<int64>(settings_->response_data.size());
}
bool ReadResponse(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefCallback> callback) override {
EXPECT_IO_THREAD();
EXPECT_EQ(settings_->incomplete_type,
RequestRunSettings::INCOMPLETE_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:
RequestRunSettings* const settings_;
const base::Closure destroy_callback_;
int process_request_ct_ = 0;
int get_response_headers_ct_ = 0;
int read_response_ct_ = 0;
int cancel_ct_ = 0;
CefRefPtr<CefCallback> incomplete_callback_;
IMPLEMENT_REFCOUNTING(IncompleteSchemeHandler);
DISALLOW_COPY_AND_ASSIGN(IncompleteSchemeHandler);
};
class RequestSchemeHandlerFactory : public CefSchemeHandlerFactory { class RequestSchemeHandlerFactory : public CefSchemeHandlerFactory {
public: public:
RequestSchemeHandlerFactory() {} RequestSchemeHandlerFactory() {}
@ -595,13 +730,22 @@ class RequestSchemeHandlerFactory : public CefSchemeHandlerFactory {
CefRefPtr<CefFrame> frame, CefRefPtr<CefFrame> frame,
const CefString& scheme_name, const CefString& scheme_name,
CefRefPtr<CefRequest> request) override { CefRefPtr<CefRequest> request) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO)); EXPECT_IO_THREAD();
handler_create_ct_++;
const base::Closure destroy_callback =
base::Bind(&RequestSchemeHandlerFactory::OnHandlerDestroyed, this);
RequestDataMap::Entry entry = data_map_.Find(request->GetURL()); RequestDataMap::Entry entry = data_map_.Find(request->GetURL());
if (entry.type == RequestDataMap::Entry::TYPE_NORMAL) { if (entry.type == RequestDataMap::Entry::TYPE_NORMAL) {
return new RequestSchemeHandler(entry.settings); if (entry.settings->incomplete_type ==
RequestRunSettings::INCOMPLETE_NONE) {
return new RequestSchemeHandler(entry.settings, destroy_callback);
}
return new IncompleteSchemeHandler(entry.settings, destroy_callback);
} else if (entry.type == RequestDataMap::Entry::TYPE_REDIRECT) { } else if (entry.type == RequestDataMap::Entry::TYPE_REDIRECT) {
return new RequestRedirectSchemeHandler(entry.redirect_request, return new RequestRedirectSchemeHandler(
entry.redirect_response); entry.redirect_request, entry.redirect_response, destroy_callback);
} }
// Unknown test. // Unknown test.
@ -629,6 +773,19 @@ class RequestSchemeHandlerFactory : public CefSchemeHandlerFactory {
data_map_.AddSchemeHandler(settings); data_map_.AddSchemeHandler(settings);
} }
void OnHandlerDestroyed() {
if (!CefCurrentlyOn(TID_IO)) {
CefPostTask(
TID_IO,
base::Bind(&RequestSchemeHandlerFactory::OnHandlerDestroyed, this));
return;
}
handler_destroy_ct_++;
MaybeShutdown();
}
void Shutdown(const base::Closure& complete_callback) { void Shutdown(const base::Closure& complete_callback) {
if (!CefCurrentlyOn(TID_IO)) { if (!CefCurrentlyOn(TID_IO)) {
CefPostTask(TID_IO, base::Bind(&RequestSchemeHandlerFactory::Shutdown, CefPostTask(TID_IO, base::Bind(&RequestSchemeHandlerFactory::Shutdown,
@ -636,13 +793,29 @@ class RequestSchemeHandlerFactory : public CefSchemeHandlerFactory {
return; return;
} }
EXPECT_TRUE(shutdown_callback_.is_null());
shutdown_callback_ = complete_callback;
data_map_.SetOwnerTaskRunner(nullptr); data_map_.SetOwnerTaskRunner(nullptr);
complete_callback.Run();
MaybeShutdown();
} }
private: private:
void MaybeShutdown() {
if (!shutdown_callback_.is_null() &&
handler_create_ct_ == handler_destroy_ct_) {
shutdown_callback_.Run();
shutdown_callback_.Reset();
}
}
RequestDataMap data_map_; RequestDataMap data_map_;
int handler_create_ct_ = 0;
int handler_destroy_ct_ = 0;
base::Closure shutdown_callback_;
IMPLEMENT_REFCOUNTING(RequestSchemeHandlerFactory); IMPLEMENT_REFCOUNTING(RequestSchemeHandlerFactory);
}; };
@ -878,7 +1051,12 @@ class RequestServerHandler : public CefServerHandler {
int connection_id, int connection_id,
CefRefPtr<CefResponse> response, CefRefPtr<CefResponse> response,
const std::string& response_data) { const std::string& response_data) {
int response_code = response->GetStatus(); const int response_code = response->GetStatus();
if (response_code <= 0) {
// Intentionally not responding for incomplete request tests.
return;
}
const CefString& content_type = response->GetMimeType(); const CefString& content_type = response->GetMimeType();
int64 content_length = static_cast<int64>(response_data.size()); int64 content_length = static_cast<int64>(response_data.size());
@ -888,6 +1066,11 @@ class RequestServerHandler : public CefServerHandler {
server->SendHttpResponse(connection_id, response_code, content_type, server->SendHttpResponse(connection_id, response_code, content_type,
content_length, extra_headers); content_length, extra_headers);
if (response_data == kIncompleteDoNotSendData) {
// Intentionally not sending data for incomplete request tests.
return;
}
if (content_length != 0) { if (content_length != 0) {
server->SendRawData(connection_id, response_data.data(), server->SendRawData(connection_id, response_data.data(),
response_data.size()); response_data.size());
@ -949,16 +1132,7 @@ class RequestClient : public CefURLRequestClient {
RequestCompleteCallback; RequestCompleteCallback;
explicit RequestClient(const RequestCompleteCallback& complete_callback) explicit RequestClient(const RequestCompleteCallback& complete_callback)
: complete_callback_(complete_callback), : complete_callback_(complete_callback) {
request_complete_ct_(0),
upload_progress_ct_(0),
download_progress_ct_(0),
download_data_ct_(0),
upload_total_(0),
download_total_(0),
status_(UR_UNKNOWN),
error_code_(ERR_NONE),
response_was_cached_(false) {
EXPECT_FALSE(complete_callback_.is_null()); EXPECT_FALSE(complete_callback_.is_null());
} }
@ -1015,22 +1189,22 @@ class RequestClient : public CefURLRequestClient {
} }
private: private:
RequestCompleteCallback complete_callback_; const RequestCompleteCallback complete_callback_;
public: public:
int request_complete_ct_; int request_complete_ct_ = 0;
int upload_progress_ct_; int upload_progress_ct_ = 0;
int download_progress_ct_; int download_progress_ct_ = 0;
int download_data_ct_; int download_data_ct_ = 0;
int64 upload_total_; int64 upload_total_ = 0;
int64 download_total_; int64 download_total_ = 0;
std::string download_data_; std::string download_data_;
CefRefPtr<CefRequest> request_; CefRefPtr<CefRequest> request_;
CefURLRequest::Status status_; CefURLRequest::Status status_ = UR_UNKNOWN;
CefURLRequest::ErrorCode error_code_; CefURLRequest::ErrorCode error_code_ = ERR_NONE;
CefRefPtr<CefResponse> response_; CefRefPtr<CefResponse> response_;
bool response_was_cached_; bool response_was_cached_ = false;
private: private:
IMPLEMENT_REFCOUNTING(RequestClient); IMPLEMENT_REFCOUNTING(RequestClient);
@ -1046,11 +1220,13 @@ class RequestTestRunner : public base::RefCountedThreadSafe<RequestTestRunner> {
RequestTestRunner(bool is_browser_process, RequestTestRunner(bool is_browser_process,
bool is_server_backend, bool is_server_backend,
bool use_frame_method, bool use_frame_method,
bool run_in_browser_process) bool run_in_browser_process,
const base::Closure& incomplete_request_callback)
: is_browser_process_(is_browser_process), : is_browser_process_(is_browser_process),
is_server_backend_(is_server_backend), is_server_backend_(is_server_backend),
use_frame_method_(use_frame_method), use_frame_method_(use_frame_method),
run_in_browser_process_(run_in_browser_process) { run_in_browser_process_(run_in_browser_process),
incomplete_request_callback_(incomplete_request_callback) {
owner_task_runner_ = CefTaskRunner::GetForCurrentThread(); owner_task_runner_ = CefTaskRunner::GetForCurrentThread();
EXPECT_TRUE(owner_task_runner_.get()); EXPECT_TRUE(owner_task_runner_.get());
EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread());
@ -1096,11 +1272,16 @@ class RequestTestRunner : public base::RefCountedThreadSafe<RequestTestRunner> {
MultipleRunTest); MultipleRunTest);
REGISTER_TEST(REQTEST_CACHE_DISABLE_HEADER, SetupCacheDisableHeaderTest, REGISTER_TEST(REQTEST_CACHE_DISABLE_HEADER, SetupCacheDisableHeaderTest,
MultipleRunTest); MultipleRunTest);
REGISTER_TEST(REQTEST_INCOMPLETE_PROCESS_REQUEST,
SetupIncompleteProcessRequestTest, SingleRunTest);
REGISTER_TEST(REQTEST_INCOMPLETE_READ_RESPONSE,
SetupIncompleteReadResponseTest, SingleRunTest);
} }
void Destroy() { void Destroy() {
owner_task_runner_ = nullptr; owner_task_runner_ = nullptr;
request_context_ = nullptr; request_context_ = nullptr;
incomplete_request_callback_.Reset();
} }
// Called in the browser process to set the request context that will be used // Called in the browser process to set the request context that will be used
@ -1764,6 +1945,44 @@ class RequestTestRunner : public base::RefCountedThreadSafe<RequestTestRunner> {
complete_callback.Run(); complete_callback.Run();
} }
void SetupIncompleteProcessRequestTest(
const base::Closure& complete_callback) {
// Start with the normal get test.
SetupGetTestShared();
settings_.incomplete_type = RequestRunSettings::INCOMPLETE_PROCESS_REQUEST;
// There will be no response and the request will be aborted.
settings_.response = CefResponse::Create();
settings_.response_data.clear();
settings_.expected_error_code = ERR_ABORTED;
settings_.expected_status = UR_FAILED;
settings_.expect_download_progress = false;
settings_.expect_download_data = false;
complete_callback.Run();
}
void SetupIncompleteReadResponseTest(const base::Closure& complete_callback) {
// Start with the normal get test.
SetupGetTestShared();
settings_.incomplete_type = RequestRunSettings::INCOMPLETE_READ_RESPONSE;
// There will be a response but the request will be aborted without
// receiving any data.
settings_.response_data = kIncompleteDoNotSendData;
settings_.expected_error_code = ERR_ABORTED;
settings_.expected_status = UR_FAILED;
// TODO(network): Download progress notifications are sent for incomplete
// (with no data sent) requests in the browser process but not the renderer
// process. Consider standardizing this behavior.
settings_.expect_download_progress = is_browser_process_;
settings_.expect_download_data = false;
complete_callback.Run();
}
// Send a request. |complete_callback| will be executed on request completion. // Send a request. |complete_callback| will be executed on request completion.
void SendRequest( void SendRequest(
const RequestClient::RequestCompleteCallback& complete_callback) { const RequestClient::RequestCompleteCallback& complete_callback) {
@ -1781,6 +2000,11 @@ class RequestTestRunner : public base::RefCountedThreadSafe<RequestTestRunner> {
} else { } else {
CefURLRequest::Create(request, client.get(), request_context_); CefURLRequest::Create(request, client.get(), request_context_);
} }
if (settings_.incomplete_type != RequestRunSettings::INCOMPLETE_NONE) {
incomplete_request_callback_.Run();
incomplete_request_callback_.Reset();
}
} }
// Verify a response. // Verify a response.
@ -2018,6 +2242,9 @@ class RequestTestRunner : public base::RefCountedThreadSafe<RequestTestRunner> {
const bool use_frame_method_; const bool use_frame_method_;
const bool run_in_browser_process_; const bool run_in_browser_process_;
// Used with incomplete request tests.
base::Closure incomplete_request_callback_;
// Primary thread runner for the object that owns us. In the browser process // Primary thread runner for the object that owns us. In the browser process
// this will be the UI thread and in the renderer process this will be the // this will be the UI thread and in the renderer process this will be the
// RENDERER thread. // RENDERER thread.
@ -2074,8 +2301,9 @@ class RequestRendererTest : public ClientAppRenderer::Delegate {
frame_ = frame; frame_ = frame;
test_mode_ = static_cast<RequestTestMode>(args->GetInt(0)); test_mode_ = static_cast<RequestTestMode>(args->GetInt(0));
test_runner_ = new RequestTestRunner(false, args->GetBool(1), test_runner_ = new RequestTestRunner(
use_frame_method, false); false, args->GetBool(1), use_frame_method, false,
base::Bind(&RequestRendererTest::OnIncompleteRequest, this));
// Setup the test. This will create the objects that we test against but // Setup the test. This will create the objects that we test against but
// not register any backend (because we're in the render process). // not register any backend (because we're in the render process).
@ -2107,9 +2335,34 @@ class RequestRendererTest : public ClientAppRenderer::Delegate {
base::Bind(&RequestRendererTest::OnShutdownComplete, this)); base::Bind(&RequestRendererTest::OnShutdownComplete, this));
} }
void OnIncompleteRequest() {
EXPECT_TRUE(CefCurrentlyOn(TID_RENDERER));
// This method will only be called for incomplete requests.
EXPECT_NE(test_runner_->settings_.incomplete_type,
RequestRunSettings::INCOMPLETE_NONE);
// Check if the test has failed.
bool result = !TestFailed();
// The browser will be closed to abort in-progress requests.
CefRefPtr<CefProcessMessage> return_msg =
CefProcessMessage::Create(kIncompleteRequestTestMsg);
EXPECT_TRUE(return_msg->GetArgumentList()->SetBool(0, result));
browser_->GetMainFrame()->SendProcessMessage(PID_BROWSER, return_msg);
}
void OnShutdownComplete() { void OnShutdownComplete() {
EXPECT_TRUE(CefCurrentlyOn(TID_RENDERER)); EXPECT_TRUE(CefCurrentlyOn(TID_RENDERER));
if (test_runner_->settings_.incomplete_type !=
RequestRunSettings::INCOMPLETE_NONE) {
// For incomplete tests there's a race between process destruction due to
// the browser closing, and the test possibly completing due to request
// cancellation. We therefore ignore test completion in this case.
return;
}
// Check if the test has failed. // Check if the test has failed.
bool result = !TestFailed(); bool result = !TestFailed();
@ -2179,8 +2432,9 @@ class RequestTestHandler : public TestHandler {
void PreSetupContinue() { void PreSetupContinue() {
EXPECT_TRUE(CefCurrentlyOn(TID_UI)); EXPECT_TRUE(CefCurrentlyOn(TID_UI));
test_runner_ = new RequestTestRunner(true, test_server_backend_, test_runner_ = new RequestTestRunner(
test_frame_method_, test_in_browser_); true, test_server_backend_, test_frame_method_, test_in_browser_,
base::Bind(&RequestTestHandler::OnIncompleteRequest, this));
// Get or create the request context. // Get or create the request context.
if (context_mode_ == CONTEXT_GLOBAL) { if (context_mode_ == CONTEXT_GLOBAL) {
@ -2332,17 +2586,57 @@ class RequestTestHandler : public TestHandler {
EXPECT_TRUE(message->IsReadOnly()); EXPECT_TRUE(message->IsReadOnly());
EXPECT_FALSE(test_in_browser_); EXPECT_FALSE(test_in_browser_);
EXPECT_FALSE(got_message_);
got_message_.yes(); got_message_.yes();
if (message->GetArgumentList()->GetBool(0)) if (message->GetArgumentList()->GetBool(0))
got_success_.yes(); got_success_.yes();
// Renderer process test is complete. const std::string& message_name = message->GetName();
OnRunComplete(); if (message_name == kRequestTestMsg) {
// Renderer process test is complete.
OnRunComplete();
} else if (message_name == kIncompleteRequestTestMsg) {
// Incomplete renderer tests will not complete normally. Instead, trigger
// browser close and then signal completion from OnBeforeClose.
OnIncompleteRequest();
}
return true; return true;
} }
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
if (!test_in_browser_ && test_runner_->settings_.incomplete_type !=
RequestRunSettings::INCOMPLETE_NONE) {
// Incomplete tests running in the renderer process will never recieve the
// test complete process message, so call the method here.
OnRunComplete();
}
TestHandler::OnBeforeClose(browser);
}
// Incomplete tests will not complete normally. Instead, we trigger a browser
// close to abort in-progress requests.
void OnIncompleteRequest() {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI,
base::Bind(&RequestTestHandler::OnIncompleteRequest, this));
return;
}
EXPECT_TRUE(test_frame_method_);
EXPECT_NE(RequestRunSettings::INCOMPLETE_NONE,
test_runner_->settings_.incomplete_type);
// TestComplete will eventually be called from DestroyTest instead of being
// triggered by browser destruction.
SetSignalCompletionWhenAllBrowsersClose(false);
CefPostDelayedTask(
TID_UI, base::Bind(&TestHandler::CloseBrowser, GetBrowser(), false),
100);
}
// Test run is complete. It ran in either the browser or render process. // Test run is complete. It ran in either the browser or render process.
void OnRunComplete() { void OnRunComplete() {
GetTestCookie(test_runner_->GetRequestContext(), test_server_backend_, GetTestCookie(test_runner_->GetRequestContext(), test_server_backend_,
@ -2379,15 +2673,21 @@ class RequestTestHandler : public TestHandler {
TestHandler::DestroyTest(); TestHandler::DestroyTest();
// Need to call TestComplete() explicitly if testing in the browser and // For non-global contexts OnTestComplete() will be called when the
// using the global context. Otherwise, TestComplete() will be called when // RequestContextHandler is destroyed.
// the browser is destroyed (for render test + global context) or when the bool call_test_complete = false;
// temporary context is destroyed. if (context_mode_ == CONTEXT_GLOBAL) {
const bool call_test_complete = (test_in_browser_ && !test_frame_method_ && if (test_in_browser_ && !test_frame_method_) {
context_mode_ == CONTEXT_GLOBAL); // These tests don't create a browser that would signal implicitly.
call_test_complete = true;
} else if (!SignalCompletionWhenAllBrowsersClose()) {
// These tests close the browser to terminate in-progress requests
// before test completion.
call_test_complete = true;
}
}
// Release our reference to the context. Do not access any object members // Release references to the context and handler.
// after this call because |this| might be deleted.
test_runner_->Destroy(); test_runner_->Destroy();
if (call_test_complete) if (call_test_complete)
@ -2401,6 +2701,9 @@ class RequestTestHandler : public TestHandler {
return; return;
} }
EXPECT_FALSE(got_on_test_complete_);
got_on_test_complete_.yes();
if (!context_tmpdir_.IsEmpty()) { if (!context_tmpdir_.IsEmpty()) {
// Wait a bit for cache file handles to close after browser or request // Wait a bit for cache file handles to close after browser or request
// context destruction. // context destruction.
@ -2479,6 +2782,8 @@ class RequestTestHandler : public TestHandler {
TrackCallback got_message_; TrackCallback got_message_;
TrackCallback got_success_; TrackCallback got_success_;
TrackCallback got_on_test_complete_;
IMPLEMENT_REFCOUNTING(RequestTestHandler); IMPLEMENT_REFCOUNTING(RequestTestHandler);
}; };
@ -2487,13 +2792,22 @@ bool IsTestSupported(RequestTestMode test_mode,
bool test_in_browser, bool test_in_browser,
bool test_server_backend, bool test_server_backend,
bool test_frame_method) { bool test_frame_method) {
if (IsNetworkServiceEnabled() && !test_in_browser && !test_server_backend && if (IsNetworkServiceEnabled()) {
!test_frame_method) { if (!test_in_browser && !test_server_backend && !test_frame_method) {
// When NetworkService is enabled requests from the render process can only // When NetworkService is enabled requests from the render process can
// reach non-server backends when using the CefFrame::CreateURLRequest // only reach non-server backends when using the
// method. // CefFrame::CreateURLRequest method.
return false; return false;
}
} else {
if (test_mode == REQTEST_INCOMPLETE_PROCESS_REQUEST ||
test_mode == REQTEST_INCOMPLETE_READ_RESPONSE) {
// The old network implementation does not support the same behavior
// for canceling incomplete requests.
return false;
}
} }
return true; return true;
} }
@ -2599,6 +2913,33 @@ void RegisterURLRequestCustomSchemes(
REQ_TEST_SET(WithoutFrame, false) REQ_TEST_SET(WithoutFrame, false)
REQ_TEST_SET(WithFrame, true) REQ_TEST_SET(WithFrame, true)
// Define tests that can only run with a frame.
#define REQ_TEST_FRAME_SET_EX(suffix, context_mode, test_server_backend) \
REQ_TEST(BrowserIncompleteProcessRequest##suffix, \
REQTEST_INCOMPLETE_PROCESS_REQUEST, context_mode, true, \
test_server_backend, true) \
REQ_TEST(BrowserIncompleteReadResponse##suffix, \
REQTEST_INCOMPLETE_READ_RESPONSE, context_mode, true, \
test_server_backend, true) \
REQ_TEST(RendererIncompleteProcessRequest##suffix, \
REQTEST_INCOMPLETE_PROCESS_REQUEST, context_mode, false, \
test_server_backend, true) \
REQ_TEST(RendererIncompleteReadResponse##suffix, \
REQTEST_INCOMPLETE_READ_RESPONSE, context_mode, false, \
test_server_backend, true)
#define REQ_TEST_FRAME_SET() \
REQ_TEST_FRAME_SET_EX(ContextGlobalCustomWithFrame, CONTEXT_GLOBAL, false) \
REQ_TEST_FRAME_SET_EX(ContextInMemoryCustomWithFrame, CONTEXT_INMEMORY, \
false) \
REQ_TEST_FRAME_SET_EX(ContextOnDiskCustomWithFrame, CONTEXT_ONDISK, false) \
REQ_TEST_FRAME_SET_EX(ContextGlobalServerWithFrame, CONTEXT_GLOBAL, true) \
REQ_TEST_FRAME_SET_EX(ContextInMemoryServerWithFrame, CONTEXT_INMEMORY, \
true) \
REQ_TEST_FRAME_SET_EX(ContextOnDiskServerWithFrame, CONTEXT_ONDISK, true)
REQ_TEST_FRAME_SET()
// Cache tests can only be run with the server backend. // Cache tests can only be run with the server backend.
#define REQ_TEST_CACHE_SET_EX(suffix, context_mode, test_frame_method) \ #define REQ_TEST_CACHE_SET_EX(suffix, context_mode, test_frame_method) \
REQ_TEST(BrowserGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \ REQ_TEST(BrowserGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \