From 087319efdbfbbd05b839a9b67f4ef2a4f3515908 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Fri, 21 Aug 2009 02:14:47 +0000 Subject: [PATCH] libcef: - Add support for custom scheme handlers (entry #49, initial version by heshiming). cefclient: - Add custom scheme handler test. git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@37 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- include/cef.h | 55 +++ include/cef_capi.h | 51 +++ libcef/libcef.vcproj | 4 + libcef/scheme_impl.cc | 411 ++++++++++++++++++ libcef_dll/cpptoc/scheme_handler_cpptoc.cc | 85 ++++ libcef_dll/cpptoc/scheme_handler_cpptoc.h | 35 ++ .../cpptoc/scheme_handler_factory_cpptoc.cc | 44 ++ .../cpptoc/scheme_handler_factory_cpptoc.h | 35 ++ libcef_dll/ctocpp/scheme_handler_ctocpp.cc | 61 +++ libcef_dll/ctocpp/scheme_handler_ctocpp.h | 44 ++ .../ctocpp/scheme_handler_factory_ctocpp.cc | 35 ++ .../ctocpp/scheme_handler_factory_ctocpp.h | 41 ++ libcef_dll/libcef_dll.cc | 23 + libcef_dll/libcef_dll.vcproj | 48 ++ libcef_dll/wrapper/libcef_dll_wrapper.cc | 12 + libcef_dll/wrapper/libcef_dll_wrapper.vcproj | 16 + tests/cefclient/cefclient.cpp | 297 ++++++++++--- tests/cefclient/cefclient.rc | 1 + tests/cefclient/resource.h | 1 + 19 files changed, 1229 insertions(+), 70 deletions(-) create mode 100644 libcef/scheme_impl.cc create mode 100644 libcef_dll/cpptoc/scheme_handler_cpptoc.cc create mode 100644 libcef_dll/cpptoc/scheme_handler_cpptoc.h create mode 100644 libcef_dll/cpptoc/scheme_handler_factory_cpptoc.cc create mode 100644 libcef_dll/cpptoc/scheme_handler_factory_cpptoc.h create mode 100644 libcef_dll/ctocpp/scheme_handler_ctocpp.cc create mode 100644 libcef_dll/ctocpp/scheme_handler_ctocpp.h create mode 100644 libcef_dll/ctocpp/scheme_handler_factory_ctocpp.cc create mode 100644 libcef_dll/ctocpp/scheme_handler_factory_ctocpp.h diff --git a/include/cef.h b/include/cef.h index 4d405dd94..f53252871 100644 --- a/include/cef.h +++ b/include/cef.h @@ -50,6 +50,8 @@ class CefHandler; class CefPostData; class CefPostDataElement; class CefRequest; +class CefSchemeHandler; +class CefSchemeHandlerFactory; class CefStreamReader; class CefStreamWriter; class CefV8Handler; @@ -141,6 +143,16 @@ bool CefRegisterExtension(const std::wstring& extension_name, CefRefPtr handler); +// Register a custom scheme handler factory for the specified |scheme_name| and +// |host_name|. All URLs beginning with scheme_name://host_name/ can be handled +// by CefSchemeHandler instances returned by the factory. Specify an empty +// |host_name| value to match all host names. +/*--cef()--*/ +bool CefRegisterScheme(const std::wstring& scheme_name, + const std::wstring& host_name, + CefRefPtr factory); + + // Interface defining the the reference count implementation methods. All // framework classes must implement the CefBase class. class CefBase @@ -930,4 +942,47 @@ public: std::wstring& exception) =0; }; + +// Class that creates CefSchemeHandler instances. +/*--cef(source=client)--*/ +class CefSchemeHandlerFactory : public CefBase +{ +public: + // Return a new scheme handler instance to handle the request. + /*--cef()--*/ + virtual CefRefPtr Create() =0; +}; + + +// Class used to represent a custom scheme handler interface. +/*--cef(source=client)--*/ +class CefSchemeHandler : public CefBase +{ +public: + // Process the request. All response generation should take place in this + // method. If there is no response set |response_length| to zero and + // ReadResponse() will not be called. If the response length is not known then + // 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. Otherwise, + // 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. If there is a response set + // |mime_type| to the mime type for the response. + /*--cef()--*/ + virtual bool ProcessRequest(CefRefPtr request, + std::wstring& mime_type, int* response_length) =0; + + // Cancel processing of the request. + /*--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; +}; + + #endif // _CEF_H diff --git a/include/cef_capi.h b/include/cef_capi.h index b699d105f..6c8db308f 100644 --- a/include/cef_capi.h +++ b/include/cef_capi.h @@ -126,6 +126,13 @@ CEF_EXPORT void cef_do_message_loop_work(); CEF_EXPORT int cef_register_extension(const wchar_t* extension_name, const wchar_t* javascript_code, struct _cef_v8handler_t* handler); +// Register a custom scheme handler factory for the specified |scheme_name| and +// |host_name|. All URLs beginning with scheme_name://host_name/ can be handled +// by cef_scheme_handler_t instances returned by the factory. Specify an NULL +// |host_name| value to match all host names. +CEF_EXPORT int cef_register_scheme(const wchar_t* scheme_name, + const wchar_t* host_name, struct _cef_scheme_handler_factory_t* factory); + typedef struct _cef_base_t { // Size of the data structure. @@ -769,6 +776,50 @@ CEF_EXPORT cef_v8value_t* cef_v8value_create_function(const wchar_t* name, cef_v8handler_t* handler); +// Structure that creates cef_scheme_handler_t instances. +typedef struct _cef_scheme_handler_factory_t +{ + // Base structure. + cef_base_t base; + + // Return a new scheme handler instance to handle the request. + struct _cef_scheme_handler_t* (CEF_CALLBACK *create)( + struct _cef_scheme_handler_factory_t* self); + +} cef_scheme_handler_factory_t; + + +// Structure used to represent a custom scheme handler structure. +typedef struct _cef_scheme_handler_t +{ + // Base structure. + 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 and + // read_response() will not be called. If the response length is not known + // then 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. + // Otherwise, 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. If there is a + // response set |mime_type| to the mime type for the response. + int (CEF_CALLBACK *process_request)(struct _cef_scheme_handler_t* self, + struct _cef_request_t* request, cef_string_t* mime_type, + int* response_length); + + // Cancel processing of the request. + void (CEF_CALLBACK *cancel)(struct _cef_scheme_handler_t* self); + + // 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. + int (CEF_CALLBACK *read_response)(struct _cef_scheme_handler_t* self, + void* data_out, int bytes_to_read, int* bytes_read); + +} cef_scheme_handler_t; + + #ifdef __cplusplus } #endif diff --git a/libcef/libcef.vcproj b/libcef/libcef.vcproj index 9c62948b0..9dbfac271 100644 --- a/libcef/libcef.vcproj +++ b/libcef/libcef.vcproj @@ -373,6 +373,10 @@ RelativePath=".\request_impl.h" > + + diff --git a/libcef/scheme_impl.cc b/libcef/scheme_impl.cc new file mode 100644 index 000000000..a0b50235d --- /dev/null +++ b/libcef/scheme_impl.cc @@ -0,0 +1,411 @@ +// Copyright (c) 2009 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "precompiled_libcef.h" +#include "base/lazy_instance.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/worker_pool.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "net/base/upload_data.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_filter.h" +#include "net/url_request/url_request_job.h" + +#include "include/cef.h" +#include "tracker.h" +#include "context.h" +#include "request_impl.h" + +#include + + +// Memory manager. + +base::LazyInstance g_scheme_tracker(base::LINKER_INITIALIZED); + +class TrackBase : public CefTrackObject +{ +public: + TrackBase(CefBase* base) { base_ = base; } + +protected: + CefRefPtr base_; +}; + +static void TrackAdd(CefTrackObject* object) +{ + g_scheme_tracker.Pointer()->Add(object); +} + +static void TrackDelete(CefTrackObject* object) +{ + g_scheme_tracker.Pointer()->Delete(object); +} + + +// URLRequestJob implementation. + +class CefUrlRequestJob : public URLRequestJob { +public: + CefUrlRequestJob(URLRequest* request, CefRefPtr handler) + : URLRequestJob(request), + url_(request->url()), + handler_(handler), + response_length_(0), + remaining_bytes_(0) { } + + virtual ~CefUrlRequestJob(){} + + virtual void Start() + { + handler_->Cancel(); + // Continue asynchronously. + DCHECK(!async_resolver_); + async_resolver_ = new AsyncResolver(this); + WorkerPool::PostTask(FROM_HERE, NewRunnableMethod( + async_resolver_.get(), &AsyncResolver::Resolve, url_), true); + return; + } + + virtual void Kill() + { + if (async_resolver_) { + async_resolver_->Cancel(); + async_resolver_ = NULL; + } + + URLRequestJob::Kill(); + } + + virtual bool ReadRawData(net::IOBuffer* dest, int dest_size, int *bytes_read) + { + 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; + return true; + } + } + + virtual bool IsRedirectResponse(GURL* location, int* http_status_code) + { + return false; + } + + virtual bool GetContentEncodings( + std::vector* encoding_types) + { + DCHECK(encoding_types->empty()); + + return !encoding_types->empty(); + } + + virtual bool GetMimeType(std::string* mime_type) const + { + DCHECK(request_); + // call handler to get mime type + *mime_type = mime_type_; + return true; + } + + virtual void SetExtraRequestHeaders(const std::string& headers) + { + } + + CefRefPtr handler_; + std::string mime_type_; + int response_length_; + +protected: + GURL url_; + +private: + void DidResolve(const GURL& url) + { + async_resolver_ = NULL; + + // We may have been orphaned... + if (!request_) + return; + + remaining_bytes_ = response_length_; + if (remaining_bytes_>0) + set_expected_content_size(remaining_bytes_); + NotifyHeadersComplete(); + } + + int64 remaining_bytes_; + std::string m_response; + + class AsyncResolver : + public base::RefCountedThreadSafe { + public: + explicit AsyncResolver(CefUrlRequestJob* owner) + : owner_(owner), owner_loop_(MessageLoop::current()) { + } + + void Resolve(const GURL& url) { + AutoLock locked(lock_); + ////////////////////////////////////////////////////////////////////////// + // safe to perform long operation here + CefRefPtr req(CefRequest::CreateRequest()); + req->SetURL(UTF8ToWide(url.spec())); + req->SetMethod(UTF8ToWide(owner_->request()->method())); + + // check to see if we have post data + net::UploadData* data = owner_->request()->get_upload(); + if (data) { + CefRefPtr postdata(CefPostData::CreatePostData()); + static_cast(postdata.get())->Set(*data); + req->SetPostData(postdata); + } + owner_->handler_->Cancel(); + std::wstring mime_type; + int response_length = 0; + // handler should complete content generation in ProcessRequest + bool res = owner_->handler_->ProcessRequest(req, mime_type, + &response_length); + if (res) { + owner_->mime_type_ = WideToUTF8(mime_type); + owner_->response_length_ = response_length; + } + ////////////////////////////////////////////////////////////////////////// + if (owner_loop_) { + owner_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &AsyncResolver::ReturnResults, url)); + } + } + + void Cancel() { + owner_->handler_->Cancel(); + + owner_ = NULL; + + AutoLock locked(lock_); + owner_loop_ = NULL; + } + + private: + void ReturnResults(const GURL& url) { + if (owner_) + owner_->DidResolve(url); + } + + CefUrlRequestJob* owner_; + + Lock lock_; + MessageLoop* owner_loop_; + }; + + friend class AsyncResolver; + scoped_refptr async_resolver_; + + DISALLOW_COPY_AND_ASSIGN(CefUrlRequestJob); +}; + + +// URLRequestFilter clone that manages the CefSchemeHandlerFactory pointers. + +class CefUrlRequestFilter { +public: + // scheme,hostname -> ProtocolFactory + typedef std::map, + CefSchemeHandlerFactory*> HandlerMap; + + // Singleton instance for use. + static CefUrlRequestFilter* GetInstance() + { + if (!shared_instance_) + shared_instance_ = new CefUrlRequestFilter; + return shared_instance_; + } + + static URLRequestJob* Factory(URLRequest* request, + const std::string& scheme) + { + // Returning null here just means that the built-in handler will be used. + return GetInstance()->FindRequestHandler(request, scheme); + } + + void AddHostnameHandler(const std::string& scheme, + const std::string& hostname, + CefSchemeHandlerFactory* factory) + { + handler_map_[make_pair(scheme, hostname)] = factory; + + // Register with the ProtocolFactory. + URLRequest::RegisterProtocolFactory(scheme, + &CefUrlRequestFilter::Factory); + } + + void RemoveHostnameHandler(const std::string& scheme, + const std::string& hostname) + { + HandlerMap::iterator iter = + handler_map_.find(make_pair(scheme, hostname)); + DCHECK(iter != handler_map_.end()); + + handler_map_.erase(iter); + } + + // Clear all the existing URL handlers and unregister with the + // ProtocolFactory. Resets the hit count. + void ClearHandlers() + { + // Unregister with the ProtocolFactory. + std::set schemes; + for (HandlerMap::const_iterator i = handler_map_.begin(); + i != handler_map_.end(); ++i) { + schemes.insert(i->first.first); + } + for (std::set::const_iterator scheme = schemes.begin(); + scheme != schemes.end(); ++scheme) { + URLRequest::RegisterProtocolFactory(*scheme, NULL); + } + + handler_map_.clear(); + hit_count_ = 0; + } + + CefSchemeHandlerFactory* FindRequestHandlerFactory(URLRequest* request, + const std::string& scheme) + { + CefSchemeHandlerFactory* factory = NULL; + if (request->url().is_valid()) { + // Check for a map with a hostname first. + const std::string& hostname = request->url().host(); + + HandlerMap::iterator i = handler_map_.find(make_pair(scheme, hostname)); + if (i != handler_map_.end()) + factory = i->second; + } + + if (!factory) { + // Check for a map with no specified hostname. + HandlerMap::iterator i = + handler_map_.find(make_pair(scheme, std::string())); + if (i != handler_map_.end()) + factory = i->second; + } + + return factory; + } + + // Returns the number of times a handler was used to service a request. + int hit_count() const { return hit_count_; } + +protected: + CefUrlRequestFilter() : hit_count_(0) { } + + // Helper method that looks up the request in the handler_map_. + URLRequestJob* FindRequestHandler(URLRequest* request, + const std::string& scheme) + { + URLRequestJob* job = NULL; + CefSchemeHandlerFactory* factory = + FindRequestHandlerFactory(request, scheme); + if (factory) { + CefRefPtr handler = factory->Create(); + if (handler.get()) + job = new CefUrlRequestJob(request, handler); + } + + if (job) { + DLOG(INFO) << "URLRequestFilter hit for " << request->url().spec(); + hit_count_++; + } + return job; + } + + // Maps hostnames to factories. Hostnames take priority over URLs. + HandlerMap handler_map_; + + int hit_count_; + +private: + // Singleton instance. + static CefUrlRequestFilter* shared_instance_; + + DISALLOW_EVIL_CONSTRUCTORS(CefUrlRequestFilter); +}; + +CefUrlRequestFilter* CefUrlRequestFilter::shared_instance_ = NULL; + + +class SchemeRequestJobWrapper { +public: + SchemeRequestJobWrapper(const std::string& scheme_name, + const std::string& host_name, + CefSchemeHandlerFactory* factory) + : factory_(factory), scheme_name_(scheme_name), host_name_(host_name) + { + // The reference will be released when the application exits. + TrackAdd(new TrackBase(factory)); + } + + void RegisterScheme() + { + // we need to store the pointer of this handler because + // we can't pass it as a parameter to the factory method + CefUrlRequestFilter::GetInstance()->AddHostnameHandler( + scheme_name_, host_name_, factory_); + } + + void AddRef() {} + void Release() {} + +private: + CefSchemeHandlerFactory* factory_; + std::string scheme_name_; + std::string host_name_; +}; + +bool CefRegisterScheme(const std::wstring& scheme_name, + const std::wstring& host_name, + CefRefPtr factory) +{ + // Verify that the context is already initialized + if(!_Context.get()) + return false; + + SchemeRequestJobWrapper* wrapper = new SchemeRequestJobWrapper( + WideToUTF8(scheme_name), WideToUTF8(host_name), factory); + + PostTask(FROM_HERE, NewRunnableMethod(wrapper, + &SchemeRequestJobWrapper::RegisterScheme)); + return true; +} diff --git a/libcef_dll/cpptoc/scheme_handler_cpptoc.cc b/libcef_dll/cpptoc/scheme_handler_cpptoc.cc new file mode 100644 index 000000000..a1b1d565a --- /dev/null +++ b/libcef_dll/cpptoc/scheme_handler_cpptoc.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2009 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 "../precompiled_libcef.h" +#include "cpptoc/scheme_handler_cpptoc.h" +#include "ctocpp/request_ctocpp.h" +#include "../transfer_util.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* mime_type, int* response_length) +{ + DCHECK(self); + DCHECK(request); + DCHECK(mime_type); + DCHECK(response_length); + if(!self || !request || !mime_type || !response_length) + return 0; + + std::wstring mimeTypeStr; + if(*mime_type) + mimeTypeStr = *mime_type; + + bool rv = CefSchemeHandlerCppToC::Get(self)->ProcessRequest( + CefRequestCToCpp::Wrap(request), mimeTypeStr, response_length); + + transfer_string_contents(mimeTypeStr, mime_type); + + return rv?1:0; +} + +void CEF_CALLBACK scheme_handler_cancel(struct _cef_scheme_handler_t* self) +{ + DCHECK(self); + if(!self) + return; + + 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. + +CefSchemeHandlerCppToC::CefSchemeHandlerCppToC(CefSchemeHandler* cls) + : CefCppToC( + cls) +{ + struct_.struct_.process_request = scheme_handler_process_request; + struct_.struct_.cancel = scheme_handler_cancel; + struct_.struct_.read_response = scheme_handler_read_response; +} + +#ifdef _DEBUG +long CefCppToC::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/cpptoc/scheme_handler_cpptoc.h b/libcef_dll/cpptoc/scheme_handler_cpptoc.h new file mode 100644 index 000000000..d8ac0eee8 --- /dev/null +++ b/libcef_dll/cpptoc/scheme_handler_cpptoc.h @@ -0,0 +1,35 @@ +// Copyright (c) 2009 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 _SCHEMEHANDLER_CPPTOC_H +#define _SCHEMEHANDLER_CPPTOC_H + +#ifndef USING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed wrapper-side only") +#else // USING_CEF_SHARED + +#include "cef.h" +#include "cef_capi.h" +#include "cpptoc.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed wrapper-side only. +class CefSchemeHandlerCppToC + : public CefCppToC +{ +public: + CefSchemeHandlerCppToC(CefSchemeHandler* cls); + virtual ~CefSchemeHandlerCppToC() {} +}; + +#endif // USING_CEF_SHARED +#endif // _SCHEMEHANDLER_CPPTOC_H + diff --git a/libcef_dll/cpptoc/scheme_handler_factory_cpptoc.cc b/libcef_dll/cpptoc/scheme_handler_factory_cpptoc.cc new file mode 100644 index 000000000..55d5de329 --- /dev/null +++ b/libcef_dll/cpptoc/scheme_handler_factory_cpptoc.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2009 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 "../precompiled_libcef.h" +#include "cpptoc/scheme_handler_cpptoc.h" +#include "cpptoc/scheme_handler_factory_cpptoc.h" + + +// MEMBER FUNCTIONS - Body may be edited by hand. + +struct _cef_scheme_handler_t* CEF_CALLBACK scheme_handler_factory_create( + struct _cef_scheme_handler_factory_t* self) +{ + CefRefPtr rv = + CefSchemeHandlerFactoryCppToC::Get(self)->Create(); + + return CefSchemeHandlerCppToC::Wrap(rv); +} + + +// CONSTRUCTOR - Do not edit by hand. + +CefSchemeHandlerFactoryCppToC::CefSchemeHandlerFactoryCppToC( + CefSchemeHandlerFactory* cls) + : CefCppToC(cls) +{ + struct_.struct_.create = scheme_handler_factory_create; +} + +#ifdef _DEBUG +long CefCppToC::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/cpptoc/scheme_handler_factory_cpptoc.h b/libcef_dll/cpptoc/scheme_handler_factory_cpptoc.h new file mode 100644 index 000000000..0af25ca7f --- /dev/null +++ b/libcef_dll/cpptoc/scheme_handler_factory_cpptoc.h @@ -0,0 +1,35 @@ +// Copyright (c) 2009 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 _SCHEMEHANDLERFACTORY_CPPTOC_H +#define _SCHEMEHANDLERFACTORY_CPPTOC_H + +#ifndef USING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed wrapper-side only") +#else // USING_CEF_SHARED + +#include "cef.h" +#include "cef_capi.h" +#include "cpptoc.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed wrapper-side only. +class CefSchemeHandlerFactoryCppToC + : public CefCppToC +{ +public: + CefSchemeHandlerFactoryCppToC(CefSchemeHandlerFactory* cls); + virtual ~CefSchemeHandlerFactoryCppToC() {} +}; + +#endif // USING_CEF_SHARED +#endif // _SCHEMEHANDLERFACTORY_CPPTOC_H + diff --git a/libcef_dll/ctocpp/scheme_handler_ctocpp.cc b/libcef_dll/ctocpp/scheme_handler_ctocpp.cc new file mode 100644 index 000000000..5b5148d72 --- /dev/null +++ b/libcef_dll/ctocpp/scheme_handler_ctocpp.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2009 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 "../precompiled_libcef.h" +#include "cpptoc/request_cpptoc.h" +#include "ctocpp/scheme_handler_ctocpp.h" +#include "../transfer_util.h" + + +// VIRTUAL METHODS - Body may be edited by hand. + +bool CefSchemeHandlerCToCpp::ProcessRequest(CefRefPtr request, + std::wstring& mime_type, int* response_length) +{ + if(CEF_MEMBER_MISSING(struct_, process_request)) + return false; + + cef_string_t mimeTypeRet = NULL; + if(!mime_type.empty()) + mimeTypeRet = cef_string_alloc(mime_type.c_str()); + + int rv = struct_->process_request(struct_, CefRequestCppToC::Wrap(request), + &mimeTypeRet, response_length); + + transfer_string_contents(mimeTypeRet, mime_type, true); + + return rv; +} + +void CefSchemeHandlerCToCpp::Cancel() +{ + if(CEF_MEMBER_MISSING(struct_, cancel)) + return; + + 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); +} + + +#ifdef _DEBUG +long CefCToCpp::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/ctocpp/scheme_handler_ctocpp.h b/libcef_dll/ctocpp/scheme_handler_ctocpp.h new file mode 100644 index 000000000..fe73a1039 --- /dev/null +++ b/libcef_dll/ctocpp/scheme_handler_ctocpp.h @@ -0,0 +1,44 @@ +// Copyright (c) 2009 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 _SCHEMEHANDLER_CTOCPP_H +#define _SCHEMEHANDLER_CTOCPP_H + +#ifndef BUILDING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed DLL-side only") +#else // BUILDING_CEF_SHARED + +#include "cef.h" +#include "cef_capi.h" +#include "ctocpp.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed DLL-side only. +class CefSchemeHandlerCToCpp + : public CefCToCpp +{ +public: + CefSchemeHandlerCToCpp(cef_scheme_handler_t* str) + : CefCToCpp(str) {} + virtual ~CefSchemeHandlerCToCpp() {} + + // CefSchemeHandler methods + virtual bool ProcessRequest(CefRefPtr request, + std::wstring& mime_type, int* response_length); + virtual void Cancel(); + virtual bool ReadResponse(void* data_out, int bytes_to_read, int* bytes_read); +}; + +#endif // BUILDING_CEF_SHARED +#endif // _SCHEMEHANDLER_CTOCPP_H + diff --git a/libcef_dll/ctocpp/scheme_handler_factory_ctocpp.cc b/libcef_dll/ctocpp/scheme_handler_factory_ctocpp.cc new file mode 100644 index 000000000..4a6228c0d --- /dev/null +++ b/libcef_dll/ctocpp/scheme_handler_factory_ctocpp.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2009 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 "../precompiled_libcef.h" +#include "ctocpp/scheme_handler_ctocpp.h" +#include "ctocpp/scheme_handler_factory_ctocpp.h" + + +// VIRTUAL METHODS - Body may be edited by hand. + +CefRefPtr CefSchemeHandlerFactoryCToCpp::Create() +{ + if(CEF_MEMBER_MISSING(struct_, create)) + return NULL; + + _cef_scheme_handler_t* rv = struct_->create(struct_); + + return CefSchemeHandlerCToCpp::Wrap(rv); +} + + +#ifdef _DEBUG +long CefCToCpp::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/ctocpp/scheme_handler_factory_ctocpp.h b/libcef_dll/ctocpp/scheme_handler_factory_ctocpp.h new file mode 100644 index 000000000..e4746567a --- /dev/null +++ b/libcef_dll/ctocpp/scheme_handler_factory_ctocpp.h @@ -0,0 +1,41 @@ +// Copyright (c) 2009 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 _SCHEMEHANDLERFACTORY_CTOCPP_H +#define _SCHEMEHANDLERFACTORY_CTOCPP_H + +#ifndef BUILDING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed DLL-side only") +#else // BUILDING_CEF_SHARED + +#include "cef.h" +#include "cef_capi.h" +#include "ctocpp.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed DLL-side only. +class CefSchemeHandlerFactoryCToCpp + : public CefCToCpp +{ +public: + CefSchemeHandlerFactoryCToCpp(cef_scheme_handler_factory_t* str) + : CefCToCpp(str) {} + virtual ~CefSchemeHandlerFactoryCToCpp() {} + + // CefSchemeHandlerFactory methods + virtual CefRefPtr Create(); +}; + +#endif // BUILDING_CEF_SHARED +#endif // _SCHEMEHANDLERFACTORY_CTOCPP_H + diff --git a/libcef_dll/libcef_dll.cc b/libcef_dll/libcef_dll.cc index 4cf793cb7..cd4e175b7 100644 --- a/libcef_dll/libcef_dll.cc +++ b/libcef_dll/libcef_dll.cc @@ -16,6 +16,8 @@ #include "cpptoc/stream_writer_cpptoc.h" #include "cpptoc/v8value_cpptoc.h" #include "ctocpp/handler_ctocpp.h" +#include "ctocpp/scheme_handler_ctocpp.h" +#include "ctocpp/scheme_handler_factory_ctocpp.h" #include "ctocpp/v8handler_ctocpp.h" #include "base/string_util.h" @@ -43,6 +45,8 @@ CEF_EXPORT void cef_shutdown() DCHECK(CefStreamWriterCppToC::DebugObjCt == 0); DCHECK(CefV8ValueCppToC::DebugObjCt == 0); DCHECK(CefHandlerCToCpp::DebugObjCt == 0); + DCHECK(CefSchemeHandlerCToCpp::DebugObjCt == 0); + DCHECK(CefSchemeHandlerFactoryCToCpp::DebugObjCt == 0); DCHECK(CefV8HandlerCToCpp::DebugObjCt == 0); #endif // _DEBUG } @@ -107,3 +111,22 @@ CEF_EXPORT int cef_register_plugin(const cef_plugin_info_t* plugin_info) return CefRegisterPlugin(pluginInfo); } + +CEF_EXPORT int cef_register_scheme(const wchar_t* scheme_name, + const wchar_t* host_name, struct _cef_scheme_handler_factory_t* factory) +{ + DCHECK(scheme_name); + DCHECK(factory); + if(!scheme_name || !factory) + return 0; + + std::wstring nameStr, codeStr; + + if(scheme_name) + nameStr = scheme_name; + if(host_name) + codeStr = host_name; + + return CefRegisterScheme(nameStr, codeStr, + CefSchemeHandlerFactoryCToCpp::Wrap(factory)); +} diff --git a/libcef_dll/libcef_dll.vcproj b/libcef_dll/libcef_dll.vcproj index d9e967617..ac52f35c4 100644 --- a/libcef_dll/libcef_dll.vcproj +++ b/libcef_dll/libcef_dll.vcproj @@ -430,6 +430,54 @@ RelativePath=".\ctocpp\handler_ctocpp.h" > + + + + + + + + + + + + + + + + + + + + diff --git a/libcef_dll/wrapper/libcef_dll_wrapper.cc b/libcef_dll/wrapper/libcef_dll_wrapper.cc index 86201da4b..25ba5f3d7 100644 --- a/libcef_dll/wrapper/libcef_dll_wrapper.cc +++ b/libcef_dll/wrapper/libcef_dll_wrapper.cc @@ -8,6 +8,8 @@ #include "cef_nplugin.h" #include "cef_nplugin_capi.h" #include "../cpptoc/handler_cpptoc.h" +#include "../cpptoc/scheme_handler_cpptoc.h" +#include "../cpptoc/scheme_handler_factory_cpptoc.h" #include "../cpptoc/v8handler_cpptoc.h" #include "../ctocpp/browser_ctocpp.h" #include "../ctocpp/post_data_ctocpp.h" @@ -31,6 +33,8 @@ void CefShutdown() #ifdef _DEBUG // Check that all wrapper objects have been destroyed DCHECK(CefHandlerCppToC::DebugObjCt == 0); + DCHECK(CefSchemeHandlerCppToC::DebugObjCt == 0); + DCHECK(CefSchemeHandlerFactoryCppToC::DebugObjCt == 0); DCHECK(CefV8HandlerCppToC::DebugObjCt == 0); DCHECK(CefBrowserCToCpp::DebugObjCt == 0); DCHECK(CefRequestCToCpp::DebugObjCt == 0); @@ -95,3 +99,11 @@ bool CefRegisterPlugin(const struct CefPluginInfo& plugin_info) return (cef_register_plugin(&pluginInfo) ? true : false); } + +bool CefRegisterScheme(const std::wstring& scheme_name, + const std::wstring& host_name, + CefRefPtr factory) +{ + return cef_register_scheme(scheme_name.c_str(), host_name.c_str(), + CefSchemeHandlerFactoryCppToC::Wrap(factory)); +} diff --git a/libcef_dll/wrapper/libcef_dll_wrapper.vcproj b/libcef_dll/wrapper/libcef_dll_wrapper.vcproj index d983c7043..eb99f1539 100644 --- a/libcef_dll/wrapper/libcef_dll_wrapper.vcproj +++ b/libcef_dll/wrapper/libcef_dll_wrapper.vcproj @@ -141,6 +141,22 @@ RelativePath="..\cpptoc\handler_cpptoc.h" > + + + + + + + + diff --git a/tests/cefclient/cefclient.cpp b/tests/cefclient/cefclient.cpp index 5a7463b1c..e56a4edcf 100644 --- a/tests/cefclient/cefclient.cpp +++ b/tests/cefclient/cefclient.cpp @@ -33,11 +33,107 @@ INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); // Convert a std::string to a std::wstring std::wstring StringToWString(const std::string& s) { - std::wstring temp(s.length(),L' '); - std::copy(s.begin(), s.end(), temp.begin()); - return temp; + wchar_t* wch; + UINT bytes = MultiByteToWideChar(CP_ACP, 0, s.c_str(), s.size()+1, NULL, 0); + wch = new wchar_t[bytes]; + if(wch) + bytes = MultiByteToWideChar(CP_ACP, 0, s.c_str(), s.size()+1, wch, bytes); + std::wstring str = wch; + delete [] wch; + return str; } +// Convert a std::wstring to a std::string +std::string WStringToString(const std::wstring& s) +{ + char* ch; + UINT bytes = WideCharToMultiByte(CP_ACP, 0, s.c_str(), s.size()+1, NULL, 0, + NULL, NULL); + ch = new char[bytes]; + if(ch) + bytes = WideCharToMultiByte(CP_ACP, 0, s.c_str(), s.size()+1, ch, bytes, + NULL, NULL); + std::string str = ch; + delete [] ch; + return str; +} + +// Load a resource of type BINARY +bool LoadBinaryResource(int binaryId, DWORD &dwSize, LPBYTE &pBytes) +{ + HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(binaryId), + MAKEINTRESOURCE(256)); + if(hRes) + { + HGLOBAL hGlob = LoadResource(hInst, hRes); + if(hGlob) + { + dwSize = SizeofResource(hInst, hRes); + pBytes = (LPBYTE)LockResource(hGlob); + if(dwSize > 0 && pBytes) + return true; + } + } + + return false; +} + +// Dump the contents of the request into a string. +void DumpRequestContents(CefRefPtr request, std::wstring& str) +{ + std::wstringstream ss; + + ss << L"URL: " << request->GetURL(); + ss << L"\nMethod: " << request->GetMethod(); + + CefRequest::HeaderMap headerMap; + request->GetHeaderMap(headerMap); + if(headerMap.size() > 0) { + ss << L"\nHeaders:"; + CefRequest::HeaderMap::const_iterator it = headerMap.begin(); + for(; it != headerMap.end(); ++it) { + ss << L"\n\t" << (*it).first << L": " << (*it).second; + } + } + + CefRefPtr postData = request->GetPostData(); + if(postData.get()) { + CefPostData::ElementVector elements; + postData->GetElements(elements); + if(elements.size() > 0) { + ss << L"\nPost Data:"; + CefRefPtr element; + CefPostData::ElementVector::const_iterator it = elements.begin(); + for(; it != elements.end(); ++it) { + element = (*it); + if(element->GetType() == PDE_TYPE_BYTES) { + // the element is composed of bytes + ss << L"\n\tBytes: "; + if(element->GetBytesCount() == 0) + ss << L"(empty)"; + else { + // retrieve the data. + size_t size = element->GetBytesCount(); + char* bytes = new char[size]; + element->GetBytes(size, bytes); + ss << StringToWString(std::string(bytes, size)); + delete [] bytes; + } + } else if(element->GetType() == PDE_TYPE_FILE) { + ss << L"\n\tFile: " << element->GetFile(); + } + } + } + } + + str = ss.str(); +} + +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + + // Implementation of the V8 handler class for the "cef.test" extension. class ClientV8ExtensionHandler : public CefThreadSafeBase { @@ -97,6 +193,123 @@ private: std::wstring test_param_; }; + +// Implementation of the schema handler for client:// requests. +class ClientSchemeHandler : public CefThreadSafeBase +{ +public: + ClientSchemeHandler() : size_(0), offset_(0), bytes_(NULL) {} + + // Process the request. All response generation should take place in this + // method. If there is no response set |response_length| to zero and + // ReadResponse() will not be called. If the response length is not known then + // 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. Otherwise, + // 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. If there is a response set + // |mime_type| to the mime type for the response. + virtual bool ProcessRequest(CefRefPtr request, + std::wstring& mime_type, int* response_length) + { + bool handled = false; + + Lock(); + std::wstring url = request->GetURL(); + if(wcsstr(url.c_str(), L"handler.html") != NULL) { + // Build the response html + html_ = "Client Scheme Handler" + "This contents of this page page are served by the " + "ClientSchemeHandler class handling the client:// protocol." + "
You should see an image:" + "
";
+      
+      // Output a string representation of the request
+      std::wstring dump;
+      DumpRequestContents(request, dump);
+      html_.append(WStringToString(dump));
+
+      html_.append("

Try the test form:" + "
" + "" + "" + "" + "
"); + + handled = true; + size_ = html_.size(); + bytes_ = (LPBYTE)html_.c_str(); + + // Set the resulting mime type + mime_type = L"text/html"; + } + else if(wcsstr(url.c_str(), L"client.gif") != NULL) { + // Load the response image + if(LoadBinaryResource(IDS_LOGO, size_, bytes_)) { + handled = true; + // Set the resulting mime type + mime_type = L"image/jpg"; + } + } + + // Set the resulting response length + *response_length = size_; + Unlock(); + + return handled; + } + + // Cancel processing of the request. + virtual void Cancel() + { + } + + // 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) + { + bool has_data = false; + *bytes_read = 0; + + Lock(); + + if(offset_ < size_) { + // Copy the next block of data into the buffer. + int transfer_size = min(bytes_to_read, static_cast(size_ - offset_)); + memcpy(data_out, bytes_ + offset_, transfer_size); + offset_ += transfer_size; + + *bytes_read = transfer_size; + has_data = true; + } + + Unlock(); + + return has_data; + } + +private: + DWORD size_, offset_; + LPBYTE bytes_; + std::string html_; +}; + +// Implementation of the factory for for creating schema handlers. +class ClientSchemeHandlerFactory : + public CefThreadSafeBase +{ +public: + // Return a new scheme handler instance to handle the request. + virtual CefRefPtr Create() + { + return new ClientSchemeHandler(); + } +}; + + +// Program entry point function. int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, @@ -156,6 +369,10 @@ int APIENTRY _tWinMain(HINSTANCE hInstance, L"})();"; CefRegisterExtension(L"v8/test", code, new ClientV8ExtensionHandler()); + // Register the scheme handler factory for requests using the client:// + // protocol in the tests domain. + CefRegisterScheme(L"client", L"tests", new ClientSchemeHandlerFactory()); + MSG msg; HACCEL hAccelTable; @@ -260,26 +477,6 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) return TRUE; } -// Load a resource of type BINARY -bool LoadBinaryResource(int binaryId, DWORD &dwSize, LPBYTE &pBytes) -{ - HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(binaryId), - MAKEINTRESOURCE(256)); - if(hRes) - { - HGLOBAL hGlob = LoadResource(hInst, hRes); - if(hGlob) - { - dwSize = SizeofResource(hInst, hRes); - pBytes = (LPBYTE)LockResource(hGlob); - if(dwSize > 0 && pBytes) - return true; - } - } - - return false; -} - // Implementation of the V8 handler class for the "window.cef_test.Dump" // function. class ClientV8FunctionHandler : public CefThreadSafeBase @@ -577,54 +774,10 @@ public: std::wstring url = request->GetURL(); if(url == L"http://tests/request") { // Show the request contents - std::wstringstream ss; - - ss << L"URL: " << url; - ss << L"\nMethod: " << request->GetMethod(); - - CefRequest::HeaderMap headerMap; - request->GetHeaderMap(headerMap); - if(headerMap.size() > 0) { - ss << L"\nHeaders:"; - CefRequest::HeaderMap::const_iterator it = headerMap.begin(); - for(; it != headerMap.end(); ++it) { - ss << L"\n\t" << (*it).first << L": " << (*it).second; - } - } - - CefRefPtr postData = request->GetPostData(); - if(postData.get()) { - CefPostData::ElementVector elements; - postData->GetElements(elements); - if(elements.size() > 0) { - ss << L"\nPost Data:"; - CefRefPtr element; - CefPostData::ElementVector::const_iterator it = elements.begin(); - for(; it != elements.end(); ++it) { - element = (*it); - if(element->GetType() == PDE_TYPE_BYTES) { - // the element is composed of bytes - ss << L"\n\tBytes: "; - if(element->GetBytesCount() == 0) - ss << L"(empty)"; - else { - // retrieve the data. - size_t size = element->GetBytesCount(); - char* bytes = new char[size]; - element->GetBytes(size, bytes); - ss << StringToWString(std::string(bytes, size)); - delete [] bytes; - } - } else if(element->GetType() == PDE_TYPE_FILE) { - ss << L"\n\tFile: " << element->GetFile(); - } - } - } - } - - std::wstring str = ss.str(); + std::wstring dump; + DumpRequestContents(request, dump); resourceStream = CefStreamReader::CreateForData( - (void*)str.c_str(), str.size() * sizeof(wchar_t)); + (void*)dump.c_str(), dump.size() * sizeof(wchar_t)); mimeType = L"text/plain"; } else if(wcsstr(url.c_str(), L"logo.gif") != NULL) { // Any time we find "logo.gif" in the URL substitute in our own image @@ -1110,6 +1263,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) browser->GetMainFrame()->LoadRequest(request); } return 0; + case ID_TESTS_SCHEME_HANDLER: // Test the scheme handler + if(browser.get()) + browser->GetMainFrame()->LoadURL(L"client://tests/handler.html"); + return 0; } } break; diff --git a/tests/cefclient/cefclient.rc b/tests/cefclient/cefclient.rc index 7cbe22e5c..3655dfb2e 100644 --- a/tests/cefclient/cefclient.rc +++ b/tests/cefclient/cefclient.rc @@ -63,6 +63,7 @@ BEGIN MENUITEM "Plugin", ID_TESTS_PLUGIN MENUITEM "Popup Window", ID_TESTS_POPUP MENUITEM "Request", ID_TESTS_REQUEST + MENUITEM "Scheme Handler", ID_TESTS_SCHEME_HANDLER END END diff --git a/tests/cefclient/resource.h b/tests/cefclient/resource.h index cff30e323..44dad4cd6 100644 --- a/tests/cefclient/resource.h +++ b/tests/cefclient/resource.h @@ -27,6 +27,7 @@ #define ID_TESTS_PLUGIN 32774 #define ID_TESTS_POPUP 32775 #define ID_TESTS_REQUEST 32776 +#define ID_TESTS_SCHEME_HANDLER 32777 #define IDC_STATIC -1 #define IDS_LOGO 1000