Commit 37ad5a5c authored by Adam Rice's avatar Adam Rice Committed by Commit Bot

Support transfer of WritableStream

Allow WritableStream objects to be transferred with postMessage(). Add
support for serialization and deserialization of WritableStreams.

WritableStreams share the stream_ports array with ReadableStreams, using
indexes starting after the last ReadableStream in the array.

As with ReadableStreams, the functionality is hidden behind the
TransferableStreams blink feature, which much be enabled explicitly for
it to work.

This CL only contains rudimentary tests. In-depth layout tests will be
added later.

BUG=894838

Change-Id: I237f740e4750faa5976481baa161c85bacd51687
Reviewed-on: https://chromium-review.googlesource.com/c/1353034Reviewed-by: default avatarYuki Shiino <yukishiino@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Commit-Queue: Adam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612151}
parent 945049a7
...@@ -68,6 +68,7 @@ enum SerializationTag { ...@@ -68,6 +68,7 @@ enum SerializationTag {
// OffscreenCanvas. For OffscreenCanvas // OffscreenCanvas. For OffscreenCanvas
// transfer // transfer
kReadableStreamTransferTag = 'r', // index:uint32_t kReadableStreamTransferTag = 'r', // index:uint32_t
kWritableStreamTransferTag = 'w', // index:uint32_t
kDOMPointTag = 'Q', // x:Double, y:Double, z:Double, w:Double kDOMPointTag = 'Q', // x:Double, y:Double, z:Double, w:Double
kDOMPointReadOnlyTag = 'W', // x:Double, y:Double, z:Double, w:Double kDOMPointReadOnlyTag = 'W', // x:Double, y:Double, z:Double, w:Double
kDOMRectTag = 'E', // x:Double, y:Double, width:Double, height:Double kDOMRectTag = 'E', // x:Double, y:Double, width:Double, height:Double
......
...@@ -49,10 +49,12 @@ ...@@ -49,10 +49,12 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_offscreen_canvas.h" #include "third_party/blink/renderer/bindings/core/v8/v8_offscreen_canvas.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream.h" #include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_shared_array_buffer.h" #include "third_party/blink/renderer/bindings/core/v8/v8_shared_array_buffer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_writable_stream.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/messaging/message_port.h" #include "third_party/blink/renderer/core/messaging/message_port.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h" #include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.h" #include "third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.h"
#include "third_party/blink/renderer/platform/bindings/dom_data_store.h" #include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
...@@ -394,6 +396,22 @@ void SerializedScriptValue::TransferReadableStreams( ...@@ -394,6 +396,22 @@ void SerializedScriptValue::TransferReadableStreams(
} }
} }
void SerializedScriptValue::TransferWritableStreams(
ScriptState* script_state,
const WritableStreamArray& writable_streams,
ExceptionState& exception_state) {
auto* execution_context = ExecutionContext::From(script_state);
for (WritableStream* writable_stream : writable_streams) {
mojo::MessagePipe pipe;
MessagePort* local_port = MessagePort::Create(*execution_context);
local_port->Entangle(std::move(pipe.handle0));
writable_stream->Serialize(script_state, local_port, exception_state);
if (exception_state.HadException())
return;
stream_channels_.push_back(MessagePortChannel(std::move(pipe.handle1)));
}
}
void SerializedScriptValue::TransferArrayBuffers( void SerializedScriptValue::TransferArrayBuffers(
v8::Isolate* isolate, v8::Isolate* isolate,
const ArrayBufferArray& array_buffers, const ArrayBufferArray& array_buffers,
...@@ -562,6 +580,18 @@ bool SerializedScriptValue::ExtractTransferables( ...@@ -562,6 +580,18 @@ bool SerializedScriptValue::ExtractTransferables(
return false; return false;
} }
transferables.readable_streams.push_back(stream); transferables.readable_streams.push_back(stream);
} else if (RuntimeEnabledFeatures::TransferableStreamsEnabled() &&
V8WritableStream::HasInstance(transferable_object, isolate)) {
WritableStream* stream = V8WritableStream::ToImpl(
v8::Local<v8::Object>::Cast(transferable_object));
if (transferables.writable_streams.Contains(stream)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kDataCloneError,
"WritableStream at index " + String::Number(i) +
" is a duplicate of an earlier WritableStream.");
return false;
}
transferables.writable_streams.push_back(stream);
} else { } else {
exception_state.ThrowTypeError("Value at index " + String::Number(i) + exception_state.ThrowTypeError("Value at index " + String::Number(i) +
" does not have a transferable type."); " does not have a transferable type.");
......
...@@ -295,6 +295,9 @@ class CORE_EXPORT SerializedScriptValue ...@@ -295,6 +295,9 @@ class CORE_EXPORT SerializedScriptValue
void TransferReadableStreams(ScriptState*, void TransferReadableStreams(ScriptState*,
const ReadableStreamArray&, const ReadableStreamArray&,
ExceptionState&); ExceptionState&);
void TransferWritableStreams(ScriptState*,
const WritableStreamArray&,
ExceptionState&);
void CloneSharedArrayBuffers(SharedArrayBufferArray&); void CloneSharedArrayBuffers(SharedArrayBufferArray&);
DataBufferPtr data_buffer_; DataBufferPtr data_buffer_;
size_t data_buffer_size_ = 0; size_t data_buffer_size_ = 0;
......
...@@ -18,6 +18,7 @@ class OffscreenCanvas; ...@@ -18,6 +18,7 @@ class OffscreenCanvas;
class MessagePort; class MessagePort;
class MojoHandle; class MojoHandle;
class ReadableStream; class ReadableStream;
class WritableStream;
using ArrayBufferArray = HeapVector<Member<DOMArrayBufferBase>>; using ArrayBufferArray = HeapVector<Member<DOMArrayBufferBase>>;
using ImageBitmapArray = HeapVector<Member<ImageBitmap>>; using ImageBitmapArray = HeapVector<Member<ImageBitmap>>;
...@@ -25,6 +26,7 @@ using OffscreenCanvasArray = HeapVector<Member<OffscreenCanvas>>; ...@@ -25,6 +26,7 @@ using OffscreenCanvasArray = HeapVector<Member<OffscreenCanvas>>;
using MessagePortArray = HeapVector<Member<MessagePort>>; using MessagePortArray = HeapVector<Member<MessagePort>>;
using MojoHandleArray = HeapVector<Member<blink::MojoHandle>>; using MojoHandleArray = HeapVector<Member<blink::MojoHandle>>;
using ReadableStreamArray = HeapVector<Member<ReadableStream>>; using ReadableStreamArray = HeapVector<Member<ReadableStream>>;
using WritableStreamArray = HeapVector<Member<WritableStream>>;
class CORE_EXPORT Transferables final { class CORE_EXPORT Transferables final {
STACK_ALLOCATED(); STACK_ALLOCATED();
...@@ -39,6 +41,7 @@ class CORE_EXPORT Transferables final { ...@@ -39,6 +41,7 @@ class CORE_EXPORT Transferables final {
MessagePortArray message_ports; MessagePortArray message_ports;
MojoHandleArray mojo_handles; MojoHandleArray mojo_handles;
ReadableStreamArray readable_streams; ReadableStreamArray readable_streams;
WritableStreamArray writable_streams;
}; };
// Along with extending |Transferables| to hold a new kind of transferable // Along with extending |Transferables| to hold a new kind of transferable
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include "third_party/blink/renderer/core/mojo/mojo_handle.h" #include "third_party/blink/renderer/core/mojo/mojo_handle.h"
#include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h" #include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h" #include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.h" #include "third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
...@@ -523,6 +524,18 @@ ScriptWrappable* V8ScriptValueDeserializer::ReadDOMObject( ...@@ -523,6 +524,18 @@ ScriptWrappable* V8ScriptValueDeserializer::ReadDOMObject(
script_state_, (*transferred_stream_ports_)[index].Get(), script_state_, (*transferred_stream_ports_)[index].Get(),
exception_state); exception_state);
} }
case kWritableStreamTransferTag: {
if (!RuntimeEnabledFeatures::TransferableStreamsEnabled())
return nullptr;
uint32_t index = 0;
if (!ReadUint32(&index) || !transferred_stream_ports_ ||
index >= transferred_stream_ports_->size()) {
return nullptr;
}
return WritableStream::Deserialize(
script_state_, (*transferred_stream_ports_)[index].Get(),
exception_state);
}
default: default:
break; break;
} }
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream.h" #include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_shared_array_buffer.h" #include "third_party/blink/renderer/bindings/core/v8/v8_shared_array_buffer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h" #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_writable_stream.h"
#include "third_party/blink/renderer/core/geometry/dom_matrix.h" #include "third_party/blink/renderer/core/geometry/dom_matrix.h"
#include "third_party/blink/renderer/core/geometry/dom_matrix_read_only.h" #include "third_party/blink/renderer/core/geometry/dom_matrix_read_only.h"
#include "third_party/blink/renderer/core/geometry/dom_point.h" #include "third_party/blink/renderer/core/geometry/dom_point.h"
...@@ -35,6 +36,7 @@ ...@@ -35,6 +36,7 @@
#include "third_party/blink/renderer/core/html/canvas/image_data.h" #include "third_party/blink/renderer/core/html/canvas/image_data.h"
#include "third_party/blink/renderer/core/mojo/mojo_handle.h" #include "third_party/blink/renderer/core/mojo/mojo_handle.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h" #include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_base.h" #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_base.h"
#include "third_party/blink/renderer/platform/file_metadata.h" #include "third_party/blink/renderer/platform/file_metadata.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
...@@ -172,6 +174,10 @@ void V8ScriptValueSerializer::FinalizeTransfer( ...@@ -172,6 +174,10 @@ void V8ScriptValueSerializer::FinalizeTransfer(
script_state_, transferables_->readable_streams, exception_state); script_state_, transferables_->readable_streams, exception_state);
if (exception_state.HadException()) if (exception_state.HadException())
return; return;
serialized_script_value_->TransferWritableStreams(
script_state_, transferables_->writable_streams, exception_state);
if (exception_state.HadException())
return;
} }
} }
} }
...@@ -486,6 +492,35 @@ bool V8ScriptValueSerializer::WriteDOMObject(ScriptWrappable* wrappable, ...@@ -486,6 +492,35 @@ bool V8ScriptValueSerializer::WriteDOMObject(ScriptWrappable* wrappable,
WriteUint32(static_cast<uint32_t>(index)); WriteUint32(static_cast<uint32_t>(index));
return true; return true;
} }
if (wrapper_type_info == &V8WritableStream::wrapper_type_info &&
RuntimeEnabledFeatures::TransferableStreamsEnabled()) {
WritableStream* stream = wrappable->ToImpl<WritableStream>();
size_t index = kNotFound;
if (transferables_)
index = transferables_->writable_streams.Find(stream);
if (index == kNotFound) {
exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError,
"A WritableStream could not be cloned "
"because it was not transferred.");
return false;
}
if (stream->IsLocked(script_state_, exception_state).value_or(true)) {
if (exception_state.HadException())
return false;
exception_state.ThrowDOMException(
DOMExceptionCode::kDataCloneError,
"A WritableStream could not be cloned because it was locked");
return false;
}
WriteTag(kWritableStreamTransferTag);
DCHECK(transferables_);
// The index calculation depends on the order that TransferReadableStreams
// and TransferWritableStreams are called in
// V8ScriptValueSerializer::FinalizeTransfer.
WriteUint32(
static_cast<uint32_t>(index + transferables_->readable_streams.size()));
return true;
}
return false; return false;
} }
......
This is a testharness.js-based test.
FAIL window.postMessage should be able to transfer a WritableStream Failed to execute 'postMessage' on 'Window': Value at index 0 does not have a transferable type.
FAIL a locked WritableStream should not be transferable assert_throws: postMessage should throw function "() => postMessage(ws, '*', [ws])" threw object "TypeError: Failed to execute 'postMessage' on 'Window': Value at index 0 does not have a transferable type." that is not a DOMException DataCloneError: property "code" is equal to undefined, expected 25
FAIL window.postMessage should be able to transfer a {readable, writable} pair Failed to execute 'postMessage' on 'Window': Value at index 0 does not have a transferable type.
Harness: the test ran to completion.
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script>
'use strict';
promise_test(t => {
const orig = new WritableStream();
const promise = new Promise(resolve => {
addEventListener('message', t.step_func(evt => {
const transferred = evt.data;
assert_equals(transferred.constructor, WritableStream,
'transferred should be a WritableStream in this realm');
assert_true(transferred instanceof WritableStream,
'instanceof check should pass');
// Perform a brand-check on |transferred|.
const writer = WritableStream.prototype.getWriter.call(transferred);
resolve();
}), {once: true});
});
postMessage(orig, '*', [orig]);
assert_true(orig.locked, 'the original stream should be locked');
return promise;
}, 'window.postMessage should be able to transfer a WritableStream');
test(() => {
const ws = new WritableStream();
const writer = ws.getWriter();
assert_throws('DataCloneError', () => postMessage(ws, '*', [ws]),
'postMessage should throw');
}, 'a locked WritableStream should not be transferable');
promise_test(t => {
const {writable, readable} = new TransformStream();
const promise = new Promise(resolve => {
addEventListener('message', t.step_func(async evt => {
const {writable, readable} = evt.data;
const reader = readable.getReader();
const writer = writable.getWriter();
const writerPromises = Promise.all([
writer.write('hi'),
writer.close(),
]);
const {value, done} = await reader.read();
assert_false(done, 'we should not be done');
assert_equals(value, 'hi', 'chunk should have been delivered');
const readResult = await reader.read();
assert_true(readResult.done, 'readable should be closed');
await writerPromises;
resolve();
}), {once: true});
});
postMessage({writable, readable}, '*', [writable, readable]);
return promise;
}, 'window.postMessage should be able to transfer a {readable, writable} pair');
</script>
This is a testharness.js-based test.
PASS window.postMessage should be able to transfer a WritableStream
PASS a locked WritableStream should not be transferable
PASS window.postMessage should be able to transfer a {readable, writable} pair
Harness: the test ran to completion.
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