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