1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Functions for transferable streams. See design doc
6 // https://docs.google.com/document/d/1_KuZzg5c3pncLJPFa8SuVm23AP4tft6mzPCL5at3I9M/edit
7
8 #include "third_party/blink/renderer/core/streams/transferable_streams.h"
9
10 #include "base/stl_util.h"
11 #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
12 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
13 #include "third_party/blink/renderer/bindings/core/v8/v8_post_message_options.h"
14 #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
15 #include "third_party/blink/renderer/core/dom/dom_exception.h"
16 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
17 #include "third_party/blink/renderer/core/events/message_event.h"
18 #include "third_party/blink/renderer/core/messaging/message_port.h"
19 #include "third_party/blink/renderer/core/streams/miscellaneous_operations.h"
20 #include "third_party/blink/renderer/core/streams/promise_handler.h"
21 #include "third_party/blink/renderer/core/streams/readable_stream.h"
22 #include "third_party/blink/renderer/core/streams/readable_stream_default_controller.h"
23 #include "third_party/blink/renderer/core/streams/stream_algorithms.h"
24 #include "third_party/blink/renderer/core/streams/stream_promise_resolver.h"
25 #include "third_party/blink/renderer/core/streams/writable_stream.h"
26 #include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h"
27 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
28 #include "third_party/blink/renderer/platform/bindings/script_state.h"
29 #include "third_party/blink/renderer/platform/bindings/v8_binding.h"
30 #include "third_party/blink/renderer/platform/heap/heap.h"
31 #include "third_party/blink/renderer/platform/heap/visitor.h"
32 #include "third_party/blink/renderer/platform/wtf/assertions.h"
33 #include "v8/include/v8.h"
34
35 // See the design doc at
36 // https://docs.google.com/document/d/1_KuZzg5c3pncLJPFa8SuVm23AP4tft6mzPCL5at3I9M/edit
37 // for explanation of how transferable streams are constructed from the "cross
38 // realm identity transform" implemented in this file.
39
40 // The peer (the other end of the MessagePort) is untrusted as it may be
41 // compromised. This means we have to be very careful in unpacking the messages
42 // from the peer. LOG(WARNING) is used for cases where a message from the peer
43 // appears to be invalid. If this appears during ordinary testing it indicates a
44 // bug.
45 //
46 // The -vmodule=transferable_streams=3 command-line argument can be used for
47 // debugging of the protocol.
48
49 namespace blink {
50
51 namespace {
52
53 // These are the types of messages that are sent between peers.
54 enum class MessageType { kPull, kChunk, kClose, kError };
55
56 // Creates a JavaScript object with a null prototype structured like {key1:
57 // value2, key2: value2}. This is used to create objects to be serialized by
58 // postMessage.
CreateKeyValueObject(v8::Isolate * isolate,const char * key1,v8::Local<v8::Value> value1,const char * key2,v8::Local<v8::Value> value2)59 v8::Local<v8::Object> CreateKeyValueObject(v8::Isolate* isolate,
60 const char* key1,
61 v8::Local<v8::Value> value1,
62 const char* key2,
63 v8::Local<v8::Value> value2) {
64 v8::Local<v8::Name> names[] = {V8AtomicString(isolate, key1),
65 V8AtomicString(isolate, key2)};
66 v8::Local<v8::Value> values[] = {value1, value2};
67 static_assert(base::size(names) == base::size(values),
68 "names and values arrays must be the same size");
69 return v8::Object::New(isolate, v8::Null(isolate), names, values,
70 base::size(names));
71 }
72
73 // Unpacks an object created by CreateKeyValueObject(). |value1| and |value2|
74 // are out parameters. Returns false on failure.
UnpackKeyValueObject(ScriptState * script_state,v8::Local<v8::Object> object,const char * key1,v8::Local<v8::Value> * value1,const char * key2,v8::Local<v8::Value> * value2)75 bool UnpackKeyValueObject(ScriptState* script_state,
76 v8::Local<v8::Object> object,
77 const char* key1,
78 v8::Local<v8::Value>* value1,
79 const char* key2,
80 v8::Local<v8::Value>* value2) {
81 auto* isolate = script_state->GetIsolate();
82 v8::TryCatch try_catch(isolate);
83 auto context = script_state->GetContext();
84 if (!object->Get(context, V8AtomicString(isolate, key1)).ToLocal(value1)) {
85 DLOG(WARNING) << "Error reading key: '" << key1 << "'";
86 return false;
87 }
88 if (!object->Get(context, V8AtomicString(isolate, key2)).ToLocal(value2)) {
89 DLOG(WARNING) << "Error reading key: '" << key2 << "'";
90 return false;
91 }
92 return true;
93 }
94
95 // Sends a message with type |type| and contents |value| over |port|. The type
96 // is packed as a number with key "t", and the value is packed with key "v".
PackAndPostMessage(ScriptState * script_state,MessagePort * port,MessageType type,v8::Local<v8::Value> value,ExceptionState & exception_state)97 void PackAndPostMessage(ScriptState* script_state,
98 MessagePort* port,
99 MessageType type,
100 v8::Local<v8::Value> value,
101 ExceptionState& exception_state) {
102 DVLOG(3) << "PackAndPostMessage sending message type "
103 << static_cast<int>(type);
104 auto* isolate = script_state->GetIsolate();
105
106 // https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessage
107 // 1. Let message be OrdinaryObjectCreate(null).
108 // 2. Perform ! CreateDataProperty(message, "type", type).
109 // 3. Perform ! CreateDataProperty(message, "value", value).
110 v8::Local<v8::Object> packed = CreateKeyValueObject(
111 isolate, "t", v8::Number::New(isolate, static_cast<int>(type)), "v",
112 value);
113
114 // 4. Let targetPort be the port with which port is entangled, if any;
115 // otherwise let it be null.
116 // 5. Let options be «[ "transfer" → « » ]».
117 // 6. Run the message port post message steps providing targetPort, message,
118 // and options.
119 port->postMessage(script_state, ScriptValue(isolate, packed),
120 PostMessageOptions::Create(), exception_state);
121 }
122
123 // Sends a kError message to the remote side, disregarding failure.
CrossRealmTransformSendError(ScriptState * script_state,MessagePort * port,v8::Local<v8::Value> error)124 void CrossRealmTransformSendError(ScriptState* script_state,
125 MessagePort* port,
126 v8::Local<v8::Value> error) {
127 ExceptionState exception_state(script_state->GetIsolate(),
128 ExceptionState::kUnknownContext, "", "");
129
130 // https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror
131 // 1. Perform PackAndPostMessage(port, "error", error), discarding the result.
132 PackAndPostMessage(script_state, port, MessageType::kError, error,
133 exception_state);
134 if (exception_state.HadException()) {
135 DLOG(WARNING) << "Disregarding exception while sending error";
136 exception_state.ClearException();
137 }
138 }
139
140 // Same as PackAndPostMessage(), except that it attempts to handle exceptions by
141 // sending a kError message to the remote side. Any error from sending the
142 // kError message is ignored.
143 //
144 // The calling convention differs slightly from the standard to minimize
145 // verbosity at the calling sites. The function returns true for a normal
146 // completion and false for an abrupt completion.When there's an abrupt
147 // completion result.[[Value]] is stored into |error|.
PackAndPostMessageHandlingError(ScriptState * script_state,MessagePort * port,MessageType type,v8::Local<v8::Value> value,v8::Local<v8::Value> * error)148 bool PackAndPostMessageHandlingError(ScriptState* script_state,
149 MessagePort* port,
150 MessageType type,
151 v8::Local<v8::Value> value,
152 v8::Local<v8::Value>* error) {
153 ExceptionState exception_state(script_state->GetIsolate(),
154 ExceptionState::kUnknownContext, "", "");
155
156 // https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror
157 // 1. Let result be PackAndPostMessage(port, type, value).
158 PackAndPostMessage(script_state, port, type, value, exception_state);
159
160 // 2. If result is an abrupt completion,
161 if (exception_state.HadException()) {
162 // 1. Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
163 // 3. Return result as a completion record.
164 *error = exception_state.GetException();
165 CrossRealmTransformSendError(script_state, port, *error);
166 exception_state.ClearException();
167 return false;
168 }
169
170 return true;
171 }
172
173 // Base class for CrossRealmTransformWritable and CrossRealmTransformReadable.
174 // Contains common methods that are used when handling MessagePort events.
175 class CrossRealmTransformStream
176 : public GarbageCollected<CrossRealmTransformStream> {
177 public:
178 // Neither of the subclasses require finalization, so no destructor.
179
180 virtual ScriptState* GetScriptState() const = 0;
181 virtual MessagePort* GetMessagePort() const = 0;
182
183 // HandleMessage() is called by CrossRealmTransformMessageListener to handle
184 // an incoming message from the MessagePort.
185 virtual void HandleMessage(MessageType type, v8::Local<v8::Value> value) = 0;
186
187 // HandleError() is called by CrossRealmTransformErrorListener when an error
188 // event is fired on the message port. It should error the stream.
189 virtual void HandleError(v8::Local<v8::Value> error) = 0;
190
Trace(Visitor *) const191 virtual void Trace(Visitor*) const {}
192 };
193
194 // Handles MessageEvents from the MessagePort.
195 class CrossRealmTransformMessageListener final : public NativeEventListener {
196 public:
CrossRealmTransformMessageListener(CrossRealmTransformStream * target)197 explicit CrossRealmTransformMessageListener(CrossRealmTransformStream* target)
198 : target_(target) {}
199
Invoke(ExecutionContext *,Event * event)200 void Invoke(ExecutionContext*, Event* event) override {
201 // TODO(ricea): Find a way to guarantee this cast is safe.
202 MessageEvent* message = static_cast<MessageEvent*>(event);
203 ScriptState* script_state = target_->GetScriptState();
204 // The deserializer code called by message->data() looks up the ScriptState
205 // from the current context, so we need to make sure it is set.
206 ScriptState::Scope scope(script_state);
207
208 // Common to
209 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
210 // and
211 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable.
212
213 // 1. Let data be the data of the message.
214 v8::Local<v8::Value> data = message->data(script_state).V8Value();
215
216 // 2. Assert: Type(data) is Object.
217 // In the world of the standard, this is guaranteed to be true. In the real
218 // world, the data could come from a compromised renderer and be malicious.
219 if (!data->IsObject()) {
220 DLOG(WARNING) << "Invalid message from peer ignored (not object)";
221 return;
222 }
223
224 // 3. Let type be ! Get(data, "type").
225 // 4. Let value be ! Get(data, "value").
226 v8::Local<v8::Value> type;
227 v8::Local<v8::Value> value;
228 if (!UnpackKeyValueObject(script_state, data.As<v8::Object>(), "t", &type,
229 "v", &value)) {
230 DLOG(WARNING) << "Invalid message from peer ignored";
231 return;
232 }
233
234 // 5. Assert: Type(type) is String
235 // This implementation uses numbers for types rather than strings.
236 if (!type->IsNumber()) {
237 DLOG(WARNING) << "Invalid message from peer ignored (type is not number)";
238 return;
239 }
240
241 int type_value = type.As<v8::Number>()->Value();
242 DVLOG(3) << "MessageListener saw message type " << type_value;
243 target_->HandleMessage(static_cast<MessageType>(type_value), value);
244 }
245
Trace(Visitor * visitor) const246 void Trace(Visitor* visitor) const override {
247 visitor->Trace(target_);
248 NativeEventListener::Trace(visitor);
249 }
250
251 private:
252 const Member<CrossRealmTransformStream> target_;
253 };
254
255 // Handles "error" events from the MessagePort.
256 class CrossRealmTransformErrorListener final : public NativeEventListener {
257 public:
CrossRealmTransformErrorListener(CrossRealmTransformStream * target)258 explicit CrossRealmTransformErrorListener(CrossRealmTransformStream* target)
259 : target_(target) {}
260
Invoke(ExecutionContext *,Event *)261 void Invoke(ExecutionContext*, Event*) override {
262 ScriptState* script_state = target_->GetScriptState();
263
264 // Need to enter a script scope to manipulate JavaScript objects.
265 ScriptState::Scope scope(script_state);
266
267 // Common to
268 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
269 // and
270 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable.
271
272 // 1. Let error be a new "DataCloneError" DOMException.
273 v8::Local<v8::Value> error = V8ThrowDOMException::CreateOrEmpty(
274 script_state->GetIsolate(), DOMExceptionCode::kDataCloneError,
275 "chunk could not be cloned");
276
277 // 2. Perform ! CrossRealmTransformSendError(port, error).
278 auto* message_port = target_->GetMessagePort();
279 CrossRealmTransformSendError(script_state, message_port, error);
280
281 // 4. Disentangle port.
282 message_port->close();
283
284 DVLOG(3) << "ErrorListener saw messageerror";
285 target_->HandleError(error);
286 }
287
Trace(Visitor * visitor) const288 void Trace(Visitor* visitor) const override {
289 visitor->Trace(target_);
290 NativeEventListener::Trace(visitor);
291 }
292
293 private:
294 const Member<CrossRealmTransformStream> target_;
295 };
296
297 // Class for data associated with the writable side of the cross realm transform
298 // stream.
299 class CrossRealmTransformWritable final : public CrossRealmTransformStream {
300 public:
CrossRealmTransformWritable(ScriptState * script_state,MessagePort * port)301 CrossRealmTransformWritable(ScriptState* script_state, MessagePort* port)
302 : script_state_(script_state),
303 message_port_(port),
304 backpressure_promise_(
305 MakeGarbageCollected<StreamPromiseResolver>(script_state)) {}
306
307 WritableStream* CreateWritableStream(ExceptionState&);
308
GetScriptState() const309 ScriptState* GetScriptState() const override { return script_state_; }
GetMessagePort() const310 MessagePort* GetMessagePort() const override { return message_port_; }
311 void HandleMessage(MessageType type, v8::Local<v8::Value> value) override;
312 void HandleError(v8::Local<v8::Value> error) override;
313
Trace(Visitor * visitor) const314 void Trace(Visitor* visitor) const override {
315 visitor->Trace(script_state_);
316 visitor->Trace(message_port_);
317 visitor->Trace(backpressure_promise_);
318 visitor->Trace(controller_);
319 CrossRealmTransformStream::Trace(visitor);
320 }
321
322 private:
323 class WriteAlgorithm;
324 class CloseAlgorithm;
325 class AbortAlgorithm;
326
327 const Member<ScriptState> script_state_;
328 const Member<MessagePort> message_port_;
329 Member<StreamPromiseResolver> backpressure_promise_;
330 Member<WritableStreamDefaultController> controller_;
331 };
332
333 class CrossRealmTransformWritable::WriteAlgorithm final
334 : public StreamAlgorithm {
335 public:
WriteAlgorithm(CrossRealmTransformWritable * writable)336 explicit WriteAlgorithm(CrossRealmTransformWritable* writable)
337 : writable_(writable) {}
338
339 // Sends the chunk to the readable side, possibly after waiting for
340 // backpressure.
Run(ScriptState * script_state,int argc,v8::Local<v8::Value> argv[])341 v8::Local<v8::Promise> Run(ScriptState* script_state,
342 int argc,
343 v8::Local<v8::Value> argv[]) override {
344 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
345 // 8. Let writeAlgorithm be the following steps, taking a chunk argument:
346 DCHECK_EQ(argc, 1);
347 auto chunk = argv[0];
348
349 // 1. If backpressurePromise is undefined, set backpressurePromise to a
350 // promise resolved with undefined.
351
352 // As an optimization for the common case, we call DoWrite() synchronously
353 // instead. The difference is not observable because the result is only
354 // visible asynchronously anyway. This avoids doing an extra allocation and
355 // creating a TraceWrappertV8Reference.
356 if (!writable_->backpressure_promise_) {
357 return DoWrite(script_state, chunk);
358 }
359
360 auto* isolate = script_state->GetIsolate();
361
362 // 2. Return the result of reacting to backpressurePromise with the
363 // following fulfillment steps:
364 return StreamThenPromise(
365 script_state->GetContext(),
366 writable_->backpressure_promise_->V8Promise(isolate),
367 MakeGarbageCollected<DoWriteOnResolve>(script_state, chunk, this));
368 }
369
Trace(Visitor * visitor) const370 void Trace(Visitor* visitor) const override {
371 visitor->Trace(writable_);
372 StreamAlgorithm::Trace(visitor);
373 }
374
375 private:
376 // A promise handler which calls DoWrite() when the promise resolves.
377 class DoWriteOnResolve final : public PromiseHandlerWithValue {
378 public:
DoWriteOnResolve(ScriptState * script_state,v8::Local<v8::Value> chunk,WriteAlgorithm * target)379 DoWriteOnResolve(ScriptState* script_state,
380 v8::Local<v8::Value> chunk,
381 WriteAlgorithm* target)
382 : PromiseHandlerWithValue(script_state),
383 chunk_(script_state->GetIsolate(), chunk),
384 target_(target) {}
385
CallWithLocal(v8::Local<v8::Value>)386 v8::Local<v8::Value> CallWithLocal(v8::Local<v8::Value>) override {
387 ScriptState* script_state = GetScriptState();
388 return target_->DoWrite(script_state,
389 chunk_.NewLocal(script_state->GetIsolate()));
390 }
391
Trace(Visitor * visitor) const392 void Trace(Visitor* visitor) const override {
393 visitor->Trace(chunk_);
394 visitor->Trace(target_);
395 PromiseHandlerWithValue::Trace(visitor);
396 }
397
398 private:
399 const TraceWrapperV8Reference<v8::Value> chunk_;
400 const Member<WriteAlgorithm> target_;
401 };
402
403 // Sends a chunk over the message port to the readable side.
DoWrite(ScriptState * script_state,v8::Local<v8::Value> chunk)404 v8::Local<v8::Promise> DoWrite(ScriptState* script_state,
405 v8::Local<v8::Value> chunk) {
406 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
407 // 8. Let writeAlgorithm be the following steps, taking a chunk argument:
408 // 2. Return the result of reacting to backpressurePromise with the
409 // following fulfillment steps:
410 // 1. Set backpressurePromise to a new promise.
411 writable_->backpressure_promise_ =
412 MakeGarbageCollected<StreamPromiseResolver>(script_state);
413
414 v8::Local<v8::Value> error;
415
416 // 2. Let result be PackAndPostMessageHandlingError(port, "chunk",
417 // chunk).
418 bool success =
419 PackAndPostMessageHandlingError(script_state, writable_->message_port_,
420 MessageType::kChunk, chunk, &error);
421 // 3. If result is an abrupt completion,
422 if (!success) {
423 // 1. Disentangle port.
424 writable_->message_port_->close();
425
426 // 2. Return a promise rejected with result.[[Value]].
427 return PromiseReject(script_state, error);
428 }
429
430 // 4. Otherwise, return a promise resolved with undefined.
431 return PromiseResolveWithUndefined(script_state);
432 }
433
434 const Member<CrossRealmTransformWritable> writable_;
435 };
436
437 class CrossRealmTransformWritable::CloseAlgorithm final
438 : public StreamAlgorithm {
439 public:
CloseAlgorithm(CrossRealmTransformWritable * writable)440 explicit CloseAlgorithm(CrossRealmTransformWritable* writable)
441 : writable_(writable) {}
442
443 // Sends a close message to the readable side and closes the message port.
Run(ScriptState * script_state,int argc,v8::Local<v8::Value> argv[])444 v8::Local<v8::Promise> Run(ScriptState* script_state,
445 int argc,
446 v8::Local<v8::Value> argv[]) override {
447 DCHECK_EQ(argc, 0);
448
449 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
450 // 9. Let closeAlgorithm be the folowing steps:
451 v8::Local<v8::Value> error;
452 // 1. Perform ! PackAndPostMessage(port, "close", undefined).
453 // In the standard, this can't fail. However, in the implementation failure
454 // is possible, so we have to handle it.
455 bool success = PackAndPostMessageHandlingError(
456 script_state, writable_->message_port_, MessageType::kClose,
457 v8::Undefined(script_state->GetIsolate()), &error);
458
459 // 2. Disentangle port.
460 writable_->message_port_->close();
461
462 // Error the stream if an error occurred.
463 if (!success) {
464 return PromiseReject(script_state, error);
465 }
466
467 // 3. Return a promise resolved with undefined.
468 return PromiseResolveWithUndefined(script_state);
469 }
470
Trace(Visitor * visitor) const471 void Trace(Visitor* visitor) const override {
472 visitor->Trace(writable_);
473 StreamAlgorithm::Trace(visitor);
474 }
475
476 private:
477 const Member<CrossRealmTransformWritable> writable_;
478 };
479
480 class CrossRealmTransformWritable::AbortAlgorithm final
481 : public StreamAlgorithm {
482 public:
AbortAlgorithm(CrossRealmTransformWritable * writable)483 explicit AbortAlgorithm(CrossRealmTransformWritable* writable)
484 : writable_(writable) {}
485
486 // Sends an abort message to the readable side and closes the message port.
Run(ScriptState * script_state,int argc,v8::Local<v8::Value> argv[])487 v8::Local<v8::Promise> Run(ScriptState* script_state,
488 int argc,
489 v8::Local<v8::Value> argv[]) override {
490 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
491 // 10. Let abortAlgorithm be the following steps, taking a reason argument:
492 DCHECK_EQ(argc, 1);
493 auto reason = argv[0];
494
495 v8::Local<v8::Value> error;
496
497 // 1. Let result be PackAndPostMessageHandlingError(port, "error",
498 // reason).
499 bool success =
500 PackAndPostMessageHandlingError(script_state, writable_->message_port_,
501 MessageType::kError, reason, &error);
502
503 // 2. Disentangle port.
504 writable_->message_port_->close();
505
506 // 3. If result is an abrupt completion, return a promise rejected with
507 // result.[[Value]].
508 if (!success) {
509 return PromiseReject(script_state, error);
510 }
511
512 // 4. Otherwise, return a promise resolved with undefined.
513 return PromiseResolveWithUndefined(script_state);
514 }
515
Trace(Visitor * visitor) const516 void Trace(Visitor* visitor) const override {
517 visitor->Trace(writable_);
518 StreamAlgorithm::Trace(visitor);
519 }
520
521 private:
522 const Member<CrossRealmTransformWritable> writable_;
523 };
524
CreateWritableStream(ExceptionState & exception_state)525 WritableStream* CrossRealmTransformWritable::CreateWritableStream(
526 ExceptionState& exception_state) {
527 DCHECK(!controller_) << "CreateWritableStream() can only be called once";
528
529 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
530 // The order of operations is significantly different from the standard, but
531 // functionally equivalent.
532
533 // 3. Let backpressurePromise be a new promise.
534 // |backpressure_promise_| is initialized by the constructor.
535
536 // 4. Add a handler for port’s message event with the following steps:
537 // 6. Enable port’s port message queue.
538 message_port_->setOnmessage(
539 MakeGarbageCollected<CrossRealmTransformMessageListener>(this));
540
541 // 5. Add a handler for port’s messageerror event with the following steps:
542 message_port_->setOnmessageerror(
543 MakeGarbageCollected<CrossRealmTransformErrorListener>(this));
544
545 // 1. Perform ! InitializeWritableStream(stream).
546 // 2. Let controller be a new WritableStreamDefaultController.
547 // 7. Let startAlgorithm be an algorithm that returns undefined.
548 // 11. Let sizeAlgorithm be an algorithm that returns 1.
549 // 12. Perform ! SetUpWritableStreamDefaultController(stream, controller,
550 // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, 1,
551 // sizeAlgorithm).
552 auto* stream =
553 WritableStream::Create(script_state_, CreateTrivialStartAlgorithm(),
554 MakeGarbageCollected<WriteAlgorithm>(this),
555 MakeGarbageCollected<CloseAlgorithm>(this),
556 MakeGarbageCollected<AbortAlgorithm>(this), 1,
557 CreateDefaultSizeAlgorithm(), exception_state);
558
559 if (exception_state.HadException()) {
560 return nullptr;
561 }
562
563 controller_ = stream->Controller();
564 return stream;
565 }
566
HandleMessage(MessageType type,v8::Local<v8::Value> value)567 void CrossRealmTransformWritable::HandleMessage(MessageType type,
568 v8::Local<v8::Value> value) {
569 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
570 // 4. Add a handler for port’s message event with the following steps:
571 // The initial steps are done by CrossRealmTransformMessageListener
572 switch (type) {
573 // 6. If type is "pull",
574 case MessageType::kPull:
575 // 1. If backpressurePromise is not undefined,
576 if (backpressure_promise_) {
577 // 1. Resolve backpressurePromise with undefined.
578 backpressure_promise_->ResolveWithUndefined(script_state_);
579 // 2. Set backpressurePromise to undefined.
580 backpressure_promise_ = nullptr;
581 }
582 return;
583
584 // 7. Otherwise if type is "error",
585 case MessageType::kError:
586 // 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller,
587 // value).
588 WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_,
589 value);
590 // 2. If backpressurePromise is not undefined,
591 if (backpressure_promise_) {
592 // 1. Resolve backpressurePromise with undefined.
593 // 2. Set backpressurePromise to undefined.
594 backpressure_promise_->ResolveWithUndefined(script_state_);
595 backpressure_promise_ = nullptr;
596 }
597 return;
598
599 default:
600 DLOG(WARNING) << "Invalid message from peer ignored (invalid type): "
601 << static_cast<int>(type);
602 return;
603 }
604 }
605
HandleError(v8::Local<v8::Value> error)606 void CrossRealmTransformWritable::HandleError(v8::Local<v8::Value> error) {
607 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
608 // 5. Add a handler for port’s messageerror event with the following steps:
609 // The first two steps, and the last step, are performed by
610 // CrossRealmTransformErrorListener.
611
612 // 3. Perform ! WritableStreamDefaultControllerError(controller, error).
613 // TODO(ricea): Fix the standard to say ErrorIfNeeded and update the above
614 // line once that is done.
615 WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_,
616 error);
617 }
618
619 // Class for data associated with the readable side of the cross realm transform
620 // stream.
621 class CrossRealmTransformReadable final : public CrossRealmTransformStream {
622 public:
CrossRealmTransformReadable(ScriptState * script_state,MessagePort * port)623 CrossRealmTransformReadable(ScriptState* script_state, MessagePort* port)
624 : script_state_(script_state), message_port_(port) {}
625
626 ReadableStream* CreateReadableStream(ExceptionState&);
627
GetScriptState() const628 ScriptState* GetScriptState() const override { return script_state_; }
GetMessagePort() const629 MessagePort* GetMessagePort() const override { return message_port_; }
630 void HandleMessage(MessageType type, v8::Local<v8::Value> value) override;
631 void HandleError(v8::Local<v8::Value> error) override;
632
Trace(Visitor * visitor) const633 void Trace(Visitor* visitor) const override {
634 visitor->Trace(script_state_);
635 visitor->Trace(message_port_);
636 visitor->Trace(controller_);
637 CrossRealmTransformStream::Trace(visitor);
638 }
639
640 private:
641 class PullAlgorithm;
642 class CancelAlgorithm;
643
644 const Member<ScriptState> script_state_;
645 const Member<MessagePort> message_port_;
646 Member<ReadableStreamDefaultController> controller_;
647 };
648
649 class CrossRealmTransformReadable::PullAlgorithm final
650 : public StreamAlgorithm {
651 public:
PullAlgorithm(CrossRealmTransformReadable * readable)652 explicit PullAlgorithm(CrossRealmTransformReadable* readable)
653 : readable_(readable) {}
654
655 // Sends a pull message to the writable side and then waits for backpressure
656 // to clear.
Run(ScriptState * script_state,int argc,v8::Local<v8::Value> argv[])657 v8::Local<v8::Promise> Run(ScriptState* script_state,
658 int argc,
659 v8::Local<v8::Value> argv[]) override {
660 DCHECK_EQ(argc, 0);
661 auto* isolate = script_state->GetIsolate();
662
663 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
664 // 7. Let pullAlgorithm be the following steps:
665
666 v8::Local<v8::Value> error;
667
668 // 1. Perform ! PackAndPostMessage(port, "pull", undefined).
669 // In the standard this can't throw an exception, but in the implementation
670 // it can, so we need to be able to handle it.
671 bool success = PackAndPostMessageHandlingError(
672 script_state, readable_->message_port_, MessageType::kPull,
673 v8::Undefined(isolate), &error);
674
675 if (!success) {
676 readable_->message_port_->close();
677 return PromiseReject(script_state, error);
678 }
679
680 // 2. Return a promise resolved with undefined.
681 // The Streams Standard guarantees that PullAlgorithm won't be called again
682 // until Enqueue() is called.
683 return PromiseResolveWithUndefined(script_state);
684 }
685
Trace(Visitor * visitor) const686 void Trace(Visitor* visitor) const override {
687 visitor->Trace(readable_);
688 StreamAlgorithm::Trace(visitor);
689 }
690
691 private:
692 const Member<CrossRealmTransformReadable> readable_;
693 };
694
695 class CrossRealmTransformReadable::CancelAlgorithm final
696 : public StreamAlgorithm {
697 public:
CancelAlgorithm(CrossRealmTransformReadable * readable)698 explicit CancelAlgorithm(CrossRealmTransformReadable* readable)
699 : readable_(readable) {}
700
701 // Sends a cancel message to the writable side and closes the message port.
Run(ScriptState * script_state,int argc,v8::Local<v8::Value> argv[])702 v8::Local<v8::Promise> Run(ScriptState* script_state,
703 int argc,
704 v8::Local<v8::Value> argv[]) override {
705 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
706 // 8. Let cancelAlgorithm be the following steps, taking a reason argument:
707 DCHECK_EQ(argc, 1);
708 auto reason = argv[0];
709
710 v8::Local<v8::Value> error;
711
712 // 1. Let result be PackAndPostMessageHandlingError(port, "error",
713 // reason).
714 bool success =
715 PackAndPostMessageHandlingError(script_state, readable_->message_port_,
716 MessageType::kError, reason, &error);
717
718 // 2. Disentangle port.
719 readable_->message_port_->close();
720
721 // 3. If result is an abrupt completion, return a promise rejected with
722 // result.[[Value]].
723 if (!success) {
724 return PromiseReject(script_state, error);
725 }
726
727 // 4. Otherwise, return a promise resolved with undefined.
728 return PromiseResolveWithUndefined(script_state);
729 }
730
Trace(Visitor * visitor) const731 void Trace(Visitor* visitor) const override {
732 visitor->Trace(readable_);
733 StreamAlgorithm::Trace(visitor);
734 }
735
736 private:
737 const Member<CrossRealmTransformReadable> readable_;
738 };
739
CreateReadableStream(ExceptionState & exception_state)740 ReadableStream* CrossRealmTransformReadable::CreateReadableStream(
741 ExceptionState& exception_state) {
742 DCHECK(!controller_) << "CreateReadableStream can only be called once";
743
744 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
745 // The order of operations is significantly different from the standard, but
746 // functionally equivalent.
747
748 // 3. Add a handler for port’s message event with the following steps:
749 // 5. Enable port’s port message queue.
750 message_port_->setOnmessage(
751 MakeGarbageCollected<CrossRealmTransformMessageListener>(this));
752
753 // 4. Add a handler for port’s messageerror event with the following steps:
754 message_port_->setOnmessageerror(
755 MakeGarbageCollected<CrossRealmTransformErrorListener>(this));
756
757 // 6. Let startAlgorithm be an algorithm that returns undefined.
758 // 7. Let pullAlgorithm be the following steps:
759 // 8. Let cancelAlgorithm be the following steps, taking a reason argument:
760 // 9. Let sizeAlgorithm be an algorithm that returns 1.
761 // 10. Perform ! SetUpReadableStreamDefaultController(stream, controller,
762 // startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, sizeAlgorithm).
763 auto* stream = ReadableStream::Create(
764 script_state_, CreateTrivialStartAlgorithm(),
765 MakeGarbageCollected<PullAlgorithm>(this),
766 MakeGarbageCollected<CancelAlgorithm>(this),
767 /* highWaterMark = */ 0, CreateDefaultSizeAlgorithm(), exception_state);
768
769 if (exception_state.HadException()) {
770 return nullptr;
771 }
772
773 controller_ = stream->GetController();
774 return stream;
775 }
776
HandleMessage(MessageType type,v8::Local<v8::Value> value)777 void CrossRealmTransformReadable::HandleMessage(MessageType type,
778 v8::Local<v8::Value> value) {
779 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
780 // 3. Add a handler for port’s message event with the following steps:
781 // The first 5 steps are handled by CrossRealmTransformMessageListener.
782 switch (type) {
783 // 6. If type is "chunk",
784 case MessageType::kChunk:
785 // 1. Perform ! ReadableStreamDefaultControllerEnqueue(controller,
786 // value).
787 // TODO(ricea): Update ReadableStreamDefaultController::Enqueue() to match
788 // the standard so this extra check is not needed.
789 if (ReadableStreamDefaultController::CanCloseOrEnqueue(controller_)) {
790 // This can't throw because we always use the default strategy size
791 // algorithm, which doesn't throw, and always returns a valid value of
792 // 1.0.
793 ReadableStreamDefaultController::Enqueue(script_state_, controller_,
794 value, ASSERT_NO_EXCEPTION);
795 }
796 return;
797
798 // 7. Otherwise, if type is "close",
799 case MessageType::kClose:
800 // 1. Perform ! ReadableStreamDefaultControllerClose(controller).
801 // TODO(ricea): Update ReadableStreamDefaultController::Close() to match
802 // the standard so this extra check is not needed.
803 if (ReadableStreamDefaultController::CanCloseOrEnqueue(controller_)) {
804 ReadableStreamDefaultController::Close(script_state_, controller_);
805 }
806
807 // Disentangle port.
808 message_port_->close();
809 return;
810
811 // 8. Otherwise, if type is "error",
812 case MessageType::kError:
813 // 1. Perform ! ReadableStreamDefaultControllerError(controller, value).
814 ReadableStreamDefaultController::Error(script_state_, controller_, value);
815
816 // 2. Disentangle port.
817 message_port_->close();
818 return;
819
820 default:
821 DLOG(WARNING) << "Invalid message from peer ignored (invalid type): "
822 << static_cast<int>(type);
823 return;
824 }
825 }
826
HandleError(v8::Local<v8::Value> error)827 void CrossRealmTransformReadable::HandleError(v8::Local<v8::Value> error) {
828 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
829 // 4. Add a handler for port’s messageerror event with the following steps:
830 // The first two steps, and the last step, are performed by
831 // CrossRealmTransformErrorListener.
832
833 // 3. Perform ! ReadableStreamDefaultControllerError(controller, error).
834 ReadableStreamDefaultController::Error(script_state_, controller_, error);
835 }
836
837 } // namespace
838
CreateCrossRealmTransformWritable(ScriptState * script_state,MessagePort * port,ExceptionState & exception_state)839 CORE_EXPORT WritableStream* CreateCrossRealmTransformWritable(
840 ScriptState* script_state,
841 MessagePort* port,
842 ExceptionState& exception_state) {
843 return MakeGarbageCollected<CrossRealmTransformWritable>(script_state, port)
844 ->CreateWritableStream(exception_state);
845 }
846
CreateCrossRealmTransformReadable(ScriptState * script_state,MessagePort * port,ExceptionState & exception_state)847 CORE_EXPORT ReadableStream* CreateCrossRealmTransformReadable(
848 ScriptState* script_state,
849 MessagePort* port,
850 ExceptionState& exception_state) {
851 return MakeGarbageCollected<CrossRealmTransformReadable>(script_state, port)
852 ->CreateReadableStream(exception_state);
853 }
854
855 } // namespace blink
856