From 44323082b119d9278ca4d6478e2d16ffeba70e76 Mon Sep 17 00:00:00 2001 From: Nik Pavlov Date: Wed, 25 Oct 2023 18:20:55 +0000 Subject: [PATCH] Add binary format support to CefMessageRouter (fixes #3502) --- cef_paths2.gypi | 7 + include/capi/cef_v8_capi.h | 15 +- include/capi/cef_values_capi.h | 8 +- include/cef_api_hash.h | 8 +- include/cef_v8.h | 16 +- include/cef_values.h | 7 + include/wrapper/cef_message_router.h | 60 +- libcef/common/values_impl.cc | 5 + libcef/common/values_impl.h | 1 + libcef/renderer/v8_impl.cc | 55 ++ libcef/renderer/v8_impl.h | 2 + libcef_dll/cpptoc/binary_value_cpptoc.cc | 22 +- .../cpptoc/shared_memory_region_cpptoc.cc | 5 +- libcef_dll/cpptoc/v8value_cpptoc.cc | 43 +- libcef_dll/ctocpp/binary_value_ctocpp.cc | 20 +- libcef_dll/ctocpp/binary_value_ctocpp.h | 3 +- .../ctocpp/shared_memory_region_ctocpp.cc | 5 +- libcef_dll/ctocpp/v8value_ctocpp.cc | 39 +- libcef_dll/ctocpp/v8value_ctocpp.h | 4 +- libcef_dll/wrapper/cef_message_router.cc | 404 +++++------- .../wrapper/cef_message_router_utils.cc | 525 ++++++++++++++++ libcef_dll/wrapper/cef_message_router_utils.h | 92 +++ .../cefclient/browser/binary_transfer_test.cc | 60 ++ .../cefclient/browser/binary_transfer_test.h | 17 + tests/cefclient/browser/resource.h | 51 +- .../browser/resource_util_win_idmap.cc | 3 +- tests/cefclient/browser/test_runner.cc | 4 + .../cefclient/resources/binary_transfer.html | 498 +++++++++++++++ tests/cefclient/resources/other_tests.html | 1 + tests/cefclient/resources/win/cefclient.rc | 2 +- .../message_router_binary_unittest.cc | 136 +++++ .../message_router_multi_query_unittest.cc | 576 ++++++++++-------- tests/ceftests/v8_unittest.cc | 50 +- tests/ceftests/values_unittest.cc | 8 +- 34 files changed, 2218 insertions(+), 534 deletions(-) create mode 100644 libcef_dll/wrapper/cef_message_router_utils.cc create mode 100644 libcef_dll/wrapper/cef_message_router_utils.h create mode 100644 tests/cefclient/browser/binary_transfer_test.cc create mode 100644 tests/cefclient/browser/binary_transfer_test.h create mode 100644 tests/cefclient/resources/binary_transfer.html create mode 100644 tests/ceftests/message_router_binary_unittest.cc diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 1546177f2..9a5b22779 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -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', diff --git a/include/capi/cef_v8_capi.h b/include/capi/cef_v8_capi.h index 0bb3a0404..98cec92d9 100644 --- a/include/capi/cef_v8_capi.h +++ b/include/capi/cef_v8_capi.h @@ -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. /// diff --git a/include/capi/cef_values_capi.h b/include/capi/cef_values_capi.h index 50abd75f3..05347452a 100644 --- a/include/capi/cef_values_capi.h +++ b/include/capi/cef_values_capi.h @@ -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. /// diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h index f87208425..bf0a19212 100644 --- a/include/cef_api_hash.h +++ b/include/cef_api_hash.h @@ -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 diff --git a/include/cef_v8.h b/include/cef_v8.h index ab3861724..f6a01c1e7 100644 --- a/include/cef_v8.h +++ b/include/cef_v8.h @@ -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 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. /// diff --git a/include/cef_values.h b/include/cef_values.h index 7e57113f4..f7bcb79c7 100644 --- a/include/cef_values.h +++ b/include/cef_values.h @@ -278,6 +278,13 @@ class CefBinaryValue : public virtual CefBaseRefCounted { /*--cef()--*/ virtual CefRefPtr 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. /// diff --git a/include/wrapper/cef_message_router.h b/include/wrapper/cef_message_router.h index ebbf1c2de..6897603fe 100644 --- a/include/wrapper/cef_message_router.h +++ b/include/wrapper/cef_message_router.h @@ -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 browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr callback) { + return false; + } + /// /// Executed when a query has been canceled either explicitly using the /// JavaScript cancel function or implicitly due to browser destruction, diff --git a/libcef/common/values_impl.cc b/libcef/common/values_impl.cc index 98e8216c2..a95d8f563 100644 --- a/libcef/common/values_impl.cc +++ b/libcef/common/values_impl.cc @@ -606,6 +606,11 @@ CefRefPtr 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(); diff --git a/libcef/common/values_impl.h b/libcef/common/values_impl.h index fd0eadbc2..17c91eb62 100644 --- a/libcef/common/values_impl.h +++ b/libcef/common/values_impl.h @@ -177,6 +177,7 @@ class CefBinaryValueImpl : public CefValueBase { bool IsSame(CefRefPtr that) override; bool IsEqual(CefRefPtr that) override; CefRefPtr Copy() override; + const void* GetRawData() override; size_t GetSize() override; size_t GetData(void* buffer, size_t buffer_size, size_t data_offset) override; diff --git a/libcef/renderer/v8_impl.cc b/libcef/renderer/v8_impl.cc index 100adf57f..f610b7391 100644 --- a/libcef/renderer/v8_impl.cc +++ b/libcef/renderer/v8_impl.cc @@ -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 context = isolate->GetCurrentContext(); + if (context.IsEmpty()) { + DCHECK(false) << "not currently in a V8 context"; + return rv; + } + + v8::Local value = handle_->GetNewV8Handle(false); + if (!value->IsArrayBuffer()) { + DCHECK(false) << "V8 value is not an array buffer"; + return rv; + } + + v8::Local obj = value->ToObject(context).ToLocalChecked(); + return v8::Local::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 context = isolate->GetCurrentContext(); + if (context.IsEmpty()) { + DCHECK(false) << "not currently in a V8 context"; + return rv; + } + + v8::Local value = handle_->GetNewV8Handle(false); + if (!value->IsArrayBuffer()) { + DCHECK(false) << "V8 value is not an array buffer"; + return rv; + } + + v8::Local obj = value->ToObject(context).ToLocalChecked(); + v8::Local arr = v8::Local::Cast(obj); + return arr->Data(); +} + CefString CefV8ValueImpl::GetFunctionName() { CefString rv; CEF_V8_REQUIRE_OBJECT_RETURN(rv); diff --git a/libcef/renderer/v8_impl.h b/libcef/renderer/v8_impl.h index ef5177b9c..11ff77aaf 100644 --- a/libcef/renderer/v8_impl.h +++ b/libcef/renderer/v8_impl.h @@ -274,6 +274,8 @@ class CefV8ValueImpl : public CefV8Value { CefRefPtr GetArrayBufferReleaseCallback() override; bool NeuterArrayBuffer() override; + size_t GetArrayBufferByteLength() override; + void* GetArrayBufferData() override; CefString GetFunctionName() override; CefRefPtr GetFunctionHandler() override; CefRefPtr ExecuteFunction( diff --git a/libcef_dll/cpptoc/binary_value_cpptoc.cc b/libcef_dll/cpptoc/binary_value_cpptoc.cc index 6f0827c36..99273f1ea 100644 --- a/libcef_dll/cpptoc/binary_value_cpptoc.cc +++ b/libcef_dll/cpptoc/binary_value_cpptoc.cc @@ -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; } diff --git a/libcef_dll/cpptoc/shared_memory_region_cpptoc.cc b/libcef_dll/cpptoc/shared_memory_region_cpptoc.cc index bebea8dd3..c3165d7bc 100644 --- a/libcef_dll/cpptoc/shared_memory_region_cpptoc.cc +++ b/libcef_dll/cpptoc/shared_memory_region_cpptoc.cc @@ -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(); diff --git a/libcef_dll/cpptoc/v8value_cpptoc.cc b/libcef_dll/cpptoc/v8value_cpptoc.cc index 8031a7165..dcac030a4 100644 --- a/libcef_dll/cpptoc/v8value_cpptoc.cc +++ b/libcef_dll/cpptoc/v8value_cpptoc.cc @@ -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 _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; diff --git a/libcef_dll/ctocpp/binary_value_ctocpp.cc b/libcef_dll/ctocpp/binary_value_ctocpp.cc index d60844ab4..3dd155392 100644 --- a/libcef_dll/ctocpp/binary_value_ctocpp.cc +++ b/libcef_dll/ctocpp/binary_value_ctocpp.cc @@ -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 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(); diff --git a/libcef_dll/ctocpp/binary_value_ctocpp.h b/libcef_dll/ctocpp/binary_value_ctocpp.h index 414f70260..fa1902b12 100644 --- a/libcef_dll/ctocpp/binary_value_ctocpp.h +++ b/libcef_dll/ctocpp/binary_value_ctocpp.h @@ -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 that) override; bool IsEqual(CefRefPtr that) override; CefRefPtr Copy() override; + const void* GetRawData() override; size_t GetSize() override; size_t GetData(void* buffer, size_t buffer_size, size_t data_offset) override; }; diff --git a/libcef_dll/ctocpp/shared_memory_region_ctocpp.cc b/libcef_dll/ctocpp/shared_memory_region_ctocpp.cc index 691e272f4..e9000f015 100644 --- a/libcef_dll/ctocpp/shared_memory_region_ctocpp.cc +++ b/libcef_dll/ctocpp/shared_memory_region_ctocpp.cc @@ -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); diff --git a/libcef_dll/ctocpp/v8value_ctocpp.cc b/libcef_dll/ctocpp/v8value_ctocpp.cc index f8bf53fa4..5474dc405 100644 --- a/libcef_dll/ctocpp/v8value_ctocpp.cc +++ b/libcef_dll/ctocpp/v8value_ctocpp.cc @@ -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::CreateArrayBuffer( CefRefPtr 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)) { diff --git a/libcef_dll/ctocpp/v8value_ctocpp.h b/libcef_dll/ctocpp/v8value_ctocpp.h index 6cdb1da75..dd0fb6efd 100644 --- a/libcef_dll/ctocpp/v8value_ctocpp.h +++ b/libcef_dll/ctocpp/v8value_ctocpp.h @@ -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 GetArrayBufferReleaseCallback() override; bool NeuterArrayBuffer() override; + size_t GetArrayBufferByteLength() override; + void* GetArrayBufferData() override; CefString GetFunctionName() override; CefRefPtr GetFunctionHandler() override; CefRefPtr ExecuteFunction( diff --git a/libcef_dll/wrapper/cef_message_router.cc b/libcef_dll/wrapper/cef_message_router.cc index 80da8d2cf..787fcf47f 100644 --- a/libcef_dll/wrapper/cef_message_router.cc +++ b/libcef_dll/wrapper/cef_message_router.cc @@ -9,11 +9,11 @@ #include #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(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( - static_cast(memory) + sizeof(MessageHeader)); - constexpr bool copy = true; - CefString result; - result.FromString(src, string_len, copy); - return result; -} - -CefRefPtr BuildListMessage(const std::string& message_name, - int context_id, - int request_id, - const CefString& response) { - auto message = CefProcessMessage::Create(message_name); - CefRefPtr 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 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(builder->Memory()); - header->context_id = context_id; - header->request_id = request_id; - header->is_success = true; - - CopyResponseIntoMemory(builder->Memory(), response); - - return builder->Build(); -} - -CefRefPtr 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& 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(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 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& 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 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 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 callback( - new CallbackImpl(this, browser_id, query_id, persistent)); + CefRefPtr 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& 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 browser, - CefRefPtr 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 browser, + CefRefPtr frame, + int context_id, + int request_id, + const CefRefPtr& builder) { + if (auto message = builder->Build(context_id, request_id)) { frame->SendProcessMessage(PID_RENDERER, message); } } @@ -758,10 +653,16 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide { CefRefPtr arg = arguments[0]; CefRefPtr 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 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(content.payload)); + } + + return true; } private: @@ -1039,7 +939,7 @@ class CefMessageRouterRendererSideImpl : public CefMessageRouterRendererSide { int SendQuery(CefRefPtr browser, CefRefPtr frame, int context_id, - const CefString& request, + CefRefPtr request, bool persistent, CefRefPtr success_callback, CefRefPtr 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 message = - CefProcessMessage::Create(query_message_name_); - - CefRefPtr args = message->GetArgumentList(); - args->SetInt(0, context_id); - args->SetInt(1, request_id); - args->SetString(2, request); - args->SetBool(3, persistent); + CefRefPtr 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& response) { + CEF_REQUIRE_RENDERER_THREAD(); + + bool removed; + RequestInfo* info = + GetRequestInfo(browser_id, context_id, request_id, false, &removed); + if (!info) { + return; + } + + CefRefPtr context = GetContextByID(context_id); + if (context && info->success_callback && context->Enter()) { + CefRefPtr release_callback = + new cmru::BinaryValueABRCallback(response); + + CefRefPtr 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, diff --git a/libcef_dll/wrapper/cef_message_router_utils.cc b/libcef_dll/wrapper/cef_message_router_utils.cc new file mode 100644 index 000000000..9fee2a0bc --- /dev/null +++ b/libcef_dll/wrapper/cef_message_router_utils.cc @@ -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 + +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, + "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, + "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& value) { + return value->GetArrayBufferByteLength(); +} + +const CefString& GetListRepresentation(const CefString& value) { + return value; +} + +CefRefPtr GetListRepresentation( + const CefRefPtr& value) { + return CefBinaryValue::Create(value->GetArrayBufferData(), + value->GetArrayBufferByteLength()); +} + +template +size_t GetMessageSize(const T& value) { + return sizeof(Header) + GetByteLength(value); +} + +template +void CopyIntoMemory(void* memory, const void* data, size_t bytes) { + if (bytes > 0) { + void* dest = static_cast(memory) + sizeof(Header); + memcpy(dest, data, bytes); + } +} + +template +void CopyIntoMemory(void* memory, const CefRefPtr& value) { + CopyIntoMemory
(memory, value->GetArrayBufferData(), + value->GetArrayBufferByteLength()); +} + +template +void CopyIntoMemory(void* memory, const CefString& value) { + const size_t bytes = GetByteLength(value); + CopyIntoMemory
(memory, value.c_str(), bytes); +} + +template +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(memory) + sizeof(Header); + const CefString::char_type* src = + static_cast(string_data); + CefString result; + result.FromString(src, string_len, /*copy=*/true); + return result; +} + +template +constexpr bool IsCefString() { + return std::is_same_v, CefString>; +} + +template +constexpr bool IsEmpty() { + return std::is_same_v, Empty>; +} + +template +CefRefPtr BuildBrowserListMsg(const CefString& name, + int context_id, + int request_id, + const ResponseType& response) { + auto message = CefProcessMessage::Create(name); + CefRefPtr args = message->GetArgumentList(); + args->SetInt(kContextId, context_id); + args->SetInt(kRequestId, request_id); + args->SetBool(kIsSuccess, true); + + if constexpr (IsCefString()) { + args->SetString(kBrowserPayload, response); + } else if constexpr (IsEmpty()) { + 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 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 Build(int context_id, int request_id) override { + return BuildBrowserListMsg(name_, context_id, request_id, value_); + } + + private: + const CefString name_; + const CefRefPtr 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 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 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(builder->Memory(), data, size); + return new SPMResponseBuilder(builder, /*is_binary=*/true); + } + + static CefRefPtr Create(const std::string& name, + const CefString& value) { + const size_t message_size = GetMessageSize(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(builder->Memory(), value); + return new SPMResponseBuilder(builder, /*is_binary=*/false); + } + + CefRefPtr Build(int context_id, int request_id) override { + auto header = static_cast(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& builder, + bool is_binary) + : builder_(builder), is_binary_(is_binary) {} + + CefRefPtr 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 message, + CefRefPtr 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 + return const_cast(value_->GetRawData()); + } + size_t GetSize() const override { return value_->GetSize(); } + + private: + const CefRefPtr message_; + const CefRefPtr value_; + + IMPLEMENT_REFCOUNTING(BinaryValueBuffer); +}; + +class SharedMemoryRegionBuffer final : public CefBinaryBuffer { + public: + SharedMemoryRegionBuffer(const CefRefPtr& region, + size_t offset) + : region_(region), + data_(static_cast(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 region_; + void* const data_; + const size_t size_; + + IMPLEMENT_REFCOUNTING(SharedMemoryRegionBuffer); +}; + +template +CefRefPtr BuildRendererListMsg(const std::string& name, + int context_id, + int request_id, + const RequestType& request, + bool persistent) { + auto message = CefProcessMessage::Create(name); + CefRefPtr args = message->GetArgumentList(); + args->SetInt(kContextId, context_id); + args->SetInt(kRequestId, request_id); + + if constexpr (IsCefString()) { + args->SetString(kRendererPayload, request); + } else if constexpr (IsEmpty()) { + args->SetNull(kRendererPayload); + } else { + args->SetBinary(kRendererPayload, request); + } + + args->SetBool(kIsPersistent, persistent); + return message; +} + +template +CefRefPtr BuildRendererSharedMsg(const std::string& name, + int context_id, + int request_id, + const RequestType& request, + bool persistent) { + const size_t message_size = GetMessageSize(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(builder->Memory()); + header->context_id = context_id; + header->request_id = request_id; + header->is_persistent = persistent; + header->is_binary = !IsCefString(); + + CopyIntoMemory(builder->Memory(), request); + + return builder->Build(); +} + +CefRefPtr 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 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 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 BuildRendererMsg( + size_t threshold, + const std::string& name, + int context_id, + int request_id, + const CefRefPtr& 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& 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(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(region->Memory(), + region->Size())}; + } + + NOTREACHED(); + return {}; +} + +RendererMessage ParseRendererMessage( + const CefRefPtr& 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(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(region->Memory(), + region->Size()), + }; + } + + NOTREACHED(); + return {}; +} + +} // namespace cef_message_router_utils diff --git a/libcef_dll/wrapper/cef_message_router_utils.h b/libcef_dll/wrapper/cef_message_router_utils.h new file mode 100644 index 000000000..b12a98277 --- /dev/null +++ b/libcef_dll/wrapper/cef_message_router_utils.h @@ -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 + +#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 Build(int context_id, + int request_id) = 0; +}; + +struct BrowserMessage { + int context_id; + int request_id; + bool is_success; + int error_code; + std::variant> payload; +}; + +struct RendererMessage { + int context_id; + int request_id; + bool is_persistent; + std::variant> payload; +}; + +class BinaryValueABRCallback final : public CefV8ArrayBufferReleaseCallback { + public: + explicit BinaryValueABRCallback(CefRefPtr value) + : value_(std::move(value)) {} + BinaryValueABRCallback(const BinaryValueABRCallback&) = delete; + BinaryValueABRCallback& operator=(const BinaryValueABRCallback&) = delete; + + void ReleaseBuffer(void* buffer) override {} + + private: + const CefRefPtr value_; + + IMPLEMENT_REFCOUNTING(BinaryValueABRCallback); +}; + +CefRefPtr CreateBrowserResponseBuilder( + size_t threshold, + const std::string& name, + const CefString& response); + +CefRefPtr CreateBrowserResponseBuilder( + size_t threshold, + const std::string& name, + const void* data, + size_t size); + +CefRefPtr BuildRendererMsg( + size_t threshold, + const std::string& name, + int context_id, + int request_id, + const CefRefPtr& request, + bool persistent); + +BrowserMessage ParseBrowserMessage(const CefRefPtr& message); + +RendererMessage ParseRendererMessage( + const CefRefPtr& message); + +} // namespace cef_message_router_utils + +#endif // CEF_LIBCEF_DLL_WRAPPER_CEF_MESSAGE_ROUTER_UTILS_H_ diff --git a/tests/cefclient/browser/binary_transfer_test.cc b/tests/cefclient/browser/binary_transfer_test.cc new file mode 100644 index 000000000..6eb701846 --- /dev/null +++ b/tests/cefclient/browser/binary_transfer_test.cc @@ -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 +#include + +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 browser, + CefRefPtr frame, + int64_t query_id, + const CefString& request, + bool persistent, + CefRefPtr 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 browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr 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 diff --git a/tests/cefclient/browser/binary_transfer_test.h b/tests/cefclient/browser/binary_transfer_test.h new file mode 100644 index 000000000..6690c9fd1 --- /dev/null +++ b/tests/cefclient/browser/binary_transfer_test.h @@ -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_ diff --git a/tests/cefclient/browser/resource.h b/tests/cefclient/browser/resource.h index b3b5102f3..b32f3e38e 100644 --- a/tests/cefclient/browser/resource.h +++ b/tests/cefclient/browser/resource.h @@ -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 diff --git a/tests/cefclient/browser/resource_util_win_idmap.cc b/tests/cefclient/browser/resource_util_win_idmap.cc index 086932c15..17bb8740a 100644 --- a/tests/cefclient/browser/resource_util_win_idmap.cc +++ b/tests/cefclient/browser/resource_util_win_idmap.cc @@ -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", diff --git a/tests/cefclient/browser/test_runner.cc b/tests/cefclient/browser/test_runner.cc index f63fcf78e..fe0becfbe 100644 --- a/tests/cefclient/browser/test_runner.cc +++ b/tests/cefclient/browser/test_runner.cc @@ -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); diff --git a/tests/cefclient/resources/binary_transfer.html b/tests/cefclient/resources/binary_transfer.html new file mode 100644 index 000000000..947769e99 --- /dev/null +++ b/tests/cefclient/resources/binary_transfer.html @@ -0,0 +1,498 @@ + + + + + Binary vs String Transfer Benchmark + + + + + +

Binary vs String Transfer Benchmark

+ + + + + + + + +
+

+ This benchmark evaluates the message transfer speed between the + renderer process and the browser process.
It compares the + performance of binary and string message transfer. +

+

+ Note: There is no progress indication of the tests because it + significantly influences measurements.
It usually takes 30 + seconds (for 300 samples) to complete the tests. +

+
+ Samples: + + +
+ +
+ + + + + + + + + + + + + + + + + +
Message Size + String Round Trip Avg, ms + + Binary Round Trip Avg, ms + Relative Trip DifferenceString Speed, MB/sBinary Speed, MB/sRelative Speed DifferenceString Standard DeviationBinary Standard Deviation
+
+ +
+ +
+ +
+ +
+ + + + diff --git a/tests/cefclient/resources/other_tests.html b/tests/cefclient/resources/other_tests.html index 87288f46a..7c094c236 100644 --- a/tests/cefclient/resources/other_tests.html +++ b/tests/cefclient/resources/other_tests.html @@ -9,6 +9,7 @@
  • Accelerated Layers
  • Authentication (Basic) - credentials returned via GetAuthCredentials
  • Authentication (Digest) - credentials returned via GetAuthCredentials
  • +
  • Binary vs String Transfer Benchmark
  • Cursors
  • Dialogs
  • Drag & Drop
  • diff --git a/tests/cefclient/resources/win/cefclient.rc b/tests/cefclient/resources/win/cefclient.rc index 55a54d021..edd7e1b01 100644 --- a/tests/cefclient/resources/win/cefclient.rc +++ b/tests/cefclient/resources/win/cefclient.rc @@ -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 - diff --git a/tests/ceftests/message_router_binary_unittest.cc b/tests/ceftests/message_router_binary_unittest.cc new file mode 100644 index 000000000..bcd4f0d49 --- /dev/null +++ b/tests/ceftests/message_router_binary_unittest.cc @@ -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 ""; + } + + void OnNotify(CefRefPtr browser, + CefRefPtr 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 browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr 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; + +} // 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); +} diff --git a/tests/ceftests/message_router_multi_query_unittest.cc b/tests/ceftests/message_router_multi_query_unittest.cc index e9c1f10cb..01579cc1c 100644 --- a/tests/ceftests/message_router_multi_query_unittest.cc +++ b/tests/ceftests/message_router_multi_query_unittest.cc @@ -19,8 +19,18 @@ const char kMultiQueryError[] = "error"; const char kMultiQueryErrorMessage[] = "errormsg"; const int kMultiQueryPersistentResponseCount = 5; +template +constexpr bool IsCefString() { + return std::is_same_v, 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 browser, - CefRefPtr frame, - int64_t query_id, - const CefString& request, - bool persistent, - CefRefPtr callback) override { + template + bool OnQueryImpl(CefRefPtr browser, + CefRefPtr frame, + int64_t query_id, + const RequestType& request, + bool persistent, + CefRefPtr 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()) { + 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()) { + 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 browser, CefRefPtr 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; + CefRefPtr 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 GetIDBinary(const std::string& prefix, int index) const { + auto str = GetIDString(prefix, index); + return std::vector(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& request, + std::string* value, + int* index) const { + const size_t string_len = + request->GetSize() / sizeof(std::string::value_type); + const auto* src = + static_cast(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 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 browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr callback) override { + AssertMainBrowser(browser); + AssertMainFrame(frame); + + return manager_.OnQueryImpl(browser, frame, query_id, request, persistent, + callback); } void OnQueryCanceled(CefRefPtr 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 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 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 handler = \ + new MultiQuerySingleFrameTestHandler(synchronous, \ + TransferType::STRING); \ + MakeTestQueries(handler->GetManager(), some); \ + handler->ExecuteTest(); \ + ReleaseAndWaitForDestructor(handler); \ + } \ + \ + TEST(MessageRouterTest, MultiQuerySingleFrame##name##Binary) { \ + CefRefPtr 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 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 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 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 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 handler = \ + new MultiQuerySingleFrameTestHandler( \ + false, TransferType::STRING, \ + MultiQuerySingleFrameTestHandler::cancelType); \ + MakeTestQueries(handler->GetManager(), false); \ + handler->ExecuteTest(); \ + ReleaseAndWaitForDestructor(handler); \ + } \ + \ + TEST(MessageRouterTest, MultiQuerySingleFrame##name##Binary) { \ + CefRefPtr 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 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 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 browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr callback) override { + AssertMainBrowser(browser); + AssertMainFrame(frame); + + return manager_.OnQueryImpl(browser, frame, query_id, request, persistent, + callback); } void OnQueryCanceled(CefRefPtr 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(manager_map_.size()) * 1000); + url, synchronous, static_cast(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 browser, - CefRefPtr frame, - int64_t query_id, - const CefString& request, - bool persistent, - CefRefPtr callback) override { + template + bool OnQueryImpl(CefRefPtr browser, + CefRefPtr frame, + int64_t query_id, + const RequestType& request, + bool persistent, + CefRefPtr 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 browser, CefRefPtr 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 browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr callback) override { + AssertMainBrowser(browser); + EXPECT_FALSE(frame->IsMain()); + + return manager_map_.OnQueryImpl(browser, frame, query_id, request, + persistent, callback); } void OnQueryCanceled(CefRefPtr 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 handler = \ + new MultiQueryMultiFrameTestHandler(sync, cancel_with_subnav, \ + TransferType::STRING); \ + handler->ExecuteTest(); \ + ReleaseAndWaitForDestructor(handler); \ + } \ + \ + TEST(MessageRouterTest, MultiQueryMultiFrame##name##Binary) { \ + CefRefPtr 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 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 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 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 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) 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 browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr callback) override { + return manager_map_.OnQueryImpl(browser, frame, query_id, request, + persistent, callback); } void OnQueryCanceled(CefRefPtr 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 handler = - new MultiQueryMultiBrowserTestHandler(true, true); - handler->ExecuteTest(); - ReleaseAndWaitForDestructor(handler); -} +#define MQMB_TEST(name, sync, same_origin) \ + TEST(MessageRouterTest, MultiQueryMultiBrowser##name##String) { \ + CefRefPtr handler = \ + new MultiQueryMultiBrowserTestHandler(sync, same_origin, \ + TransferType::STRING); \ + handler->ExecuteTest(); \ + ReleaseAndWaitForDestructor(handler); \ + } \ + \ + TEST(MessageRouterTest, MultiQueryMultiBrowser##name##Binary) { \ + CefRefPtr 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 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 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 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 handler = - new MultiQueryMultiNavigateTestHandler(true, true); - handler->ExecuteTest(); - ReleaseAndWaitForDestructor(handler); -} +#define MQMN_TEST(name, sync, same_origin) \ + TEST(MessageRouterTest, MultiQueryMultiNavigate##name##String) { \ + CefRefPtr handler = \ + new MultiQueryMultiNavigateTestHandler(sync, same_origin, \ + TransferType::STRING); \ + handler->ExecuteTest(); \ + ReleaseAndWaitForDestructor(handler); \ + } \ + \ + TEST(MessageRouterTest, MultiQueryMultiNavigate##name##Binary) { \ + CefRefPtr 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 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 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 handler = - new MultiQueryMultiNavigateTestHandler(false, false); - handler->ExecuteTest(); - ReleaseAndWaitForDestructor(handler); -} +MQMN_TEST(DifferentOriginAsync, false, false) diff --git a/tests/ceftests/v8_unittest.cc b/tests/ceftests/v8_unittest.cc index a84d55a98..afe55f7d1 100644 --- a/tests/ceftests/v8_unittest.cc +++ b/tests/ceftests/v8_unittest.cc @@ -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(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(data), static_data); CefRefPtr 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 owner = + new TestArrayBufferReleaseCallback(); + + // Enter the V8 context + CefRefPtr context = GetContext(); + EXPECT_TRUE(context->Enter()); + + const size_t zero_size = 0; + void* null_data = nullptr; + + CefRefPtr value = + CefV8Value::CreateArrayBuffer(null_data, zero_size, owner); + EXPECT_EQ(value->GetArrayBufferByteLength(), zero_size); + EXPECT_EQ(value->GetArrayBufferData(), null_data); + + CefRefPtr 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) diff --git a/tests/ceftests/values_unittest.cc b/tests/ceftests/values_unittest.cc index fa91ad989..565674e1b 100644 --- a/tests/ceftests/values_unittest.cc +++ b/tests/ceftests/values_unittest.cc @@ -44,12 +44,12 @@ const char* kStringValue = "My string value"; void TestBinary(CefRefPtr 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 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));