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

Support ImageData cloning in the V8-based structured clone path.

This paves the way for other DOM wrappers to be serialized.

BUG=148757

Review-Url: https://codereview.chromium.org/2323413002
Cr-Commit-Position: refs/heads/master@{#419172}
parent d4d44f9c
...@@ -236,6 +236,7 @@ bindings_unittest_files = ...@@ -236,6 +236,7 @@ bindings_unittest_files =
"core/v8/V8BindingForTesting.h", "core/v8/V8BindingForTesting.h",
"core/v8/V8BindingTest.cpp", "core/v8/V8BindingTest.cpp",
"core/v8/V8ScriptRunnerTest.cpp", "core/v8/V8ScriptRunnerTest.cpp",
"core/v8/serialization/V8ScriptValueSerializerTest.cpp",
], ],
"abspath") "abspath")
bindings_unittest_files += bindings_modules_v8_unittest_files bindings_unittest_files += bindings_modules_v8_unittest_files
...@@ -127,6 +127,7 @@ public: ...@@ -127,6 +127,7 @@ public:
// This method clears out the exception which |this| has. // This method clears out the exception which |this| has.
void reject(ScriptPromiseResolver*); void reject(ScriptPromiseResolver*);
ContextType context() const { return m_context; }
const char* propertyName() const { return m_propertyName; } const char* propertyName() const { return m_propertyName; }
const char* interfaceName() const { return m_interfaceName; } const char* interfaceName() const { return m_interfaceName; }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "bindings/core/v8/V8BindingForTesting.h" #include "bindings/core/v8/V8BindingForTesting.h"
#include "core/frame/Settings.h"
#include "core/testing/DummyPageHolder.h" #include "core/testing/DummyPageHolder.h"
namespace blink { namespace blink {
...@@ -37,7 +38,9 @@ V8TestingScope::V8TestingScope() ...@@ -37,7 +38,9 @@ V8TestingScope::V8TestingScope()
, m_handleScope(isolate()) , m_handleScope(isolate())
, m_context(getScriptState()->context()) , m_context(getScriptState()->context())
, m_contextScope(context()) , m_contextScope(context())
, m_tryCatch(isolate())
{ {
frame().settings()->setScriptEnabled(true);
} }
ScriptState* V8TestingScope::getScriptState() const ScriptState* V8TestingScope::getScriptState() const
......
...@@ -49,6 +49,7 @@ private: ...@@ -49,6 +49,7 @@ private:
v8::HandleScope m_handleScope; v8::HandleScope m_handleScope;
v8::Local<v8::Context> m_context; v8::Local<v8::Context> m_context;
v8::Context::Scope m_contextScope; v8::Context::Scope m_contextScope;
v8::TryCatch m_tryCatch;
TrackExceptionState m_exceptionState; TrackExceptionState m_exceptionState;
}; };
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "bindings/core/v8/ToV8.h" #include "bindings/core/v8/ToV8.h"
#include "core/dom/DOMArrayBuffer.h" #include "core/dom/DOMArrayBuffer.h"
#include "core/dom/DOMSharedArrayBuffer.h" #include "core/dom/DOMSharedArrayBuffer.h"
#include "core/html/ImageData.h"
#include "platform/RuntimeEnabledFeatures.h" #include "platform/RuntimeEnabledFeatures.h"
namespace blink { namespace blink {
...@@ -19,7 +20,8 @@ V8ScriptValueDeserializer::V8ScriptValueDeserializer(RefPtr<ScriptState> scriptS ...@@ -19,7 +20,8 @@ V8ScriptValueDeserializer::V8ScriptValueDeserializer(RefPtr<ScriptState> scriptS
reinterpret_cast<const uint8_t*>( reinterpret_cast<const uint8_t*>(
m_serializedScriptValue->data().ensure16Bit(), m_serializedScriptValue->data().ensure16Bit(),
m_serializedScriptValue->data().characters16()), m_serializedScriptValue->data().characters16()),
m_serializedScriptValue->data().length() * 2) m_serializedScriptValue->data().length() * 2,
this)
{ {
DCHECK(RuntimeEnabledFeatures::v8BasedStructuredCloneEnabled()); DCHECK(RuntimeEnabledFeatures::v8BasedStructuredCloneEnabled());
m_deserializer.SetSupportsLegacyWireFormat(true); m_deserializer.SetSupportsLegacyWireFormat(true);
...@@ -78,4 +80,46 @@ void V8ScriptValueDeserializer::transfer() ...@@ -78,4 +80,46 @@ void V8ScriptValueDeserializer::transfer()
} }
} }
ScriptWrappable* V8ScriptValueDeserializer::readDOMObject(SerializationTag tag)
{
switch (tag) {
case ImageDataTag: {
uint32_t width = 0, height = 0, pixelLength = 0;
const void* pixels = nullptr;
if (!readUint32(&width)
|| !readUint32(&height)
|| !readUint32(&pixelLength)
|| !readRawBytes(pixelLength, &pixels))
return nullptr;
ImageData* imageData = ImageData::create(IntSize(width, height));
DOMUint8ClampedArray* pixelArray = imageData->data();
if (pixelArray->length() < pixelLength)
return nullptr;
memcpy(pixelArray->data(), pixels, pixelLength);
return imageData;
}
default:
break;
}
return nullptr;
}
v8::MaybeLocal<v8::Object> V8ScriptValueDeserializer::ReadHostObject(v8::Isolate* isolate)
{
DCHECK_EQ(isolate, m_scriptState->isolate());
ExceptionState exceptionState(isolate, ExceptionState::UnknownContext, nullptr, nullptr);
ScriptWrappable* wrappable = nullptr;
SerializationTag tag = PaddingTag;
if (readTag(&tag))
wrappable = readDOMObject(tag);
if (!wrappable) {
exceptionState.throwDOMException(DataCloneError, "Unable to deserialize cloned data.");
return v8::MaybeLocal<v8::Object>();
}
v8::Local<v8::Object> creationContext = m_scriptState->context()->Global();
v8::Local<v8::Value> wrapper = toV8(wrappable, creationContext, isolate);
DCHECK(wrapper->IsObject());
return wrapper.As<v8::Object>();
}
} // namespace blink } // namespace blink
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
#define V8ScriptValueDeserializer_h #define V8ScriptValueDeserializer_h
#include "bindings/core/v8/ScriptState.h" #include "bindings/core/v8/ScriptState.h"
#include "bindings/core/v8/SerializationTag.h"
#include "bindings/core/v8/SerializedScriptValue.h" #include "bindings/core/v8/SerializedScriptValue.h"
#include "core/CoreExport.h"
#include "wtf/Allocator.h" #include "wtf/Allocator.h"
#include "wtf/Noncopyable.h" #include "wtf/Noncopyable.h"
#include "wtf/RefPtr.h" #include "wtf/RefPtr.h"
...@@ -22,7 +24,7 @@ namespace blink { ...@@ -22,7 +24,7 @@ namespace blink {
// //
// A deserializer cannot be used multiple times; it is expected that its // A deserializer cannot be used multiple times; it is expected that its
// deserialize method will be invoked exactly once. // deserialize method will be invoked exactly once.
class V8ScriptValueDeserializer { class GC_PLUGIN_IGNORE("https://crbug.com/644725") CORE_EXPORT V8ScriptValueDeserializer : public v8::ValueDeserializer::Delegate {
STACK_ALLOCATED(); STACK_ALLOCATED();
WTF_MAKE_NONCOPYABLE(V8ScriptValueDeserializer); WTF_MAKE_NONCOPYABLE(V8ScriptValueDeserializer);
public: public:
...@@ -30,11 +32,27 @@ public: ...@@ -30,11 +32,27 @@ public:
v8::Local<v8::Value> deserialize(); v8::Local<v8::Value> deserialize();
protected: protected:
virtual ScriptWrappable* readDOMObject(SerializationTag);
uint32_t version() const { return m_version; } uint32_t version() const { return m_version; }
bool readTag(SerializationTag* tag)
{
const void* tagBytes = nullptr;
if (!m_deserializer.ReadRawBytes(1, &tagBytes))
return false;
*tag = static_cast<SerializationTag>(*reinterpret_cast<const uint8_t*>(tagBytes));
return true;
}
bool readUint32(uint32_t* value) { return m_deserializer.ReadUint32(value); }
bool readUint64(uint64_t* value) { return m_deserializer.ReadUint64(value); }
bool readRawBytes(size_t size, const void** data) { return m_deserializer.ReadRawBytes(size, data); }
private: private:
void transfer(); void transfer();
// v8::ValueDeserializer::Delegate
v8::MaybeLocal<v8::Object> ReadHostObject(v8::Isolate*) override;
RefPtr<ScriptState> m_scriptState; RefPtr<ScriptState> m_scriptState;
RefPtr<SerializedScriptValue> m_serializedScriptValue; RefPtr<SerializedScriptValue> m_serializedScriptValue;
v8::ValueDeserializer m_deserializer; v8::ValueDeserializer m_deserializer;
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
#include "bindings/core/v8/serialization/V8ScriptValueSerializer.h" #include "bindings/core/v8/serialization/V8ScriptValueSerializer.h"
#include "bindings/core/v8/ToV8.h" #include "bindings/core/v8/ToV8.h"
#include "bindings/core/v8/V8ImageData.h"
#include "core/dom/DOMArrayBufferBase.h" #include "core/dom/DOMArrayBufferBase.h"
#include "core/html/ImageData.h"
#include "platform/RuntimeEnabledFeatures.h" #include "platform/RuntimeEnabledFeatures.h"
#include "wtf/AutoReset.h" #include "wtf/AutoReset.h"
...@@ -83,6 +85,22 @@ void V8ScriptValueSerializer::transfer(Transferables* transferables, ExceptionSt ...@@ -83,6 +85,22 @@ void V8ScriptValueSerializer::transfer(Transferables* transferables, ExceptionSt
} }
} }
bool V8ScriptValueSerializer::writeDOMObject(ScriptWrappable* wrappable, ExceptionState& exceptionState)
{
const WrapperTypeInfo* wrapperTypeInfo = wrappable->wrapperTypeInfo();
if (wrapperTypeInfo == &V8ImageData::wrapperTypeInfo) {
ImageData* imageData = wrappable->toImpl<ImageData>();
DOMUint8ClampedArray* pixels = imageData->data();
writeTag(ImageDataTag);
writeUint32(imageData->width());
writeUint32(imageData->height());
writeUint32(pixels->length());
writeRawBytes(pixels->data(), pixels->length());
return true;
}
return false;
}
void V8ScriptValueSerializer::ThrowDataCloneError(v8::Local<v8::String> v8Message) void V8ScriptValueSerializer::ThrowDataCloneError(v8::Local<v8::String> v8Message)
{ {
DCHECK(m_exceptionState); DCHECK(m_exceptionState);
...@@ -93,4 +111,27 @@ void V8ScriptValueSerializer::ThrowDataCloneError(v8::Local<v8::String> v8Messag ...@@ -93,4 +111,27 @@ void V8ScriptValueSerializer::ThrowDataCloneError(v8::Local<v8::String> v8Messag
V8ThrowException::throwException(m_scriptState->isolate(), exception); V8ThrowException::throwException(m_scriptState->isolate(), exception);
} }
v8::Maybe<bool> V8ScriptValueSerializer::WriteHostObject(v8::Isolate* isolate, v8::Local<v8::Object> object)
{
DCHECK(m_exceptionState);
DCHECK_EQ(isolate, m_scriptState->isolate());
ExceptionState exceptionState(
isolate, m_exceptionState->context(),
m_exceptionState->interfaceName(), m_exceptionState->propertyName());
if (!V8DOMWrapper::isWrapper(isolate, object)) {
exceptionState.throwDOMException(DataCloneError, "An object could not be cloned.");
return v8::Nothing<bool>();
}
ScriptWrappable* wrappable = toScriptWrappable(object);
bool wroteDOMObject = writeDOMObject(wrappable, exceptionState);
if (wroteDOMObject) {
DCHECK(!exceptionState.hadException());
return v8::Just(true);
}
if (!exceptionState.hadException())
exceptionState.throwDOMException(DataCloneError, "An object could not be cloned.");
return v8::Nothing<bool>();
}
} // namespace blink } // namespace blink
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
#include "bindings/core/v8/ExceptionState.h" #include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptState.h" #include "bindings/core/v8/ScriptState.h"
#include "bindings/core/v8/SerializationTag.h"
#include "bindings/core/v8/SerializedScriptValue.h" #include "bindings/core/v8/SerializedScriptValue.h"
#include "core/CoreExport.h"
#include "wtf/Allocator.h" #include "wtf/Allocator.h"
#include "wtf/Noncopyable.h" #include "wtf/Noncopyable.h"
#include "wtf/RefPtr.h" #include "wtf/RefPtr.h"
...@@ -25,18 +27,34 @@ class Transferables; ...@@ -25,18 +27,34 @@ class Transferables;
// //
// A serializer cannot be used multiple times; it is expected that its serialize // A serializer cannot be used multiple times; it is expected that its serialize
// method will be invoked exactly once. // method will be invoked exactly once.
class GC_PLUGIN_IGNORE("https://crbug.com/644725") V8ScriptValueSerializer : public v8::ValueSerializer::Delegate { class GC_PLUGIN_IGNORE("https://crbug.com/644725") CORE_EXPORT V8ScriptValueSerializer : public v8::ValueSerializer::Delegate {
STACK_ALLOCATED(); STACK_ALLOCATED();
WTF_MAKE_NONCOPYABLE(V8ScriptValueSerializer); WTF_MAKE_NONCOPYABLE(V8ScriptValueSerializer);
public: public:
explicit V8ScriptValueSerializer(RefPtr<ScriptState>); explicit V8ScriptValueSerializer(RefPtr<ScriptState>);
RefPtr<SerializedScriptValue> serialize(v8::Local<v8::Value>, Transferables*, ExceptionState&); RefPtr<SerializedScriptValue> serialize(v8::Local<v8::Value>, Transferables*, ExceptionState&);
protected:
// Returns true if the DOM object was successfully written.
// If false is returned and no more specific exception is thrown, a generic
// DataCloneError message will be used.
virtual bool writeDOMObject(ScriptWrappable*, ExceptionState&);
void writeTag(SerializationTag tag)
{
uint8_t tagByte = tag;
m_serializer.WriteRawBytes(&tagByte, 1);
}
void writeUint32(uint32_t value) { m_serializer.WriteUint32(value); }
void writeUint64(uint64_t value) { m_serializer.WriteUint64(value); }
void writeRawBytes(const void* data, size_t size) { m_serializer.WriteRawBytes(data, size); }
private: private:
void transfer(Transferables*, ExceptionState&); void transfer(Transferables*, ExceptionState&);
// v8::ValueSerializer::Delegate // v8::ValueSerializer::Delegate
void ThrowDataCloneError(v8::Local<v8::String> message) override; void ThrowDataCloneError(v8::Local<v8::String> message) override;
v8::Maybe<bool> WriteHostObject(v8::Isolate*, v8::Local<v8::Object> message) override;
RefPtr<ScriptState> m_scriptState; RefPtr<ScriptState> m_scriptState;
RefPtr<SerializedScriptValue> m_serializedScriptValue; RefPtr<SerializedScriptValue> m_serializedScriptValue;
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bindings/core/v8/serialization/V8ScriptValueSerializer.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "bindings/core/v8/ScriptController.h"
#include "bindings/core/v8/ScriptSourceCode.h"
#include "bindings/core/v8/V8BindingForTesting.h"
#include "bindings/core/v8/V8DOMException.h"
#include "bindings/core/v8/V8ImageData.h"
#include "bindings/core/v8/V8StringResource.h"
#include "bindings/core/v8/serialization/V8ScriptValueDeserializer.h"
#include "core/frame/LocalFrame.h"
#include "core/html/ImageData.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
class ScopedEnableV8BasedStructuredClone {
public:
ScopedEnableV8BasedStructuredClone()
: m_wasEnabled(RuntimeEnabledFeatures::v8BasedStructuredCloneEnabled())
{
RuntimeEnabledFeatures::setV8BasedStructuredCloneEnabled(true);
}
~ScopedEnableV8BasedStructuredClone()
{
RuntimeEnabledFeatures::setV8BasedStructuredCloneEnabled(m_wasEnabled);
}
private:
bool m_wasEnabled;
};
RefPtr<SerializedScriptValue> serializedValue(const Vector<uint8_t>& bytes)
{
// TODO(jbroman): Fix this once SerializedScriptValue can take bytes without
// endianness swapping.
DCHECK_EQ(bytes.size() % 2, 0u);
return SerializedScriptValue::create(String(reinterpret_cast<const UChar*>(&bytes[0]), bytes.size() / 2));
}
v8::Local<v8::Value> roundTrip(v8::Local<v8::Value> value, V8TestingScope& scope)
{
RefPtr<ScriptState> scriptState = scope.getScriptState();
ExceptionState& exceptionState = scope.getExceptionState();
RefPtr<SerializedScriptValue> serializedScriptValue =
V8ScriptValueSerializer(scriptState).serialize(value, nullptr, exceptionState);
DCHECK_EQ(!serializedScriptValue, exceptionState.hadException());
EXPECT_TRUE(serializedScriptValue);
if (!serializedScriptValue)
return v8::Local<v8::Value>();
return V8ScriptValueDeserializer(scriptState, serializedScriptValue).deserialize();
}
v8::Local<v8::Value> eval(const String& source, V8TestingScope& scope)
{
return scope.frame().script().executeScriptInMainWorldAndReturnValue(source);
}
String toJSON(v8::Local<v8::Object> object, const V8TestingScope& scope)
{
return v8StringToWebCoreString<String>(
v8::JSON::Stringify(scope.context(), object).ToLocalChecked(),
DoNotExternalize);
}
// Checks for a DOM exception, including a rethrown one.
::testing::AssertionResult hadDOMException(const StringView& name, ScriptState* scriptState, ExceptionState& exceptionState)
{
if (!exceptionState.hadException())
return ::testing::AssertionFailure() << "no exception thrown";
DOMException* domException = V8DOMException::toImplWithTypeCheck(scriptState->isolate(), exceptionState.getException());
if (!domException)
return ::testing::AssertionFailure() << "exception thrown was not a DOMException";
if (domException->name() != name)
return ::testing::AssertionFailure() << "was " << domException->name();
return ::testing::AssertionSuccess();
}
TEST(V8ScriptValueSerializerTest, RoundTripJSONLikeValue)
{
// Ensure that simple JavaScript objects work.
// There are more exhaustive tests of JavaScript objects in V8.
ScopedEnableV8BasedStructuredClone enable;
V8TestingScope scope;
v8::Local<v8::Value> object = eval("({ foo: [1, 2, 3], bar: 'baz' })", scope);
DCHECK(object->IsObject());
v8::Local<v8::Value> result = roundTrip(object, scope);
ASSERT_TRUE(result->IsObject());
EXPECT_NE(object, result);
EXPECT_EQ(toJSON(object.As<v8::Object>(), scope), toJSON(result.As<v8::Object>(), scope));
}
TEST(V8ScriptValueSerializerTest, ThrowsDataCloneError)
{
// Ensure that a proper DataCloneError DOMException is thrown when issues
// are encountered in V8 (for example, cloning a symbol). It should be an
// instance of DOMException, and it should have a proper descriptive
// message.
ScopedEnableV8BasedStructuredClone enable;
V8TestingScope scope;
ScriptState* scriptState = scope.getScriptState();
ExceptionState exceptionState(scope.isolate(), ExceptionState::ExecutionContext, "Window", "postMessage");
v8::Local<v8::Value> symbol = eval("Symbol()", scope);
DCHECK(symbol->IsSymbol());
ASSERT_FALSE(V8ScriptValueSerializer(scriptState).serialize(symbol, nullptr, exceptionState));
ASSERT_TRUE(hadDOMException("DataCloneError", scriptState, exceptionState));
DOMException* domException = V8DOMException::toImpl(exceptionState.getException().As<v8::Object>());
EXPECT_TRUE(domException->toString().contains("postMessage"));
}
TEST(V8ScriptValueSerializerTest, RethrowsScriptError)
{
// Ensure that other exceptions, like those thrown by script, are properly
// rethrown.
ScopedEnableV8BasedStructuredClone enable;
V8TestingScope scope;
ScriptState* scriptState = scope.getScriptState();
ExceptionState exceptionState(scope.isolate(), ExceptionState::ExecutionContext, "Window", "postMessage");
v8::Local<v8::Value> exception = eval("myException=new Error()", scope);
v8::Local<v8::Value> object = eval("({ get a() { throw myException; }})", scope);
DCHECK(object->IsObject());
ASSERT_FALSE(V8ScriptValueSerializer(scriptState).serialize(object, nullptr, exceptionState));
ASSERT_TRUE(exceptionState.hadException());
EXPECT_EQ(exception, exceptionState.getException());
}
TEST(V8ScriptValueSerializerTest, DeserializationErrorReturnsNull)
{
// If there's a problem during deserialization, it results in null, but no
// exception.
ScopedEnableV8BasedStructuredClone enable;
V8TestingScope scope;
ScriptState* scriptState = scope.getScriptState();
RefPtr<SerializedScriptValue> invalid = SerializedScriptValue::create("invalid data");
v8::Local<v8::Value> result = V8ScriptValueDeserializer(scriptState, invalid).deserialize();
EXPECT_TRUE(result->IsNull());
EXPECT_FALSE(scope.getExceptionState().hadException());
}
TEST(V8ScriptValueSerializerTest, RoundTripImageData)
{
// ImageData objects should serialize and deserialize correctly.
ScopedEnableV8BasedStructuredClone enable;
V8TestingScope scope;
ImageData* imageData = ImageData::create(2, 1, ASSERT_NO_EXCEPTION);
imageData->data()->data()[0] = 200;
v8::Local<v8::Value> wrapper = toV8(imageData, scope.context()->Global(), scope.isolate());
v8::Local<v8::Value> result = roundTrip(wrapper, scope);
ASSERT_TRUE(V8ImageData::hasInstance(result, scope.isolate()));
ImageData* newImageData = V8ImageData::toImpl(result.As<v8::Object>());
EXPECT_NE(imageData, newImageData);
EXPECT_EQ(imageData->size(), newImageData->size());
EXPECT_EQ(imageData->data()->length(), newImageData->data()->length());
EXPECT_EQ(200, newImageData->data()->data()[0]);
}
TEST(V8ScriptValueSerializerTest, DecodeImageData)
{
// Backward compatibility with existing serialized ImageData objects must be
// maintained. Add more cases if the format changes; don't remove tests for
// old versions.
ScopedEnableV8BasedStructuredClone enable;
V8TestingScope scope;
ScriptState* scriptState = scope.getScriptState();
RefPtr<SerializedScriptValue> input = serializedValue({
0xff, 0x09, 0x3f, 0x00, 0x23, 0x02, 0x01, 0x08,
0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
v8::Local<v8::Value> result = V8ScriptValueDeserializer(scriptState, input).deserialize();
ASSERT_TRUE(V8ImageData::hasInstance(result, scope.isolate()));
ImageData* newImageData = V8ImageData::toImpl(result.As<v8::Object>());
EXPECT_EQ(IntSize(2, 1), newImageData->size());
EXPECT_EQ(8u, newImageData->data()->length());
EXPECT_EQ(200, newImageData->data()->data()[0]);
}
} // namespace
} // namespace blink
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