Commit 06c431ff authored by Adam Rice's avatar Adam Rice Committed by Commit Bot

Add spec references for transferable streams

Now that the standard changes for transferable streams have landed, add
comments to the source referencing the spec.

No functional changes.

BUG=894838

Change-Id: I3107c62a361257fb6bad81f45e6d108de4a74622
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2359812Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Commit-Queue: Adam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798972}
parent f4cc5f52
...@@ -456,9 +456,18 @@ void SerializedScriptValue::TransferTransformStreams( ...@@ -456,9 +456,18 @@ void SerializedScriptValue::TransferTransformStreams(
// a MessagePortChannel, and returns the other end as a MessagePort. // a MessagePortChannel, and returns the other end as a MessagePort.
MessagePort* SerializedScriptValue::AddStreamChannel( MessagePort* SerializedScriptValue::AddStreamChannel(
ExecutionContext* execution_context) { ExecutionContext* execution_context) {
// Used for both https://streams.spec.whatwg.org/#rs-transfer and
// https://streams.spec.whatwg.org/#ws-transfer.
// 2. Let port1 be a new MessagePort in the current Realm.
// 3. Let port2 be a new MessagePort in the current Realm.
MessagePortDescriptorPair pipe; MessagePortDescriptorPair pipe;
auto* local_port = MakeGarbageCollected<MessagePort>(*execution_context); auto* local_port = MakeGarbageCollected<MessagePort>(*execution_context);
// 4. Entangle port1 and port2.
local_port->Entangle(pipe.TakePort0()); local_port->Entangle(pipe.TakePort0());
// 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
// « port2 »).
stream_channels_.push_back(MessagePortChannel(pipe.TakePort1())); stream_channels_.push_back(MessagePortChannel(pipe.TakePort1()));
return local_port; return local_port;
} }
......
...@@ -580,18 +580,30 @@ ScriptWrappable* V8ScriptValueDeserializer::ReadDOMObject( ...@@ -580,18 +580,30 @@ ScriptWrappable* V8ScriptValueDeserializer::ReadDOMObject(
index + 1 >= transferred_stream_ports_->size()) { index + 1 >= transferred_stream_ports_->size()) {
return nullptr; return nullptr;
} }
// https://streams.spec.whatwg.org/#ts-transfer
// 1. Let readableRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[readable]], the
// current Realm).
ReadableStream* readable = ReadableStream::Deserialize( ReadableStream* readable = ReadableStream::Deserialize(
script_state_, (*transferred_stream_ports_)[index].Get(), script_state_, (*transferred_stream_ports_)[index].Get(),
exception_state); exception_state);
if (!readable) if (!readable)
return nullptr; return nullptr;
// 2. Let writableRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[writable]], the
// current Realm).
WritableStream* writable = WritableStream::Deserialize( WritableStream* writable = WritableStream::Deserialize(
script_state_, (*transferred_stream_ports_)[index + 1].Get(), script_state_, (*transferred_stream_ports_)[index + 1].Get(),
exception_state); exception_state);
if (!writable) if (!writable)
return nullptr; return nullptr;
// 3. Set value.[[readable]] to readableRecord.[[Deserialized]].
// 4. Set value.[[writable]] to writableRecord.[[Deserialized]].
// 5. Set value.[[backpressure]], value.[[backpressureChangePromise]], and
// value.[[controller]] to undefined.
return MakeGarbageCollected<TransformStream>(readable, writable); return MakeGarbageCollected<TransformStream>(readable, writable);
} }
case kDOMExceptionTag: { case kDOMExceptionTag: {
......
...@@ -580,6 +580,11 @@ bool V8ScriptValueSerializer::WriteDOMObject(ScriptWrappable* wrappable, ...@@ -580,6 +580,11 @@ bool V8ScriptValueSerializer::WriteDOMObject(ScriptWrappable* wrappable,
"because it was not transferred."); "because it was not transferred.");
return false; return false;
} }
// https://streams.spec.whatwg.org/#ts-transfer
// 3. If ! IsReadableStreamLocked(readable) is true, throw a
// "DataCloneError" DOMException.
// 4. If ! IsWritableStreamLocked(writable) is true, throw a
// "DataCloneError" DOMException.
if (stream->Readable()->locked() || stream->Writable()->locked()) { if (stream->Readable()->locked() || stream->Writable()->locked()) {
exception_state.ThrowDOMException( exception_state.ThrowDOMException(
DOMExceptionCode::kDataCloneError, DOMExceptionCode::kDataCloneError,
......
...@@ -1606,20 +1606,38 @@ void ReadableStream::LockAndDisturb(ScriptState* script_state) { ...@@ -1606,20 +1606,38 @@ void ReadableStream::LockAndDisturb(ScriptState* script_state) {
void ReadableStream::Serialize(ScriptState* script_state, void ReadableStream::Serialize(ScriptState* script_state,
MessagePort* port, MessagePort* port,
ExceptionState& exception_state) { ExceptionState& exception_state) {
// https://streams.spec.whatwg.org/#rs-transfer
// 1. If ! IsReadableStreamLocked(value) is true, throw a "DataCloneError"
// DOMException.
if (IsLocked(this)) { if (IsLocked(this)) {
exception_state.ThrowTypeError("Cannot transfer a locked stream"); exception_state.ThrowTypeError("Cannot transfer a locked stream");
return; return;
} }
// Done by SerializedScriptValue::TransferReadableStream():
// 2. Let port1 be a new MessagePort in the current Realm.
// 3. Let port2 be a new MessagePort in the current Realm.
// 4. Entangle port1 and port2.
// 5. Let writable be a new WritableStream in the current Realm.
// 6. Perform ! SetUpCrossRealmTransformWritable(writable, port1).
auto* writable = auto* writable =
CreateCrossRealmTransformWritable(script_state, port, exception_state); CreateCrossRealmTransformWritable(script_state, port, exception_state);
if (exception_state.HadException()) { if (exception_state.HadException()) {
return; return;
} }
// 7. Let promise be ! ReadableStreamPipeTo(value, writable, false, false,
// false).
auto promise = auto promise =
PipeTo(script_state, this, writable, MakeGarbageCollected<PipeOptions>()); PipeTo(script_state, this, writable, MakeGarbageCollected<PipeOptions>());
// 8. Set promise.[[PromiseIsHandled]] to true.
promise.MarkAsHandled(); promise.MarkAsHandled();
// This step is done in a roundabout way by the caller:
// 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
// « port2 »).
} }
ReadableStream* ReadableStream::Deserialize(ScriptState* script_state, ReadableStream* ReadableStream::Deserialize(ScriptState* script_state,
...@@ -1629,6 +1647,17 @@ ReadableStream* ReadableStream::Deserialize(ScriptState* script_state, ...@@ -1629,6 +1647,17 @@ ReadableStream* ReadableStream::Deserialize(ScriptState* script_state,
// run author code. // run author code.
v8::Isolate::AllowJavascriptExecutionScope allow_js( v8::Isolate::AllowJavascriptExecutionScope allow_js(
script_state->GetIsolate()); script_state->GetIsolate());
// https://streams.spec.whatwg.org/#rs-transfer
// These steps are done by V8ScriptValueDeserializer::ReadDOMObject().
// 1. Let deserializedRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
// Realm).
// 2. Let port be deserializedRecord.[[Deserialized]].
// 3. Perform ! SetUpCrossRealmTransformReadable(value, port).
// In the standard |value| contains an uninitialized ReadableStream. In the
// implementation, we create the stream here.
auto* readable = auto* readable =
CreateCrossRealmTransformReadable(script_state, port, exception_state); CreateCrossRealmTransformReadable(script_state, port, exception_state);
if (exception_state.HadException()) { if (exception_state.HadException()) {
......
...@@ -50,7 +50,7 @@ namespace blink { ...@@ -50,7 +50,7 @@ namespace blink {
namespace { namespace {
// These are the types of messages that are sent between peers. // These are the types of messages that are sent between peers.
enum class MessageType { kPull, kCancel, kChunk, kClose, kAbort, kError }; enum class MessageType { kPull, kChunk, kClose, kError };
// Creates a JavaScript object with a null prototype structured like {key1: // Creates a JavaScript object with a null prototype structured like {key1:
// value2, key2: value2}. This is used to create objects to be serialized by // value2, key2: value2}. This is used to create objects to be serialized by
...@@ -101,19 +101,33 @@ void PackAndPostMessage(ScriptState* script_state, ...@@ -101,19 +101,33 @@ void PackAndPostMessage(ScriptState* script_state,
DVLOG(3) << "PackAndPostMessage sending message type " DVLOG(3) << "PackAndPostMessage sending message type "
<< static_cast<int>(type); << static_cast<int>(type);
auto* isolate = script_state->GetIsolate(); auto* isolate = script_state->GetIsolate();
// https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessage
// 1. Let message be OrdinaryObjectCreate(null).
// 2. Perform ! CreateDataProperty(message, "type", type).
// 3. Perform ! CreateDataProperty(message, "value", value).
v8::Local<v8::Object> packed = CreateKeyValueObject( v8::Local<v8::Object> packed = CreateKeyValueObject(
isolate, "t", v8::Number::New(isolate, static_cast<int>(type)), "v", isolate, "t", v8::Number::New(isolate, static_cast<int>(type)), "v",
value); value);
// 4. Let targetPort be the port with which port is entangled, if any;
// otherwise let it be null.
// 5. Let options be «[ "transfer" → « » ]».
// 6. Run the message port post message steps providing targetPort, message,
// and options.
port->postMessage(script_state, ScriptValue(isolate, packed), port->postMessage(script_state, ScriptValue(isolate, packed),
PostMessageOptions::Create(), exception_state); PostMessageOptions::Create(), exception_state);
} }
// Sends a kError message to the remote side, disregarding failure. // Sends a kError message to the remote side, disregarding failure.
void SendError(ScriptState* script_state, void CrossRealmTransformSendError(ScriptState* script_state,
MessagePort* port, MessagePort* port,
v8::Local<v8::Value> error) { v8::Local<v8::Value> error) {
ExceptionState exception_state(script_state->GetIsolate(), ExceptionState exception_state(script_state->GetIsolate(),
ExceptionState::kUnknownContext, "", ""); ExceptionState::kUnknownContext, "", "");
// https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror
// 1. Perform PackAndPostMessage(port, "error", error), discarding the result.
PackAndPostMessage(script_state, port, MessageType::kError, error, PackAndPostMessage(script_state, port, MessageType::kError, error,
exception_state); exception_state);
if (exception_state.HadException()) { if (exception_state.HadException()) {
...@@ -123,21 +137,31 @@ void SendError(ScriptState* script_state, ...@@ -123,21 +137,31 @@ void SendError(ScriptState* script_state,
} }
// Same as PackAndPostMessage(), except that it attempts to handle exceptions by // Same as PackAndPostMessage(), except that it attempts to handle exceptions by
// sending a kError message to the remote side. On failure |error| is set to the // sending a kError message to the remote side. Any error from sending the
// original exception and the function returns false. Any error from sending the
// kError message is ignored. // kError message is ignored.
bool PackAndPostMessageHandlingExceptions(ScriptState* script_state, //
// The calling convention differs slightly from the standard to minimize
// verbosity at the calling sites. The function returns true for a normal
// completion and false for an abrupt completion.When there's an abrupt
// completion result.[[Value]] is stored into |error|.
bool PackAndPostMessageHandlingError(ScriptState* script_state,
MessagePort* port, MessagePort* port,
MessageType type, MessageType type,
v8::Local<v8::Value> value, v8::Local<v8::Value> value,
v8::Local<v8::Value>* error) { v8::Local<v8::Value>* error) {
ExceptionState exception_state(script_state->GetIsolate(), ExceptionState exception_state(script_state->GetIsolate(),
ExceptionState::kUnknownContext, "", ""); ExceptionState::kUnknownContext, "", "");
// https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror
// 1. Let result be PackAndPostMessage(port, type, value).
PackAndPostMessage(script_state, port, type, value, exception_state); PackAndPostMessage(script_state, port, type, value, exception_state);
// 2. If result is an abrupt completion,
if (exception_state.HadException()) { if (exception_state.HadException()) {
// 1. Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
// 3. Return result as a completion record.
*error = exception_state.GetException(); *error = exception_state.GetException();
SendError(script_state, port, *error); CrossRealmTransformSendError(script_state, port, *error);
exception_state.ClearException(); exception_state.ClearException();
return false; return false;
} }
...@@ -179,12 +203,25 @@ class CrossRealmTransformMessageListener final : public NativeEventListener { ...@@ -179,12 +203,25 @@ class CrossRealmTransformMessageListener final : public NativeEventListener {
// The deserializer code called by message->data() looks up the ScriptState // The deserializer code called by message->data() looks up the ScriptState
// from the current context, so we need to make sure it is set. // from the current context, so we need to make sure it is set.
ScriptState::Scope scope(script_state); ScriptState::Scope scope(script_state);
// Common to
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// and
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable.
// 1. Let data be the data of the message.
v8::Local<v8::Value> data = message->data(script_state).V8Value(); v8::Local<v8::Value> data = message->data(script_state).V8Value();
// 2. Assert: Type(data) is Object.
// In the world of the standard, this is guaranteed to be true. In the real
// world, the data could come from a compromised renderer and be malicious.
if (!data->IsObject()) { if (!data->IsObject()) {
DLOG(WARNING) << "Invalid message from peer ignored (not object)"; DLOG(WARNING) << "Invalid message from peer ignored (not object)";
return; return;
} }
// 3. Let type be ! Get(data, "type").
// 4. Let value be ! Get(data, "value").
v8::Local<v8::Value> type; v8::Local<v8::Value> type;
v8::Local<v8::Value> value; v8::Local<v8::Value> value;
if (!UnpackKeyValueObject(script_state, data.As<v8::Object>(), "t", &type, if (!UnpackKeyValueObject(script_state, data.As<v8::Object>(), "t", &type,
...@@ -193,6 +230,8 @@ class CrossRealmTransformMessageListener final : public NativeEventListener { ...@@ -193,6 +230,8 @@ class CrossRealmTransformMessageListener final : public NativeEventListener {
return; return;
} }
// 5. Assert: Type(type) is String
// This implementation uses numbers for types rather than strings.
if (!type->IsNumber()) { if (!type->IsNumber()) {
DLOG(WARNING) << "Invalid message from peer ignored (type is not number)"; DLOG(WARNING) << "Invalid message from peer ignored (type is not number)";
return; return;
...@@ -220,14 +259,24 @@ class CrossRealmTransformErrorListener final : public NativeEventListener { ...@@ -220,14 +259,24 @@ class CrossRealmTransformErrorListener final : public NativeEventListener {
void Invoke(ExecutionContext*, Event*) override { void Invoke(ExecutionContext*, Event*) override {
ScriptState* script_state = target_->GetScriptState(); ScriptState* script_state = target_->GetScriptState();
// Common to
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// and
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable.
// 1. Let error be a new "DataCloneError" DOMException.
const auto* error = const auto* error =
DOMException::Create("chunk could not be cloned", "DataCloneError"); DOMException::Create("chunk could not be cloned", "DataCloneError");
auto* message_port = target_->GetMessagePort(); auto* message_port = target_->GetMessagePort();
v8::Local<v8::Value> error_value = ToV8(error, script_state); v8::Local<v8::Value> error_value = ToV8(error, script_state);
SendError(script_state, message_port, error_value); // 2. Perform ! CrossRealmTransformSendError(port, error).
CrossRealmTransformSendError(script_state, message_port, error_value);
// 4. Disentangle port.
message_port->close(); message_port->close();
target_->HandleError(error_value); target_->HandleError(error_value);
} }
...@@ -287,14 +336,26 @@ class CrossRealmTransformWritable::WriteAlgorithm final ...@@ -287,14 +336,26 @@ class CrossRealmTransformWritable::WriteAlgorithm final
v8::Local<v8::Promise> Run(ScriptState* script_state, v8::Local<v8::Promise> Run(ScriptState* script_state,
int argc, int argc,
v8::Local<v8::Value> argv[]) override { v8::Local<v8::Value> argv[]) override {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// 8. Let writeAlgorithm be the following steps, taking a chunk argument:
DCHECK_EQ(argc, 1); DCHECK_EQ(argc, 1);
auto chunk = argv[0]; auto chunk = argv[0];
// 1. If backpressurePromise is undefined, set backpressurePromise to a
// promise resolved with undefined.
// As an optimization for the common case, we call DoWrite() synchronously
// instead. The difference is not observable because the result is only
// visible asynchronously anyway. This avoids doing an extra allocation and
// creating a TraceWrappertV8Reference.
if (!writable_->backpressure_promise_) { if (!writable_->backpressure_promise_) {
return DoWrite(script_state, chunk); return DoWrite(script_state, chunk);
} }
auto* isolate = script_state->GetIsolate(); auto* isolate = script_state->GetIsolate();
// 2. Return the result of reacting to backpressurePromise with the
// following fulfillment steps:
return StreamThenPromise( return StreamThenPromise(
script_state->GetContext(), script_state->GetContext(),
writable_->backpressure_promise_->V8Promise(isolate), writable_->backpressure_promise_->V8Promise(isolate),
...@@ -337,18 +398,31 @@ class CrossRealmTransformWritable::WriteAlgorithm final ...@@ -337,18 +398,31 @@ class CrossRealmTransformWritable::WriteAlgorithm final
// Sends a chunk over the message port to the readable side. // Sends a chunk over the message port to the readable side.
v8::Local<v8::Promise> DoWrite(ScriptState* script_state, v8::Local<v8::Promise> DoWrite(ScriptState* script_state,
v8::Local<v8::Value> chunk) { v8::Local<v8::Value> chunk) {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// 8. Let writeAlgorithm be the following steps, taking a chunk argument:
// 2. Return the result of reacting to backpressurePromise with the
// following fulfillment steps:
// 1. Set backpressurePromise to a new promise.
writable_->backpressure_promise_ = writable_->backpressure_promise_ =
MakeGarbageCollected<StreamPromiseResolver>(script_state); MakeGarbageCollected<StreamPromiseResolver>(script_state);
v8::Local<v8::Value> error; v8::Local<v8::Value> error;
bool success = PackAndPostMessageHandlingExceptions(
script_state, writable_->message_port_, MessageType::kChunk, chunk, // 2. Let result be PackAndPostMessageHandlingError(port, "chunk",
&error); // chunk).
bool success =
PackAndPostMessageHandlingError(script_state, writable_->message_port_,
MessageType::kChunk, chunk, &error);
// 3. If result is an abrupt completion,
if (!success) { if (!success) {
// 1. Disentangle port.
writable_->message_port_->close(); writable_->message_port_->close();
// 2. Return a promise rejected with result.[[Value]].
return PromiseReject(script_state, error); return PromiseReject(script_state, error);
} }
// 4. Otherwise, return a promise resolved with undefined.
return PromiseResolveWithUndefined(script_state); return PromiseResolveWithUndefined(script_state);
} }
...@@ -367,17 +441,25 @@ class CrossRealmTransformWritable::CloseAlgorithm final ...@@ -367,17 +441,25 @@ class CrossRealmTransformWritable::CloseAlgorithm final
v8::Local<v8::Value> argv[]) override { v8::Local<v8::Value> argv[]) override {
DCHECK_EQ(argc, 0); DCHECK_EQ(argc, 0);
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// 9. Let closeAlgorithm be the folowing steps:
v8::Local<v8::Value> error; v8::Local<v8::Value> error;
bool success = PackAndPostMessageHandlingExceptions( // 1. Perform ! PackAndPostMessage(port, "close", undefined).
// In the standard, this can't fail. However, in the implementation failure
// is possible, so we have to handle it.
bool success = PackAndPostMessageHandlingError(
script_state, writable_->message_port_, MessageType::kClose, script_state, writable_->message_port_, MessageType::kClose,
v8::Undefined(script_state->GetIsolate()), &error); v8::Undefined(script_state->GetIsolate()), &error);
// 2. Disentangle port.
writable_->message_port_->close(); writable_->message_port_->close();
// Error the stream if an error occurred.
if (!success) { if (!success) {
return PromiseReject(script_state, error); return PromiseReject(script_state, error);
} }
// 3. Return a promise resolved with undefined.
return PromiseResolveWithUndefined(script_state); return PromiseResolveWithUndefined(script_state);
} }
...@@ -400,20 +482,29 @@ class CrossRealmTransformWritable::AbortAlgorithm final ...@@ -400,20 +482,29 @@ class CrossRealmTransformWritable::AbortAlgorithm final
v8::Local<v8::Promise> Run(ScriptState* script_state, v8::Local<v8::Promise> Run(ScriptState* script_state,
int argc, int argc,
v8::Local<v8::Value> argv[]) override { v8::Local<v8::Value> argv[]) override {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// 10. Let abortAlgorithm be the following steps, taking a reason argument:
DCHECK_EQ(argc, 1); DCHECK_EQ(argc, 1);
auto reason = argv[0]; auto reason = argv[0];
v8::Local<v8::Value> error; v8::Local<v8::Value> error;
bool success = PackAndPostMessageHandlingExceptions(
script_state, writable_->message_port_, MessageType::kAbort, reason,
&error);
// 1. Let result be PackAndPostMessageHandlingError(port, "error",
// reason).
bool success =
PackAndPostMessageHandlingError(script_state, writable_->message_port_,
MessageType::kError, reason, &error);
// 2. Disentangle port.
writable_->message_port_->close(); writable_->message_port_->close();
// 3. If result is an abrupt completion, return a promise rejected with
// result.[[Value]].
if (!success) { if (!success) {
return PromiseReject(script_state, error); return PromiseReject(script_state, error);
} }
// 4. Otherwise, return a promise resolved with undefined.
return PromiseResolveWithUndefined(script_state); return PromiseResolveWithUndefined(script_state);
} }
...@@ -430,11 +521,29 @@ WritableStream* CrossRealmTransformWritable::CreateWritableStream( ...@@ -430,11 +521,29 @@ WritableStream* CrossRealmTransformWritable::CreateWritableStream(
ExceptionState& exception_state) { ExceptionState& exception_state) {
DCHECK(!controller_) << "CreateWritableStream() can only be called once"; DCHECK(!controller_) << "CreateWritableStream() can only be called once";
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// The order of operations is significantly different from the standard, but
// functionally equivalent.
// 3. Let backpressurePromise be a new promise.
// |backpressure_promise_| is initialized by the constructor.
// 4. Add a handler for port’s message event with the following steps:
// 6. Enable port’s port message queue.
message_port_->setOnmessage( message_port_->setOnmessage(
MakeGarbageCollected<CrossRealmTransformMessageListener>(this)); MakeGarbageCollected<CrossRealmTransformMessageListener>(this));
// 5. Add a handler for port’s messageerror event with the following steps:
message_port_->setOnmessageerror( message_port_->setOnmessageerror(
MakeGarbageCollected<CrossRealmTransformErrorListener>(this)); MakeGarbageCollected<CrossRealmTransformErrorListener>(this));
// 1. Perform ! InitializeWritableStream(stream).
// 2. Let controller be a new WritableStreamDefaultController.
// 7. Let startAlgorithm be an algorithm that returns undefined.
// 11. Let sizeAlgorithm be an algorithm that returns 1.
// 12. Perform ! SetUpWritableStreamDefaultController(stream, controller,
// startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, 1,
// sizeAlgorithm).
auto* stream = auto* stream =
WritableStream::Create(script_state_, CreateTrivialStartAlgorithm(), WritableStream::Create(script_state_, CreateTrivialStartAlgorithm(),
MakeGarbageCollected<WriteAlgorithm>(this), MakeGarbageCollected<WriteAlgorithm>(this),
...@@ -452,24 +561,35 @@ WritableStream* CrossRealmTransformWritable::CreateWritableStream( ...@@ -452,24 +561,35 @@ WritableStream* CrossRealmTransformWritable::CreateWritableStream(
void CrossRealmTransformWritable::HandleMessage(MessageType type, void CrossRealmTransformWritable::HandleMessage(MessageType type,
v8::Local<v8::Value> value) { v8::Local<v8::Value> value) {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// 4. Add a handler for port’s message event with the following steps:
// The initial steps are done by CrossRealmTransformMessageListener
switch (type) { switch (type) {
// 6. If type is "pull",
case MessageType::kPull: case MessageType::kPull:
// 1. If backpressurePromise is not undefined,
if (backpressure_promise_) { if (backpressure_promise_) {
// 1. Resolve backpressurePromise with undefined.
backpressure_promise_->ResolveWithUndefined(script_state_); backpressure_promise_->ResolveWithUndefined(script_state_);
// 2. Set backpressurePromise to undefined.
backpressure_promise_ = nullptr; backpressure_promise_ = nullptr;
} }
return; return;
case MessageType::kCancel: // 7. Otherwise if type is "error",
case MessageType::kError: { case MessageType::kError:
// 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller,
// value).
WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_, WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_,
value); value);
// 2. If backpressurePromise is not undefined,
if (backpressure_promise_) { if (backpressure_promise_) {
// 1. Resolve backpressurePromise with undefined.
// 2. Set backpressurePromise to undefined.
backpressure_promise_->ResolveWithUndefined(script_state_); backpressure_promise_->ResolveWithUndefined(script_state_);
backpressure_promise_ = nullptr; backpressure_promise_ = nullptr;
} }
return; return;
}
default: default:
DLOG(WARNING) << "Invalid message from peer ignored (invalid type): " DLOG(WARNING) << "Invalid message from peer ignored (invalid type): "
...@@ -479,6 +599,14 @@ void CrossRealmTransformWritable::HandleMessage(MessageType type, ...@@ -479,6 +599,14 @@ void CrossRealmTransformWritable::HandleMessage(MessageType type,
} }
void CrossRealmTransformWritable::HandleError(v8::Local<v8::Value> error) { void CrossRealmTransformWritable::HandleError(v8::Local<v8::Value> error) {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// 5. Add a handler for port’s messageerror event with the following steps:
// The first two steps, and the last step, are performed by
// CrossRealmTransformErrorListener.
// 3. Perform ! WritableStreamDefaultControllerError(controller, error).
// TODO(ricea): Fix the standard to say ErrorIfNeeded and update the above
// line once that is done.
WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_, WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_,
error); error);
} }
...@@ -511,7 +639,6 @@ class CrossRealmTransformReadable final : public CrossRealmTransformStream { ...@@ -511,7 +639,6 @@ class CrossRealmTransformReadable final : public CrossRealmTransformStream {
const Member<ScriptState> script_state_; const Member<ScriptState> script_state_;
const Member<MessagePort> message_port_; const Member<MessagePort> message_port_;
Member<ReadableStreamDefaultController> controller_; Member<ReadableStreamDefaultController> controller_;
bool finished_ = false;
}; };
class CrossRealmTransformReadable::PullAlgorithm final class CrossRealmTransformReadable::PullAlgorithm final
...@@ -528,8 +655,15 @@ class CrossRealmTransformReadable::PullAlgorithm final ...@@ -528,8 +655,15 @@ class CrossRealmTransformReadable::PullAlgorithm final
DCHECK_EQ(argc, 0); DCHECK_EQ(argc, 0);
auto* isolate = script_state->GetIsolate(); auto* isolate = script_state->GetIsolate();
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// 7. Let pullAlgorithm be the following steps:
v8::Local<v8::Value> error; v8::Local<v8::Value> error;
bool success = PackAndPostMessageHandlingExceptions(
// 1. Perform ! PackAndPostMessage(port, "pull", undefined).
// In the standard this can't throw an exception, but in the implementation
// it can, so we need to be able to handle it.
bool success = PackAndPostMessageHandlingError(
script_state, readable_->message_port_, MessageType::kPull, script_state, readable_->message_port_, MessageType::kPull,
v8::Undefined(isolate), &error); v8::Undefined(isolate), &error);
...@@ -538,6 +672,7 @@ class CrossRealmTransformReadable::PullAlgorithm final ...@@ -538,6 +672,7 @@ class CrossRealmTransformReadable::PullAlgorithm final
return PromiseReject(script_state, error); return PromiseReject(script_state, error);
} }
// 2. Return a promise resolved with undefined.
// The Streams Standard guarantees that PullAlgorithm won't be called again // The Streams Standard guarantees that PullAlgorithm won't be called again
// until Enqueue() is called. // until Enqueue() is called.
return PromiseResolveWithUndefined(script_state); return PromiseResolveWithUndefined(script_state);
...@@ -562,21 +697,29 @@ class CrossRealmTransformReadable::CancelAlgorithm final ...@@ -562,21 +697,29 @@ class CrossRealmTransformReadable::CancelAlgorithm final
v8::Local<v8::Promise> Run(ScriptState* script_state, v8::Local<v8::Promise> Run(ScriptState* script_state,
int argc, int argc,
v8::Local<v8::Value> argv[]) override { v8::Local<v8::Value> argv[]) override {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// 8. Let cancelAlgorithm be the following steps, taking a reason argument:
DCHECK_EQ(argc, 1); DCHECK_EQ(argc, 1);
auto reason = argv[0]; auto reason = argv[0];
readable_->finished_ = true;
v8::Local<v8::Value> error; v8::Local<v8::Value> error;
bool success = PackAndPostMessageHandlingExceptions(
script_state, readable_->message_port_, MessageType::kCancel, reason,
&error);
// 1. Let result be PackAndPostMessageHandlingError(port, "error",
// reason).
bool success =
PackAndPostMessageHandlingError(script_state, readable_->message_port_,
MessageType::kError, reason, &error);
// 2. Disentangle port.
readable_->message_port_->close(); readable_->message_port_->close();
// 3. If result is an abrupt completion, return a promise rejected with
// result.[[Value]].
if (!success) { if (!success) {
return PromiseReject(script_state, error); return PromiseReject(script_state, error);
} }
// 4. Otherwise, return a promise resolved with undefined.
return PromiseResolveWithUndefined(script_state); return PromiseResolveWithUndefined(script_state);
} }
...@@ -593,11 +736,25 @@ ReadableStream* CrossRealmTransformReadable::CreateReadableStream( ...@@ -593,11 +736,25 @@ ReadableStream* CrossRealmTransformReadable::CreateReadableStream(
ExceptionState& exception_state) { ExceptionState& exception_state) {
DCHECK(!controller_) << "CreateReadableStream can only be called once"; DCHECK(!controller_) << "CreateReadableStream can only be called once";
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// The order of operations is significantly different from the standard, but
// functionally equivalent.
// 3. Add a handler for port’s message event with the following steps:
// 5. Enable port’s port message queue.
message_port_->setOnmessage( message_port_->setOnmessage(
MakeGarbageCollected<CrossRealmTransformMessageListener>(this)); MakeGarbageCollected<CrossRealmTransformMessageListener>(this));
// 4. Add a handler for port’s messageerror event with the following steps:
message_port_->setOnmessageerror( message_port_->setOnmessageerror(
MakeGarbageCollected<CrossRealmTransformErrorListener>(this)); MakeGarbageCollected<CrossRealmTransformErrorListener>(this));
// 6. Let startAlgorithm be an algorithm that returns undefined.
// 7. Let pullAlgorithm be the following steps:
// 8. Let cancelAlgorithm be the following steps, taking a reason argument:
// 9. Let sizeAlgorithm be an algorithm that returns 1.
// 10. Perform ! SetUpReadableStreamDefaultController(stream, controller,
// startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, sizeAlgorithm).
auto* stream = ReadableStream::Create( auto* stream = ReadableStream::Create(
script_state_, CreateTrivialStartAlgorithm(), script_state_, CreateTrivialStartAlgorithm(),
MakeGarbageCollected<PullAlgorithm>(this), MakeGarbageCollected<PullAlgorithm>(this),
...@@ -614,8 +771,16 @@ ReadableStream* CrossRealmTransformReadable::CreateReadableStream( ...@@ -614,8 +771,16 @@ ReadableStream* CrossRealmTransformReadable::CreateReadableStream(
void CrossRealmTransformReadable::HandleMessage(MessageType type, void CrossRealmTransformReadable::HandleMessage(MessageType type,
v8::Local<v8::Value> value) { v8::Local<v8::Value> value) {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// 3. Add a handler for port’s message event with the following steps:
// The first 5 steps are handled by CrossRealmTransformMessageListener.
switch (type) { switch (type) {
case MessageType::kChunk: { // 6. If type is "chunk",
case MessageType::kChunk:
// 1. Perform ! ReadableStreamDefaultControllerEnqueue(controller,
// value).
// TODO(ricea): Update ReadableStreamDefaultController::Enqueue() to match
// the standard so this extra check is not needed.
if (ReadableStreamDefaultController::CanCloseOrEnqueue(controller_)) { if (ReadableStreamDefaultController::CanCloseOrEnqueue(controller_)) {
// This can't throw because we always use the default strategy size // This can't throw because we always use the default strategy size
// algorithm, which doesn't throw, and always returns a valid value of // algorithm, which doesn't throw, and always returns a valid value of
...@@ -624,23 +789,28 @@ void CrossRealmTransformReadable::HandleMessage(MessageType type, ...@@ -624,23 +789,28 @@ void CrossRealmTransformReadable::HandleMessage(MessageType type,
value, ASSERT_NO_EXCEPTION); value, ASSERT_NO_EXCEPTION);
} }
return; return;
}
// 7. Otherwise, if type is "close",
case MessageType::kClose: case MessageType::kClose:
finished_ = true; // 1. Perform ! ReadableStreamDefaultControllerClose(controller).
// TODO(ricea): Update ReadableStreamDefaultController::Close() to match
// the standard so this extra check is not needed.
if (ReadableStreamDefaultController::CanCloseOrEnqueue(controller_)) { if (ReadableStreamDefaultController::CanCloseOrEnqueue(controller_)) {
ReadableStreamDefaultController::Close(script_state_, controller_); ReadableStreamDefaultController::Close(script_state_, controller_);
} }
// Disentangle port.
message_port_->close(); message_port_->close();
return; return;
case MessageType::kAbort: // 8. Otherwise, if type is "error",
case MessageType::kError: { case MessageType::kError:
finished_ = true; // 1. Perform ! ReadableStreamDefaultControllerError(controller, value).
ReadableStreamDefaultController::Error(script_state_, controller_, value); ReadableStreamDefaultController::Error(script_state_, controller_, value);
// 2. Disentangle port.
message_port_->close(); message_port_->close();
return; return;
}
default: default:
DLOG(WARNING) << "Invalid message from peer ignored (invalid type): " DLOG(WARNING) << "Invalid message from peer ignored (invalid type): "
...@@ -650,6 +820,12 @@ void CrossRealmTransformReadable::HandleMessage(MessageType type, ...@@ -650,6 +820,12 @@ void CrossRealmTransformReadable::HandleMessage(MessageType type,
} }
void CrossRealmTransformReadable::HandleError(v8::Local<v8::Value> error) { void CrossRealmTransformReadable::HandleError(v8::Local<v8::Value> error) {
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// 4. Add a handler for port’s messageerror event with the following steps:
// The first two steps, and the last step, are performed by
// CrossRealmTransformErrorListener.
// 3. Perform ! ReadableStreamDefaultControllerError(controller, error).
ReadableStreamDefaultController::Error(script_state_, controller_, error); ReadableStreamDefaultController::Error(script_state_, controller_, error);
} }
......
...@@ -20,12 +20,16 @@ class WritableStream; ...@@ -20,12 +20,16 @@ class WritableStream;
// Creates the writable side of a cross-realm identity transform stream, using // Creates the writable side of a cross-realm identity transform stream, using
// |port| for communication. |port| must be entangled with another MessagePort // |port| for communication. |port| must be entangled with another MessagePort
// which is passed to CreateCrossRealmTransformReadable(). // which is passed to CreateCrossRealmTransformReadable().
// Equivalent to SetUpCrossRealmTransformWritable in the standard:
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
CORE_EXPORT WritableStream* CreateCrossRealmTransformWritable(ScriptState*, CORE_EXPORT WritableStream* CreateCrossRealmTransformWritable(ScriptState*,
MessagePort* port, MessagePort* port,
ExceptionState&); ExceptionState&);
// Creates the readable side of a cross-realm identity transform stream. |port| // Creates the readable side of a cross-realm identity transform stream. |port|
// is used symmetrically with CreateCrossRealmTransformWritable(). // is used symmetrically with CreateCrossRealmTransformWritable().
// Equivalent to SetUpCrossRealmTransformReadable in the standard:
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
CORE_EXPORT ReadableStream* CreateCrossRealmTransformReadable(ScriptState*, CORE_EXPORT ReadableStream* CreateCrossRealmTransformReadable(ScriptState*,
MessagePort* port, MessagePort* port,
ExceptionState&); ExceptionState&);
......
...@@ -225,21 +225,39 @@ WritableStream* WritableStream::CreateWithCountQueueingStrategy( ...@@ -225,21 +225,39 @@ WritableStream* WritableStream::CreateWithCountQueueingStrategy(
void WritableStream::Serialize(ScriptState* script_state, void WritableStream::Serialize(ScriptState* script_state,
MessagePort* port, MessagePort* port,
ExceptionState& exception_state) { ExceptionState& exception_state) {
// https://streams.spec.whatwg.org/#ws-transfer
// 1. If ! IsWritableStreamLocked(value) is true, throw a "DataCloneError"
// DOMException.
if (IsLocked(this)) { if (IsLocked(this)) {
exception_state.ThrowTypeError("Cannot transfer a locked stream"); exception_state.ThrowTypeError("Cannot transfer a locked stream");
return; return;
} }
// Done by SerializedScriptValue::TransferWritableStream():
// 2. Let port1 be a new MessagePort in the current Realm.
// 3. Let port2 be a new MessagePort in the current Realm.
// 4. Entangle port1 and port2.
// 5. Let readable be a new ReadableStream in the current Realm.
// 6. Perform ! SetUpCrossRealmTransformReadable(readable, port1).
auto* readable = auto* readable =
CreateCrossRealmTransformReadable(script_state, port, exception_state); CreateCrossRealmTransformReadable(script_state, port, exception_state);
if (exception_state.HadException()) { if (exception_state.HadException()) {
return; return;
} }
// 7. Let promise be ! ReadableStreamPipeTo(readable, value, false, false,
// false).
auto promise = ReadableStream::PipeTo( auto promise = ReadableStream::PipeTo(
script_state, readable, this, script_state, readable, this,
MakeGarbageCollected<ReadableStream::PipeOptions>()); MakeGarbageCollected<ReadableStream::PipeOptions>());
// 8. Set promise.[[PromiseIsHandled]] to true.
promise.MarkAsHandled(); promise.MarkAsHandled();
// This step is done in a roundabout way by the caller:
// 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, «
// port2 »).
} }
WritableStream* WritableStream::Deserialize(ScriptState* script_state, WritableStream* WritableStream::Deserialize(ScriptState* script_state,
...@@ -249,6 +267,17 @@ WritableStream* WritableStream::Deserialize(ScriptState* script_state, ...@@ -249,6 +267,17 @@ WritableStream* WritableStream::Deserialize(ScriptState* script_state,
// run author code. // run author code.
v8::Isolate::AllowJavascriptExecutionScope allow_js( v8::Isolate::AllowJavascriptExecutionScope allow_js(
script_state->GetIsolate()); script_state->GetIsolate());
// https://streams.spec.whatwg.org/#ws-transfer
// These step is done by V8ScriptValueDeserializer::ReadDOMObject().
// 1. Let deserializedRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
// Realm).
// 2. Let port be deserializedRecord.[[Deserialized]].
// 3. Perform ! SetUpCrossRealmTransformWritable(value, port).
// In the standard |value| contains an unitialized WritableStream. In the
// implementation, we create the stream here.
auto* writable = auto* writable =
CreateCrossRealmTransformWritable(script_state, port, exception_state); CreateCrossRealmTransformWritable(script_state, port, exception_state);
if (exception_state.HadException()) { if (exception_state.HadException()) {
......
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