From 88a46e0b446a3b281c7af5a3990602400ee2d7a1 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Wed, 17 Aug 2011 01:55:07 +0000 Subject: [PATCH] Support asynchronous continuation of custom scheme handler responses (issue #269). git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@278 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- cef.gyp | 4 + include/cef.h | 85 +++-- include/cef_capi.h | 78 +++-- libcef/browser_resource_loader_bridge.cc | 2 +- libcef/scheme_impl.cc | 297 +++++++++++------- .../cpptoc/scheme_handler_callback_cpptoc.cc | 65 ++++ .../cpptoc/scheme_handler_callback_cpptoc.h | 35 +++ libcef_dll/cpptoc/scheme_handler_cpptoc.cc | 57 ++-- .../ctocpp/scheme_handler_callback_ctocpp.cc | 47 +++ .../ctocpp/scheme_handler_callback_ctocpp.h | 43 +++ libcef_dll/ctocpp/scheme_handler_ctocpp.cc | 38 ++- libcef_dll/ctocpp/scheme_handler_ctocpp.h | 10 +- tests/cefclient/scheme_test.cpp | 68 ++-- tests/unittests/scheme_handler_unittest.cc | 122 +++++-- 14 files changed, 693 insertions(+), 258 deletions(-) create mode 100644 libcef_dll/cpptoc/scheme_handler_callback_cpptoc.cc create mode 100644 libcef_dll/cpptoc/scheme_handler_callback_cpptoc.h create mode 100644 libcef_dll/ctocpp/scheme_handler_callback_ctocpp.cc create mode 100644 libcef_dll/ctocpp/scheme_handler_callback_ctocpp.h diff --git a/cef.gyp b/cef.gyp index 68a935e30..a03c1773e 100644 --- a/cef.gyp +++ b/cef.gyp @@ -340,6 +340,8 @@ 'libcef_dll/cpptoc/request_cpptoc.h', 'libcef_dll/cpptoc/response_cpptoc.cc', 'libcef_dll/cpptoc/response_cpptoc.h', + 'libcef_dll/cpptoc/scheme_handler_callback_cpptoc.cc', + 'libcef_dll/cpptoc/scheme_handler_callback_cpptoc.h', 'libcef_dll/cpptoc/stream_reader_cpptoc.cc', 'libcef_dll/cpptoc/stream_reader_cpptoc.h', 'libcef_dll/cpptoc/stream_writer_cpptoc.cc', @@ -538,6 +540,8 @@ 'libcef_dll/ctocpp/request_ctocpp.h', 'libcef_dll/ctocpp/response_ctocpp.cc', 'libcef_dll/ctocpp/response_ctocpp.h', + 'libcef_dll/ctocpp/scheme_handler_callback_ctocpp.cc', + 'libcef_dll/ctocpp/scheme_handler_callback_ctocpp.h', 'libcef_dll/ctocpp/stream_reader_ctocpp.cc', 'libcef_dll/ctocpp/stream_reader_ctocpp.h', 'libcef_dll/ctocpp/stream_writer_ctocpp.cc', diff --git a/include/cef.h b/include/cef.h index ecc196286..df5bcd5bb 100644 --- a/include/cef.h +++ b/include/cef.h @@ -2468,9 +2468,35 @@ public: CefRefPtr request) =0; }; +/// +// Class used to facilitate asynchronous responses to custom scheme handler +// requests. The methods of this class may be called on any thread. +/// +/*--cef(source=library)--*/ +class CefSchemeHandlerCallback : public virtual CefBase +{ +public: + /// + // Notify that header information is now available for retrieval. + /// + /*--cef()--*/ + virtual void HeadersAvailable() =0; + + /// + // Notify that response data is now available for reading. + /// + /*--cef()--*/ + virtual void BytesAvailable() =0; + + /// + // Cancel processing of the request. + /// + /*--cef()--*/ + virtual void Cancel() =0; +}; /// -// Class used to represent a custom scheme handler interface. The methods of +// Class used to implement a custom scheme handler interface. The methods of // this class will always be called on the IO thread. /// /*--cef(source=client)--*/ @@ -2478,38 +2504,49 @@ class CefSchemeHandler : public virtual CefBase { public: /// - // Process the request. All response generation should take place in this - // method. If there is no response set |response_length| to zero or return - // false and ReadResponse() will not be called. If the response length is not - // known set |response_length| to -1 and ReadResponse() will be called until - // it returns false or until the value of |bytes_read| is set to 0. If the - // response length is known set |response_length| to a positive value and - // ReadResponse() will be called until it returns false, the value of - // |bytes_read| is set to 0 or the specified number of bytes have been read. - // Use the |response| object to set the mime type, http status code and - // optional header values for the response and return true. To redirect the - // request to a new URL set |redirectUrl| to the new URL and return true. + // Begin processing the request. To handle the request return true and call + // HeadersAvailable() once the response header information is available + // (HeadersAvailable() can also be called from inside this method if header + // information is available immediately). To redirect the request to a new + // URL set |redirectUrl| to the new URL and return true. To cancel the request + // return false. /// /*--cef()--*/ virtual bool ProcessRequest(CefRefPtr request, CefString& redirectUrl, - CefRefPtr response, - int* response_length) =0; + CefRefPtr callback) =0; /// - // Cancel processing of the request. + // Retrieve response header information. If the response length is not known + // set |response_length| to -1 and ReadResponse() will be called until it + // returns false. If the response length is known set |response_length| + // to a positive value and ReadResponse() will be called until it returns + // false or the specified number of bytes have been read. Use the |response| + // object to set the mime type, http status code and other optional header + // values. + /// + /*--cef()--*/ + virtual void GetResponseHeaders(CefRefPtr response, + int64& response_length) =0; + + /// + // Read response data. If data is available immediately copy up to + // |bytes_to_read| bytes into |data_out|, set |bytes_read| to the number of + // bytes copied, and return true. To read the data at a later time set + // |bytes_read| to 0, return true and call BytesAvailable() when the data is + // available. To indicate response completion return false. + /// + /*--cef()--*/ + virtual bool ReadResponse(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) =0; + + /// + // Request processing has been canceled. /// /*--cef()--*/ virtual void Cancel() =0; - - /// - // Copy up to |bytes_to_read| bytes into |data_out|. If the copy succeeds - // set |bytes_read| to the number of bytes copied and return true. If the - // copy fails return false and ReadResponse() will not be called again. - /// - /*--cef()--*/ - virtual bool ReadResponse(void* data_out, int bytes_to_read, - int* bytes_read) =0; }; diff --git a/include/cef_capi.h b/include/cef_capi.h index 8c7c2b89c..ffb15ccc7 100644 --- a/include/cef_capi.h +++ b/include/cef_capi.h @@ -2270,7 +2270,36 @@ typedef struct _cef_scheme_handler_factory_t /// -// Structure used to represent a custom scheme handler structure. The functions +// Structure used to facilitate asynchronous responses to custom scheme handler +// requests. The functions of this structure may be called on any thread. +/// +typedef struct _cef_scheme_handler_callback_t +{ + // Base structure. + cef_base_t base; + + /// + // Notify that header information is now available for retrieval. + /// + void (CEF_CALLBACK *headers_available)( + struct _cef_scheme_handler_callback_t* self); + + /// + // Notify that response data is now available for reading. + /// + void (CEF_CALLBACK *bytes_available)( + struct _cef_scheme_handler_callback_t* self); + + /// + // Cancel processing of the request. + /// + void (CEF_CALLBACK *cancel)(struct _cef_scheme_handler_callback_t* self); + +} cef_scheme_handler_callback_t; + + +/// +// Structure used to implement a custom scheme handler structure. The functions // of this structure will always be called on the IO thread. /// typedef struct _cef_scheme_handler_t @@ -2279,35 +2308,44 @@ typedef struct _cef_scheme_handler_t cef_base_t base; /// - // Process the request. All response generation should take place in this - // function. If there is no response set |response_length| to zero or return - // false (0) and read_response() will not be called. If the response length is - // not known set |response_length| to -1 and read_response() will be called - // until it returns false (0) or until the value of |bytes_read| is set to 0. - // If the response length is known set |response_length| to a positive value - // and read_response() will be called until it returns false (0), the value of - // |bytes_read| is set to 0 or the specified number of bytes have been read. - // Use the |response| object to set the mime type, http status code and - // optional header values for the response and return true (1). To redirect - // the request to a new URL set |redirectUrl| to the new URL and return true - // (1). + // Begin processing the request. To handle the request return true (1) and + // call headers_available() once the response header information is available + // (headers_available() can also be called from inside this function if header + // information is available immediately). To redirect the request to a new URL + // set |redirectUrl| to the new URL and return true (1). To cancel the request + // return false (0). /// int (CEF_CALLBACK *process_request)(struct _cef_scheme_handler_t* self, struct _cef_request_t* request, cef_string_t* redirectUrl, - struct _cef_response_t* response, int* response_length); + struct _cef_scheme_handler_callback_t* callback); /// - // Cancel processing of the request. + // Retrieve response header information. If the response length is not known + // set |response_length| to -1 and read_response() will be called until it + // returns false (0). If the response length is known set |response_length| to + // a positive value and read_response() will be called until it returns false + // (0) or the specified number of bytes have been read. Use the |response| + // object to set the mime type, http status code and other optional header + // values. /// - void (CEF_CALLBACK *cancel)(struct _cef_scheme_handler_t* self); + void (CEF_CALLBACK *get_response_headers)(struct _cef_scheme_handler_t* self, + struct _cef_response_t* response, int64* response_length); /// - // Copy up to |bytes_to_read| bytes into |data_out|. If the copy succeeds set - // |bytes_read| to the number of bytes copied and return true (1). If the copy - // fails return false (0) and read_response() will not be called again. + // Read response data. If data is available immediately copy up to + // |bytes_to_read| bytes into |data_out|, set |bytes_read| to the number of + // bytes copied, and return true (1). To read the data at a later time set + // |bytes_read| to 0, return true (1) and call bytes_available() when the data + // is available. To indicate response completion return false (0). /// int (CEF_CALLBACK *read_response)(struct _cef_scheme_handler_t* self, - void* data_out, int bytes_to_read, int* bytes_read); + void* data_out, int bytes_to_read, int* bytes_read, + struct _cef_scheme_handler_callback_t* callback); + + /// + // Request processing has been canceled. + /// + void (CEF_CALLBACK *cancel)(struct _cef_scheme_handler_t* self); } cef_scheme_handler_t; diff --git a/libcef/browser_resource_loader_bridge.cc b/libcef/browser_resource_loader_bridge.cc index a4ada8988..522f749d0 100644 --- a/libcef/browser_resource_loader_bridge.cc +++ b/libcef/browser_resource_loader_bridge.cc @@ -529,7 +529,7 @@ class RequestProxy : public net::URLRequest::Delegate, request_->Start(); - if (request_->has_upload() && + if (request_.get() && request_->has_upload() && params->load_flags & net::LOAD_ENABLE_UPLOAD_PROGRESS) { upload_progress_timer_.Start( base::TimeDelta::FromMilliseconds( diff --git a/libcef/scheme_impl.cc b/libcef/scheme_impl.cc index 33f65269b..c58efbaa6 100644 --- a/libcef/scheme_impl.cc +++ b/libcef/scheme_impl.cc @@ -33,6 +33,7 @@ #include +using net::URLRequestStatus; using WebKit::WebSecurityPolicy; using WebKit::WebString; @@ -104,30 +105,53 @@ public: CefUrlRequestJob(net::URLRequest* request, CefRefPtr handler) : net::URLRequestJob(request), - handler_(handler), - response_length_(0), - url_(request->url()), - remaining_bytes_(0) { } + handler_(handler), + remaining_bytes_(0) + { + } - virtual ~CefUrlRequestJob(){} + virtual ~CefUrlRequestJob() + { + } virtual void Start() OVERRIDE { - handler_->Cancel(); - // Continue asynchronously. - DCHECK(!async_resolver_); - response_ = new CefResponseImpl(); - async_resolver_ = new AsyncResolver(this); - CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( - async_resolver_.get(), &AsyncResolver::Resolve, url_)); + REQUIRE_IOT(); + + if (!callback_) + callback_ = new Callback(this); + + CefRefPtr req(CefRequest::CreateRequest()); + CefString redirectUrl; + + // Populate the request data. + static_cast(req.get())->Set(request()); + + // Handler can decide whether to process the request. + bool rv = handler_->ProcessRequest(req, redirectUrl, callback_.get()); + if (!rv) { + // Cancel the request. + NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, ERR_ABORTED)); + } else if (!redirectUrl.empty()) { + // Treat the request as a redirect. + std::string redirectUrlStr = redirectUrl; + redirect_url_ = GURL(redirectUrlStr); + NotifyHeadersComplete(); + } + return; } virtual void Kill() OVERRIDE { - if (async_resolver_) { - async_resolver_->Cancel(); - async_resolver_ = NULL; + REQUIRE_IOT(); + + // Notify the handler that the request has been canceled. + handler_->Cancel(); + + if (callback_) { + callback_->Detach(); + callback_ = NULL; } net::URLRequestJob::Kill(); @@ -136,52 +160,62 @@ public: virtual bool ReadRawData(net::IOBuffer* dest, int dest_size, int *bytes_read) OVERRIDE { + REQUIRE_IOT(); + DCHECK_NE(dest_size, 0); DCHECK(bytes_read); - // When remaining_bytes_>=0, it means the handler knows the content size - // before hand. We continue to read until - if (remaining_bytes_>=0) { - if (remaining_bytes_ < dest_size) - dest_size = static_cast(remaining_bytes_); - - // If we should copy zero bytes because |remaining_bytes_| is zero, short - // circuit here. - if (!dest_size) { - *bytes_read = 0; - return true; - } - - // remaining_bytes > 0 - bool rv = handler_->ReadResponse(dest->data(), dest_size, bytes_read); - remaining_bytes_ -= *bytes_read; - if (!rv) { - // handler indicated no further data to read - *bytes_read = 0; - } - return true; - } else { - // The handler returns -1 for GetResponseLength, this means the handler - // doesn't know the content size before hand. We do basically the same - // thing, except for checking the return value for handler_->ReadResponse, - // which is an indicator for no further data to be read. - bool rv = handler_->ReadResponse(dest->data(), dest_size, bytes_read); - if (!rv) - // handler indicated no further data to read - *bytes_read = 0; + if (remaining_bytes_ == 0) { + // No more data to read. + *bytes_read = 0; return true; + } else if (remaining_bytes_ > 0 && remaining_bytes_ < dest_size) { + // The handler knows the content size beforehand. + dest_size = static_cast(remaining_bytes_); } + + // Read response data from the handler. + bool rv = handler_->ReadResponse(dest->data(), dest_size, *bytes_read, + callback_.get()); + if (!rv) { + // The handler has indicated completion of the request. + *bytes_read = 0; + return true; + } else if(*bytes_read == 0) { + if (!GetStatus().is_io_pending()) { + // Report our status as IO pending. + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); + callback_->SetDestination(dest, dest_size); + } + return false; + } else if(*bytes_read > dest_size) { + // Normalize the return value. + *bytes_read = dest_size; + } + + if(remaining_bytes_ > 0) + remaining_bytes_ -= *bytes_read; + + // Continue calling this method. + return true; } - virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { - CefResponseImpl* responseImpl = - static_cast(response_.get()); - info->headers = responseImpl->GetResponseHeaders(); + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE + { + REQUIRE_IOT(); + + if (response_.get()) { + CefResponseImpl* responseImpl = + static_cast(response_.get()); + info->headers = responseImpl->GetResponseHeaders(); + } } virtual bool IsRedirectResponse(GURL* location, int* http_status_code) OVERRIDE { + REQUIRE_IOT(); + if (redirect_url_.is_valid()) { // Redirect to the new URL. *http_status_code = 303; @@ -193,105 +227,132 @@ public: virtual bool GetMimeType(std::string* mime_type) const OVERRIDE { - DCHECK(request_); - // call handler to get mime type - *mime_type = response_->GetMimeType(); + REQUIRE_IOT(); + + if (response_.get()) + *mime_type = response_->GetMimeType(); return true; } CefRefPtr handler_; CefRefPtr response_; - int response_length_; - -protected: - GURL url_; - GURL redirect_url_; private: - void DidResolve(const GURL& url) + void SendHeaders() { - async_resolver_ = NULL; + REQUIRE_IOT(); // We may have been orphaned... - if (!request_) + if (!request()) return; - remaining_bytes_ = response_length_; - if (remaining_bytes_>0) + response_ = new CefResponseImpl(); + remaining_bytes_ = 0; + + // Get header information from the handler. + handler_->GetResponseHeaders(response_, remaining_bytes_); + + if (remaining_bytes_ > 0) set_expected_content_size(remaining_bytes_); + + // Continue processing the request. NotifyHeadersComplete(); } - int64 remaining_bytes_; - std::string m_response; - - class AsyncResolver : - public base::RefCountedThreadSafe { + // Client callback for asynchronous response continuation. + class Callback : public CefSchemeHandlerCallback + { public: - explicit AsyncResolver(CefUrlRequestJob* owner) - : owner_(owner), owner_loop_(MessageLoop::current()) { - } + Callback(CefUrlRequestJob* job) + : job_(job), + dest_(NULL), + dest_size_() {} - void Resolve(const GURL& url) { - base::AutoLock locked(lock_); - if (!owner_ || !owner_loop_) - return; - - ////////////////////////////////////////////////////////////////////////// - // safe to perform long operation here - CefRefPtr req(CefRequest::CreateRequest()); - - // populate the request data - static_cast(req.get())->Set(owner_->request()); - - owner_->handler_->Cancel(); - - int response_length = 0; - CefString redirectUrl; - - // handler should complete content generation in ProcessRequest - bool res = owner_->handler_->ProcessRequest(req, redirectUrl, - owner_->response_, &response_length); - if (res) { - if (!redirectUrl.empty()) { - // Treat the request as a redirect. - std::string redirectUrlStr = redirectUrl; - owner_->redirect_url_ = GURL(redirectUrlStr); - owner_->response_length_ = 0; - } else { - owner_->response_length_ = response_length; + virtual void HeadersAvailable() OVERRIDE + { + if (CefThread::CurrentlyOn(CefThread::IO)) { + // Currently on IO thread. + if (job_ && !job_->has_response_started()) { + // Send header information. + job_->SendHeaders(); } - } - - ////////////////////////////////////////////////////////////////////////// - if (owner_loop_) { - owner_loop_->PostTask(FROM_HERE, NewRunnableMethod( - this, &AsyncResolver::ReturnResults, url)); + } else { + // Execute this method on the IO thread. + CefThread::PostTask(CefThread::IO, FROM_HERE, + NewRunnableMethod(this, &Callback::HeadersAvailable)); } } - void Cancel() { - owner_->handler_->Cancel(); + virtual void BytesAvailable() OVERRIDE + { + if (CefThread::CurrentlyOn(CefThread::IO)) { + // Currently on IO thread. + if (job_ && job_->has_response_started() && + job_->GetStatus().is_io_pending()) { + // Read the bytes. They should be available but, if not, wait again. + int bytes_read = 0; + if (job_->ReadRawData(dest_, dest_size_, &bytes_read)) { + if (bytes_read > 0) { + // Clear the IO_PENDING status. + job_->SetStatus(URLRequestStatus()); - base::AutoLock locked(lock_); - owner_ = NULL; - owner_loop_ = NULL; + // Notify about the available bytes. + job_->NotifyReadComplete(bytes_read); + + dest_ = NULL; + dest_size_ = NULL; + } + } else { + // All done. + job_->NotifyDone(URLRequestStatus()); + } + } + } else { + // Execute this method on the IO thread. + CefThread::PostTask(CefThread::IO, FROM_HERE, + NewRunnableMethod(this, &Callback::BytesAvailable)); + } } + virtual void Cancel() OVERRIDE + { + if (CefThread::CurrentlyOn(CefThread::IO)) { + // Currently on IO thread. + if (job_) + job_->Kill(); + } else { + // Execute this method on the IO thread. + CefThread::PostTask(CefThread::IO, FROM_HERE, + NewRunnableMethod(this, &Callback::Cancel)); + } + } + + void Detach() + { + REQUIRE_IOT(); + job_ = NULL; + } + + void SetDestination(net::IOBuffer* dest, int dest_size) + { + dest_ = dest; + dest_size_ = dest_size; + } + + static bool ImplementsThreadSafeReferenceCounting() { return true; } + private: - void ReturnResults(const GURL& url) { - if (owner_) - owner_->DidResolve(url); - } + CefUrlRequestJob* job_; - CefUrlRequestJob* owner_; + net::IOBuffer* dest_; + int dest_size_; - base::Lock lock_; - MessageLoop* owner_loop_; + IMPLEMENT_REFCOUNTING(Callback); }; - friend class AsyncResolver; - scoped_refptr async_resolver_; + GURL redirect_url_; + int64 remaining_bytes_; + CefRefPtr callback_; DISALLOW_COPY_AND_ASSIGN(CefUrlRequestJob); }; diff --git a/libcef_dll/cpptoc/scheme_handler_callback_cpptoc.cc b/libcef_dll/cpptoc/scheme_handler_callback_cpptoc.cc new file mode 100644 index 000000000..a2507a3e4 --- /dev/null +++ b/libcef_dll/cpptoc/scheme_handler_callback_cpptoc.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// A portion of this file was generated by the CEF translator tool. When +// making changes by hand only do so within the body of existing function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// + +#include "libcef_dll/cpptoc/scheme_handler_callback_cpptoc.h" + + +// MEMBER FUNCTIONS - Body may be edited by hand. + +void CEF_CALLBACK scheme_handler_callback_headers_available( + struct _cef_scheme_handler_callback_t* self) +{ + DCHECK(self); + if (!self) + return; + + CefSchemeHandlerCallbackCppToC::Get(self)->HeadersAvailable(); +} + +void CEF_CALLBACK scheme_handler_callback_bytes_available( + struct _cef_scheme_handler_callback_t* self) +{ + DCHECK(self); + if (!self) + return; + + CefSchemeHandlerCallbackCppToC::Get(self)->BytesAvailable(); +} + +void CEF_CALLBACK scheme_handler_callback_cancel( + struct _cef_scheme_handler_callback_t* self) +{ + DCHECK(self); + if (!self) + return; + + CefSchemeHandlerCallbackCppToC::Get(self)->Cancel(); +} + + +// CONSTRUCTOR - Do not edit by hand. + +CefSchemeHandlerCallbackCppToC::CefSchemeHandlerCallbackCppToC( + CefSchemeHandlerCallback* cls) + : CefCppToC(cls) +{ + struct_.struct_.headers_available = scheme_handler_callback_headers_available; + struct_.struct_.bytes_available = scheme_handler_callback_bytes_available; + struct_.struct_.cancel = scheme_handler_callback_cancel; +} + +#ifndef NDEBUG +template<> long CefCppToC::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/cpptoc/scheme_handler_callback_cpptoc.h b/libcef_dll/cpptoc/scheme_handler_callback_cpptoc.h new file mode 100644 index 000000000..3f28ed81f --- /dev/null +++ b/libcef_dll/cpptoc/scheme_handler_callback_cpptoc.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// +#ifndef _SCHEMEHANDLERCALLBACK_CPPTOC_H +#define _SCHEMEHANDLERCALLBACK_CPPTOC_H + +#ifndef BUILDING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed DLL-side only") +#else // BUILDING_CEF_SHARED + +#include "include/cef.h" +#include "include/cef_capi.h" +#include "libcef_dll/cpptoc/cpptoc.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed DLL-side only. +class CefSchemeHandlerCallbackCppToC + : public CefCppToC +{ +public: + CefSchemeHandlerCallbackCppToC(CefSchemeHandlerCallback* cls); + virtual ~CefSchemeHandlerCallbackCppToC() {} +}; + +#endif // BUILDING_CEF_SHARED +#endif // _SCHEMEHANDLERCALLBACK_CPPTOC_H + diff --git a/libcef_dll/cpptoc/scheme_handler_cpptoc.cc b/libcef_dll/cpptoc/scheme_handler_cpptoc.cc index 1b553dae3..52b8748f0 100644 --- a/libcef_dll/cpptoc/scheme_handler_cpptoc.cc +++ b/libcef_dll/cpptoc/scheme_handler_cpptoc.cc @@ -13,26 +13,54 @@ #include "libcef_dll/cpptoc/scheme_handler_cpptoc.h" #include "libcef_dll/ctocpp/request_ctocpp.h" #include "libcef_dll/ctocpp/response_ctocpp.h" +#include "libcef_dll/ctocpp/scheme_handler_callback_ctocpp.h" // MEMBER FUNCTIONS - Body may be edited by hand. int CEF_CALLBACK scheme_handler_process_request( struct _cef_scheme_handler_t* self, cef_request_t* request, - cef_string_t* redirectUrl, cef_response_t* response, int* response_length) + cef_string_t* redirectUrl, cef_scheme_handler_callback_t* callback) { DCHECK(self); DCHECK(request); DCHECK(redirectUrl); - DCHECK(response); - DCHECK(response_length); - if(!self || !request || !redirectUrl || !response || !response_length) + if(!self || !request || !redirectUrl) return 0; CefString redirectUrlStr(redirectUrl); return CefSchemeHandlerCppToC::Get(self)->ProcessRequest( CefRequestCToCpp::Wrap(request), redirectUrlStr, - CefResponseCToCpp::Wrap(response), response_length); + CefSchemeHandlerCallbackCToCpp::Wrap(callback)); +} + +void CEF_CALLBACK scheme_handler_get_response_headers( + struct _cef_scheme_handler_t* self, cef_response_t* response, + int64* response_length) +{ + DCHECK(self); + DCHECK(response); + DCHECK(response_length); + if (!self || !response || !response_length) + return; + + CefSchemeHandlerCppToC::Get(self)->GetResponseHeaders( + CefResponseCToCpp::Wrap(response), *response_length); +} + +int CEF_CALLBACK scheme_handler_read_response( + struct _cef_scheme_handler_t* self, void* data_out, int bytes_to_read, + int* bytes_read, cef_scheme_handler_callback_t* callback) +{ + DCHECK(self); + DCHECK(data_out); + DCHECK(bytes_read); + if(!self || !data_out || !bytes_read) + return 0; + + return CefSchemeHandlerCppToC::Get(self)->ReadResponse( + data_out, bytes_to_read, *bytes_read, + CefSchemeHandlerCallbackCToCpp::Wrap(callback)) ? 1 : 0; } void CEF_CALLBACK scheme_handler_cancel(struct _cef_scheme_handler_t* self) @@ -44,22 +72,6 @@ void CEF_CALLBACK scheme_handler_cancel(struct _cef_scheme_handler_t* self) CefSchemeHandlerCppToC::Get(self)->Cancel(); } -int CEF_CALLBACK scheme_handler_read_response( - struct _cef_scheme_handler_t* self, void* data_out, int bytes_to_read, - int* bytes_read) -{ - DCHECK(self); - DCHECK(data_out); - DCHECK(bytes_read); - if(!self || !data_out || !bytes_read) - return 0; - - bool rv = CefSchemeHandlerCppToC::Get(self)->ReadResponse( - data_out, bytes_to_read, bytes_read); - - return rv?1:0; -} - // CONSTRUCTOR - Do not edit by hand. @@ -68,8 +80,9 @@ CefSchemeHandlerCppToC::CefSchemeHandlerCppToC(CefSchemeHandler* cls) cls) { struct_.struct_.process_request = scheme_handler_process_request; - struct_.struct_.cancel = scheme_handler_cancel; + struct_.struct_.get_response_headers = scheme_handler_get_response_headers; struct_.struct_.read_response = scheme_handler_read_response; + struct_.struct_.cancel = scheme_handler_cancel; } #ifndef NDEBUG diff --git a/libcef_dll/ctocpp/scheme_handler_callback_ctocpp.cc b/libcef_dll/ctocpp/scheme_handler_callback_ctocpp.cc new file mode 100644 index 000000000..12b74f750 --- /dev/null +++ b/libcef_dll/ctocpp/scheme_handler_callback_ctocpp.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// A portion of this file was generated by the CEF translator tool. When +// making changes by hand only do so within the body of existing static and +// virtual method implementations. See the translator.README.txt file in the +// tools directory for more information. +// + +#include "libcef_dll/ctocpp/scheme_handler_callback_ctocpp.h" + + +// VIRTUAL METHODS - Body may be edited by hand. + +void CefSchemeHandlerCallbackCToCpp::HeadersAvailable() +{ + if (CEF_MEMBER_MISSING(struct_, headers_available)) + return; + + struct_->headers_available(struct_); +} + +void CefSchemeHandlerCallbackCToCpp::BytesAvailable() +{ + if (CEF_MEMBER_MISSING(struct_, bytes_available)) + return; + + struct_->bytes_available(struct_); +} + +void CefSchemeHandlerCallbackCToCpp::Cancel() +{ + if (CEF_MEMBER_MISSING(struct_, cancel)) + return; + + struct_->cancel(struct_); +} + + +#ifndef NDEBUG +template<> long CefCToCpp::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/ctocpp/scheme_handler_callback_ctocpp.h b/libcef_dll/ctocpp/scheme_handler_callback_ctocpp.h new file mode 100644 index 000000000..b258777cd --- /dev/null +++ b/libcef_dll/ctocpp/scheme_handler_callback_ctocpp.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// ------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// + +#ifndef _SCHEMEHANDLERCALLBACK_CTOCPP_H +#define _SCHEMEHANDLERCALLBACK_CTOCPP_H + +#ifndef USING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed wrapper-side only") +#else // USING_CEF_SHARED + +#include "include/cef.h" +#include "include/cef_capi.h" +#include "libcef_dll/ctocpp/ctocpp.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed wrapper-side only. +class CefSchemeHandlerCallbackCToCpp + : public CefCToCpp +{ +public: + CefSchemeHandlerCallbackCToCpp(cef_scheme_handler_callback_t* str) + : CefCToCpp(str) {} + virtual ~CefSchemeHandlerCallbackCToCpp() {} + + // CefSchemeHandlerCallback methods + virtual void HeadersAvailable() OVERRIDE; + virtual void BytesAvailable() OVERRIDE; + virtual void Cancel() OVERRIDE; +}; + +#endif // USING_CEF_SHARED +#endif // _SCHEMEHANDLERCALLBACK_CTOCPP_H + diff --git a/libcef_dll/ctocpp/scheme_handler_ctocpp.cc b/libcef_dll/ctocpp/scheme_handler_ctocpp.cc index 6728bb051..9326e6282 100644 --- a/libcef_dll/ctocpp/scheme_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/scheme_handler_ctocpp.cc @@ -12,21 +12,41 @@ #include "libcef_dll/cpptoc/request_cpptoc.h" #include "libcef_dll/cpptoc/response_cpptoc.h" +#include "libcef_dll/cpptoc/scheme_handler_callback_cpptoc.h" #include "libcef_dll/ctocpp/scheme_handler_ctocpp.h" // VIRTUAL METHODS - Body may be edited by hand. bool CefSchemeHandlerCToCpp::ProcessRequest(CefRefPtr request, - CefString& redirectUrl, CefRefPtr response, - int* response_length) + CefString& redirectUrl, CefRefPtr callback) { if(CEF_MEMBER_MISSING(struct_, process_request)) return false; return struct_->process_request(struct_, CefRequestCppToC::Wrap(request), - redirectUrl.GetWritableStruct(), CefResponseCppToC::Wrap(response), - response_length) ? true : false; + redirectUrl.GetWritableStruct(), + CefSchemeHandlerCallbackCppToC::Wrap(callback)) ? true : false; +} + +void CefSchemeHandlerCToCpp::GetResponseHeaders(CefRefPtr response, + int64& response_length) +{ + if (CEF_MEMBER_MISSING(struct_, get_response_headers)) + return; + + struct_->get_response_headers(struct_, CefResponseCppToC::Wrap(response), + &response_length); +} + +bool CefSchemeHandlerCToCpp::ReadResponse(void* data_out, int bytes_to_read, + int& bytes_read, CefRefPtr callback) +{ + if(CEF_MEMBER_MISSING(struct_, read_response)) + return false; + + return struct_->read_response(struct_, data_out, bytes_to_read, &bytes_read, + CefSchemeHandlerCallbackCppToC::Wrap(callback)) ? true : false; } void CefSchemeHandlerCToCpp::Cancel() @@ -37,16 +57,6 @@ void CefSchemeHandlerCToCpp::Cancel() struct_->cancel(struct_); } -bool CefSchemeHandlerCToCpp::ReadResponse(void* data_out, int bytes_to_read, - int* bytes_read) -{ - if(CEF_MEMBER_MISSING(struct_, read_response)) - return false; - - return struct_->read_response(struct_, data_out, bytes_to_read, bytes_read) ? - true : false; -} - #ifndef NDEBUG template<> long CefCToCpp request, - CefString& redirectUrl, CefRefPtr response, - int* response_length) OVERRIDE; + CefString& redirectUrl, + CefRefPtr callback) OVERRIDE; + virtual void GetResponseHeaders(CefRefPtr response, + int64& response_length) OVERRIDE; + virtual bool ReadResponse(void* data_out, int bytes_to_read, int& bytes_read, + CefRefPtr callback) OVERRIDE; virtual void Cancel() OVERRIDE; - virtual bool ReadResponse(void* data_out, int bytes_to_read, - int* bytes_read) OVERRIDE; }; #endif // BUILDING_CEF_SHARED diff --git a/tests/cefclient/scheme_test.cpp b/tests/cefclient/scheme_test.cpp index c10c83b6f..01daf0833 100644 --- a/tests/cefclient/scheme_test.cpp +++ b/tests/cefclient/scheme_test.cpp @@ -19,21 +19,10 @@ class ClientSchemeHandler : public CefSchemeHandler public: ClientSchemeHandler() : offset_(0) {} - // Process the request. All response generation should take place in this - // method. If there is no response set |response_length| to zero or return - // false and ReadResponse() will not be called. If the response length is not - // known set |response_length| to -1 and ReadResponse() will be called until - // it returns false or until the value of |bytes_read| is set to 0. If the - // response length is known set |response_length| to a positive value and - // ReadResponse() will be called until it returns false, the value of - // |bytes_read| is set to 0 or the specified number of bytes have been read. - // Use the |response| object to set the mime type, http status code and - // optional header values for the response and return true. To redirect the - // request to a new URL set |redirectUrl| to the new URL and return true. virtual bool ProcessRequest(CefRefPtr request, CefString& redirectUrl, - CefRefPtr response, - int* response_length) + CefRefPtr callback) + OVERRIDE { REQUIRE_IO_THREAD(); @@ -65,8 +54,7 @@ public: handled = true; // Set the resulting mime type - response->SetMimeType("text/html"); - response->SetStatus(200); + mime_type_ = "text/html"; } else if(strstr(url.c_str(), "client.png") != NULL) { // Load the response image @@ -77,40 +65,55 @@ public: data_ = std::string(reinterpret_cast(pBytes), dwSize); handled = true; // Set the resulting mime type - response->SetMimeType("image/jpg"); - response->SetStatus(200); + mime_type_ = "image/jpg"; } #elif defined(OS_MACOSX) if(LoadBinaryResource("logo.png", data_)) { handled = true; - response->SetMimeType("image/png"); - response->SetStatus(200); + // Set the resulting mime type + mime_type_ = "image/png"; } #endif } - // Set the resulting response length - *response_length = data_.length(); - - return handled; + if (handled) { + // Indicate the headers are available. + callback->HeadersAvailable(); + return true; + } + + return false; } - // Cancel processing of the request. - virtual void Cancel() + virtual void GetResponseHeaders(CefRefPtr response, + int64& response_length) OVERRIDE + { + REQUIRE_IO_THREAD(); + + ASSERT(!data_.empty()); + + response->SetMimeType(mime_type_); + response->SetStatus(200); + + // Set the resulting response length + response_length = data_.length(); + } + + virtual void Cancel() OVERRIDE { REQUIRE_IO_THREAD(); } - // Copy up to |bytes_to_read| bytes into |data_out|. If the copy succeeds - // set |bytes_read| to the number of bytes copied and return true. If the - // copy fails return false and ReadResponse() will not be called again. - virtual bool ReadResponse(void* data_out, int bytes_to_read, - int* bytes_read) + virtual bool ReadResponse(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) + OVERRIDE { REQUIRE_IO_THREAD(); bool has_data = false; - *bytes_read = 0; + bytes_read = 0; AutoLock lock_scope(this); @@ -121,7 +124,7 @@ public: memcpy(data_out, data_.c_str() + offset_, transfer_size); offset_ += transfer_size; - *bytes_read = transfer_size; + bytes_read = transfer_size; has_data = true; } @@ -130,6 +133,7 @@ public: private: std::string data_; + std::string mime_type_; size_t offset_; IMPLEMENT_REFCOUNTING(ClientSchemeHandler); diff --git a/tests/unittests/scheme_handler_unittest.cc b/tests/unittests/scheme_handler_unittest.cc index 67baf03ed..818230eee 100644 --- a/tests/unittests/scheme_handler_unittest.cc +++ b/tests/unittests/scheme_handler_unittest.cc @@ -12,7 +12,8 @@ class TestResults public: TestResults() : status_code(0), - sub_status_code(0) + sub_status_code(0), + delay(0) { } @@ -27,6 +28,7 @@ public: sub_status_code = 0; sub_allow_origin.clear(); exit_url.clear(); + delay = 0; got_request.reset(); got_read.reset(); got_output.reset(); @@ -51,6 +53,9 @@ public: std::string sub_allow_origin; std::string exit_url; + // Delay for returning scheme handler results. + int delay; + TrackCallback got_request, got_read, @@ -149,21 +154,57 @@ class ClientSchemeHandler : public CefSchemeHandler { public: ClientSchemeHandler(TestResults* tr) - : test_results_(tr), offset_(0), is_sub_(false) {} + : test_results_(tr), offset_(0), is_sub_(false), has_delayed_(false) {} - virtual bool ProcessRequest(CefRefPtr request, + virtual bool ProcessRequest(CefRefPtr request, CefString& redirectUrl, - CefRefPtr response, - int* response_length) + CefRefPtr callback) + OVERRIDE { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + bool handled = false; + std::string url = request->GetURL(); is_sub_ = (!test_results_->sub_url.empty() && test_results_->sub_url == url); if (is_sub_) { test_results_->got_sub_request.yes(); + + if (!test_results_->sub_html.empty()) + handled = true; + } else { + EXPECT_EQ(url, test_results_->url); + + test_results_->got_request.yes(); + + if (!test_results_->redirect_url.empty()) { + redirectUrl = test_results_->redirect_url; + return true; // don't call Continue() for URL redirects. + } else if (!test_results_->html.empty()) { + handled = true; + } + } + + if (handled) { + if (test_results_->delay > 0) { + // Continue after the delay. + CefPostDelayedTask(TID_IO, NewCefRunnableMethod(callback.get(), + &CefSchemeHandlerCallback::HeadersAvailable), test_results_->delay); + } else { + // Continue immediately. + callback->HeadersAvailable(); + } + return true; + } + return false; + } + + virtual void GetResponseHeaders(CefRefPtr response, + int64& response_length) OVERRIDE + { + if (is_sub_) { response->SetStatus(test_results_->sub_status_code); if (!test_results_->sub_allow_origin.empty()) { @@ -177,38 +218,45 @@ public: if (!test_results_->sub_html.empty()) { response->SetMimeType("text/html"); - *response_length = test_results_->sub_html.size(); - return true; + response_length = test_results_->sub_html.size(); } } else { - EXPECT_EQ(url, test_results_->url); - - test_results_->got_request.yes(); - response->SetStatus(test_results_->status_code); - if (!test_results_->redirect_url.empty()) { - redirectUrl = test_results_->redirect_url; - return true; - } else if (!test_results_->html.empty()) { + if (!test_results_->html.empty()) { response->SetMimeType("text/html"); - *response_length = test_results_->html.size(); - return true; + response_length = test_results_->html.size(); } } - - return false; } - virtual void Cancel() + virtual void Cancel() OVERRIDE { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } - virtual bool ReadResponse(void* data_out, int bytes_to_read, int* bytes_read) + virtual bool ReadResponse(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) + OVERRIDE { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + if (test_results_->delay > 0) { + if (!has_delayed_) { + // Continue after a delay. + CefPostDelayedTask(TID_IO, + NewCefRunnableMethod(this, + &ClientSchemeHandler::ContinueAfterDelay, callback), + test_results_->delay); + bytes_read = 0; + return true; + } + + has_delayed_ = false; + } + std::string* data; if (is_sub_) { @@ -220,7 +268,7 @@ public: } bool has_data = false; - *bytes_read = 0; + bytes_read = 0; AutoLock lock_scope(this); @@ -231,7 +279,7 @@ public: memcpy(data_out, data->c_str() + offset_, transfer_size); offset_ += transfer_size; - *bytes_read = transfer_size; + bytes_read = transfer_size; has_data = true; } @@ -239,9 +287,16 @@ public: } private: + void ContinueAfterDelay(CefRefPtr callback) + { + has_delayed_ = true; + callback->BytesAvailable(); + } + TestResults* test_results_; size_t offset_; bool is_sub_; + bool has_delayed_; IMPLEMENT_REFCOUNTING(ClientSchemeHandler); IMPLEMENT_LOCKING(ClientSchemeHandler); @@ -466,6 +521,27 @@ TEST(SchemeHandlerTest, CustomStandardNormalResponse) ClearTestSchemes(); } +// Test that a custom standard scheme can return normal results with delayed +// responses. +TEST(SchemeHandlerTest, CustomStandardNormalResponseDelayed) +{ + RegisterTestScheme("customstd", "test"); + g_TestResults.url = "customstd://test/run.html"; + g_TestResults.html = + "

Success!

"; + g_TestResults.status_code = 200; + g_TestResults.delay = 100; + + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); + handler->ExecuteTest(); + + EXPECT_TRUE(g_TestResults.got_request); + EXPECT_TRUE(g_TestResults.got_read); + EXPECT_TRUE(g_TestResults.got_output); + + ClearTestSchemes(); +} + // Test that a custom nonstandard scheme can return normal results. TEST(SchemeHandlerTest, CustomNonStandardNormalResponse) {