mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Allow embedders to post Dart objects on send ports from the native side. (#14477)
This is a non-breaking addition to the stable Flutter Embedder API and exposes a subset of the functionality provided by Dart_PostCObject API in a stable and tested manner to custom embedder implementations. Send port acquisition can currently be done as described in the unit-test but there may be opportunities to extend this API in the future to access ports more easily or create ports from the native side. The following capabilities of the the Dart_PostCObject API are explicitly NOT exposed: * Object arrays: This allows callers to create complex object graphs but only using the primitives specified in the native API. I could find no current use case for this and would have made the implementation a lot more complex. This is something we can add in the future if necessary however. * Capabilities and ports: Again no use cases and I honestly I didn’t understand how to use capabilities. If needed, these can be added at a later point by appending to the union. Fixes https://github.com/flutter/flutter/issues/46624 Fixes b/145982720
This commit is contained in:
parent
2026b83d74
commit
f05832153b
@ -31,6 +31,8 @@ using closure = std::function<void()>;
|
||||
///
|
||||
class ScopedCleanupClosure {
|
||||
public:
|
||||
ScopedCleanupClosure() = default;
|
||||
|
||||
ScopedCleanupClosure(const fml::closure& closure) : closure_(closure) {}
|
||||
|
||||
~ScopedCleanupClosure() {
|
||||
@ -39,6 +41,12 @@ class ScopedCleanupClosure {
|
||||
}
|
||||
}
|
||||
|
||||
fml::closure SetClosure(const fml::closure& closure) {
|
||||
auto old_closure = closure_;
|
||||
closure_ = closure;
|
||||
return old_closure;
|
||||
}
|
||||
|
||||
fml::closure Release() {
|
||||
fml::closure closure = closure_;
|
||||
closure_ = nullptr;
|
||||
|
||||
@ -1050,67 +1050,6 @@ TEST_F(ShellTest, Screenshot) {
|
||||
DestroyShell(std::move(shell));
|
||||
}
|
||||
|
||||
enum class MemsetPatternOp {
|
||||
kMemsetPatternOpSetBuffer,
|
||||
kMemsetPatternOpCheckBuffer,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief Depending on the operation, either scribbles a known pattern
|
||||
/// into the buffer or checks if that pattern is present in an
|
||||
/// existing buffer. This is a portable variant of the
|
||||
/// memset_pattern class of methods that also happen to do assert
|
||||
/// that the same pattern exists.
|
||||
///
|
||||
/// @param buffer The buffer
|
||||
/// @param[in] size The size
|
||||
/// @param[in] op The operation
|
||||
///
|
||||
/// @return If the result of the operation was a success.
|
||||
///
|
||||
static bool MemsetPatternSetOrCheck(uint8_t* buffer,
|
||||
size_t size,
|
||||
MemsetPatternOp op) {
|
||||
if (buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pattern = reinterpret_cast<const uint8_t*>("dErP");
|
||||
constexpr auto pattern_length = 4;
|
||||
|
||||
uint8_t* start = buffer;
|
||||
uint8_t* p = buffer;
|
||||
|
||||
while ((start + size) - p >= pattern_length) {
|
||||
switch (op) {
|
||||
case MemsetPatternOp::kMemsetPatternOpSetBuffer:
|
||||
memmove(p, pattern, pattern_length);
|
||||
break;
|
||||
case MemsetPatternOp::kMemsetPatternOpCheckBuffer:
|
||||
if (memcmp(pattern, p, pattern_length) != 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
};
|
||||
p += pattern_length;
|
||||
}
|
||||
|
||||
if ((start + size) - p != 0) {
|
||||
switch (op) {
|
||||
case MemsetPatternOp::kMemsetPatternOpSetBuffer:
|
||||
memmove(p, pattern, (start + size) - p);
|
||||
break;
|
||||
case MemsetPatternOp::kMemsetPatternOpCheckBuffer:
|
||||
if (memcmp(pattern, p, (start + size) - p) != 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_F(ShellTest, CanConvertToAndFromMappings) {
|
||||
const size_t buffer_size = 2 << 20;
|
||||
|
||||
|
||||
@ -8,8 +8,10 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "flutter/fml/build_config.h"
|
||||
#include "flutter/fml/closure.h"
|
||||
#include "flutter/fml/make_copyable.h"
|
||||
#include "flutter/fml/native_library.h"
|
||||
#include "third_party/dart/runtime/include/dart_native_api.h"
|
||||
|
||||
#if OS_WIN
|
||||
#define FLUTTER_EXPORT __declspec(dllexport)
|
||||
@ -1583,3 +1585,128 @@ FlutterEngineResult FlutterEngineUpdateLocales(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||
bool FlutterEngineRunsAOTCompiledDartCode(void) {
|
||||
return flutter::DartVM::IsRunningPrecompiledCode();
|
||||
}
|
||||
|
||||
FlutterEngineResult FlutterEnginePostDartObject(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
FlutterEngineDartPort port,
|
||||
const FlutterEngineDartObject* object) {
|
||||
if (engine == nullptr) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle.");
|
||||
}
|
||||
|
||||
if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)->IsValid()) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine not running.");
|
||||
}
|
||||
|
||||
if (port == ILLEGAL_PORT) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments,
|
||||
"Attempted to post to an illegal port.");
|
||||
}
|
||||
|
||||
if (object == nullptr) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments,
|
||||
"Invalid Dart object to post.");
|
||||
}
|
||||
|
||||
Dart_CObject dart_object = {};
|
||||
fml::ScopedCleanupClosure typed_data_finalizer;
|
||||
|
||||
switch (object->type) {
|
||||
case kFlutterEngineDartObjectTypeNull:
|
||||
dart_object.type = Dart_CObject_kNull;
|
||||
break;
|
||||
case kFlutterEngineDartObjectTypeBool:
|
||||
dart_object.type = Dart_CObject_kBool;
|
||||
dart_object.value.as_bool = object->bool_value;
|
||||
break;
|
||||
case kFlutterEngineDartObjectTypeInt32:
|
||||
dart_object.type = Dart_CObject_kInt32;
|
||||
dart_object.value.as_int32 = object->int32_value;
|
||||
break;
|
||||
case kFlutterEngineDartObjectTypeInt64:
|
||||
dart_object.type = Dart_CObject_kInt64;
|
||||
dart_object.value.as_int64 = object->int64_value;
|
||||
break;
|
||||
case kFlutterEngineDartObjectTypeDouble:
|
||||
dart_object.type = Dart_CObject_kDouble;
|
||||
dart_object.value.as_double = object->double_value;
|
||||
break;
|
||||
case kFlutterEngineDartObjectTypeString:
|
||||
if (object->string_value == nullptr) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments,
|
||||
"kFlutterEngineDartObjectTypeString must be "
|
||||
"a null terminated string but was null.");
|
||||
}
|
||||
dart_object.type = Dart_CObject_kString;
|
||||
dart_object.value.as_string = const_cast<char*>(object->string_value);
|
||||
break;
|
||||
case kFlutterEngineDartObjectTypeBuffer: {
|
||||
auto* buffer = SAFE_ACCESS(object->buffer_value, buffer, nullptr);
|
||||
if (buffer == nullptr) {
|
||||
return LOG_EMBEDDER_ERROR(kInvalidArguments,
|
||||
"kFlutterEngineDartObjectTypeBuffer must "
|
||||
"specify a buffer but found nullptr.");
|
||||
}
|
||||
auto buffer_size = SAFE_ACCESS(object->buffer_value, buffer_size, 0);
|
||||
auto callback =
|
||||
SAFE_ACCESS(object->buffer_value, buffer_collect_callback, nullptr);
|
||||
auto user_data = SAFE_ACCESS(object->buffer_value, user_data, nullptr);
|
||||
|
||||
// The the user has provided a callback, let them manage the lifecycle of
|
||||
// the underlying data. If not, copy it out from the provided buffer.
|
||||
|
||||
if (callback == nullptr) {
|
||||
dart_object.type = Dart_CObject_kTypedData;
|
||||
dart_object.value.as_typed_data.type = Dart_TypedData_kUint8;
|
||||
dart_object.value.as_typed_data.length = buffer_size;
|
||||
dart_object.value.as_typed_data.values = buffer;
|
||||
} else {
|
||||
struct ExternalTypedDataPeer {
|
||||
void* user_data = nullptr;
|
||||
VoidCallback trampoline = nullptr;
|
||||
};
|
||||
auto peer = new ExternalTypedDataPeer();
|
||||
peer->user_data = user_data;
|
||||
peer->trampoline = callback;
|
||||
// This finalizer is set so that in case of failure of the
|
||||
// Dart_PostCObject below, we collect the peer. The embedder is still
|
||||
// responsible for collecting the buffer in case of non-kSuccess returns
|
||||
// from this method. This finalizer must be released in case of kSuccess
|
||||
// returns from this method.
|
||||
typed_data_finalizer.SetClosure([peer]() {
|
||||
// This is the tiny object we use as the peer to the Dart call so that
|
||||
// we can attach the a trampoline to the embedder supplied callback.
|
||||
// In case of error, we need to collect this object lest we introduce
|
||||
// a tiny leak.
|
||||
delete peer;
|
||||
});
|
||||
dart_object.type = Dart_CObject_kExternalTypedData;
|
||||
dart_object.value.as_external_typed_data.type = Dart_TypedData_kUint8;
|
||||
dart_object.value.as_external_typed_data.length = buffer_size;
|
||||
dart_object.value.as_external_typed_data.data = buffer;
|
||||
dart_object.value.as_external_typed_data.peer = peer;
|
||||
dart_object.value.as_external_typed_data.callback =
|
||||
+[](void* unused_isolate_callback_data,
|
||||
Dart_WeakPersistentHandle unused_handle, void* peer) {
|
||||
auto typed_peer = reinterpret_cast<ExternalTypedDataPeer*>(peer);
|
||||
typed_peer->trampoline(typed_peer->user_data);
|
||||
delete typed_peer;
|
||||
};
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
return LOG_EMBEDDER_ERROR(
|
||||
kInvalidArguments,
|
||||
"Invalid FlutterEngineDartObjectType type specified.");
|
||||
}
|
||||
|
||||
if (!Dart_PostCObject(port, &dart_object)) {
|
||||
return LOG_EMBEDDER_ERROR(kInternalInconsistency,
|
||||
"Could not post the object to the Dart VM.");
|
||||
}
|
||||
|
||||
// On a successful call, the VM takes ownership of and is responsible for
|
||||
// invoking the finalizer.
|
||||
typed_data_finalizer.Release();
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
@ -861,6 +861,79 @@ typedef struct {
|
||||
const char* variant_code;
|
||||
} FlutterLocale;
|
||||
|
||||
typedef int64_t FlutterEngineDartPort;
|
||||
|
||||
typedef enum {
|
||||
kFlutterEngineDartObjectTypeNull,
|
||||
kFlutterEngineDartObjectTypeBool,
|
||||
kFlutterEngineDartObjectTypeInt32,
|
||||
kFlutterEngineDartObjectTypeInt64,
|
||||
kFlutterEngineDartObjectTypeDouble,
|
||||
kFlutterEngineDartObjectTypeString,
|
||||
/// The object will be made available to Dart code as an instance of
|
||||
/// Uint8List.
|
||||
kFlutterEngineDartObjectTypeBuffer,
|
||||
} FlutterEngineDartObjectType;
|
||||
|
||||
typedef struct {
|
||||
/// The size of this struct. Must be sizeof(FlutterEngineDartBuffer).
|
||||
size_t struct_size;
|
||||
/// An opaque baton passed back to the embedder when the
|
||||
/// buffer_collect_callback is invoked. The engine does not interpret this
|
||||
/// field in any way.
|
||||
void* user_data;
|
||||
/// This is an optional field.
|
||||
///
|
||||
/// When specified, the engine will assume that the buffer is owned by the
|
||||
/// embedder. When the data is no longer needed by any isolate, this callback
|
||||
/// will be made on an internal engine managed thread. The embedder is free to
|
||||
/// collect the buffer here. When this field is specified, it is the embedders
|
||||
/// responsibility to keep the buffer alive and not modify it till this
|
||||
/// callback is invoked by the engine. The user data specified in the callback
|
||||
/// is the value of `user_data` field in this struct.
|
||||
///
|
||||
/// When NOT specified, the VM creates an internal copy of the buffer. The
|
||||
/// caller is free to modify the buffer as necessary or collect it immediately
|
||||
/// after the call to `FlutterEnginePostDartObject`.
|
||||
///
|
||||
/// @attention The buffer_collect_callback is will only be invoked by the
|
||||
/// engine when the `FlutterEnginePostDartObject` method
|
||||
/// returns kSuccess. In case of non-successful calls to this
|
||||
/// method, it is the embedders responsibility to collect the
|
||||
/// buffer.
|
||||
VoidCallback buffer_collect_callback;
|
||||
/// A pointer to the bytes of the buffer. When the buffer is owned by the
|
||||
/// embedder (by specifying the `buffer_collect_callback`), Dart code may
|
||||
/// modify that embedder owned buffer. For this reason, it is important that
|
||||
/// this buffer not have page protections that restrict writing to this
|
||||
/// buffer.
|
||||
uint8_t* buffer;
|
||||
/// The size of the buffer.
|
||||
size_t buffer_size;
|
||||
} FlutterEngineDartBuffer;
|
||||
|
||||
/// This struct specifies the native representation of a Dart object that can be
|
||||
/// sent via a send port to any isolate in the VM that has the corresponding
|
||||
/// receive port.
|
||||
///
|
||||
/// All fields in this struct are copied out in the call to
|
||||
/// `FlutterEnginePostDartObject` and the caller is free to reuse or collect
|
||||
/// this struct after that call.
|
||||
typedef struct {
|
||||
FlutterEngineDartObjectType type;
|
||||
union {
|
||||
bool bool_value;
|
||||
int32_t int32_value;
|
||||
int64_t int64_value;
|
||||
double double_value;
|
||||
/// A null terminated string. This string will be copied by the VM in the
|
||||
/// call to `FlutterEnginePostDartObject` and must be collected by the
|
||||
/// embedder after that call is made.
|
||||
const char* string_value;
|
||||
const FlutterEngineDartBuffer* buffer_value;
|
||||
};
|
||||
} FlutterEngineDartObject;
|
||||
|
||||
typedef struct {
|
||||
/// The size of this struct. Must be sizeof(FlutterProjectArgs).
|
||||
size_t struct_size;
|
||||
@ -1535,6 +1608,37 @@ FlutterEngineResult FlutterEngineUpdateLocales(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||
FLUTTER_EXPORT
|
||||
bool FlutterEngineRunsAOTCompiledDartCode(void);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief Posts a Dart object to specified send port. The corresponding
|
||||
/// receive port for send port can be in any isolate running in the
|
||||
/// VM. This isolate can also be the root isolate for an
|
||||
/// unrelated engine. The engine parameter is necessary only to
|
||||
/// ensure the call is not made when no engine (and hence no VM) is
|
||||
/// running.
|
||||
///
|
||||
/// Unlike the platform messages mechanism, there are no threading
|
||||
/// restrictions when using this API. Message can be posted on any
|
||||
/// thread and they will be made available to isolate on which the
|
||||
/// corresponding send port is listening.
|
||||
///
|
||||
/// However, it is the embedders responsibility to ensure that the
|
||||
/// call is not made during an ongoing call the
|
||||
/// `FlutterEngineDeinitialize` or `FlutterEngineShutdown` on
|
||||
/// another thread.
|
||||
///
|
||||
/// @param[in] engine. A running engine instance.
|
||||
/// @param[in] port The send port to send the object to.
|
||||
/// @param[in] object The object to send to the isolate with the
|
||||
/// corresponding receive port.
|
||||
///
|
||||
/// @return If the message was posted to the send port.
|
||||
///
|
||||
FLUTTER_EXPORT
|
||||
FlutterEngineResult FlutterEnginePostDartObject(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
FlutterEngineDartPort port,
|
||||
const FlutterEngineDartObject* object);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'dart:isolate';
|
||||
import 'dart:ffi';
|
||||
import 'dart:core';
|
||||
import 'dart:convert';
|
||||
|
||||
@ -659,3 +661,14 @@ void scene_builder_with_clips() {
|
||||
};
|
||||
window.scheduleFrame();
|
||||
}
|
||||
|
||||
|
||||
void sendObjectToNativeCode(dynamic object) native 'SendObjectToNativeCode';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void objects_can_be_posted() {
|
||||
ReceivePort port = ReceivePort();
|
||||
port.listen((dynamic message){ sendObjectToNativeCode(message); });
|
||||
signalNativeCount(port.sendPort.nativePort);
|
||||
}
|
||||
|
||||
|
||||
@ -3606,5 +3606,204 @@ TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) {
|
||||
latch.Wait();
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, ObjectsCanBePostedViaPorts) {
|
||||
auto& context = GetEmbedderContext();
|
||||
EmbedderConfigBuilder builder(context);
|
||||
builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024));
|
||||
builder.SetDartEntrypoint("objects_can_be_posted");
|
||||
|
||||
// Synchronously acquire the send port from the Dart end. We will be using
|
||||
// this to send message. The Dart end will just echo those messages back to us
|
||||
// for inspection.
|
||||
FlutterEngineDartPort port = 0;
|
||||
fml::AutoResetWaitableEvent event;
|
||||
context.AddNativeCallback("SignalNativeCount",
|
||||
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||
port = tonic::DartConverter<int64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 0));
|
||||
event.Signal();
|
||||
}));
|
||||
auto engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
event.Wait();
|
||||
ASSERT_NE(port, 0);
|
||||
|
||||
using Trampoline = std::function<void(Dart_Handle message)>;
|
||||
Trampoline trampoline;
|
||||
|
||||
context.AddNativeCallback("SendObjectToNativeCode",
|
||||
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||
FML_CHECK(trampoline);
|
||||
auto trampoline_copy = trampoline;
|
||||
trampoline = nullptr;
|
||||
trampoline_copy(Dart_GetNativeArgument(args, 0));
|
||||
}));
|
||||
|
||||
// Check null.
|
||||
{
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeNull;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
ASSERT_TRUE(Dart_IsNull(handle));
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
// Check bool.
|
||||
{
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeBool;
|
||||
object.bool_value = true;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(handle));
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
// Check int32.
|
||||
{
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeInt32;
|
||||
object.int32_value = 1988;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
ASSERT_EQ(tonic::DartConverter<int32_t>::FromDart(handle), 1988);
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
// Check int64.
|
||||
{
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeInt64;
|
||||
object.int64_value = 1988;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
ASSERT_EQ(tonic::DartConverter<int64_t>::FromDart(handle), 1988);
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
// Check double.
|
||||
{
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeDouble;
|
||||
object.double_value = 1988.0;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
ASSERT_DOUBLE_EQ(tonic::DartConverter<double>::FromDart(handle), 1988.0);
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
// Check string.
|
||||
{
|
||||
const char* message = "Hello. My name is Inigo Montoya.";
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeString;
|
||||
object.string_value = message;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
ASSERT_EQ(tonic::DartConverter<std::string>::FromDart(handle),
|
||||
std::string{message});
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
// Check buffer (copied out).
|
||||
{
|
||||
std::vector<uint8_t> message;
|
||||
message.resize(1988);
|
||||
|
||||
ASSERT_TRUE(MemsetPatternSetOrCheck(
|
||||
message, MemsetPatternOp::kMemsetPatternOpSetBuffer));
|
||||
|
||||
FlutterEngineDartBuffer buffer = {};
|
||||
|
||||
buffer.struct_size = sizeof(buffer);
|
||||
buffer.user_data = nullptr;
|
||||
buffer.buffer_collect_callback = nullptr;
|
||||
buffer.buffer = message.data();
|
||||
buffer.buffer_size = message.size();
|
||||
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeBuffer;
|
||||
object.buffer_value = &buffer;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
intptr_t length = 0;
|
||||
Dart_ListLength(handle, &length);
|
||||
ASSERT_EQ(length, 1988);
|
||||
// TODO(chinmaygarde); The std::vector<uint8_t> specialization for
|
||||
// DartConvertor in tonic is broken which is preventing the buffer
|
||||
// being checked here. Fix tonic and strengthen this check. For now, just
|
||||
// the buffer length is checked.
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message;
|
||||
fml::AutoResetWaitableEvent buffer_released_latch;
|
||||
|
||||
// Check buffer (caller own buffer with zero copy transfer).
|
||||
{
|
||||
message.resize(1988);
|
||||
|
||||
ASSERT_TRUE(MemsetPatternSetOrCheck(
|
||||
message, MemsetPatternOp::kMemsetPatternOpSetBuffer));
|
||||
|
||||
FlutterEngineDartBuffer buffer = {};
|
||||
|
||||
buffer.struct_size = sizeof(buffer);
|
||||
buffer.user_data = &buffer_released_latch;
|
||||
buffer.buffer_collect_callback = +[](void* user_data) {
|
||||
reinterpret_cast<fml::AutoResetWaitableEvent*>(user_data)->Signal();
|
||||
};
|
||||
buffer.buffer = message.data();
|
||||
buffer.buffer_size = message.size();
|
||||
|
||||
FlutterEngineDartObject object = {};
|
||||
object.type = kFlutterEngineDartObjectTypeBuffer;
|
||||
object.buffer_value = &buffer;
|
||||
trampoline = [&](Dart_Handle handle) {
|
||||
intptr_t length = 0;
|
||||
Dart_ListLength(handle, &length);
|
||||
ASSERT_EQ(length, 1988);
|
||||
// TODO(chinmaygarde); The std::vector<uint8_t> specialization for
|
||||
// DartConvertor in tonic is broken which is preventing the buffer
|
||||
// being checked here. Fix tonic and strengthen this check. For now, just
|
||||
// the buffer length is checked.
|
||||
event.Signal();
|
||||
};
|
||||
ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object),
|
||||
kSuccess);
|
||||
event.Wait();
|
||||
}
|
||||
|
||||
engine.reset();
|
||||
|
||||
// We cannot determine when the VM will GC objects that might have external
|
||||
// typed data finalizers. Since we need to ensure that we correctly wired up
|
||||
// finalizers from the embedders, we force the VM to collect all objects but
|
||||
// just shutting it down.
|
||||
buffer_released_latch.Wait();
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@ -53,5 +53,50 @@ std::unique_ptr<fml::Mapping> OpenFixtureAsMapping(std::string fixture_name) {
|
||||
return fml::FileMapping::CreateReadOnly(OpenFixture(fixture_name));
|
||||
}
|
||||
|
||||
bool MemsetPatternSetOrCheck(uint8_t* buffer, size_t size, MemsetPatternOp op) {
|
||||
if (buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pattern = reinterpret_cast<const uint8_t*>("dErP");
|
||||
constexpr auto pattern_length = 4;
|
||||
|
||||
uint8_t* start = buffer;
|
||||
uint8_t* p = buffer;
|
||||
|
||||
while ((start + size) - p >= pattern_length) {
|
||||
switch (op) {
|
||||
case MemsetPatternOp::kMemsetPatternOpSetBuffer:
|
||||
memmove(p, pattern, pattern_length);
|
||||
break;
|
||||
case MemsetPatternOp::kMemsetPatternOpCheckBuffer:
|
||||
if (memcmp(pattern, p, pattern_length) != 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
};
|
||||
p += pattern_length;
|
||||
}
|
||||
|
||||
if ((start + size) - p != 0) {
|
||||
switch (op) {
|
||||
case MemsetPatternOp::kMemsetPatternOpSetBuffer:
|
||||
memmove(p, pattern, (start + size) - p);
|
||||
break;
|
||||
case MemsetPatternOp::kMemsetPatternOpCheckBuffer:
|
||||
if (memcmp(pattern, p, (start + size) - p) != 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemsetPatternSetOrCheck(std::vector<uint8_t>& buffer, MemsetPatternOp op) {
|
||||
return MemsetPatternSetOrCheck(buffer.data(), buffer.size(), op);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#define TESTING_TESTING_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "flutter/fml/file.h"
|
||||
#include "flutter/fml/mapping.h"
|
||||
@ -62,6 +63,28 @@ std::unique_ptr<fml::Mapping> OpenFixtureAsMapping(std::string fixture_name);
|
||||
///
|
||||
std::string GetCurrentTestName();
|
||||
|
||||
enum class MemsetPatternOp {
|
||||
kMemsetPatternOpSetBuffer,
|
||||
kMemsetPatternOpCheckBuffer,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief Depending on the operation, either scribbles a known pattern
|
||||
/// into the buffer or checks if that pattern is present in an
|
||||
/// existing buffer. This is a portable variant of the
|
||||
/// memset_pattern class of methods that also happen to do assert
|
||||
/// that the same pattern exists.
|
||||
///
|
||||
/// @param buffer The buffer
|
||||
/// @param[in] size The size
|
||||
/// @param[in] op The operation
|
||||
///
|
||||
/// @return If the result of the operation was a success.
|
||||
///
|
||||
bool MemsetPatternSetOrCheck(uint8_t* buffer, size_t size, MemsetPatternOp op);
|
||||
|
||||
bool MemsetPatternSetOrCheck(std::vector<uint8_t>& buffer, MemsetPatternOp op);
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user