Commit d825492b authored by jbroman's avatar jbroman Committed by Commit bot

Support delegating serialization of host objects.

This exposes an interface for the embedder to provide a delegate which can
serialize or deserialize embedder-specific objects, like Blink's DOM wrappers.

BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2327653002
Cr-Commit-Position: refs/heads/master@{#39422}
parent 05c4afb0
......@@ -1681,6 +1681,13 @@ class V8_EXPORT ValueSerializer {
* type.
*/
virtual void ThrowDataCloneError(Local<String> message) = 0;
/*
* The embedder overrides this method to write some kind of host object, if
* possible. If not, a suitable exception should be thrown and
* Nothing<bool>() returned.
*/
virtual Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object);
};
explicit ValueSerializer(Isolate* isolate);
......@@ -1718,6 +1725,15 @@ class V8_EXPORT ValueSerializer {
void TransferSharedArrayBuffer(uint32_t transfer_id,
Local<SharedArrayBuffer> shared_array_buffer);
/*
* Write raw data in various common formats to the buffer.
* Note that integer types are written in base-128 varint format, not with a
* binary copy. For use during an override of Delegate::WriteHostObject.
*/
void WriteUint32(uint32_t value);
void WriteUint64(uint64_t value);
void WriteRawBytes(const void* source, size_t length);
private:
ValueSerializer(const ValueSerializer&) = delete;
void operator=(const ValueSerializer&) = delete;
......@@ -1736,7 +1752,21 @@ class V8_EXPORT ValueSerializer {
*/
class V8_EXPORT ValueDeserializer {
public:
class V8_EXPORT Delegate {
public:
virtual ~Delegate() {}
/*
* The embedder overrides this method to read some kind of host object, if
* possible. If not, a suitable exception should be thrown and
* MaybeLocal<Object>() returned.
*/
virtual MaybeLocal<Object> ReadHostObject(Isolate* isolate);
};
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size,
Delegate* delegate);
~ValueDeserializer();
/*
......@@ -1781,6 +1811,15 @@ class V8_EXPORT ValueDeserializer {
*/
uint32_t GetWireFormatVersion() const;
/*
* Reads raw data in various common formats to the buffer.
* Note that integer types are read in base-128 varint format, not with a
* binary copy. For use during an override of Delegate::ReadHostObject.
*/
V8_WARN_UNUSED_RESULT bool ReadUint32(uint32_t* value);
V8_WARN_UNUSED_RESULT bool ReadUint64(uint64_t* value);
V8_WARN_UNUSED_RESULT bool ReadRawBytes(size_t length, const void** data);
private:
ValueDeserializer(const ValueDeserializer&) = delete;
void operator=(const ValueDeserializer&) = delete;
......
......@@ -2885,6 +2885,15 @@ MaybeLocal<String> JSON::Stringify(Local<Context> context,
// --- V a l u e S e r i a l i z a t i o n ---
Maybe<bool> ValueSerializer::Delegate::WriteHostObject(Isolate* v8_isolate,
Local<Object> object) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
isolate->ScheduleThrow(*isolate->factory()->NewError(
isolate->error_function(), i::MessageTemplate::kDataCloneError,
Utils::OpenHandle(*object)));
return Nothing<bool>();
}
struct ValueSerializer::PrivateData {
explicit PrivateData(i::Isolate* i, ValueSerializer::Delegate* delegate)
: isolate(i), serializer(i, delegate) {}
......@@ -2929,9 +2938,30 @@ void ValueSerializer::TransferSharedArrayBuffer(
transfer_id, Utils::OpenHandle(*shared_array_buffer));
}
void ValueSerializer::WriteUint32(uint32_t value) {
private_->serializer.WriteUint32(value);
}
void ValueSerializer::WriteUint64(uint64_t value) {
private_->serializer.WriteUint64(value);
}
void ValueSerializer::WriteRawBytes(const void* source, size_t length) {
private_->serializer.WriteRawBytes(source, length);
}
MaybeLocal<Object> ValueDeserializer::Delegate::ReadHostObject(
Isolate* v8_isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
isolate->ScheduleThrow(*isolate->factory()->NewError(
isolate->error_function(),
i::MessageTemplate::kDataCloneDeserializationError));
return MaybeLocal<Object>();
}
struct ValueDeserializer::PrivateData {
PrivateData(i::Isolate* i, i::Vector<const uint8_t> data)
: isolate(i), deserializer(i, data) {}
PrivateData(i::Isolate* i, i::Vector<const uint8_t> data, Delegate* delegate)
: isolate(i), deserializer(i, data, delegate) {}
i::Isolate* isolate;
i::ValueDeserializer deserializer;
bool has_aborted = false;
......@@ -2939,14 +2969,18 @@ struct ValueDeserializer::PrivateData {
};
ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data,
size_t size) {
size_t size)
: ValueDeserializer(isolate, data, size, nullptr) {}
ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data,
size_t size, Delegate* delegate) {
if (base::IsValueInRangeForNumericType<int>(size)) {
private_ =
new PrivateData(reinterpret_cast<i::Isolate*>(isolate),
i::Vector<const uint8_t>(data, static_cast<int>(size)));
private_ = new PrivateData(
reinterpret_cast<i::Isolate*>(isolate),
i::Vector<const uint8_t>(data, static_cast<int>(size)), delegate);
} else {
private_ = new PrivateData(reinterpret_cast<i::Isolate*>(isolate),
i::Vector<const uint8_t>(nullptr, 0));
i::Vector<const uint8_t>(nullptr, 0), nullptr);
private_->has_aborted = true;
}
}
......@@ -3028,6 +3062,18 @@ void ValueDeserializer::TransferSharedArrayBuffer(
transfer_id, Utils::OpenHandle(*shared_array_buffer));
}
bool ValueDeserializer::ReadUint32(uint32_t* value) {
return private_->deserializer.ReadUint32(value);
}
bool ValueDeserializer::ReadUint64(uint64_t* value) {
return private_->deserializer.ReadUint64(value);
}
bool ValueDeserializer::ReadRawBytes(size_t length, const void** data) {
return private_->deserializer.ReadRawBytes(length, data);
}
// --- D a t a ---
bool Value::FullIsUndefined() const {
......
......@@ -206,6 +206,14 @@ uint8_t* ValueSerializer::ReserveRawBytes(size_t bytes) {
return &buffer_[old_size];
}
void ValueSerializer::WriteUint32(uint32_t value) {
WriteVarint<uint32_t>(value);
}
void ValueSerializer::WriteUint64(uint64_t value) {
WriteVarint<uint64_t>(value);
}
void ValueSerializer::TransferArrayBuffer(uint32_t transfer_id,
Handle<JSArrayBuffer> array_buffer) {
DCHECK(!array_buffer_transfer_map_.Find(array_buffer));
......@@ -353,8 +361,11 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
case JS_ARRAY_TYPE:
return WriteJSArray(Handle<JSArray>::cast(receiver));
case JS_OBJECT_TYPE:
case JS_API_OBJECT_TYPE:
return WriteJSObject(Handle<JSObject>::cast(receiver));
case JS_API_OBJECT_TYPE: {
Handle<JSObject> js_object = Handle<JSObject>::cast(receiver);
return js_object->GetInternalFieldCount() ? WriteHostObject(js_object)
: WriteJSObject(js_object);
}
case JS_DATE_TYPE:
WriteJSDate(JSDate::cast(*receiver));
return Just(true);
......@@ -701,6 +712,20 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView* view) {
return Just(true);
}
Maybe<bool> ValueSerializer::WriteHostObject(Handle<JSObject> object) {
if (!delegate_) {
isolate_->Throw(*isolate_->factory()->NewError(
isolate_->error_function(), MessageTemplate::kDataCloneError, object));
return Nothing<bool>();
}
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
Maybe<bool> result =
delegate_->WriteHostObject(v8_isolate, Utils::ToLocal(object));
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
DCHECK(!result.IsNothing());
return result;
}
Maybe<uint32_t> ValueSerializer::WriteJSObjectPropertiesSlow(
Handle<JSObject> object, Handle<FixedArray> keys) {
uint32_t properties_written = 0;
......@@ -751,8 +776,10 @@ void ValueSerializer::ThrowDataCloneError(
}
ValueDeserializer::ValueDeserializer(Isolate* isolate,
Vector<const uint8_t> data)
Vector<const uint8_t> data,
v8::ValueDeserializer::Delegate* delegate)
: isolate_(isolate),
delegate_(delegate),
position_(data.start()),
end_(data.start() + data.length()),
pretenure_(data.length() > kPretenureThreshold ? TENURED : NOT_TENURED),
......@@ -865,6 +892,21 @@ Maybe<Vector<const uint8_t>> ValueDeserializer::ReadRawBytes(int size) {
return Just(Vector<const uint8_t>(start, size));
}
bool ValueDeserializer::ReadUint32(uint32_t* value) {
return ReadVarint<uint32_t>().To(value);
}
bool ValueDeserializer::ReadUint64(uint64_t* value) {
return ReadVarint<uint64_t>().To(value);
}
bool ValueDeserializer::ReadRawBytes(size_t length, const void** data) {
if (length > static_cast<size_t>(end_ - position_)) return false;
*data = position_;
position_ += length;
return true;
}
void ValueDeserializer::TransferArrayBuffer(
uint32_t transfer_id, Handle<JSArrayBuffer> array_buffer) {
if (array_buffer_transfer_map_.is_null()) {
......@@ -978,7 +1020,10 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
return ReadTransferredJSArrayBuffer(is_shared);
}
default:
return MaybeHandle<Object>();
// TODO(jbroman): Introduce an explicit tag for host objects to avoid
// having to treat every unknown tag as a potential host object.
position_--;
return ReadHostObject();
}
}
......@@ -1328,6 +1373,22 @@ MaybeHandle<JSArrayBufferView> ValueDeserializer::ReadJSArrayBufferView(
return typed_array;
}
MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() {
if (!delegate_) return MaybeHandle<JSObject>();
STACK_CHECK(isolate_, MaybeHandle<JSObject>());
uint32_t id = next_id_++;
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
v8::Local<v8::Object> object;
if (!delegate_->ReadHostObject(v8_isolate).ToLocal(&object)) {
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
return MaybeHandle<JSObject>();
}
Handle<JSObject> js_object =
Handle<JSObject>::cast(Utils::OpenHandle(*object));
AddObjectWithID(id, js_object);
return js_object;
}
Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
Handle<JSObject> object, SerializationTag end_tag) {
for (uint32_t num_properties = 0;; num_properties++) {
......
......@@ -69,6 +69,14 @@ class ValueSerializer {
void TransferArrayBuffer(uint32_t transfer_id,
Handle<JSArrayBuffer> array_buffer);
/*
* Publicly exposed wire format writing methods.
* These are intended for use within the delegate's WriteHostObject method.
*/
void WriteUint32(uint32_t value);
void WriteUint64(uint64_t value);
void WriteRawBytes(const void* source, size_t length);
private:
// Writing the wire format.
void WriteTag(SerializationTag tag);
......@@ -79,7 +87,6 @@ class ValueSerializer {
void WriteDouble(double value);
void WriteOneByteString(Vector<const uint8_t> chars);
void WriteTwoByteString(Vector<const uc16> chars);
void WriteRawBytes(const void* source, size_t length);
uint8_t* ReserveRawBytes(size_t bytes);
// Writing V8 objects of various kinds.
......@@ -98,6 +105,7 @@ class ValueSerializer {
Maybe<bool> WriteJSSet(Handle<JSSet> map) WARN_UNUSED_RESULT;
Maybe<bool> WriteJSArrayBuffer(JSArrayBuffer* array_buffer);
Maybe<bool> WriteJSArrayBufferView(JSArrayBufferView* array_buffer);
Maybe<bool> WriteHostObject(Handle<JSObject> object) WARN_UNUSED_RESULT;
/*
* Reads the specified keys from the object and writes key-value pairs to the
......@@ -138,7 +146,8 @@ class ValueSerializer {
*/
class ValueDeserializer {
public:
ValueDeserializer(Isolate* isolate, Vector<const uint8_t> data);
ValueDeserializer(Isolate* isolate, Vector<const uint8_t> data,
v8::ValueDeserializer::Delegate* delegate);
~ValueDeserializer();
/*
......@@ -175,6 +184,14 @@ class ValueDeserializer {
void TransferArrayBuffer(uint32_t transfer_id,
Handle<JSArrayBuffer> array_buffer);
/*
* Publicly exposed wire format writing methods.
* These are intended for use within the delegate's WriteHostObject method.
*/
bool ReadUint32(uint32_t* value) WARN_UNUSED_RESULT;
bool ReadUint64(uint64_t* value) WARN_UNUSED_RESULT;
bool ReadRawBytes(size_t length, const void** data) WARN_UNUSED_RESULT;
private:
// Reading the wire format.
Maybe<SerializationTag> PeekTag() const WARN_UNUSED_RESULT;
......@@ -208,6 +225,7 @@ class ValueDeserializer {
WARN_UNUSED_RESULT;
MaybeHandle<JSArrayBufferView> ReadJSArrayBufferView(
Handle<JSArrayBuffer> buffer) WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadHostObject() WARN_UNUSED_RESULT;
/*
* Reads key-value pairs into the object until the specified end tag is
......@@ -222,6 +240,7 @@ class ValueDeserializer {
void AddObjectWithID(uint32_t id, Handle<JSReceiver> object);
Isolate* const isolate_;
v8::ValueDeserializer::Delegate* const delegate_;
const uint8_t* position_;
const uint8_t* const end_;
PretenureFlag pretenure_;
......
......@@ -11,16 +11,48 @@
#include "src/api.h"
#include "src/base/build_config.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
using ::testing::_;
using ::testing::Invoke;
class ValueSerializerTest : public TestWithIsolate {
protected:
ValueSerializerTest()
: serialization_context_(Context::New(isolate())),
deserialization_context_(Context::New(isolate())) {}
deserialization_context_(Context::New(isolate())) {
// Create a host object type that can be tested through
// serialization/deserialization delegates below.
Local<FunctionTemplate> function_template = v8::FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<Value>& args) {
args.Holder()->SetInternalField(0, args[0]);
args.Holder()->SetInternalField(1, args[1]);
});
function_template->InstanceTemplate()->SetInternalFieldCount(2);
function_template->InstanceTemplate()->SetAccessor(
StringFromUtf8("value"),
[](Local<String> property, const PropertyCallbackInfo<Value>& args) {
args.GetReturnValue().Set(args.Holder()->GetInternalField(0));
});
function_template->InstanceTemplate()->SetAccessor(
StringFromUtf8("value2"),
[](Local<String> property, const PropertyCallbackInfo<Value>& args) {
args.GetReturnValue().Set(args.Holder()->GetInternalField(1));
});
for (Local<Context> context :
{serialization_context_, deserialization_context_}) {
context->Global()
->CreateDataProperty(
context, StringFromUtf8("ExampleHostObject"),
function_template->GetFunction(context).ToLocalChecked())
.ToChecked();
}
host_object_constructor_template_ = function_template;
}
const Local<Context>& serialization_context() {
return serialization_context_;
......@@ -30,7 +62,11 @@ class ValueSerializerTest : public TestWithIsolate {
}
// Overridden in more specific fixtures.
virtual ValueSerializer::Delegate* GetSerializerDelegate() { return nullptr; }
virtual void BeforeEncode(ValueSerializer*) {}
virtual ValueDeserializer::Delegate* GetDeserializerDelegate() {
return nullptr;
}
virtual void BeforeDecode(ValueDeserializer*) {}
template <typename InputFunctor, typename OutputFunctor>
......@@ -52,7 +88,7 @@ class ValueSerializerTest : public TestWithIsolate {
Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) {
Local<Context> context = serialization_context();
ValueSerializer serializer(isolate());
ValueSerializer serializer(isolate(), GetSerializerDelegate());
BeforeEncode(&serializer);
serializer.WriteHeader();
if (!serializer.WriteValue(context, value).FromMaybe(false)) {
......@@ -93,7 +129,8 @@ class ValueSerializerTest : public TestWithIsolate {
Context::Scope scope(context);
TryCatch try_catch(isolate());
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()));
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
......@@ -116,7 +153,8 @@ class ValueSerializerTest : public TestWithIsolate {
Context::Scope scope(context);
TryCatch try_catch(isolate());
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()));
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
......@@ -138,7 +176,8 @@ class ValueSerializerTest : public TestWithIsolate {
Context::Scope scope(context);
TryCatch try_catch(isolate());
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()));
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
BeforeDecode(&deserializer);
Maybe<bool> header_result = deserializer.ReadHeader(context);
......@@ -176,9 +215,18 @@ class ValueSerializerTest : public TestWithIsolate {
return std::string(*utf8, utf8.length());
}
Local<Object> NewHostObject(Local<Context> context, int argc,
Local<Value> argv[]) {
return host_object_constructor_template_->GetFunction(context)
.ToLocalChecked()
->NewInstance(context, argc, argv)
.ToLocalChecked();
}
private:
Local<Context> serialization_context_;
Local<Context> deserialization_context_;
Local<FunctionTemplate> host_object_constructor_template_;
DISALLOW_COPY_AND_ASSIGN(ValueSerializerTest);
};
......@@ -2024,5 +2072,213 @@ TEST_F(ValueSerializerTestWithSharedArrayBufferTransfer,
InvalidEncodeTest("new SharedArrayBuffer(32)");
}
TEST_F(ValueSerializerTest, UnsupportedHostObject) {
InvalidEncodeTest("new ExampleHostObject()");
InvalidEncodeTest("({ a: new ExampleHostObject() })");
}
class ValueSerializerTestWithHostObject : public ValueSerializerTest {
protected:
ValueSerializerTestWithHostObject() : serializer_delegate_(this) {}
static const uint8_t kExampleHostObjectTag;
void WriteExampleHostObjectTag() {
serializer_->WriteRawBytes(&kExampleHostObjectTag, 1);
}
bool ReadExampleHostObjectTag() {
const void* tag;
return deserializer_->ReadRawBytes(1, &tag) &&
*reinterpret_cast<const uint8_t*>(tag) == kExampleHostObjectTag;
}
// GMock doesn't use the "override" keyword.
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winconsistent-missing-override"
#endif
class SerializerDelegate : public ValueSerializer::Delegate {
public:
explicit SerializerDelegate(ValueSerializerTestWithHostObject* test)
: test_(test) {}
MOCK_METHOD2(WriteHostObject,
Maybe<bool>(Isolate* isolate, Local<Object> object));
void ThrowDataCloneError(Local<String> message) override {
test_->isolate()->ThrowException(Exception::Error(message));
}
private:
ValueSerializerTestWithHostObject* test_;
};
class DeserializerDelegate : public ValueDeserializer::Delegate {
public:
MOCK_METHOD1(ReadHostObject, MaybeLocal<Object>(Isolate* isolate));
};
#if __clang__
#pragma clang diagnostic pop
#endif
ValueSerializer::Delegate* GetSerializerDelegate() override {
return &serializer_delegate_;
}
void BeforeEncode(ValueSerializer* serializer) override {
serializer_ = serializer;
}
ValueDeserializer::Delegate* GetDeserializerDelegate() override {
return &deserializer_delegate_;
}
void BeforeDecode(ValueDeserializer* deserializer) override {
deserializer_ = deserializer;
}
SerializerDelegate serializer_delegate_;
DeserializerDelegate deserializer_delegate_;
ValueSerializer* serializer_;
ValueDeserializer* deserializer_;
friend class SerializerDelegate;
friend class DeserializerDelegate;
};
// This is a tag that's not used in V8.
const uint8_t ValueSerializerTestWithHostObject::kExampleHostObjectTag = '+';
TEST_F(ValueSerializerTestWithHostObject, RoundTripUint32) {
// The host can serialize data as uint32_t.
EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
.WillRepeatedly(Invoke([this](Isolate*, Local<Object> object) {
uint32_t value = 0;
EXPECT_TRUE(object->GetInternalField(0)
->Uint32Value(serialization_context())
.To(&value));
WriteExampleHostObjectTag();
serializer_->WriteUint32(value);
return Just(true);
}));
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
.WillRepeatedly(Invoke([this](Isolate*) {
EXPECT_TRUE(ReadExampleHostObjectTag());
uint32_t value = 0;
EXPECT_TRUE(deserializer_->ReadUint32(&value));
Local<Value> argv[] = {Integer::NewFromUnsigned(isolate(), value)};
return NewHostObject(deserialization_context(), arraysize(argv), argv);
}));
RoundTripTest("new ExampleHostObject(42)", [this](Local<Value> value) {
ASSERT_TRUE(value->IsObject());
ASSERT_TRUE(Object::Cast(*value)->InternalFieldCount());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 42"));
});
RoundTripTest(
"new ExampleHostObject(0xCAFECAFE)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 0xCAFECAFE"));
});
}
TEST_F(ValueSerializerTestWithHostObject, RoundTripUint64) {
// The host can serialize data as uint64_t.
EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
.WillRepeatedly(Invoke([this](Isolate*, Local<Object> object) {
uint32_t value = 0, value2 = 0;
EXPECT_TRUE(object->GetInternalField(0)
->Uint32Value(serialization_context())
.To(&value));
EXPECT_TRUE(object->GetInternalField(1)
->Uint32Value(serialization_context())
.To(&value2));
WriteExampleHostObjectTag();
serializer_->WriteUint64((static_cast<uint64_t>(value) << 32) | value2);
return Just(true);
}));
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
.WillRepeatedly(Invoke([this](Isolate*) {
EXPECT_TRUE(ReadExampleHostObjectTag());
uint64_t value_packed;
EXPECT_TRUE(deserializer_->ReadUint64(&value_packed));
Local<Value> argv[] = {
Integer::NewFromUnsigned(isolate(),
static_cast<uint32_t>(value_packed >> 32)),
Integer::NewFromUnsigned(isolate(),
static_cast<uint32_t>(value_packed))};
return NewHostObject(deserialization_context(), arraysize(argv), argv);
}));
RoundTripTest("new ExampleHostObject(42, 0)", [this](Local<Value> value) {
ASSERT_TRUE(value->IsObject());
ASSERT_TRUE(Object::Cast(*value)->InternalFieldCount());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 42"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.value2 === 0"));
});
RoundTripTest(
"new ExampleHostObject(0xFFFFFFFF, 0x12345678)",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 0xFFFFFFFF"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.value2 === 0x12345678"));
});
}
TEST_F(ValueSerializerTestWithHostObject, RoundTripRawBytes) {
// The host can serialize arbitrary raw bytes.
const struct {
uint64_t u64;
uint32_t u32;
char str[12];
} sample_data = {0x1234567812345678, 0x87654321, "Hello world"};
EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
.WillRepeatedly(
Invoke([this, &sample_data](Isolate*, Local<Object> object) {
WriteExampleHostObjectTag();
serializer_->WriteRawBytes(&sample_data, sizeof(sample_data));
return Just(true);
}));
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
.WillRepeatedly(Invoke([this, &sample_data](Isolate*) {
EXPECT_TRUE(ReadExampleHostObjectTag());
const void* copied_data = nullptr;
EXPECT_TRUE(
deserializer_->ReadRawBytes(sizeof(sample_data), &copied_data));
if (copied_data) {
EXPECT_EQ(0, memcmp(&sample_data, copied_data, sizeof(sample_data)));
}
return NewHostObject(deserialization_context(), 0, nullptr);
}));
RoundTripTest("new ExampleHostObject()", [this](Local<Value> value) {
ASSERT_TRUE(value->IsObject());
ASSERT_TRUE(Object::Cast(*value)->InternalFieldCount());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
});
}
TEST_F(ValueSerializerTestWithHostObject, RoundTripSameObject) {
// If the same object exists in two places, the delegate should be invoked
// only once, and the objects should be the same (by reference equality) on
// the other side.
EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
.WillOnce(Invoke([this](Isolate*, Local<Object> object) {
WriteExampleHostObjectTag();
return Just(true);
}));
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
.WillOnce(Invoke([this](Isolate*) {
EXPECT_TRUE(ReadExampleHostObjectTag());
return NewHostObject(deserialization_context(), 0, nullptr);
}));
RoundTripTest(
"({ a: new ExampleHostObject(), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.a instanceof ExampleHostObject"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
} // namespace
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment