Add binary format support to CefMessageRouter (fixes #3502)

This commit is contained in:
Nik Pavlov 2023-10-25 18:20:55 +00:00 committed by Marshall Greenblatt
parent 1b74ac5124
commit 44323082b1
34 changed files with 2218 additions and 534 deletions

View File

@ -147,6 +147,8 @@
'libcef_dll/wrapper/cef_byte_read_handler.cc',
'libcef_dll/wrapper/cef_closure_task.cc',
'libcef_dll/wrapper/cef_message_router.cc',
'libcef_dll/wrapper/cef_message_router_utils.cc',
'libcef_dll/wrapper/cef_message_router_utils.h',
'libcef_dll/wrapper/cef_resource_manager.cc',
'libcef_dll/wrapper/cef_scoped_temp_dir.cc',
'libcef_dll/wrapper/cef_stream_resource_handler.cc',
@ -218,6 +220,8 @@
'tests/shared/browser/util_win.h',
],
'cefclient_sources_browser': [
'tests/cefclient/browser/binary_transfer_test.cc',
'tests/cefclient/browser/binary_transfer_test.h',
'tests/cefclient/browser/binding_test.cc',
'tests/cefclient/browser/binding_test.h',
'tests/cefclient/browser/browser_window.cc',
@ -309,6 +313,7 @@
'tests/cefclient/resources/dialogs.html',
'tests/cefclient/resources/draggable.html',
'tests/cefclient/resources/ipc_performance.html',
'tests/cefclient/resources/binary_transfer.html',
'tests/cefclient/resources/localstorage.html',
'tests/cefclient/resources/logo.png',
'tests/cefclient/resources/media_router.html',
@ -496,6 +501,7 @@
'tests/ceftests/jsdialog_unittest.cc',
'tests/ceftests/life_span_unittest.cc',
'tests/ceftests/media_access_unittest.cc',
'tests/ceftests/message_router_binary_unittest.cc',
'tests/ceftests/message_router_harness_unittest.cc',
'tests/ceftests/message_router_multi_query_unittest.cc',
'tests/ceftests/message_router_single_query_unittest.cc',
@ -603,6 +609,7 @@
'tests/ceftests/dom_unittest.cc',
'tests/ceftests/frame_unittest.cc',
'tests/ceftests/media_access_unittest.cc',
'tests/ceftests/message_router_binary_unittest.cc',
'tests/ceftests/message_router_harness_unittest.cc',
'tests/ceftests/message_router_multi_query_unittest.cc',
'tests/ceftests/message_router_single_query_unittest.cc',

View File

@ -33,7 +33,7 @@
// by hand. See the translator.README.txt file in the tools directory for
// more information.
//
// $hash=42de7c0e6f5ec529d9182fe4cbf2c1edfacd7392$
// $hash=865ca5bff4a0867d0c25cb41bd2aa808cf3fddbd$
//
#ifndef CEF_INCLUDE_CAPI_CEF_V8_CAPI_H_
@ -679,6 +679,19 @@ typedef struct _cef_v8value_t {
///
int(CEF_CALLBACK* neuter_array_buffer)(struct _cef_v8value_t* self);
///
/// Returns the length (in bytes) of the ArrayBuffer.
///
size_t(CEF_CALLBACK* get_array_buffer_byte_length)(
struct _cef_v8value_t* self);
///
/// Returns a pointer to the beginning of the memory block for this
/// ArrayBuffer backing store. The returned pointer is valid as long as the
/// cef_v8value_t is alive.
///
void*(CEF_CALLBACK* get_array_buffer_data)(struct _cef_v8value_t* self);
///
/// Returns the function name.
///

View File

@ -33,7 +33,7 @@
// by hand. See the translator.README.txt file in the tools directory for
// more information.
//
// $hash=1b8f7f620685c30b91c8fa656e1a01d182684ae6$
// $hash=7b8fee9d4a0530782ed62f5741820708f110e24e$
//
#ifndef CEF_INCLUDE_CAPI_CEF_VALUES_CAPI_H_
@ -265,6 +265,12 @@ typedef struct _cef_binary_value_t {
struct _cef_binary_value_t*(CEF_CALLBACK* copy)(
struct _cef_binary_value_t* self);
///
/// Returns a pointer to the beginning of the memory block. The returned
/// pointer is valid as long as the cef_binary_value_t is alive.
///
const void*(CEF_CALLBACK* get_raw_data)(struct _cef_binary_value_t* self);
///
/// Returns the data size.
///

View File

@ -42,13 +42,13 @@
// way that may cause binary incompatibility with other builds. The universal
// hash value will change if any platform is affected whereas the platform hash
// values will change only if that particular platform is affected.
#define CEF_API_HASH_UNIVERSAL "647a2df6b870951e70487997f30e75960d609632"
#define CEF_API_HASH_UNIVERSAL "1920c69ce75671bf41d947677d76cc92e9b0ca3b"
#if defined(OS_WIN)
#define CEF_API_HASH_PLATFORM "4a22d727f3f1f625844191f0f5adf297a2f17fa7"
#define CEF_API_HASH_PLATFORM "c1c5f6ed3f032d525d36e737388b0b850f5e0c59"
#elif defined(OS_MAC)
#define CEF_API_HASH_PLATFORM "84a450a85c81d6cb16dda55d7f07047264d1e205"
#define CEF_API_HASH_PLATFORM "5a278846114ac057af673f522377552f41e09179"
#elif defined(OS_LINUX)
#define CEF_API_HASH_PLATFORM "b7cbd7d044e02cb1854184cc1d5d621605b8476b"
#define CEF_API_HASH_PLATFORM "e94defd6a38fb3d2933f5f417ec0e727d6d35abf"
#endif
#ifdef __cplusplus

View File

@ -520,7 +520,7 @@ class CefV8Value : public virtual CefBaseRefCounted {
/// or CefV8Accessor callback, or in combination with calling Enter() and
/// Exit() on a stored CefV8Context reference.
///
/*--cef()--*/
/*--cef(optional_param=buffer)--*/
static CefRefPtr<CefV8Value> CreateArrayBuffer(
void* buffer,
size_t length,
@ -866,6 +866,20 @@ class CefV8Value : public virtual CefBaseRefCounted {
/*--cef()--*/
virtual bool NeuterArrayBuffer() = 0;
///
/// Returns the length (in bytes) of the ArrayBuffer.
///
/*--cef()--*/
virtual size_t GetArrayBufferByteLength() = 0;
///
/// Returns a pointer to the beginning of the memory block for this
/// ArrayBuffer backing store. The returned pointer is valid as long as the
/// CefV8Value is alive.
///
/*--cef()--*/
virtual void* GetArrayBufferData() = 0;
// FUNCTION METHODS - These methods are only available on functions.
///

View File

@ -278,6 +278,13 @@ class CefBinaryValue : public virtual CefBaseRefCounted {
/*--cef()--*/
virtual CefRefPtr<CefBinaryValue> Copy() = 0;
///
/// Returns a pointer to the beginning of the memory block.
/// The returned pointer is valid as long as the CefBinaryValue is alive.
///
/*--cef()--*/
virtual const void* GetRawData() = 0;
///
/// Returns the data size.
///

View File

@ -219,6 +219,38 @@ struct CefMessageRouterConfig {
size_t message_size_threshold;
};
///
/// This class acts as a container for managing binary data. It retains
/// references to the underlying backing store, ensuring it is valid as long as
/// the CefBinaryBuffer exists. This allows efficient, zero-copy access to data
/// received from another process.
///
/// This class is not designed to be thread-safe, and it is the user's
/// responsibility to synchronize access from multiple threads to ensure data
/// integrity.
///
class CefBinaryBuffer : public CefBaseRefCounted {
public:
///
/// Returns the read-only pointer to the memory. Returns nullptr if
/// |GetSize()| returns zero. The returned pointer is only valid for the life
/// span of this object.
///
virtual const void* GetData() const = 0;
///
/// Returns the writable pointer to the memory. Returns nullptr if
/// |GetSize()| returns zero. The returned pointer is only valid for the life
/// span of this object.
///
virtual void* GetData() = 0;
///
/// Returns the size of the data.
///
virtual size_t GetSize() const = 0;
};
///
/// Implements the browser side of query routing. The methods of this class may
/// be called on any browser process thread unless otherwise indicated.
@ -238,10 +270,17 @@ class CefMessageRouterBrowserSide
public:
///
/// Notify the associated JavaScript onSuccess callback that the query has
/// completed successfully with the specified |response|.
/// completed successfully with the specified string |response|.
///
virtual void Success(const CefString& response) = 0;
///
/// Notify the associated JavaScript onSuccess callback that the query has
/// completed successfully with binary data. A |data| pointer to the binary
/// data can be nullptr only if the |size| is 0.
///
virtual void Success(const void* data, size_t size) = 0;
///
/// Notify the associated JavaScript onFailure callback that the query has
/// failed with the specified |error_code| and |error_message|.
@ -276,6 +315,25 @@ class CefMessageRouterBrowserSide
return false;
}
///
/// Executed when a new query is received. |query_id| uniquely identifies
/// the query for the life span of the router. Return true to handle the
/// query or false to propagate the query to other registered handlers, if
/// any. If no handlers return true from this method then the query will be
/// automatically canceled with an error code of -1 delivered to the
/// JavaScript onFailure callback. If this method returns true then a
/// Callback method must be executed either in this method or asynchronously
/// to complete the query.
///
virtual bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
CefRefPtr<const CefBinaryBuffer> request,
bool persistent,
CefRefPtr<Callback> callback) {
return false;
}
///
/// Executed when a query has been canceled either explicitly using the
/// JavaScript cancel function or implicitly due to browser destruction,

View File

@ -606,6 +606,11 @@ CefRefPtr<CefBinaryValue> CefBinaryValueImpl::Copy() {
return new CefBinaryValueImpl(const_value().Clone());
}
const void* CefBinaryValueImpl::GetRawData() {
CEF_VALUE_VERIFY_RETURN(false, nullptr);
return const_value().GetBlob().data();
}
size_t CefBinaryValueImpl::GetSize() {
CEF_VALUE_VERIFY_RETURN(false, 0);
return const_value().GetBlob().size();

View File

@ -177,6 +177,7 @@ class CefBinaryValueImpl : public CefValueBase<CefBinaryValue, base::Value> {
bool IsSame(CefRefPtr<CefBinaryValue> that) override;
bool IsEqual(CefRefPtr<CefBinaryValue> that) override;
CefRefPtr<CefBinaryValue> Copy() override;
const void* GetRawData() override;
size_t GetSize() override;
size_t GetData(void* buffer, size_t buffer_size, size_t data_offset) override;

View File

@ -2368,6 +2368,61 @@ bool CefV8ValueImpl::NeuterArrayBuffer() {
return true;
}
size_t CefV8ValueImpl::GetArrayBufferByteLength() {
size_t rv = 0;
CEF_V8_REQUIRE_ISOLATE_RETURN(rv);
if (type_ != TYPE_OBJECT) {
DCHECK(false) << "V8 value is not an object";
return rv;
}
v8::Isolate* isolate = handle_->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
if (context.IsEmpty()) {
DCHECK(false) << "not currently in a V8 context";
return rv;
}
v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
if (!value->IsArrayBuffer()) {
DCHECK(false) << "V8 value is not an array buffer";
return rv;
}
v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
return v8::Local<v8::ArrayBuffer>::Cast(obj)->ByteLength();
}
void* CefV8ValueImpl::GetArrayBufferData() {
void* rv = nullptr;
CEF_V8_REQUIRE_ISOLATE_RETURN(rv);
if (type_ != TYPE_OBJECT) {
DCHECK(false) << "V8 value is not an object";
return rv;
}
v8::Isolate* isolate = handle_->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
if (context.IsEmpty()) {
DCHECK(false) << "not currently in a V8 context";
return rv;
}
v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
if (!value->IsArrayBuffer()) {
DCHECK(false) << "V8 value is not an array buffer";
return rv;
}
v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
v8::Local<v8::ArrayBuffer> arr = v8::Local<v8::ArrayBuffer>::Cast(obj);
return arr->Data();
}
CefString CefV8ValueImpl::GetFunctionName() {
CefString rv;
CEF_V8_REQUIRE_OBJECT_RETURN(rv);

View File

@ -274,6 +274,8 @@ class CefV8ValueImpl : public CefV8Value {
CefRefPtr<CefV8ArrayBufferReleaseCallback> GetArrayBufferReleaseCallback()
override;
bool NeuterArrayBuffer() override;
size_t GetArrayBufferByteLength() override;
void* GetArrayBufferData() override;
CefString GetFunctionName() override;
CefRefPtr<CefV8Handler> GetFunctionHandler() override;
CefRefPtr<CefV8Value> ExecuteFunction(

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=905b24443e08e2d3e2464d5f4138e97904be3e9e$
// $hash=8da36dc268f2f9beb26abc105656f3b04b1d46ed$
//
#include "libcef_dll/cpptoc/binary_value_cpptoc.h"
@ -140,6 +140,25 @@ binary_value_copy(struct _cef_binary_value_t* self) {
return CefBinaryValueCppToC::Wrap(_retval);
}
const void* CEF_CALLBACK
binary_value_get_raw_data(struct _cef_binary_value_t* self) {
shutdown_checker::AssertNotShutdown();
DCHECK(self);
if (!self) {
return NULL;
}
// This manual implementation can be removed once support for 'const void*'
// is integrated into the CEF translator tool (issue #3591).
// Execute
const void* _retval = CefBinaryValueCppToC::Get(self)->GetRawData();
// Return type: simple_byaddr
return _retval;
}
size_t CEF_CALLBACK binary_value_get_size(struct _cef_binary_value_t* self) {
shutdown_checker::AssertNotShutdown();
@ -193,6 +212,7 @@ CefBinaryValueCppToC::CefBinaryValueCppToC() {
GetStruct()->is_same = binary_value_is_same;
GetStruct()->is_equal = binary_value_is_equal;
GetStruct()->copy = binary_value_copy;
GetStruct()->get_raw_data = binary_value_get_raw_data;
GetStruct()->get_size = binary_value_get_size;
GetStruct()->get_data = binary_value_get_data;
}

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=3bc6db85e54dc87c1e592291be01820547e0989f$
// $hash=4356ad718a385149741e4c8bbbe5d5290466ed40$
//
#include "libcef_dll/cpptoc/shared_memory_region_cpptoc.h"
@ -64,6 +64,9 @@ shared_memory_region_memory(struct _cef_shared_memory_region_t* self) {
return NULL;
}
// This manual implementation can be removed once support for 'void*'
// is integrated into the CEF translator tool (issue #3591).
// Execute
void* _retval = CefSharedMemoryRegionCppToC::Get(self)->Memory();

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=ecd6caa0c415b57e93bc66f3c7a4cfb547f022c1$
// $hash=21d8fb47eb282f40fb9d602f44b8c1fd4ff44dea$
//
#include "libcef_dll/cpptoc/v8value_cpptoc.h"
@ -138,16 +138,12 @@ CEF_EXPORT cef_v8value_t* cef_v8value_create_array_buffer(
cef_v8array_buffer_release_callback_t* release_callback) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: buffer; type: simple_byaddr
DCHECK(buffer);
if (!buffer) {
return NULL;
}
// Verify param: release_callback; type: refptr_diff
DCHECK(release_callback);
if (!release_callback) {
return NULL;
}
// Unverified params: buffer
// Execute
CefRefPtr<CefV8Value> _retval = CefV8Value::CreateArrayBuffer(
@ -948,6 +944,38 @@ int CEF_CALLBACK v8value_neuter_array_buffer(struct _cef_v8value_t* self) {
return _retval;
}
size_t CEF_CALLBACK
v8value_get_array_buffer_byte_length(struct _cef_v8value_t* self) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
DCHECK(self);
if (!self) {
return 0;
}
// Execute
size_t _retval = CefV8ValueCppToC::Get(self)->GetArrayBufferByteLength();
// Return type: simple
return _retval;
}
void* CEF_CALLBACK v8value_get_array_buffer_data(struct _cef_v8value_t* self) {
DCHECK(self);
if (!self) {
return NULL;
}
// This manual implementation can be removed once support for 'void*'
// is integrated into the CEF translator tool (issue #3591).
// Execute
void* _retval = CefV8ValueCppToC::Get(self)->GetArrayBufferData();
// Return type: simple_byaddr
return _retval;
}
cef_string_userfree_t CEF_CALLBACK
v8value_get_function_name(struct _cef_v8value_t* self) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
@ -1153,6 +1181,9 @@ CefV8ValueCppToC::CefV8ValueCppToC() {
GetStruct()->get_array_buffer_release_callback =
v8value_get_array_buffer_release_callback;
GetStruct()->neuter_array_buffer = v8value_neuter_array_buffer;
GetStruct()->get_array_buffer_byte_length =
v8value_get_array_buffer_byte_length;
GetStruct()->get_array_buffer_data = v8value_get_array_buffer_data;
GetStruct()->get_function_name = v8value_get_function_name;
GetStruct()->get_function_handler = v8value_get_function_handler;
GetStruct()->execute_function = v8value_execute_function;

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=caaf5dfe8d56c784e213b1590c21194f08084b70$
// $hash=f42bab3d9e4ff45faf1fd8071646a8e5bed64177$
//
#include "libcef_dll/ctocpp/binary_value_ctocpp.h"
@ -139,6 +139,24 @@ CefRefPtr<CefBinaryValue> CefBinaryValueCToCpp::Copy() {
return CefBinaryValueCToCpp::Wrap(_retval);
}
NO_SANITIZE("cfi-icall") const void* CefBinaryValueCToCpp::GetRawData() {
shutdown_checker::AssertNotShutdown();
cef_binary_value_t* _struct = GetStruct();
if (CEF_MEMBER_MISSING(_struct, get_raw_data)) {
return nullptr;
}
// This manual implementation can be removed once support for 'const void*'
// is integrated into the CEF translator tool (issue #3591).
// Execute
const void* _retval = _struct->get_raw_data(_struct);
// Return type: simple_byaddr
return _retval;
}
NO_SANITIZE("cfi-icall") size_t CefBinaryValueCToCpp::GetSize() {
shutdown_checker::AssertNotShutdown();

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=b6f011a6c26b4264084eb68dae0d63032c07013c$
// $hash=2e0ac9b73ba6bdb4b07ee0f8c445974359c5862f$
//
#ifndef CEF_LIBCEF_DLL_CTOCPP_BINARY_VALUE_CTOCPP_H_
@ -39,6 +39,7 @@ class CefBinaryValueCToCpp : public CefCToCppRefCounted<CefBinaryValueCToCpp,
bool IsSame(CefRefPtr<CefBinaryValue> that) override;
bool IsEqual(CefRefPtr<CefBinaryValue> that) override;
CefRefPtr<CefBinaryValue> Copy() override;
const void* GetRawData() override;
size_t GetSize() override;
size_t GetData(void* buffer, size_t buffer_size, size_t data_offset) override;
};

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=31516110398f9fe682988645d74ac8789b712181$
// $hash=a396f422ed18fe4aae90d4fef3750b4726279c7e$
//
#include "libcef_dll/ctocpp/shared_memory_region_ctocpp.h"
@ -59,6 +59,9 @@ NO_SANITIZE("cfi-icall") void* CefSharedMemoryRegionCToCpp::Memory() {
return NULL;
}
// This manual implementation can be removed once support for 'void*'
// is integrated into the CEF translator tool (issue #3591).
// Execute
void* _retval = _struct->memory(_struct);

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=459c331b0c02f55c4b700761ad2132d7320fd467$
// $hash=f67b0996d7e2133f3f28f2d8ba5446c5ff40aaba$
//
#include "libcef_dll/ctocpp/v8value_ctocpp.h"
@ -147,16 +147,12 @@ CefRefPtr<CefV8Value> CefV8Value::CreateArrayBuffer(
CefRefPtr<CefV8ArrayBufferReleaseCallback> release_callback) {
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: buffer; type: simple_byaddr
DCHECK(buffer);
if (!buffer) {
return nullptr;
}
// Verify param: release_callback; type: refptr_diff
DCHECK(release_callback.get());
if (!release_callback.get()) {
return nullptr;
}
// Unverified params: buffer
// Execute
cef_v8value_t* _retval = cef_v8value_create_array_buffer(
@ -957,6 +953,37 @@ NO_SANITIZE("cfi-icall") bool CefV8ValueCToCpp::NeuterArrayBuffer() {
return _retval ? true : false;
}
NO_SANITIZE("cfi-icall") size_t CefV8ValueCToCpp::GetArrayBufferByteLength() {
cef_v8value_t* _struct = GetStruct();
if (CEF_MEMBER_MISSING(_struct, get_array_buffer_byte_length)) {
return 0;
}
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Execute
size_t _retval = _struct->get_array_buffer_byte_length(_struct);
// Return type: simple
return _retval;
}
NO_SANITIZE("cfi-icall") void* CefV8ValueCToCpp::GetArrayBufferData() {
cef_v8value_t* _struct = GetStruct();
if (CEF_MEMBER_MISSING(_struct, get_array_buffer_data)) {
return nullptr;
}
// This manual implementation can be removed once support for 'void*'
// is integrated into the CEF translator tool (issue #3591).
// Execute
void* _retval = _struct->get_array_buffer_data(_struct);
// Return type: simple_byaddr
return _retval;
}
NO_SANITIZE("cfi-icall") CefString CefV8ValueCToCpp::GetFunctionName() {
cef_v8value_t* _struct = GetStruct();
if (CEF_MEMBER_MISSING(_struct, get_function_name)) {

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=cab5b018f6706a3c8496865e0c9f30fcbc94cdd8$
// $hash=c81cc0910be6678c0512c5423b8fc5dc1df42743$
//
#ifndef CEF_LIBCEF_DLL_CTOCPP_V8VALUE_CTOCPP_H_
@ -83,6 +83,8 @@ class CefV8ValueCToCpp
CefRefPtr<CefV8ArrayBufferReleaseCallback> GetArrayBufferReleaseCallback()
override;
bool NeuterArrayBuffer() override;
size_t GetArrayBufferByteLength() override;
void* GetArrayBufferData() override;
CefString GetFunctionName() override;
CefRefPtr<CefV8Handler> GetFunctionHandler() override;
CefRefPtr<CefV8Value> ExecuteFunction(

View File

@ -9,11 +9,11 @@
#include <set>
#include "include/base/cef_callback.h"
#include "include/cef_shared_process_message_builder.h"
#include "include/cef_task.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
#include "libcef_dll/wrapper/cef_browser_info_map.h"
#include "libcef_dll/wrapper/cef_message_router_utils.h"
namespace {
@ -47,126 +47,7 @@ bool ValidateConfig(CefMessageRouterConfig& config) {
return true;
}
struct MessageHeader {
int context_id;
int request_id;
bool is_success;
};
struct ParsedMessage {
int context_id;
int request_id;
bool success;
int error_code;
CefString message;
};
size_t GetByteLength(const CefString& value) {
return value.size() * sizeof(CefString::char_type);
}
size_t GetMessageSize(const CefString& response) {
return sizeof(MessageHeader) + GetByteLength(response);
}
void CopyResponseIntoMemory(void* memory, const CefString& response) {
const size_t bytes = response.size() * sizeof(CefString::char_type);
void* dest = static_cast<uint8_t*>(memory) + sizeof(MessageHeader);
memcpy(dest, response.c_str(), bytes);
}
CefString GetStringFromMemory(const void* memory, size_t size) {
const size_t bytes = size - sizeof(MessageHeader);
const size_t string_len = bytes / sizeof(CefString::char_type);
const CefString::char_type* src =
reinterpret_cast<const CefString::char_type*>(
static_cast<const uint8_t*>(memory) + sizeof(MessageHeader));
constexpr bool copy = true;
CefString result;
result.FromString(src, string_len, copy);
return result;
}
CefRefPtr<CefProcessMessage> BuildListMessage(const std::string& message_name,
int context_id,
int request_id,
const CefString& response) {
auto message = CefProcessMessage::Create(message_name);
CefRefPtr<CefListValue> args = message->GetArgumentList();
args->SetInt(0, context_id);
args->SetInt(1, request_id);
args->SetBool(2, true); // Indicates a success result.
args->SetString(3, response);
return message;
}
CefRefPtr<CefProcessMessage> BuildBinaryMessage(const std::string& message_name,
int context_id,
int request_id,
const CefString& response) {
const size_t message_size = GetMessageSize(response);
auto builder =
CefSharedProcessMessageBuilder::Create(message_name, message_size);
if (!builder->IsValid()) {
LOG(ERROR) << "Failed to allocate shared memory region of size "
<< message_size;
// Use list message as a fallback
return BuildListMessage(message_name, context_id, request_id, response);
}
auto header = static_cast<MessageHeader*>(builder->Memory());
header->context_id = context_id;
header->request_id = request_id;
header->is_success = true;
CopyResponseIntoMemory(builder->Memory(), response);
return builder->Build();
}
CefRefPtr<CefProcessMessage> BuildMessage(size_t threshold,
const std::string& message_name,
int context_id,
int request_id,
const CefString& response) {
if (GetByteLength(response) <= threshold) {
return BuildListMessage(message_name, context_id, request_id, response);
} else {
return BuildBinaryMessage(message_name, context_id, request_id, response);
}
}
ParsedMessage ParseMessage(const CefRefPtr<CefProcessMessage>& message) {
if (auto args = message->GetArgumentList()) {
DCHECK_GT(args->GetSize(), 3U);
const int context_id = args->GetInt(0);
const int request_id = args->GetInt(1);
const bool is_success = args->GetBool(2);
if (is_success) {
return ParsedMessage{context_id, request_id, is_success, 0,
args->GetString(3)};
}
DCHECK_EQ(args->GetSize(), 5U);
return ParsedMessage{context_id, request_id, is_success, args->GetInt(3),
args->GetString(4)};
}
if (const auto region = message->GetSharedMemoryRegion()) {
if (region->IsValid()) {
DCHECK_GE(region->Size(), sizeof(MessageHeader));
auto header = static_cast<const MessageHeader*>(region->Memory());
DCHECK(header->is_success);
return ParsedMessage{
header->context_id, header->request_id, header->is_success, 0,
GetStringFromMemory(region->Memory(), region->Size())};
}
}
return ParsedMessage{};
}
namespace cmru = cef_message_router_utils;
/**
* @brief A helper template for generating ID values.
@ -204,11 +85,15 @@ class CefMessageRouterBrowserSideImpl : public CefMessageRouterBrowserSide {
CallbackImpl(CefRefPtr<CefMessageRouterBrowserSideImpl> router,
int browser_id,
int64_t query_id,
bool persistent)
bool persistent,
size_t message_size_threshold,
const std::string& query_message_name)
: router_(router),
browser_id_(browser_id),
query_id_(query_id),
persistent_(persistent) {}
persistent_(persistent),
message_size_threshold_(message_size_threshold),
query_message_name_(query_message_name) {}
CallbackImpl(const CallbackImpl&) = delete;
CallbackImpl& operator=(const CallbackImpl&) = delete;
@ -221,44 +106,36 @@ class CefMessageRouterBrowserSideImpl : public CefMessageRouterBrowserSide {
}
void Success(const CefString& response) override {
if (!CefCurrentlyOn(TID_UI)) {
// Must execute on the UI thread to access member variables.
CefPostTask(TID_UI,
base::BindOnce(&CallbackImpl::Success, this, response));
return;
}
auto builder = cmru::CreateBrowserResponseBuilder(
message_size_threshold_, query_message_name_, response);
if (router_) {
CefPostTask(
TID_UI,
base::BindOnce(&CefMessageRouterBrowserSideImpl::OnCallbackSuccess,
router_.get(), browser_id_, query_id_, response));
// We need to post task here for two reasons:
// 1) To safely access member variables.
// 2) To let the router to persist the query information before
// the Success callback is executed.
CefPostTask(TID_UI,
base::BindOnce(&CallbackImpl::SuccessImpl, this, builder));
}
if (!persistent_) {
// Non-persistent callbacks are only good for a single use.
router_ = nullptr;
}
}
void Success(const void* data, size_t size) override {
auto builder = cmru::CreateBrowserResponseBuilder(
message_size_threshold_, query_message_name_, data, size);
// We need to post task here for two reasons:
// 1) To safely access member variables.
// 2) To let the router to persist the query information before
// the Success callback is executed.
CefPostTask(TID_UI,
base::BindOnce(&CallbackImpl::SuccessImpl, this, builder));
}
void Failure(int error_code, const CefString& error_message) override {
if (!CefCurrentlyOn(TID_UI)) {
// Must execute on the UI thread to access member variables.
CefPostTask(TID_UI, base::BindOnce(&CallbackImpl::Failure, this,
error_code, error_message));
return;
}
if (router_) {
CefPostTask(
TID_UI,
base::BindOnce(&CefMessageRouterBrowserSideImpl::OnCallbackFailure,
router_.get(), browser_id_, query_id_, error_code,
error_message));
// Failure always invalidates the callback.
router_ = nullptr;
}
// We need to post task here for two reasons:
// 1) To safely access member variables.
// 2) To give previosly submitted tasks by the Success calls to execute
// before we invalidate the callback.
CefPostTask(TID_UI, base::BindOnce(&CallbackImpl::FailureImpl, this,
error_code, error_message));
}
void Detach() {
@ -267,10 +144,37 @@ class CefMessageRouterBrowserSideImpl : public CefMessageRouterBrowserSide {
}
private:
void SuccessImpl(const CefRefPtr<cmru::BrowserResponseBuilder>& builder) {
if (!router_) {
return;
}
router_->OnCallbackSuccess(browser_id_, query_id_, builder);
if (!persistent_) {
// Non-persistent callbacks are only good for a single use.
router_ = nullptr;
}
}
void FailureImpl(int error_code, const CefString& error_message) {
if (!router_) {
return;
}
router_->OnCallbackFailure(browser_id_, query_id_, error_code,
error_message);
// Failure always invalidates the callback.
router_ = nullptr;
}
CefRefPtr<CefMessageRouterBrowserSideImpl> router_;
const int browser_id_;
const int64_t query_id_;
const bool persistent_;
const size_t message_size_threshold_;
const std::string query_message_name_;
IMPLEMENT_REFCOUNTING(CallbackImpl);
};
@ -388,13 +292,10 @@ class CefMessageRouterBrowserSideImpl : public CefMessageRouterBrowserSide {
const std::string& message_name = message->GetName();
if (message_name == query_message_name_) {
CefRefPtr<CefListValue> args = message->GetArgumentList();
DCHECK_EQ(args->GetSize(), 4U);
const int context_id = args->GetInt(0);
const int request_id = args->GetInt(1);
const CefString& request = args->GetString(2);
const bool persistent = args->GetBool(3);
cmru::RendererMessage content = cmru::ParseRendererMessage(message);
const int context_id = content.context_id;
const int request_id = content.request_id;
const bool persistent = content.is_persistent;
if (handler_set_.empty()) {
// No handlers so cancel the query.
@ -405,40 +306,38 @@ class CefMessageRouterBrowserSideImpl : public CefMessageRouterBrowserSide {
const int browser_id = browser->GetIdentifier();
const int64_t query_id = query_id_generator_.GetNextId();
CefRefPtr<CallbackImpl> callback(
new CallbackImpl(this, browser_id, query_id, persistent));
CefRefPtr<CallbackImpl> callback =
new CallbackImpl(this, browser_id, query_id, persistent,
config_.message_size_threshold, query_message_name_);
// Make a copy of the handler list in case the user adds or removes a
// handler while we're iterating.
HandlerSet handler_set = handler_set_;
const HandlerSet handlers = handler_set_;
bool handled = false;
HandlerSet::const_iterator it_handler = handler_set.begin();
for (; it_handler != handler_set.end(); ++it_handler) {
handled = (*it_handler)
->OnQuery(browser, frame, query_id, request, persistent,
callback.get());
if (handled) {
break;
}
}
Handler* handler = std::visit(
[&](const auto& arg) -> CefMessageRouterBrowserSide::Handler* {
for (auto handler : handlers) {
bool handled = handler->OnQuery(browser, frame, query_id, arg,
persistent, callback.get());
if (handled) {
return handler;
}
}
return nullptr;
},
content.payload);
// If the query isn't handled nothing should be keeping a reference to
// the callback.
DCHECK(handled || callback->HasOneRef());
DCHECK(handler != nullptr || callback->HasOneRef());
if (handled) {
if (handler) {
// Persist the query information until the callback executes.
// It's safe to do this here because the callback will execute
// asynchronously.
QueryInfo* info = new QueryInfo;
info->browser = browser;
info->frame = frame;
info->context_id = context_id;
info->request_id = request_id;
info->persistent = persistent;
info->callback = callback;
info->handler = *(it_handler);
QueryInfo* info =
new QueryInfo{browser, frame, context_id, request_id,
persistent, callback, handler};
browser_query_info_map_.Add(browser_id, query_id, info);
} else {
// Invalidate the callback.
@ -527,15 +426,17 @@ class CefMessageRouterBrowserSideImpl : public CefMessageRouterBrowserSide {
}
// Called by CallbackImpl on success.
void OnCallbackSuccess(int browser_id,
int64_t query_id,
const CefString& response) {
void OnCallbackSuccess(
int browser_id,
int64_t query_id,
const CefRefPtr<cmru::BrowserResponseBuilder>& builder) {
CEF_REQUIRE_UI_THREAD();
bool removed;
QueryInfo* info = GetQueryInfo(browser_id, query_id, false, &removed);
if (info) {
SendQuerySuccess(info, response);
SendQuerySuccess(info->browser, info->frame, info->context_id,
info->request_id, builder);
if (removed) {
delete info;
}
@ -558,19 +459,13 @@ class CefMessageRouterBrowserSideImpl : public CefMessageRouterBrowserSide {
}
}
void SendQuerySuccess(QueryInfo* info, const CefString& response) {
SendQuerySuccess(info->browser, info->frame, info->context_id,
info->request_id, response);
}
void SendQuerySuccess(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int context_id,
int request_id,
const CefString& response) {
if (auto message =
BuildMessage(config_.message_size_threshold, query_message_name_,
context_id, request_id, response)) {
void SendQuerySuccess(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int context_id,
int request_id,
const CefRefPtr<cmru::BrowserResponseBuilder>& builder) {
if (auto message = builder->Build(context_id, request_id)) {
frame->SendProcessMessage(PID_RENDERER, message);
}
}
@ -758,10 +653,16 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide {
CefRefPtr<CefV8Value> arg = arguments[0];
CefRefPtr<CefV8Value> requestVal = arg->GetValue(kMemberRequest);
if (!requestVal.get() || !requestVal->IsString()) {
if (!requestVal.get()) {
exception = "Invalid arguments; object member '" +
std::string(kMemberRequest) + "' is required";
return true;
}
if (!requestVal->IsString() && !requestVal->IsArrayBuffer()) {
exception = "Invalid arguments; object member '" +
std::string(kMemberRequest) +
"' is required and must have type string";
"' must have type string or ArrayBuffer";
return true;
}
@ -804,9 +705,11 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide {
(persistentVal.get() && persistentVal->GetBoolValue());
const int request_id = router_->SendQuery(
context->GetBrowser(), context->GetFrame(), context_id,
requestVal->GetStringValue(), persistent, successVal, failureVal);
context->GetBrowser(), context->GetFrame(), context_id, requestVal,
persistent, successVal, failureVal);
retval = CefV8Value::CreateInt(request_id);
return true;
} else if (name == config_.js_cancel_function) {
if (arguments.size() != 1 || !arguments[0]->IsInt()) {
@ -958,29 +861,26 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide {
CefRefPtr<CefProcessMessage> message) override {
CEF_REQUIRE_RENDERER_THREAD();
const std::string& message_name = message->GetName();
if (message_name == query_message_name_) {
auto content = ParseMessage(message);
if (content.success) {
CefPostTask(
TID_RENDERER,
base::BindOnce(
&CefMessageRouterRendererSideImpl::ExecuteSuccessCallback, this,
browser->GetIdentifier(), content.context_id,
content.request_id, content.message));
} else {
CefPostTask(
TID_RENDERER,
base::BindOnce(
&CefMessageRouterRendererSideImpl::ExecuteFailureCallback, this,
browser->GetIdentifier(), content.context_id,
content.request_id, content.error_code, content.message));
}
return true;
if (message->GetName() != query_message_name_) {
return false;
}
return false;
cmru::BrowserMessage content = cmru::ParseBrowserMessage(message);
if (content.is_success) {
std::visit(
[&](const auto& arg) {
ExecuteSuccessCallback(browser->GetIdentifier(), content.context_id,
content.request_id, arg);
},
content.payload);
} else {
ExecuteFailureCallback(browser->GetIdentifier(), content.context_id,
content.request_id, content.error_code,
std::get<CefString>(content.payload));
}
return true;
}
private:
@ -1039,7 +939,7 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide {
int SendQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int context_id,
const CefString& request,
CefRefPtr<CefV8Value> request,
bool persistent,
CefRefPtr<CefV8Value> success_callback,
CefRefPtr<CefV8Value> failure_callback) {
@ -1053,14 +953,9 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide {
browser_request_info_map_.Add(browser->GetIdentifier(),
std::make_pair(context_id, request_id), info);
CefRefPtr<CefProcessMessage> message =
CefProcessMessage::Create(query_message_name_);
CefRefPtr<CefListValue> args = message->GetArgumentList();
args->SetInt(0, context_id);
args->SetInt(1, request_id);
args->SetString(2, request);
args->SetBool(3, persistent);
CefRefPtr<CefProcessMessage> message = cmru::BuildRendererMsg(
config_.message_size_threshold, query_message_name_, context_id,
request_id, request, persistent);
frame->SendProcessMessage(PID_BROWSER, message);
@ -1162,6 +1057,41 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide {
}
}
// Execute the onSuccess JavaScript callback.
void ExecuteSuccessCallback(int browser_id,
int context_id,
int request_id,
const CefRefPtr<CefBinaryBuffer>& response) {
CEF_REQUIRE_RENDERER_THREAD();
bool removed;
RequestInfo* info =
GetRequestInfo(browser_id, context_id, request_id, false, &removed);
if (!info) {
return;
}
CefRefPtr<CefV8Context> context = GetContextByID(context_id);
if (context && info->success_callback && context->Enter()) {
CefRefPtr<cmru::BinaryValueABRCallback> release_callback =
new cmru::BinaryValueABRCallback(response);
CefRefPtr<CefV8Value> value = CefV8Value::CreateArrayBuffer(
response->GetData(), response->GetSize(), release_callback);
context->Exit();
CefV8ValueList args;
args.push_back(value);
info->success_callback->ExecuteFunctionWithContext(context, nullptr,
args);
}
if (removed) {
delete info;
}
}
// Execute the onFailure JavaScript callback.
void ExecuteFailureCallback(int browser_id,
int context_id,

View File

@ -0,0 +1,525 @@
// Copyright (c) 2023 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include "libcef_dll/wrapper/cef_message_router_utils.h"
#include "include/cef_shared_process_message_builder.h"
#include <type_traits>
namespace cef_message_router_utils {
namespace {
constexpr int kNoError = 0;
constexpr size_t kContextId = 0;
constexpr size_t kRequestId = 1;
constexpr size_t kRendererPayload = 2;
constexpr size_t kIsSuccess = 2;
constexpr size_t kBrowserPayload = 3;
constexpr size_t kIsPersistent = 3;
struct BrowserMsgHeader {
int context_id;
int request_id;
bool is_binary;
};
static_assert(
std::is_trivially_copyable_v<BrowserMsgHeader>,
"Copying non-trivially-copyable object across memory spaces is dangerous");
struct RendererMsgHeader {
int context_id;
int request_id;
bool is_persistent;
bool is_binary;
};
static_assert(
std::is_trivially_copyable_v<RendererMsgHeader>,
"Copying non-trivially-copyable object across memory spaces is dangerous");
//
// This is a workaround for handling empty CefBinaryValues, as it's not possible
// to create an empty one directly. We use this empty struct as a tag to invoke
// the SetNull function within the BuildBrowserListMsg and BuildRendererListMsg
// functions.
//
struct Empty {};
size_t GetByteLength(const CefString& value) {
return value.size() * sizeof(CefString::char_type);
}
size_t GetByteLength(const CefRefPtr<CefV8Value>& value) {
return value->GetArrayBufferByteLength();
}
const CefString& GetListRepresentation(const CefString& value) {
return value;
}
CefRefPtr<CefBinaryValue> GetListRepresentation(
const CefRefPtr<CefV8Value>& value) {
return CefBinaryValue::Create(value->GetArrayBufferData(),
value->GetArrayBufferByteLength());
}
template <class Header, class T>
size_t GetMessageSize(const T& value) {
return sizeof(Header) + GetByteLength(value);
}
template <class Header>
void CopyIntoMemory(void* memory, const void* data, size_t bytes) {
if (bytes > 0) {
void* dest = static_cast<uint8_t*>(memory) + sizeof(Header);
memcpy(dest, data, bytes);
}
}
template <class Header>
void CopyIntoMemory(void* memory, const CefRefPtr<CefV8Value>& value) {
CopyIntoMemory<Header>(memory, value->GetArrayBufferData(),
value->GetArrayBufferByteLength());
}
template <class Header>
void CopyIntoMemory(void* memory, const CefString& value) {
const size_t bytes = GetByteLength(value);
CopyIntoMemory<Header>(memory, value.c_str(), bytes);
}
template <class Header>
CefString GetStringFromMemory(const void* memory, size_t size) {
const size_t bytes = size - sizeof(Header);
const size_t string_len = bytes / sizeof(CefString::char_type);
const void* string_data =
static_cast<const uint8_t*>(memory) + sizeof(Header);
const CefString::char_type* src =
static_cast<const CefString::char_type*>(string_data);
CefString result;
result.FromString(src, string_len, /*copy=*/true);
return result;
}
template <typename T>
constexpr bool IsCefString() {
return std::is_same_v<std::remove_cv_t<T>, CefString>;
}
template <typename T>
constexpr bool IsEmpty() {
return std::is_same_v<std::remove_cv_t<T>, Empty>;
}
template <class ResponseType>
CefRefPtr<CefProcessMessage> BuildBrowserListMsg(const CefString& name,
int context_id,
int request_id,
const ResponseType& response) {
auto message = CefProcessMessage::Create(name);
CefRefPtr<CefListValue> args = message->GetArgumentList();
args->SetInt(kContextId, context_id);
args->SetInt(kRequestId, request_id);
args->SetBool(kIsSuccess, true);
if constexpr (IsCefString<ResponseType>()) {
args->SetString(kBrowserPayload, response);
} else if constexpr (IsEmpty<ResponseType>()) {
args->SetNull(kBrowserPayload);
} else {
args->SetBinary(kBrowserPayload, response);
}
return message;
}
class EmptyResponseBuilder final : public BrowserResponseBuilder {
public:
explicit EmptyResponseBuilder(const std::string& name) : name_(name) {}
EmptyResponseBuilder(const EmptyResponseBuilder&) = delete;
EmptyResponseBuilder& operator=(const EmptyResponseBuilder&) = delete;
CefRefPtr<CefProcessMessage> Build(int context_id, int request_id) override {
return BuildBrowserListMsg(name_, context_id, request_id, Empty{});
}
private:
const CefString name_;
IMPLEMENT_REFCOUNTING(EmptyResponseBuilder);
};
class BinaryResponseBuilder final : public BrowserResponseBuilder {
public:
BinaryResponseBuilder(const std::string& name, const void* data, size_t size)
: name_(name), value_(CefBinaryValue::Create(data, size)) {}
BinaryResponseBuilder(const BinaryResponseBuilder&) = delete;
BinaryResponseBuilder& operator=(const BinaryResponseBuilder&) = delete;
CefRefPtr<CefProcessMessage> Build(int context_id, int request_id) override {
return BuildBrowserListMsg(name_, context_id, request_id, value_);
}
private:
const CefString name_;
const CefRefPtr<CefBinaryValue> value_;
IMPLEMENT_REFCOUNTING(BinaryResponseBuilder);
};
class StringResponseBuilder final : public BrowserResponseBuilder {
public:
StringResponseBuilder(const std::string& name, const CefString& value)
: name_(name), value_(value) {}
StringResponseBuilder(const StringResponseBuilder&) = delete;
StringResponseBuilder& operator=(const StringResponseBuilder&) = delete;
CefRefPtr<CefProcessMessage> Build(int context_id, int request_id) override {
return BuildBrowserListMsg(name_, context_id, request_id, value_);
}
private:
const CefString name_;
const CefString value_;
IMPLEMENT_REFCOUNTING(StringResponseBuilder);
};
// SharedProcessMessageResponseBuilder
class SPMResponseBuilder final : public BrowserResponseBuilder {
public:
SPMResponseBuilder(const SPMResponseBuilder&) = delete;
SPMResponseBuilder& operator=(const SPMResponseBuilder&) = delete;
static CefRefPtr<BrowserResponseBuilder> Create(const std::string& name,
const void* data,
size_t size) {
const size_t message_size = sizeof(BrowserMsgHeader) + size;
auto builder = CefSharedProcessMessageBuilder::Create(name, message_size);
if (!builder->IsValid()) {
LOG(ERROR) << "Failed to allocate shared memory region of size "
<< message_size;
return new BinaryResponseBuilder(name, data, size);
}
CopyIntoMemory<BrowserMsgHeader>(builder->Memory(), data, size);
return new SPMResponseBuilder(builder, /*is_binary=*/true);
}
static CefRefPtr<BrowserResponseBuilder> Create(const std::string& name,
const CefString& value) {
const size_t message_size = GetMessageSize<BrowserMsgHeader>(value);
auto builder = CefSharedProcessMessageBuilder::Create(name, message_size);
if (!builder->IsValid()) {
LOG(ERROR) << "Failed to allocate shared memory region of size "
<< message_size;
return new StringResponseBuilder(name, value);
}
CopyIntoMemory<BrowserMsgHeader>(builder->Memory(), value);
return new SPMResponseBuilder(builder, /*is_binary=*/false);
}
CefRefPtr<CefProcessMessage> Build(int context_id, int request_id) override {
auto header = static_cast<BrowserMsgHeader*>(builder_->Memory());
header->context_id = context_id;
header->request_id = request_id;
header->is_binary = is_binary_;
return builder_->Build();
}
private:
explicit SPMResponseBuilder(
const CefRefPtr<CefSharedProcessMessageBuilder>& builder,
bool is_binary)
: builder_(builder), is_binary_(is_binary) {}
CefRefPtr<CefSharedProcessMessageBuilder> builder_;
const bool is_binary_;
IMPLEMENT_REFCOUNTING(SPMResponseBuilder);
};
class EmptyBinaryBuffer final : public CefBinaryBuffer {
public:
EmptyBinaryBuffer() = default;
EmptyBinaryBuffer(const EmptyBinaryBuffer&) = delete;
EmptyBinaryBuffer& operator=(const EmptyBinaryBuffer&) = delete;
const void* GetData() const override { return nullptr; }
void* GetData() override { return nullptr; }
size_t GetSize() const override { return 0; }
private:
IMPLEMENT_REFCOUNTING(EmptyBinaryBuffer);
};
class BinaryValueBuffer final : public CefBinaryBuffer {
public:
BinaryValueBuffer(CefRefPtr<CefProcessMessage> message,
CefRefPtr<CefBinaryValue> value)
: message_(std::move(message)), value_(std::move(value)) {}
BinaryValueBuffer(const BinaryValueBuffer&) = delete;
BinaryValueBuffer& operator=(const BinaryValueBuffer&) = delete;
const void* GetData() const override { return value_->GetRawData(); }
void* GetData() override {
// This is not UB as long as underlying storage is vector<uint8_t>
return const_cast<void*>(value_->GetRawData());
}
size_t GetSize() const override { return value_->GetSize(); }
private:
const CefRefPtr<CefProcessMessage> message_;
const CefRefPtr<CefBinaryValue> value_;
IMPLEMENT_REFCOUNTING(BinaryValueBuffer);
};
class SharedMemoryRegionBuffer final : public CefBinaryBuffer {
public:
SharedMemoryRegionBuffer(const CefRefPtr<CefSharedMemoryRegion>& region,
size_t offset)
: region_(region),
data_(static_cast<uint8_t*>(region->Memory()) + offset),
size_(region->Size() - offset) {}
SharedMemoryRegionBuffer(const SharedMemoryRegionBuffer&) = delete;
SharedMemoryRegionBuffer& operator=(const SharedMemoryRegionBuffer&) = delete;
const void* GetData() const override { return data_; }
void* GetData() override { return data_; }
size_t GetSize() const override { return size_; }
private:
const CefRefPtr<CefSharedMemoryRegion> region_;
void* const data_;
const size_t size_;
IMPLEMENT_REFCOUNTING(SharedMemoryRegionBuffer);
};
template <class RequestType>
CefRefPtr<CefProcessMessage> BuildRendererListMsg(const std::string& name,
int context_id,
int request_id,
const RequestType& request,
bool persistent) {
auto message = CefProcessMessage::Create(name);
CefRefPtr<CefListValue> args = message->GetArgumentList();
args->SetInt(kContextId, context_id);
args->SetInt(kRequestId, request_id);
if constexpr (IsCefString<RequestType>()) {
args->SetString(kRendererPayload, request);
} else if constexpr (IsEmpty<RequestType>()) {
args->SetNull(kRendererPayload);
} else {
args->SetBinary(kRendererPayload, request);
}
args->SetBool(kIsPersistent, persistent);
return message;
}
template <class RequestType>
CefRefPtr<CefProcessMessage> BuildRendererSharedMsg(const std::string& name,
int context_id,
int request_id,
const RequestType& request,
bool persistent) {
const size_t message_size = GetMessageSize<RendererMsgHeader>(request);
auto builder = CefSharedProcessMessageBuilder::Create(name, message_size);
if (!builder->IsValid()) {
LOG(ERROR) << "Failed to allocate shared memory region of size "
<< message_size;
return BuildRendererListMsg(name, context_id, request_id,
GetListRepresentation(request), persistent);
}
auto header = static_cast<RendererMsgHeader*>(builder->Memory());
header->context_id = context_id;
header->request_id = request_id;
header->is_persistent = persistent;
header->is_binary = !IsCefString<RequestType>();
CopyIntoMemory<RendererMsgHeader>(builder->Memory(), request);
return builder->Build();
}
CefRefPtr<CefProcessMessage> BuildRendererMsg(size_t threshold,
const std::string& name,
int context_id,
int request_id,
const CefString& request,
bool persistent) {
if (GetByteLength(request) < threshold) {
return BuildRendererListMsg(name, context_id, request_id, request,
persistent);
}
return BuildRendererSharedMsg(name, context_id, request_id, request,
persistent);
}
} // namespace
CefRefPtr<BrowserResponseBuilder> CreateBrowserResponseBuilder(
size_t threshold,
const std::string& name,
const CefString& response) {
if (GetByteLength(response) < threshold) {
return new StringResponseBuilder(name, response);
}
return SPMResponseBuilder::Create(name, response);
}
CefRefPtr<BrowserResponseBuilder> CreateBrowserResponseBuilder(
size_t threshold,
const std::string& name,
const void* data,
size_t size) {
if (size == 0) {
return new EmptyResponseBuilder(name);
}
if (size < threshold) {
return new BinaryResponseBuilder(name, data, size);
}
return SPMResponseBuilder::Create(name, data, size);
}
CefRefPtr<CefProcessMessage> BuildRendererMsg(
size_t threshold,
const std::string& name,
int context_id,
int request_id,
const CefRefPtr<CefV8Value>& request,
bool persistent) {
if (request->IsString()) {
return BuildRendererMsg(threshold, name, context_id, request_id,
request->GetStringValue(), persistent);
}
const auto size = request->GetArrayBufferByteLength();
if (size == 0) {
return BuildRendererListMsg(name, context_id, request_id, Empty{},
persistent);
}
if (size < threshold) {
return BuildRendererListMsg(name, context_id, request_id,
GetListRepresentation(request), persistent);
}
return BuildRendererSharedMsg(name, context_id, request_id, request,
persistent);
}
BrowserMessage ParseBrowserMessage(
const CefRefPtr<CefProcessMessage>& message) {
if (auto args = message->GetArgumentList()) {
DCHECK_GT(args->GetSize(), 3U);
const int context_id = args->GetInt(kContextId);
const int request_id = args->GetInt(kRequestId);
const bool is_success = args->GetBool(kIsSuccess);
if (is_success) {
DCHECK_EQ(args->GetSize(), 4U);
const auto payload_type = args->GetType(kBrowserPayload);
if (payload_type == CefValueType::VTYPE_STRING) {
return {context_id, request_id, is_success, kNoError,
args->GetString(kBrowserPayload)};
}
if (payload_type == CefValueType::VTYPE_BINARY) {
return {
context_id, request_id, is_success, kNoError,
new BinaryValueBuffer(message, args->GetBinary(kBrowserPayload))};
}
DCHECK(payload_type == CefValueType::VTYPE_NULL);
return {context_id, request_id, is_success, kNoError,
new EmptyBinaryBuffer()};
}
DCHECK_EQ(args->GetSize(), 5U);
return {context_id, request_id, is_success, args->GetInt(3),
args->GetString(4)};
}
const auto region = message->GetSharedMemoryRegion();
if (region && region->IsValid()) {
DCHECK_GE(region->Size(), sizeof(BrowserMsgHeader));
auto header = static_cast<const BrowserMsgHeader*>(region->Memory());
if (header->is_binary) {
return {header->context_id, header->request_id, true, kNoError,
new SharedMemoryRegionBuffer(region, sizeof(BrowserMsgHeader))};
}
return {header->context_id, header->request_id, true, kNoError,
GetStringFromMemory<BrowserMsgHeader>(region->Memory(),
region->Size())};
}
NOTREACHED();
return {};
}
RendererMessage ParseRendererMessage(
const CefRefPtr<CefProcessMessage>& message) {
if (auto args = message->GetArgumentList()) {
DCHECK_EQ(args->GetSize(), 4U);
const int context_id = args->GetInt(kContextId);
const int request_id = args->GetInt(kRequestId);
const auto payload_type = args->GetType(kRendererPayload);
const bool persistent = args->GetBool(kIsPersistent);
if (payload_type == CefValueType::VTYPE_STRING) {
return {context_id, request_id, persistent,
args->GetString(kRendererPayload)};
}
if (payload_type == CefValueType::VTYPE_BINARY) {
return {
context_id, request_id, persistent,
new BinaryValueBuffer(message, args->GetBinary(kRendererPayload))};
}
DCHECK(payload_type == CefValueType::VTYPE_NULL);
return {context_id, request_id, persistent, new EmptyBinaryBuffer()};
}
const auto region = message->GetSharedMemoryRegion();
if (region && region->IsValid()) {
DCHECK_GE(region->Size(), sizeof(RendererMsgHeader));
auto header = static_cast<const RendererMsgHeader*>(region->Memory());
if (header->is_binary) {
return {header->context_id, header->request_id, header->is_persistent,
new SharedMemoryRegionBuffer(region, sizeof(RendererMsgHeader))};
}
return {
header->context_id,
header->request_id,
header->is_persistent,
GetStringFromMemory<RendererMsgHeader>(region->Memory(),
region->Size()),
};
}
NOTREACHED();
return {};
}
} // namespace cef_message_router_utils

View File

@ -0,0 +1,92 @@
// Copyright (c) 2023 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.
#ifndef CEF_LIBCEF_DLL_WRAPPER_CEF_MESSAGE_ROUTER_UTILS_H_
#define CEF_LIBCEF_DLL_WRAPPER_CEF_MESSAGE_ROUTER_UTILS_H_
#pragma once
#include <variant>
#include "include/wrapper/cef_message_router.h"
namespace cef_message_router_utils {
///
/// This class handles the task of copying user data, such as CefString or
/// binary values like (void*, size_t), directly to an appropriate buffer based
/// on the user data type and size.
///
/// There are four specializations of this abstract class. The appropriate
/// specialization is chosen by the `CreateBrowserResponseBuilder` function,
/// based on the provided data type and size. For instance, for a "short"
/// CefString, a StringResponseBuilder specialization is used, and for an empty
/// binary value - EmptyResponseBuilder.
///
class BrowserResponseBuilder : public CefBaseRefCounted {
public:
///
/// Creates a new CefProcessMessage from the data provided to the builder.
/// Returns nullptr for invalid instances. Invalidates this builder instance.
///
virtual CefRefPtr<CefProcessMessage> Build(int context_id,
int request_id) = 0;
};
struct BrowserMessage {
int context_id;
int request_id;
bool is_success;
int error_code;
std::variant<CefString, CefRefPtr<CefBinaryBuffer>> payload;
};
struct RendererMessage {
int context_id;
int request_id;
bool is_persistent;
std::variant<CefString, CefRefPtr<const CefBinaryBuffer>> payload;
};
class BinaryValueABRCallback final : public CefV8ArrayBufferReleaseCallback {
public:
explicit BinaryValueABRCallback(CefRefPtr<CefBinaryBuffer> value)
: value_(std::move(value)) {}
BinaryValueABRCallback(const BinaryValueABRCallback&) = delete;
BinaryValueABRCallback& operator=(const BinaryValueABRCallback&) = delete;
void ReleaseBuffer(void* buffer) override {}
private:
const CefRefPtr<CefBinaryBuffer> value_;
IMPLEMENT_REFCOUNTING(BinaryValueABRCallback);
};
CefRefPtr<BrowserResponseBuilder> CreateBrowserResponseBuilder(
size_t threshold,
const std::string& name,
const CefString& response);
CefRefPtr<BrowserResponseBuilder> CreateBrowserResponseBuilder(
size_t threshold,
const std::string& name,
const void* data,
size_t size);
CefRefPtr<CefProcessMessage> BuildRendererMsg(
size_t threshold,
const std::string& name,
int context_id,
int request_id,
const CefRefPtr<CefV8Value>& request,
bool persistent);
BrowserMessage ParseBrowserMessage(const CefRefPtr<CefProcessMessage>& message);
RendererMessage ParseRendererMessage(
const CefRefPtr<CefProcessMessage>& message);
} // namespace cef_message_router_utils
#endif // CEF_LIBCEF_DLL_WRAPPER_CEF_MESSAGE_ROUTER_UTILS_H_

View File

@ -0,0 +1,60 @@
// Copyright (c) 2023 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include "tests/cefclient/browser/binary_transfer_test.h"
#include <algorithm>
#include <string>
namespace {
const char kTestUrlPath[] = "/binary_transfer";
// Handle messages in the browser process.
class Handler final : public CefMessageRouterBrowserSide::Handler {
public:
// Called due to cefQuery execution in binary_transfer.html.
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
// Only handle messages from the test URL.
const std::string& url = frame->GetURL();
if (!client::test_runner::IsTestURL(url, kTestUrlPath)) {
return false;
}
callback->Success(request);
return true;
}
// Called due to cefQuery execution in binary_transfer.html.
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
CefRefPtr<const CefBinaryBuffer> request,
bool persistent,
CefRefPtr<Callback> callback) override {
// Only handle messages from the test URL.
const std::string& url = frame->GetURL();
if (!client::test_runner::IsTestURL(url, kTestUrlPath)) {
return false;
}
callback->Success(request->GetData(), request->GetSize());
return true;
}
};
} // namespace
namespace client::binary_transfer_test {
void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
handlers.insert(new Handler());
}
} // namespace client::binary_transfer_test

View File

@ -0,0 +1,17 @@
// Copyright (c) 2023 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.
#ifndef CEF_TESTS_CEFCLIENT_BROWSER_BINARY_TRANSFER_TEST_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_BINARY_TRANSFER_TEST_H_
#pragma once
#include "tests/cefclient/browser/test_runner.h"
namespace client::binary_transfer_test {
void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers);
} // namespace client::binary_transfer_test
#endif // CEF_TESTS_CEFCLIENT_BROWSER_BINARY_TRANSFER_TEST_H_

View File

@ -44,31 +44,32 @@
#define ID_TESTS_UNMUTE_AUDIO 32717
#define ID_TESTS_LAST 32717
#define IDC_STATIC -1
#define IDS_BINDING_HTML 1000
#define IDS_DIALOGS_HTML 1001
#define IDS_DRAGGABLE_HTML 1002
#define IDS_IPC_PERFORMANCE_HTML 1003
#define IDS_LOCALSTORAGE_HTML 1004
#define IDS_LOGO_PNG 1005
#define IDS_MEDIA_ROUTER_HTML 1006
#define IDS_MENU_ICON_1X_PNG 1007
#define IDS_MENU_ICON_2X_PNG 1008
#define IDS_OSRTEST_HTML 1009
#define IDS_OTHER_TESTS_HTML 1010
#define IDS_PDF_HTML 1011
#define IDS_PDF_PDF 1012
#define IDS_PERFORMANCE_HTML 1013
#define IDS_PERFORMANCE2_HTML 1014
#define IDS_PREFERENCES_HTML 1015
#define IDS_RESPONSE_FILTER_HTML 1016
#define IDS_SERVER_HTML 1017
#define IDS_TRANSPARENCY_HTML 1018
#define IDS_URLREQUEST_HTML 1019
#define IDS_WEBSOCKET_HTML 1020
#define IDS_WINDOW_HTML 1021
#define IDS_WINDOW_ICON_1X_PNG 1022
#define IDS_WINDOW_ICON_2X_PNG 1023
#define IDS_XMLHTTPREQUEST_HTML 1024
#define IDS_BINARY_TRANSFER_HTML 1000
#define IDS_BINDING_HTML 1001
#define IDS_DIALOGS_HTML 1002
#define IDS_DRAGGABLE_HTML 1003
#define IDS_IPC_PERFORMANCE_HTML 1004
#define IDS_LOCALSTORAGE_HTML 1005
#define IDS_LOGO_PNG 1006
#define IDS_MEDIA_ROUTER_HTML 1007
#define IDS_MENU_ICON_1X_PNG 1008
#define IDS_MENU_ICON_2X_PNG 1009
#define IDS_OSRTEST_HTML 1010
#define IDS_OTHER_TESTS_HTML 1011
#define IDS_PDF_HTML 1012
#define IDS_PDF_PDF 1013
#define IDS_PERFORMANCE_HTML 1014
#define IDS_PERFORMANCE2_HTML 1015
#define IDS_PREFERENCES_HTML 1016
#define IDS_RESPONSE_FILTER_HTML 1017
#define IDS_SERVER_HTML 1018
#define IDS_TRANSPARENCY_HTML 1019
#define IDS_URLREQUEST_HTML 1020
#define IDS_WEBSOCKET_HTML 1021
#define IDS_WINDOW_HTML 1022
#define IDS_WINDOW_ICON_1X_PNG 1023
#define IDS_WINDOW_ICON_2X_PNG 1024
#define IDS_XMLHTTPREQUEST_HTML 1025
#define IDS_EXTENSIONS_SET_PAGE_COLOR_ICON_PNG 1030
#define IDS_EXTENSIONS_SET_PAGE_COLOR_MANIFEST_JSON 1031

View File

@ -13,7 +13,8 @@ int GetResourceId(const char* resource_name) {
static struct _resource_map {
const char* name;
int id;
} resource_map[] = {{"binding.html", IDS_BINDING_HTML},
} resource_map[] = {{"binary_transfer.html", IDS_BINARY_TRANSFER_HTML},
{"binding.html", IDS_BINDING_HTML},
{"dialogs.html", IDS_DIALOGS_HTML},
{"draggable.html", IDS_DRAGGABLE_HTML},
{"extensions/set_page_color/icon.png",

View File

@ -16,6 +16,7 @@
#include "include/views/cef_browser_view.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_stream_resource_handler.h"
#include "tests/cefclient/browser/binary_transfer_test.h"
#include "tests/cefclient/browser/binding_test.h"
#include "tests/cefclient/browser/client_handler.h"
#include "tests/cefclient/browser/dialog_test.h"
@ -842,6 +843,9 @@ bool IsTestURL(const std::string& url, const std::string& path) {
void CreateMessageHandlers(MessageHandlerSet& handlers) {
handlers.insert(new PromptHandler);
// Create the binary trasfer test handlers.
binary_transfer_test::CreateMessageHandlers(handlers);
// Create the binding test handlers.
binding_test::CreateMessageHandlers(handlers);

View File

@ -0,0 +1,498 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Binary vs String Transfer Benchmark</title>
<script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script>
<style>
body {
font-family: Tahoma, Serif;
font-size: 10pt;
}
.info {
font-size: 12pt;
}
.left {
text-align: left;
}
.right {
text-align: right;
}
.positive {
color: green;
font-weight: bold;
}
.negative {
color: red;
font-weight: bold;
}
.center {
text-align: center;
}
table.resultTable {
border: 1px solid black;
border-collapse: collapse;
empty-cells: show;
width: 100%;
}
table.resultTable td {
padding: 2px 4px;
border: 1px solid black;
}
table.resultTable > thead > tr {
font-weight: bold;
background: lightblue;
}
table.resultTable > tbody > tr:nth-child(odd) {
background: white;
}
table.resultTable > tbody > tr:nth-child(even) {
background: lightgray;
}
.hide {
display: none;
}
</style>
</head>
<body background-color="white">
<h1>Binary vs String Transfer Benchmark</h1>
<table>
<tr>
<td>
<p class="info">
This benchmark evaluates the message transfer speed between the
renderer process and the browser process. <br />It compares the
performance of binary and string message transfer.
</p>
<p class="info">
<b>Note:</b> There is no progress indication of the tests because it
significantly influences measurements. <br />It usually takes 30
seconds (for 300 samples) to complete the tests.
</p>
</td>
</tr>
<tr>
<td>
Samples:
<input
id="sSamples"
type="text"
value="300"
required
pattern="[0-9]+"
/>
<button id="sRun" autofocus onclick="runTestSuite()">Run</button>
</td>
</tr>
</table>
<div style="padding-top: 10px; padding-bottom: 10px">
<table id="resultTable" class="resultTable">
<thead>
<tr>
<td class="center" style="width: 8%">Message Size</td>
<td class="center" style="width: 8%">
String Round Trip Avg,&nbsp;ms
</td>
<td class="center" style="width: 8%">
Binary Round Trip Avg,&nbsp;ms
</td>
<td class="center" style="width: 10%">Relative Trip Difference</td>
<td class="center" style="width: 8%">String Speed,&nbsp;MB/s</td>
<td class="center" style="width: 8%">Binary Speed,&nbsp;MB/s</td>
<td class="center" style="width: 10%">Relative Speed Difference</td>
<td class="center" style="width: 8%">String Standard Deviation</td>
<td class="center" style="width: 8%">Binary Standard Deviation</td>
</tr>
</thead>
<tbody>
<!-- result rows here -->
</tbody>
</table>
</div>
<div id="round_trip_avg_chart">
<!-- Average round trip linear chart will be drawn inside this DIV -->
</div>
<div id="box_plot_chart">
<!-- Box plot of round trip time will be drawn inside this DIV -->
</div>
<script type="text/javascript">
let tests = [];
let box_plot_test_data = [];
let round_trip_avg_plot_data = [];
function nextTestSuite(testIndex) {
const nextTestIndex = testIndex + 1;
setTimeout(execTestSuite, 0, nextTestIndex);
}
function generateRandomString(size) {
// Symbols that will be encoded as two bytes in UTF-8
// so we compare transfer of the same amount of bytes
const characters =
"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя";
let randomString = "";
for (let i = 0; i < size; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
randomString += characters.charAt(randomIndex);
}
return randomString;
}
function generateRandomArrayBuffer(size) {
const buffer = new ArrayBuffer(size);
const uint8Array = new Uint8Array(buffer);
for (let i = 0; i < uint8Array.length; i++) {
uint8Array[i] = Math.floor(Math.random() * 256);
}
return buffer;
}
function reportError(errorCode, errorMessage) {
console.error(`ErrorCode:${errorCode} Message:${errorMessage}`);
}
function sendString(size, testIndex) {
const request = generateRandomString(size);
const startTime = performance.now();
const onSuccess = (response) => {
const roundTrip = performance.now() - startTime;
const test = tests[testIndex];
test.totalRoundTrip += roundTrip;
test.sample++;
box_plot_test_data[testIndex].x.push(roundTrip);
setTimeout(execTest, 0, testIndex);
};
window.cefQuery({
request: request,
onSuccess: onSuccess,
onFailure: reportError,
});
}
function sendArrayBuffer(size, testIndex) {
const request = generateRandomArrayBuffer(size);
const startTime = performance.now();
const onSuccess = (response) => {
const roundTrip = performance.now() - startTime;
const test = tests[testIndex];
test.totalRoundTrip += roundTrip;
test.sample++;
box_plot_test_data[testIndex].x.push(roundTrip);
setTimeout(execTest, 0, testIndex);
};
window.cefQuery({
request: request,
onSuccess: onSuccess,
onFailure: reportError,
});
}
function getStandardDeviation(array, mean) {
const n = array.length;
if (n < 5) return null;
return Math.sqrt(
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) /
(n - 1)
);
}
function execTest(testIndex) {
const test = tests[testIndex];
if (test.sample >= test.totalSamples) {
return nextTestSuite(testIndex);
}
test.func(test.length, test.index);
}
function column(prepared, value) {
return (
"<td class='right'>" + (!prepared ? "-" : value.toFixed(3)) + "</td>"
);
}
function relativeDiffColumn(prepared, value, isBiggerBetter) {
if (!prepared) return "<td class='right'>-</td>";
const isPositive = value >= 0 == isBiggerBetter;
return [
"<td class='right ",
isPositive ? "positive" : "negative",
"'>",
value > 0 ? "+" : "",
value.toFixed(2),
"%</td>",
].join("");
}
function displayResult(test) {
const id = "testResultRow_" + test.index;
const markup = [
"<tr id='",
id,
"'>",
"<td class='left'>",
test.name,
"</td>",
column(test.prepared, test.avgRoundTrip),
column(test.prepared, test.avgRoundTripBin),
relativeDiffColumn(test.prepared, test.relativeTripDiff, false),
column(test.prepared, test.speed),
column(test.prepared, test.speedBinary),
relativeDiffColumn(test.prepared, test.relativeSpeedDiff, true),
"<td class='right'>",
!test.prepared || test.stdDeviation == null
? "-"
: test.stdDeviation.toFixed(3),
"</td>",
"<td class='right'>",
!test.prepared || test.stdDeviationBinary == null
? "-"
: test.stdDeviationBinary.toFixed(3),
"</td>",
"</tr>",
].join("");
const row = document.getElementById(id);
if (row) {
row.outerHTML = markup;
} else {
const tbody = document.getElementById("resultTable").tBodies[0];
tbody.insertAdjacentHTML("beforeEnd", markup);
}
}
function relativeDiff(left, right) {
if (right != 0) {
return ((left - right) / right) * 100;
}
return 0;
}
function buildTestResults(tests) {
testResults = [];
let oldRoundTrip = {
x: [],
y: [],
type: "scatter",
name: "String",
};
let newRoundTrip = {
x: [],
y: [],
type: "scatter",
name: "Binary",
};
for (let i = 0; i < tests.length / 2; i++) {
const index = testResults.length;
// Tests are in pairs - String and Binary
const test = tests[i * 2];
const testBin = tests[i * 2 + 1];
const avgRoundTrip = test.totalRoundTrip / test.totalSamples;
const avgRoundTripBin = testBin.totalRoundTrip / testBin.totalSamples;
const relativeTripDiff = relativeDiff(avgRoundTripBin, avgRoundTrip);
// In MB/s
const speed = test.byteSize / (avgRoundTrip * 1000);
const speedBinary = testBin.byteSize / (avgRoundTripBin * 1000);
const relativeSpeedDiff = relativeDiff(speedBinary, speed);
const stdDeviation = getStandardDeviation(
box_plot_test_data[test.index].x,
avgRoundTrip
);
const stdDeviationBinary = getStandardDeviation(
box_plot_test_data[testBin.index].x,
avgRoundTripBin
);
testResults.push({
name: humanFileSize(test.byteSize),
index: index,
prepared: true,
avgRoundTrip: avgRoundTrip,
avgRoundTripBin: avgRoundTripBin,
relativeTripDiff: relativeTripDiff,
speed: speed,
speedBinary: speedBinary,
relativeSpeedDiff: relativeSpeedDiff,
stdDeviation: stdDeviation,
stdDeviationBinary: stdDeviationBinary,
});
oldRoundTrip.x.push(test.byteSize);
newRoundTrip.x.push(test.byteSize);
oldRoundTrip.y.push(avgRoundTrip);
newRoundTrip.y.push(avgRoundTripBin);
}
round_trip_avg_plot_data = [oldRoundTrip, newRoundTrip];
return testResults;
}
function buildEmptyTestResults(tests) {
testResults = [];
for (let i = 0; i < tests.length / 2; i++) {
const index = testResults.length;
const test = tests[i * 2];
testResults.push({
name: humanFileSize(test.byteSize),
index: index,
prepared: false,
});
}
return testResults;
}
function resetTestsResults(totalSamples) {
if (totalSamples <= 0) totalSamples = 1;
// Reset tests results
tests.forEach((test) => {
test.sample = 0;
test.totalRoundTrip = 0;
test.totalSamples = totalSamples;
});
testResults = buildEmptyTestResults(tests);
testResults.forEach((result) => displayResult(result));
round_trip_avg_plot_data = [];
box_plot_test_data.forEach((data) => {
data.x = [];
});
}
function queueTest(name, byteSize, length, testFunc) {
const testIndex = tests.length;
test = {
name: name,
byteSize: byteSize,
length: length,
index: testIndex,
sample: 0,
totalRoundTrip: 0,
func: testFunc,
};
tests.push(test);
box_plot_test_data.push({
x: [],
type: "box",
boxpoints: "all",
name: name,
jitter: 0.3,
pointpos: -1.8,
});
}
function execTestSuite(testIndex) {
if (testIndex < tests.length) {
execTest(testIndex);
} else {
testsRunFinished();
}
}
function startTests() {
// Let the updated table render before starting the tests
setTimeout(execTestSuite, 200, 0);
}
function execQueuedTests(totalSamples) {
resetTestsResults(totalSamples);
startTests();
}
function setSettingsState(disabled) {
document.getElementById("sSamples").disabled = disabled;
document.getElementById("sRun").disabled = disabled;
}
function testsRunFinished() {
testResults = buildTestResults(tests);
testResults.forEach((result) => displayResult(result));
const round_trip_layout = {
title: "Average round trip, μs (Smaller Better)",
};
Plotly.newPlot(
"round_trip_avg_chart",
round_trip_avg_plot_data,
round_trip_layout
);
const box_plot_layout = {
title: "Round Trip Time, μs",
};
Plotly.newPlot("box_plot_chart", box_plot_test_data, box_plot_layout);
setSettingsState(false);
}
function humanFileSize(bytes) {
const step = 1024;
const originalBytes = bytes;
if (Math.abs(bytes) < step) {
return bytes + " B";
}
const units = [" KB", " MB", " GB"];
let u = -1;
let count = 0;
do {
bytes /= step;
u += 1;
count += 1;
} while (Math.abs(bytes) >= step && u < units.length - 1);
return bytes.toString() + units[u];
}
window.runTestSuite = () => {
Plotly.purge("round_trip_avg_chart");
Plotly.purge("box_plot_chart");
setSettingsState(true);
const totalSamples = parseInt(
document.getElementById("sSamples").value
);
execQueuedTests(totalSamples);
};
const totalSamples = parseInt(document.getElementById("sSamples").value);
queueTest("Empty String", 0, 0, sendString);
queueTest("Empty Binary", 0, 0, sendArrayBuffer);
for (let byteSize = 8; byteSize <= 512 * 1024; byteSize *= 2) {
// Byte size of a string is twice its length because of UTF-16 encoding
const stringLen = byteSize / 2;
queueTest(
humanFileSize(byteSize) + " String",
byteSize,
stringLen,
sendString
);
queueTest(
humanFileSize(byteSize) + " Binary",
byteSize,
byteSize,
sendArrayBuffer
);
}
resetTestsResults(totalSamples);
</script>
</body>
</html>

View File

@ -9,6 +9,7 @@
<li><a href="http://webkit.org/blog-files/3d-transforms/poster-circle.html">Accelerated Layers</a></li>
<li><a href="https://jigsaw.w3.org/HTTP/Basic/">Authentication (Basic)</a> - credentials returned via GetAuthCredentials</li>
<li><a href="https://jigsaw.w3.org/HTTP/Digest/">Authentication (Digest)</a> - credentials returned via GetAuthCredentials</li>
<li><a href="binary_transfer">Binary vs String Transfer Benchmark</a></li>
<li><a href="http://html5advent2011.digitpaint.nl/3/index.html">Cursors</a></li>
<li><a href="dialogs">Dialogs</a></li>
<li><a href="http://html5demos.com/drag">Drag & Drop</a></li>

View File

@ -29,6 +29,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// Binary
//
IDS_BINARY_TRANSFER_HTML BINARY "..\\binary_transfer.html"
IDS_BINDING_HTML BINARY "..\\binding.html"
IDS_DIALOGS_HTML BINARY "..\\dialogs.html"
IDS_DRAGGABLE_HTML BINARY "..\\draggable.html"
@ -228,4 +229,3 @@ END
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,136 @@
// Copyright (c) 2023 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include "tests/ceftests/message_router_unittest_utils.h"
namespace {
constexpr size_t kMsgSizeThresholdInBytes = 16000;
class BinaryTestHandler final : public SingleLoadTestHandler {
public:
explicit BinaryTestHandler(size_t message_size)
: message_size_(message_size) {}
std::string GetMainHTML() override {
return "<html><body><script>\n"
"function generateRandomArrayBuffer(size) {\n"
" const buffer = new ArrayBuffer(size);\n"
" const uint8Array = new Uint8Array(buffer);\n"
" for (let i = 0; i < uint8Array.length; i++) {\n"
" uint8Array[i] = Math.floor(Math.random() * 256);\n"
" }\n"
" return buffer;\n"
"}\n"
"function isEqualArrayBuffers(left, right) {\n"
" if (left.byteLength !== right.byteLength) { return false; }\n"
" const leftView = new DataView(left);\n"
" const rightView = new DataView(right);\n"
" for (let i = 0; i < left.byteLength; i++) {\n"
" if (leftView.getUint8(i) !== rightView.getUint8(i)) {\n"
" return false;\n"
" }\n"
" }\n"
" return true;\n"
"}\n"
"const request = generateRandomArrayBuffer(" +
std::to_string(message_size_) +
")\n"
"window." +
std::string(kJSQueryFunc) +
"({\n request: request,\n"
" persistent: false,\n"
" onSuccess: (response) => {\n"
" if (!isEqualArrayBuffers(request, response)) {\n"
" window.mrtNotify('error-ArrayBuffersDiffer');\n"
" } else {\n"
" window.mrtNotify('success');\n"
" }\n"
" },\n"
" onFailure: (errorCode, errorMessage) => {\n"
" window.mrtNotify('error-onFailureCalled');\n"
" }\n"
"});\n</script></body></html>";
}
void OnNotify(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const std::string& message) override {
AssertMainBrowser(browser);
AssertMainFrame(frame);
// OnNotify only be called once.
EXPECT_FALSE(got_notify_);
got_notify_.yes();
EXPECT_EQ("success", message);
DestroyTest();
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
CefRefPtr<const CefBinaryBuffer> request,
bool persistent,
CefRefPtr<Callback> callback) override {
AssertMainBrowser(browser);
AssertMainFrame(frame);
EXPECT_NE(0, query_id);
EXPECT_FALSE(persistent);
EXPECT_EQ(message_size_, request->GetSize());
got_on_query_.yes();
callback->Success(request->GetData(), request->GetSize());
return true;
}
void DestroyTest() override {
EXPECT_TRUE(got_notify_);
EXPECT_TRUE(got_on_query_);
TestHandler::DestroyTest();
}
private:
const size_t message_size_;
TrackCallback got_on_query_;
TrackCallback got_notify_;
};
using TestHandlerPtr = CefRefPtr<BinaryTestHandler>;
} // namespace
TEST(MessageRouterTest, BinaryMessageEmpty) {
const auto message_size = 0;
TestHandlerPtr handler = new BinaryTestHandler(message_size);
handler->SetMessageSizeThreshold(kMsgSizeThresholdInBytes);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
TEST(MessageRouterTest, BinaryMessageUnderThresholdSize) {
const auto under_threshold = kMsgSizeThresholdInBytes - 1;
TestHandlerPtr handler = new BinaryTestHandler(under_threshold);
handler->SetMessageSizeThreshold(kMsgSizeThresholdInBytes);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
TEST(MessageRouterTest, BinaryMessageOverThresholdSize) {
const auto over_threshold = kMsgSizeThresholdInBytes + 1;
TestHandlerPtr handler = new BinaryTestHandler(over_threshold);
handler->SetMessageSizeThreshold(kMsgSizeThresholdInBytes);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}

View File

@ -19,8 +19,18 @@ const char kMultiQueryError[] = "error";
const char kMultiQueryErrorMessage[] = "errormsg";
const int kMultiQueryPersistentResponseCount = 5;
template <typename T>
constexpr bool IsCefString() {
return std::is_same_v<std::remove_cv_t<T>, CefString>;
}
enum class TransferType {
STRING,
BINARY,
};
// Generates HTML and verifies results for multiple simultanious queries.
class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
class MultiQueryManager {
public:
enum TestType {
// Initiates a non-persistent query with a successful response.
@ -70,10 +80,12 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
MultiQueryManager(const std::string& label,
bool synchronous,
int id_offset = 0)
int id_offset = 0,
TransferType transfer_type = TransferType::STRING)
: label_(label),
synchronous_(synchronous),
id_offset_(id_offset),
transfer_type_(transfer_type),
finalized_(false),
running_(false),
manual_total_(0),
@ -83,8 +95,6 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
will_cancel_by_removing_handler_(false),
weak_ptr_factory_(this) {}
virtual ~MultiQueryManager() {}
std::string label() const { return label_; }
void AddObserver(Observer* observer) {
@ -282,12 +292,13 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
}
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
template <class RequestType>
bool OnQueryImpl(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const RequestType& request,
bool persistent,
CefRefPtr<CefMessageRouterBrowserSide::Callback> callback) {
EXPECT_TRUE(finalized_);
EXPECT_UI_THREAD();
@ -328,12 +339,25 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
if (query.type == SUCCESS) {
// Send the single success response.
callback->Success(GetIDString(kMultiQueryResponse, index));
if constexpr (IsCefString<RequestType>()) {
const auto response = GetIDString(kMultiQueryResponse, index);
callback->Success(response);
} else {
const auto response = GetIDBinary(kMultiQueryResponse, index);
callback->Success(response.data(), response.size());
}
} else if (IsPersistent(query.type)) {
// Send the required number of successful responses.
const std::string& response = GetIDString(kMultiQueryResponse, index);
for (int i = 0; i < kMultiQueryPersistentResponseCount; ++i) {
callback->Success(response);
if constexpr (IsCefString<RequestType>()) {
const auto response = GetIDString(kMultiQueryResponse, index);
for (int i = 0; i < kMultiQueryPersistentResponseCount; ++i) {
callback->Success(response);
}
} else {
const auto response = GetIDBinary(kMultiQueryResponse, index);
for (int i = 0; i < kMultiQueryPersistentResponseCount; ++i) {
callback->Success(response.data(), response.size());
}
}
}
@ -359,7 +383,7 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id) override {
int64_t query_id) {
EXPECT_TRUE(finalized_);
EXPECT_UI_THREAD();
@ -495,7 +519,7 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
// Used when a query is canceled.
int64_t query_id;
CefRefPtr<Callback> callback;
CefRefPtr<CefMessageRouterBrowserSide::Callback> callback;
TrackCallback got_query;
TrackCallback got_query_canceled;
@ -610,13 +634,24 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
const std::string& request_id_var =
GetIDString(kMultiQueryRequestId, index);
const std::string& repeat_ct_var = GetIDString(kMultiQueryRepeatCt, index);
const std::string& request_val =
const std::string& request_str =
GetIDString(std::string(kMultiQueryRequest) + ":", index);
const std::string& success_val =
GetIDString(std::string(kMultiQuerySuccess) + ":", index);
const std::string& error_val =
GetIDString(std::string(kMultiQueryError) + ":", index);
const std::string request_val =
transfer_type_ == TransferType::BINARY
? ("new TextEncoder().encode('" + request_str + "').buffer")
: "'" + request_str + "'";
const std::string response_conversion =
transfer_type_ == TransferType::BINARY
? " const decoder = new TextDecoder('utf-8');\n"
" const message = decoder.decode(response);\n"
: " const message = response;\n";
std::string html;
const bool persistent = IsPersistent(query.type);
@ -627,33 +662,29 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
html += "var " + request_id_var +
" = window.mrtQuery({\n"
" request: '" +
" request: " +
request_val +
"',\n"
" persistent: " +
(persistent ? "true" : "false") + ",\n";
",\n persistent: " + (persistent ? "true" : "false") + ",\n";
if (query.type == SUCCESS) {
const std::string& response_val = GetIDString(kMultiQueryResponse, index);
html +=
" onSuccess: function(response) {\n"
" if (response == '" +
response_val +
"')\n"
" window.mrtNotify('" +
success_val +
"');\n"
" else\n"
" window.mrtNotify('" +
error_val +
"');\n"
" },\n"
" onFailure: function(error_code, error_message) {\n"
" window.mrtNotify('" +
error_val +
"');\n"
" }\n";
html += " onSuccess: function(response) {\n" + response_conversion +
" if (message == '" + response_val +
"')\n"
" window.mrtNotify('" +
success_val +
"');\n"
" else\n"
" window.mrtNotify('" +
error_val +
"');\n"
" },\n"
" onFailure: function(error_code, error_message) {\n"
" window.mrtNotify('" +
error_val +
"');\n"
" }\n";
} else if (query.type == FAILURE) {
const std::string& error_code_val = GetIntString(index);
const std::string& error_message_val =
@ -683,17 +714,15 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
const std::string& repeat_ct =
GetIntString(kMultiQueryPersistentResponseCount);
html +=
" onSuccess: function(response) {\n"
" if (response == '" +
response_val +
"') {\n"
// Should get repeat_ct number of successful responses.
" if (++" +
repeat_ct_var + " == " + repeat_ct +
") {\n"
" window.mrtNotify('" +
success_val + "');\n";
html += " onSuccess: function(response) {\n" + response_conversion +
" if (message == '" + response_val +
"') {\n"
// Should get repeat_ct number of successful responses.
" if (++" +
repeat_ct_var + " == " + repeat_ct +
") {\n"
" window.mrtNotify('" +
success_val + "');\n";
if (query.type == PERSISTENT_SUCCESS) {
// Manually cancel the request.
@ -773,9 +802,12 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
std::string GetIDString(const std::string& prefix, int index) const {
EXPECT_TRUE(!prefix.empty());
std::stringstream ss;
ss << prefix << GetIDFromIndex(index);
return ss.str();
return prefix + std::to_string(GetIDFromIndex(index));
}
std::vector<uint8_t> GetIDBinary(const std::string& prefix, int index) const {
auto str = GetIDString(prefix, index);
return std::vector<uint8_t>(str.begin(), str.end());
}
bool SplitIDString(const std::string& str,
@ -792,6 +824,19 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
return false;
}
bool SplitIDString(const CefRefPtr<const CefBinaryBuffer>& request,
std::string* value,
int* index) const {
const size_t string_len =
request->GetSize() / sizeof(std::string::value_type);
const auto* src =
static_cast<const std::string::value_type*>(request->GetData());
CefString result;
result.FromString(src, string_len);
return SplitIDString(result, value, index);
}
std::string GetIntString(int val) const {
std::stringstream ss;
ss << val;
@ -804,6 +849,7 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
const std::string label_;
const bool synchronous_;
const int id_offset_;
const TransferType transfer_type_;
typedef std::vector<TestQuery> TestQueryVector;
TestQueryVector test_query_vector_;
@ -904,8 +950,10 @@ class MultiQuerySingleFrameTestHandler : public SingleLoadTestHandler,
MultiQuerySingleFrameTestHandler(
bool synchronous,
TransferType transfer_type,
CancelType cancel_type = CANCEL_BY_NAVIGATION)
: manager_(std::string(), synchronous), cancel_type_(cancel_type) {
: manager_(std::string(), synchronous, 0, transfer_type),
cancel_type_(cancel_type) {
manager_.AddObserver(this);
}
@ -929,8 +977,21 @@ class MultiQuerySingleFrameTestHandler : public SingleLoadTestHandler,
AssertMainBrowser(browser);
AssertMainFrame(frame);
return manager_.OnQuery(browser, frame, query_id, request, persistent,
callback);
return manager_.OnQueryImpl(browser, frame, query_id, request, persistent,
callback);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
CefRefPtr<const CefBinaryBuffer> request,
bool persistent,
CefRefPtr<Callback> callback) override {
AssertMainBrowser(browser);
AssertMainFrame(frame);
return manager_.OnQueryImpl(browser, frame, query_id, request, persistent,
callback);
}
void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
@ -994,10 +1055,22 @@ class MultiQuerySingleFrameTestHandler : public SingleLoadTestHandler,
} // namespace
#define MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(name, type, synchronous) \
TEST(MessageRouterTest, name) { \
#define MQSF_TYPE_TEST(name, type, synchronous) \
TEST(MessageRouterTest, MultiQuerySingleFrame##name##String) { \
CefRefPtr<MultiQuerySingleFrameTestHandler> handler = \
new MultiQuerySingleFrameTestHandler(synchronous); \
new MultiQuerySingleFrameTestHandler(synchronous, \
TransferType::STRING); \
MultiQueryManager* manager = handler->GetManager(); \
manager->AddTestQuery(MultiQueryManager::type); \
manager->Finalize(); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
} \
\
TEST(MessageRouterTest, MultiQuerySingleFrame##name##Binary) { \
CefRefPtr<MultiQuerySingleFrameTestHandler> handler = \
new MultiQuerySingleFrameTestHandler(synchronous, \
TransferType::BINARY); \
MultiQueryManager* manager = handler->GetManager(); \
manager->AddTestQuery(MultiQueryManager::type); \
manager->Finalize(); \
@ -1006,97 +1079,80 @@ class MultiQuerySingleFrameTestHandler : public SingleLoadTestHandler,
}
// Test the query types individually.
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncSuccess,
SUCCESS,
true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncSuccess,
SUCCESS,
false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncFailure,
FAILURE,
true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncFailure,
FAILURE,
false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncPersistentSuccess,
PERSISTENT_SUCCESS,
true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncPersistentSuccess,
PERSISTENT_SUCCESS,
false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncPersistentFailure,
PERSISTENT_FAILURE,
true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncPersistentFailure,
PERSISTENT_FAILURE,
false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameCancel, CANCEL, true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAutoCancel,
AUTOCANCEL,
true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFramePersistentAutoCancel,
PERSISTENT_AUTOCANCEL,
true)
MQSF_TYPE_TEST(SyncSuccess, SUCCESS, true)
MQSF_TYPE_TEST(AsyncSuccess, SUCCESS, false)
MQSF_TYPE_TEST(SyncFailure, FAILURE, true)
MQSF_TYPE_TEST(AsyncFailure, FAILURE, false)
MQSF_TYPE_TEST(SyncPersistentSuccess, PERSISTENT_SUCCESS, true)
MQSF_TYPE_TEST(AsyncPersistentSuccess, PERSISTENT_SUCCESS, false)
MQSF_TYPE_TEST(SyncPersistentFailure, PERSISTENT_FAILURE, true)
MQSF_TYPE_TEST(AsyncPersistentFailure, PERSISTENT_FAILURE, false)
MQSF_TYPE_TEST(Cancel, CANCEL, true)
MQSF_TYPE_TEST(AutoCancel, AUTOCANCEL, true)
MQSF_TYPE_TEST(PersistentAutoCancel, PERSISTENT_AUTOCANCEL, true)
#define MQSF_QUERY_RANGE_TEST(name, some, synchronous) \
TEST(MessageRouterTest, MultiQuerySingleFrame##name##String) { \
CefRefPtr<MultiQuerySingleFrameTestHandler> handler = \
new MultiQuerySingleFrameTestHandler(synchronous, \
TransferType::STRING); \
MakeTestQueries(handler->GetManager(), some); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
} \
\
TEST(MessageRouterTest, MultiQuerySingleFrame##name##Binary) { \
CefRefPtr<MultiQuerySingleFrameTestHandler> handler = \
new MultiQuerySingleFrameTestHandler(synchronous, \
TransferType::BINARY); \
MakeTestQueries(handler->GetManager(), some); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
}
// Test that one frame can run some queries successfully in a synchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameSyncSome) {
CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
new MultiQuerySingleFrameTestHandler(true);
MakeTestQueries(handler->GetManager(), true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// manner
MQSF_QUERY_RANGE_TEST(SyncSome, true, true)
// Test that one frame can run some queries successfully in an asynchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameAsyncSome) {
CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
new MultiQuerySingleFrameTestHandler(false);
MakeTestQueries(handler->GetManager(), true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQSF_QUERY_RANGE_TEST(AsyncSome, true, false)
// Test that one frame can run many queries successfully in a synchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameSyncMany) {
CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
new MultiQuerySingleFrameTestHandler(true);
MakeTestQueries(handler->GetManager(), false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQSF_QUERY_RANGE_TEST(SyncMany, false, true)
// Test that one frame can run many queries successfully in an asynchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameAsyncMany) {
CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
new MultiQuerySingleFrameTestHandler(false);
MakeTestQueries(handler->GetManager(), false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQSF_QUERY_RANGE_TEST(AsyncMany, false, false)
#define MQSF_QUERY_RANGE_CANCEL_TEST(name, cancelType) \
TEST(MessageRouterTest, MultiQuerySingleFrame##name##String) { \
CefRefPtr<MultiQuerySingleFrameTestHandler> handler = \
new MultiQuerySingleFrameTestHandler( \
false, TransferType::STRING, \
MultiQuerySingleFrameTestHandler::cancelType); \
MakeTestQueries(handler->GetManager(), false); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
} \
\
TEST(MessageRouterTest, MultiQuerySingleFrame##name##Binary) { \
CefRefPtr<MultiQuerySingleFrameTestHandler> handler = \
new MultiQuerySingleFrameTestHandler( \
false, TransferType::BINARY, \
MultiQuerySingleFrameTestHandler::cancelType); \
MakeTestQueries(handler->GetManager(), false); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
}
// Test that pending queries can be canceled by removing the handler.
TEST(MessageRouterTest, MultiQuerySingleFrameCancelByRemovingHandler) {
CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
new MultiQuerySingleFrameTestHandler(
false, MultiQuerySingleFrameTestHandler::CANCEL_BY_REMOVING_HANDLER);
MakeTestQueries(handler->GetManager(), false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQSF_QUERY_RANGE_CANCEL_TEST(CancelByRemovingHandler,
CANCEL_BY_REMOVING_HANDLER)
// Test that pending queries can be canceled by closing the browser.
TEST(MessageRouterTest, MultiQuerySingleFrameCancelByClosingBrowser) {
CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
new MultiQuerySingleFrameTestHandler(
false, MultiQuerySingleFrameTestHandler::CANCEL_BY_CLOSING_BROWSER);
MakeTestQueries(handler->GetManager(), false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQSF_QUERY_RANGE_CANCEL_TEST(CancelByClosingBrowser, CANCEL_BY_CLOSING_BROWSER)
namespace {
@ -1211,8 +1267,21 @@ class MultiQueryMultiHandlerTestHandler : public SingleLoadTestHandler,
AssertMainBrowser(browser);
AssertMainFrame(frame);
return manager_.OnQuery(browser, frame, query_id, request, persistent,
callback);
return manager_.OnQueryImpl(browser, frame, query_id, request, persistent,
callback);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
CefRefPtr<const CefBinaryBuffer> request,
bool persistent,
CefRefPtr<Callback> callback) override {
AssertMainBrowser(browser);
AssertMainFrame(frame);
return manager_.OnQueryImpl(browser, frame, query_id, request, persistent,
callback);
}
void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
@ -1334,8 +1403,7 @@ TEST(MessageRouterTest, MultiQueryMultiHandlerCancelByRemovingHandler) {
namespace {
// Map of managers on a per-URL basis.
class MultiQueryManagerMap : public CefMessageRouterBrowserSide::Handler,
public MultiQueryManager::Observer {
class MultiQueryManagerMap : public MultiQueryManager::Observer {
public:
class Observer {
public:
@ -1367,11 +1435,14 @@ class MultiQueryManagerMap : public CefMessageRouterBrowserSide::Handler,
EXPECT_TRUE(observer_set_.erase(observer));
}
MultiQueryManager* CreateManager(const std::string& url, bool synchronous) {
MultiQueryManager* CreateManager(const std::string& url,
bool synchronous,
TransferType transfer_type) {
EXPECT_FALSE(finalized_);
MultiQueryManager* manager = new MultiQueryManager(
url, synchronous, static_cast<int>(manager_map_.size()) * 1000);
url, synchronous, static_cast<int>(manager_map_.size()) * 1000,
transfer_type);
manager->AddObserver(this);
all_managers_.push_back(manager);
pending_managers_.push_back(manager);
@ -1412,25 +1483,26 @@ class MultiQueryManagerMap : public CefMessageRouterBrowserSide::Handler,
manager->OnNotify(browser, frame, message);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
template <class RequestType>
bool OnQueryImpl(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const RequestType& request,
bool persistent,
CefRefPtr<CefMessageRouterBrowserSide::Callback> callback) {
EXPECT_TRUE(finalized_);
if (!running_) {
running_ = true;
}
MultiQueryManager* manager = GetManager(browser, frame);
return manager->OnQuery(browser, frame, query_id, request, persistent,
callback);
return manager->OnQueryImpl(browser, frame, query_id, request, persistent,
callback);
}
void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id) override {
int64_t query_id) {
EXPECT_TRUE(finalized_);
if (!running_) {
running_ = true;
@ -1611,8 +1683,12 @@ class MultiQueryManagerMap : public CefMessageRouterBrowserSide::Handler,
class MultiQueryMultiFrameTestHandler : public SingleLoadTestHandler,
public MultiQueryManagerMap::Observer {
public:
MultiQueryMultiFrameTestHandler(bool synchronous, bool cancel_with_subnav)
: synchronous_(synchronous), cancel_with_subnav_(cancel_with_subnav) {
MultiQueryMultiFrameTestHandler(bool synchronous,
bool cancel_with_subnav,
TransferType transfer_type)
: synchronous_(synchronous),
cancel_with_subnav_(cancel_with_subnav),
transfer_type_(transfer_type) {
manager_map_.AddObserver(this);
}
@ -1657,8 +1733,21 @@ class MultiQueryMultiFrameTestHandler : public SingleLoadTestHandler,
AssertMainBrowser(browser);
EXPECT_FALSE(frame->IsMain());
return manager_map_.OnQuery(browser, frame, query_id, request, persistent,
callback);
return manager_map_.OnQueryImpl(browser, frame, query_id, request,
persistent, callback);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
CefRefPtr<const CefBinaryBuffer> request,
bool persistent,
CefRefPtr<Callback> callback) override {
AssertMainBrowser(browser);
EXPECT_FALSE(frame->IsMain());
return manager_map_.OnQueryImpl(browser, frame, query_id, request,
persistent, callback);
}
void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
@ -1709,7 +1798,8 @@ class MultiQueryMultiFrameTestHandler : public SingleLoadTestHandler,
void AddSubFrameResource(const std::string& name) {
const std::string& url = std::string(kTestDomain1) + name + ".html";
MultiQueryManager* manager = manager_map_.CreateManager(url, synchronous_);
MultiQueryManager* manager =
manager_map_.CreateManager(url, synchronous_, transfer_type_);
MakeTestQueries(manager, false, 100);
const std::string& html = manager->GetHTML(false, false);
@ -1718,6 +1808,7 @@ class MultiQueryMultiFrameTestHandler : public SingleLoadTestHandler,
const bool synchronous_;
const bool cancel_with_subnav_;
const TransferType transfer_type_;
MultiQueryManagerMap manager_map_;
@ -1726,41 +1817,38 @@ class MultiQueryMultiFrameTestHandler : public SingleLoadTestHandler,
} // namespace
#define MQMF_TEST(name, sync, cancel_with_subnav) \
TEST(MessageRouterTest, MultiQueryMultiFrame##name##String) { \
CefRefPtr<MultiQueryMultiFrameTestHandler> handler = \
new MultiQueryMultiFrameTestHandler(sync, cancel_with_subnav, \
TransferType::STRING); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
} \
\
TEST(MessageRouterTest, MultiQueryMultiFrame##name##Binary) { \
CefRefPtr<MultiQueryMultiFrameTestHandler> handler = \
new MultiQueryMultiFrameTestHandler(sync, cancel_with_subnav, \
TransferType::BINARY); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
}
// Test that multiple frames can run many queries successfully in a synchronous
// manner.
TEST(MessageRouterTest, MultiQueryMultiFrameSync) {
CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
new MultiQueryMultiFrameTestHandler(true, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMF_TEST(Sync, true, false)
// Test that multiple frames can run many queries successfully in an
// asynchronous manner.
TEST(MessageRouterTest, MultiQueryMultiFrameAsync) {
CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
new MultiQueryMultiFrameTestHandler(false, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMF_TEST(Async, false, false)
// Test that multiple frames can run many queries successfully in a synchronous
// manner. Cancel auto queries with sub-frame navigation.
TEST(MessageRouterTest, MultiQueryMultiFrameSyncSubnavCancel) {
CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
new MultiQueryMultiFrameTestHandler(true, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMF_TEST(SyncSubnavCancel, true, true)
// Test that multiple frames can run many queries successfully in an
// asynchronous manner. Cancel auto queries with sub-frame navigation.
TEST(MessageRouterTest, MultiQueryMultiFrameAsyncSubnavCancel) {
CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
new MultiQueryMultiFrameTestHandler(false, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMF_TEST(AsyncSubnavCancel, false, true)
namespace {
@ -1772,8 +1860,10 @@ class MultiQueryMultiLoadTestHandler
public MultiQueryManagerMap::Observer,
public MultiQueryManager::Observer {
public:
MultiQueryMultiLoadTestHandler(bool some, bool synchronous)
: some_(some), synchronous_(synchronous) {
MultiQueryMultiLoadTestHandler(bool some,
bool synchronous,
TransferType transfer_type)
: some_(some), synchronous_(synchronous), transfer_type_(transfer_type) {
manager_map_.AddObserver(this);
}
@ -1795,8 +1885,18 @@ class MultiQueryMultiLoadTestHandler
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
return manager_map_.OnQuery(browser, frame, query_id, request, persistent,
callback);
return manager_map_.OnQueryImpl(browser, frame, query_id, request,
persistent, callback);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
CefRefPtr<const CefBinaryBuffer> request,
bool persistent,
CefRefPtr<Callback> callback) override {
return manager_map_.OnQueryImpl(browser, frame, query_id, request,
persistent, callback);
}
void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
@ -1838,7 +1938,8 @@ class MultiQueryMultiLoadTestHandler
void AddManagedResource(const std::string& url,
bool assert_total,
bool assert_browser) {
MultiQueryManager* manager = manager_map_.CreateManager(url, synchronous_);
MultiQueryManager* manager =
manager_map_.CreateManager(url, synchronous_, transfer_type_);
manager->AddObserver(this);
MakeTestQueries(manager, some_, 75);
@ -1860,6 +1961,7 @@ class MultiQueryMultiLoadTestHandler
private:
const bool some_;
const bool synchronous_;
const TransferType transfer_type_;
std::string cancel_url_;
};
@ -1868,8 +1970,10 @@ class MultiQueryMultiLoadTestHandler
class MultiQueryMultiBrowserTestHandler
: public MultiQueryMultiLoadTestHandler {
public:
MultiQueryMultiBrowserTestHandler(bool synchronous, bool same_origin)
: MultiQueryMultiLoadTestHandler(false, synchronous),
MultiQueryMultiBrowserTestHandler(bool synchronous,
bool same_origin,
TransferType transfer_type)
: MultiQueryMultiLoadTestHandler(false, synchronous, transfer_type),
same_origin_(same_origin) {}
protected:
@ -1899,37 +2003,34 @@ class MultiQueryMultiBrowserTestHandler
} // namespace
// Test that multiple browsers can query simultaniously from the same origin.
TEST(MessageRouterTest, MultiQueryMultiBrowserSameOriginSync) {
CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
new MultiQueryMultiBrowserTestHandler(true, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
#define MQMB_TEST(name, sync, same_origin) \
TEST(MessageRouterTest, MultiQueryMultiBrowser##name##String) { \
CefRefPtr<MultiQueryMultiBrowserTestHandler> handler = \
new MultiQueryMultiBrowserTestHandler(sync, same_origin, \
TransferType::STRING); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
} \
\
TEST(MessageRouterTest, MultiQueryMultiBrowser##name##Binary) { \
CefRefPtr<MultiQueryMultiBrowserTestHandler> handler = \
new MultiQueryMultiBrowserTestHandler(sync, same_origin, \
TransferType::BINARY); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
}
// Test that multiple browsers can query simultaniously from the same origin.
TEST(MessageRouterTest, MultiQueryMultiBrowserSameOriginAsync) {
CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
new MultiQueryMultiBrowserTestHandler(false, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMB_TEST(SameOriginSync, true, true)
// Test that multiple browsers can query simultaniously from the same origin.
MQMB_TEST(SameOriginAsync, false, true)
// Test that multiple browsers can query simultaniously from different origins.
TEST(MessageRouterTest, MultiQueryMultiBrowserDifferentOriginSync) {
CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
new MultiQueryMultiBrowserTestHandler(true, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMB_TEST(DifferentOriginSync, true, false)
// Test that multiple browsers can query simultaniously from different origins.
TEST(MessageRouterTest, MultiQueryMultiBrowserDifferentOriginAsync) {
CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
new MultiQueryMultiBrowserTestHandler(false, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMB_TEST(DifferentOriginAsync, false, false)
namespace {
@ -1937,8 +2038,10 @@ namespace {
class MultiQueryMultiNavigateTestHandler
: public MultiQueryMultiLoadTestHandler {
public:
MultiQueryMultiNavigateTestHandler(bool synchronous, bool same_origin)
: MultiQueryMultiLoadTestHandler(false, synchronous),
MultiQueryMultiNavigateTestHandler(bool synchronous,
bool same_origin,
TransferType transfer_type)
: MultiQueryMultiLoadTestHandler(false, synchronous, transfer_type),
same_origin_(same_origin) {}
void OnManualQueriesCompleted(MultiQueryManager* manager) override {
@ -1981,34 +2084,31 @@ class MultiQueryMultiNavigateTestHandler
} // namespace
// Test that multiple navigations can query from the same origin.
TEST(MessageRouterTest, MultiQueryMultiNavigateSameOriginSync) {
CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
new MultiQueryMultiNavigateTestHandler(true, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
#define MQMN_TEST(name, sync, same_origin) \
TEST(MessageRouterTest, MultiQueryMultiNavigate##name##String) { \
CefRefPtr<MultiQueryMultiNavigateTestHandler> handler = \
new MultiQueryMultiNavigateTestHandler(sync, same_origin, \
TransferType::STRING); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
} \
\
TEST(MessageRouterTest, MultiQueryMultiNavigate##name##Binary) { \
CefRefPtr<MultiQueryMultiNavigateTestHandler> handler = \
new MultiQueryMultiNavigateTestHandler(sync, same_origin, \
TransferType::BINARY); \
handler->ExecuteTest(); \
ReleaseAndWaitForDestructor(handler); \
}
// Test that multiple navigations can query from the same origin.
TEST(MessageRouterTest, MultiQueryMultiNavigateSameOriginAsync) {
CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
new MultiQueryMultiNavigateTestHandler(false, true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMN_TEST(SameOriginSync, true, true)
// Test that multiple navigations can query from the same origin.
MQMN_TEST(SameOriginAsync, false, true)
// Test that multiple navigations can query from different origins.
TEST(MessageRouterTest, MultiQueryMultiNavigateDifferentOriginSync) {
CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
new MultiQueryMultiNavigateTestHandler(true, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMN_TEST(DifferentOriginSync, true, false)
// Test that multiple navigations can query from different origins.
TEST(MessageRouterTest, MultiQueryMultiNavigateDifferentOriginAsync) {
CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
new MultiQueryMultiNavigateTestHandler(false, false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
MQMN_TEST(DifferentOriginAsync, false, false)

View File

@ -59,6 +59,7 @@ enum V8TestMode {
V8TEST_ARRAY_CREATE,
V8TEST_ARRAY_VALUE,
V8TEST_ARRAY_BUFFER,
V8TEST_ARRAY_BUFFER_CREATE_EMPTY,
V8TEST_ARRAY_BUFFER_VALUE,
V8TEST_OBJECT_CREATE,
V8TEST_OBJECT_USERDATA,
@ -145,6 +146,9 @@ class V8RendererTest : public ClientAppRenderer::Delegate,
case V8TEST_ARRAY_BUFFER:
RunArrayBufferTest();
break;
case V8TEST_ARRAY_BUFFER_CREATE_EMPTY:
RunArrayBufferCreateEmptyTest();
break;
case V8TEST_ARRAY_BUFFER_VALUE:
RunArrayBufferValueTest();
break;
@ -593,6 +597,9 @@ class V8RendererTest : public ClientAppRenderer::Delegate,
EXPECT_TRUE(value.get());
EXPECT_TRUE(value->IsArrayBuffer());
EXPECT_TRUE(value->IsObject());
EXPECT_EQ(value->GetArrayBufferByteLength(), sizeof(static_data));
void* data = value->GetArrayBufferData();
EXPECT_EQ(static_cast<int*>(data), static_data);
EXPECT_FALSE(value->HasValue(0));
EXPECT_TRUE(value->GetArrayBufferReleaseCallback().get() != nullptr);
EXPECT_TRUE(((TestArrayBufferReleaseCallback*)value
@ -636,6 +643,9 @@ class V8RendererTest : public ClientAppRenderer::Delegate,
static_data[0] = 3;
value =
CefV8Value::CreateArrayBuffer(static_data, sizeof(static_data), owner);
EXPECT_EQ(value->GetArrayBufferByteLength(), sizeof(static_data));
void* data = value->GetArrayBufferData();
EXPECT_EQ(static_cast<int*>(data), static_data);
CefRefPtr<CefV8Value> object = context->GetGlobal();
EXPECT_TRUE(object.get());
@ -649,8 +659,43 @@ class V8RendererTest : public ClientAppRenderer::Delegate,
ADD_FAILURE() << exception->GetMessage().c_str();
}
EXPECT_TRUE(static_data[0] == 19);
EXPECT_TRUE(value->GetArrayBufferReleaseCallback().get() != nullptr);
EXPECT_EQ(static_data[0], 19);
EXPECT_NE(value->GetArrayBufferReleaseCallback().get(), nullptr);
EXPECT_TRUE(value->NeuterArrayBuffer());
// Exit the V8 context.
EXPECT_TRUE(context->Exit());
DestroyTest();
}
void RunArrayBufferCreateEmptyTest() {
class TestArrayBufferReleaseCallback
: public CefV8ArrayBufferReleaseCallback {
public:
void ReleaseBuffer(void* buffer) override {}
IMPLEMENT_REFCOUNTING(TestArrayBufferReleaseCallback);
};
CefRefPtr<TestArrayBufferReleaseCallback> owner =
new TestArrayBufferReleaseCallback();
// Enter the V8 context
CefRefPtr<CefV8Context> context = GetContext();
EXPECT_TRUE(context->Enter());
const size_t zero_size = 0;
void* null_data = nullptr;
CefRefPtr<CefV8Value> value =
CefV8Value::CreateArrayBuffer(null_data, zero_size, owner);
EXPECT_EQ(value->GetArrayBufferByteLength(), zero_size);
EXPECT_EQ(value->GetArrayBufferData(), null_data);
CefRefPtr<CefV8Value> object = context->GetGlobal();
EXPECT_TRUE(object.get());
EXPECT_TRUE(object->SetValue("arr", value, V8_PROPERTY_ATTRIBUTE_NONE));
EXPECT_NE(value->GetArrayBufferReleaseCallback().get(), nullptr);
EXPECT_TRUE(value->NeuterArrayBuffer());
// Exit the V8 context.
@ -3321,6 +3366,7 @@ V8_TEST(EmptyStringCreate, V8TEST_EMPTY_STRING_CREATE)
V8_TEST(ArrayCreate, V8TEST_ARRAY_CREATE)
V8_TEST(ArrayValue, V8TEST_ARRAY_VALUE)
V8_TEST(ArrayBuffer, V8TEST_ARRAY_BUFFER)
V8_TEST(ArrayBufferCreateEmpty, V8TEST_ARRAY_BUFFER_CREATE_EMPTY)
V8_TEST(ArrayBufferValue, V8TEST_ARRAY_BUFFER_VALUE)
V8_TEST(ObjectCreate, V8TEST_OBJECT_CREATE)
V8_TEST(ObjectUserData, V8TEST_OBJECT_USERDATA)

View File

@ -44,12 +44,12 @@ const char* kStringValue = "My string value";
void TestBinary(CefRefPtr<CefBinaryValue> value, char* data, size_t data_size) {
// Testing requires strings longer than 15 characters.
EXPECT_GT(data_size, 15U);
EXPECT_EQ(data_size, value->GetSize());
char* buff = new char[data_size + 1];
char old_char;
// Test direct access.
EXPECT_EQ(memcmp(value->GetRawData(), data, data_size), 0);
char* buff = new char[data_size + 1];
// Test full read.
memset(buff, 0, data_size + 1);
EXPECT_EQ(data_size, value->GetData(buff, data_size, 0));
@ -57,7 +57,7 @@ void TestBinary(CefRefPtr<CefBinaryValue> value, char* data, size_t data_size) {
// Test partial read with offset.
memset(buff, 0, data_size + 1);
old_char = data[15];
char old_char = data[15];
data[15] = 0;
EXPECT_EQ(10U, value->GetData(buff, 10, 5));
EXPECT_TRUE(!strcmp(buff, data + 5));