1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* General readable stream abstract operations. */
8
9 #include "builtin/streams/ReadableStreamOperations.h"
10
11 #include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
12 #include "mozilla/Attributes.h" // MOZ_MUST_USE
13
14 #include "jsapi.h" // JS_SetPrivate
15
16 #include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
17 #include "builtin/Promise.h" // js::RejectPromiseWithPendingError
18 #include "builtin/streams/PipeToState.h" // js::PipeToState
19 #include "builtin/streams/ReadableStream.h" // js::ReadableStream
20 #include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller
21 #include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamDefaultController{Close,Enqueue}, js::ReadableStreamControllerError, js::SourceAlgorithms
22 #include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStreamCancel
23 #include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStreamDefaultReader, js::ForAuthorCodeBool, js::ReadableStream{,Default}Reader, js::ReadableStreamDefaultReaderRead
24 #include "builtin/streams/TeeState.h" // js::TeeState
25 #include "js/CallArgs.h" // JS::CallArgs{,FromVp}
26 #include "js/Promise.h" // JS::CallOriginalPromiseThen, JS::AddPromiseReactions
27 #include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
28 #include "js/Value.h" // JS::Value, JS::UndefinedHandleValue
29 #include "vm/JSContext.h" // JSContext
30 #include "vm/NativeObject.h" // js::NativeObject
31 #include "vm/ObjectOperations.h" // js::GetProperty
32 #include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
33
34 #include "builtin/streams/HandlerFunction-inl.h" // js::NewHandler, js::TargetFromHandler
35 #include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithValue
36 #include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapReaderFromStream
37 #include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::Unwrap{Callee,Internal}Slot
38 #include "vm/JSObject-inl.h" // js::IsCallable, js::NewObjectWithClassProto
39 #include "vm/Realm-inl.h" // js::AutoRealm
40
41 using js::IsCallable;
42 using js::NewHandler;
43 using js::NewObjectWithClassProto;
44 using js::PromiseObject;
45 using js::ReadableStream;
46 using js::ReadableStreamDefaultController;
47 using js::ReadableStreamDefaultControllerEnqueue;
48 using js::ReadableStreamDefaultReader;
49 using js::ReadableStreamReader;
50 using js::SourceAlgorithms;
51 using js::TargetFromHandler;
52 using js::TeeState;
53 using js::UnwrapCalleeSlot;
54
55 using JS::CallArgs;
56 using JS::CallArgsFromVp;
57 using JS::Handle;
58 using JS::MutableHandle;
59 using JS::ObjectValue;
60 using JS::Rooted;
61 using JS::UndefinedHandleValue;
62 using JS::Value;
63
64 /*** 3.4. General readable stream abstract operations ***********************/
65
66 // Streams spec, 3.4.1. AcquireReadableStreamBYOBReader ( stream )
67 // Always inlined.
68
69 // Streams spec, 3.4.2. AcquireReadableStreamDefaultReader ( stream )
70 // Always inlined. See CreateReadableStreamDefaultReader.
71
72 /**
73 * Streams spec, 3.4.3. CreateReadableStream (
74 * startAlgorithm, pullAlgorithm, cancelAlgorithm
75 * [, highWaterMark [, sizeAlgorithm ] ] )
76 *
77 * The start/pull/cancelAlgorithm arguments are represented instead as four
78 * arguments: sourceAlgorithms, underlyingSource, pullMethod, cancelMethod.
79 * See the comment on SetUpReadableStreamDefaultController.
80 */
CreateReadableStream(JSContext * cx,SourceAlgorithms sourceAlgorithms,Handle<Value> underlyingSource,Handle<Value> pullMethod=UndefinedHandleValue,Handle<Value> cancelMethod=UndefinedHandleValue,double highWaterMark=1,Handle<Value> sizeAlgorithm=UndefinedHandleValue,Handle<JSObject * > proto=nullptr)81 static MOZ_MUST_USE ReadableStream* CreateReadableStream(
82 JSContext* cx, SourceAlgorithms sourceAlgorithms,
83 Handle<Value> underlyingSource,
84 Handle<Value> pullMethod = UndefinedHandleValue,
85 Handle<Value> cancelMethod = UndefinedHandleValue, double highWaterMark = 1,
86 Handle<Value> sizeAlgorithm = UndefinedHandleValue,
87 Handle<JSObject*> proto = nullptr) {
88 cx->check(underlyingSource, sizeAlgorithm, proto);
89 MOZ_ASSERT(sizeAlgorithm.isUndefined() || IsCallable(sizeAlgorithm));
90
91 // Step 1: If highWaterMark was not passed, set it to 1 (implicit).
92 // Step 2: If sizeAlgorithm was not passed, set it to an algorithm that
93 // returns 1 (implicit).
94 // Step 3: Assert: ! IsNonNegativeNumber(highWaterMark) is true.
95 MOZ_ASSERT(highWaterMark >= 0);
96
97 // Step 4: Let stream be ObjectCreate(the original value of ReadableStream's
98 // prototype property).
99 // Step 5: Perform ! InitializeReadableStream(stream).
100 Rooted<ReadableStream*> stream(cx,
101 ReadableStream::create(cx, nullptr, proto));
102 if (!stream) {
103 return nullptr;
104 }
105
106 // Step 6: Let controller be ObjectCreate(the original value of
107 // ReadableStreamDefaultController's prototype property).
108 // Step 7: Perform ? SetUpReadableStreamDefaultController(stream,
109 // controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
110 // highWaterMark, sizeAlgorithm).
111 if (!SetUpReadableStreamDefaultController(
112 cx, stream, sourceAlgorithms, underlyingSource, pullMethod,
113 cancelMethod, highWaterMark, sizeAlgorithm)) {
114 return nullptr;
115 }
116
117 // Step 8: Return stream.
118 return stream;
119 }
120
121 // Streams spec, 3.4.4. CreateReadableByteStream (
122 // startAlgorithm, pullAlgorithm, cancelAlgorithm
123 // [, highWaterMark [, autoAllocateChunkSize ] ] )
124 // Not implemented.
125
126 /**
127 * Streams spec, 3.4.5. InitializeReadableStream ( stream )
128 */
create(JSContext * cx,void * nsISupportsObject_alreadyAddreffed,Handle<JSObject * > proto)129 /* static */ MOZ_MUST_USE ReadableStream* ReadableStream::create(
130 JSContext* cx, void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
131 Handle<JSObject*> proto /* = nullptr */) {
132 // In the spec, InitializeReadableStream is always passed a newly created
133 // ReadableStream object. We instead create it here and return it below.
134 Rooted<ReadableStream*> stream(
135 cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
136 if (!stream) {
137 return nullptr;
138 }
139
140 JS_SetPrivate(stream, nsISupportsObject_alreadyAddreffed);
141
142 // Step 1: Set stream.[[state]] to "readable".
143 stream->initStateBits(Readable);
144 MOZ_ASSERT(stream->readable());
145
146 // Step 2: Set stream.[[reader]] and stream.[[storedError]] to
147 // undefined (implicit).
148 MOZ_ASSERT(!stream->hasReader());
149 MOZ_ASSERT(stream->storedError().isUndefined());
150
151 // Step 3: Set stream.[[disturbed]] to false (done in step 1).
152 MOZ_ASSERT(!stream->disturbed());
153
154 return stream;
155 }
156
157 // Streams spec, 3.4.6. IsReadableStream ( x )
158 // Using UnwrapAndTypeCheck templates instead.
159
160 // Streams spec, 3.4.7. IsReadableStreamDisturbed ( stream )
161 // Using stream->disturbed() instead.
162
163 /**
164 * Streams spec, 3.4.8. IsReadableStreamLocked ( stream )
165 */
locked() const166 bool ReadableStream::locked() const {
167 // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
168 // Step 2: If stream.[[reader]] is undefined, return false.
169 // Step 3: Return true.
170 // Special-casing for streams with external sources. Those can be locked
171 // explicitly via JSAPI, which is indicated by a controller flag.
172 // IsReadableStreamLocked is called from the controller's constructor, at
173 // which point we can't yet call stream->controller(), but the source also
174 // can't be locked yet.
175 if (hasController() && controller()->sourceLocked()) {
176 return true;
177 }
178 return hasReader();
179 }
180
181 // Streams spec, 3.4.9. IsReadableStreamAsyncIterator ( x )
182 //
183 // Not implemented.
184
185 /**
186 * Streams spec, 3.4.10. ReadableStreamTee steps 12.c.i-x.
187 */
TeeReaderReadHandler(JSContext * cx,unsigned argc,Value * vp)188 static bool TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp) {
189 CallArgs args = CallArgsFromVp(argc, vp);
190
191 Rooted<TeeState*> unwrappedTeeState(cx,
192 UnwrapCalleeSlot<TeeState>(cx, args, 0));
193 if (!unwrappedTeeState) {
194 return false;
195 }
196
197 Handle<Value> resultVal = args.get(0);
198
199 // Step 12.c.i: Set reading to false.
200 unwrappedTeeState->unsetReading();
201
202 // Step 12.c.ii: Assert: Type(result) is Object.
203 Rooted<JSObject*> result(cx, &resultVal.toObject());
204
205 bool done;
206 {
207 // Step 12.c.iii: Let done be ? Get(result, "done").
208 // (This can fail only if `result` was nuked.)
209 Rooted<Value> doneVal(cx);
210 if (!GetProperty(cx, result, result, cx->names().done, &doneVal)) {
211 return false;
212 }
213
214 // Step 12.c.iv: Assert: Type(done) is Boolean.
215 done = doneVal.toBoolean();
216 }
217
218 // Step 12.c.v: If done is true,
219 if (done) {
220 // Step 12.c.v.1: If canceled1 is false,
221 if (!unwrappedTeeState->canceled1()) {
222 // Step 12.c.v.1.a: Perform
223 // ! ReadableStreamDefaultControllerClose(
224 // branch1.[[readableStreamController]]).
225 Rooted<ReadableStreamDefaultController*> unwrappedBranch1(
226 cx, unwrappedTeeState->branch1());
227 if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch1)) {
228 return false;
229 }
230 }
231
232 // Step 12.c.v.2: If canceled2 is false,
233 if (!unwrappedTeeState->canceled2()) {
234 // Step 12.c.v.2.a: Perform
235 // ! ReadableStreamDefaultControllerClose(
236 // branch2.[[readableStreamController]]).
237 Rooted<ReadableStreamDefaultController*> unwrappedBranch2(
238 cx, unwrappedTeeState->branch2());
239 if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch2)) {
240 return false;
241 }
242 }
243
244 args.rval().setUndefined();
245 return true;
246 }
247
248 // Step 12.c.vi: Let value be ! Get(result, "value").
249 // (This can fail only if `result` was nuked.)
250 Rooted<Value> value(cx);
251 if (!GetProperty(cx, result, result, cx->names().value, &value)) {
252 return false;
253 }
254
255 // Step 12.c.vii: Let value1 and value2 be value.
256 // Step 12.c.viii: If canceled2 is false and cloneForBranch2 is true, set
257 // value2 to
258 // ? StructuredDeserialize(? StructuredSerialize(value2),
259 // the current Realm Record).
260 // We don't yet support any specifications that use cloneForBranch2, and
261 // the Streams spec doesn't offer any way for author code to enable it,
262 // so it's always false here.
263 auto& value1 = value;
264 MOZ_ASSERT(!unwrappedTeeState->cloneForBranch2(),
265 "support for cloneForBranch2=true is not yet implemented");
266 auto& value2 = value;
267
268 Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
269
270 // Step 12.c.ix: If canceled1 is false, perform
271 // ? ReadableStreamDefaultControllerEnqueue(
272 // branch1.[[readableStreamController]], value1).
273 if (!unwrappedTeeState->canceled1()) {
274 unwrappedController = unwrappedTeeState->branch1();
275 if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
276 value1)) {
277 return false;
278 }
279 }
280
281 // Step 12.c.x: If canceled2 is false, perform
282 // ? ReadableStreamDefaultControllerEnqueue(
283 // branch2.[[readableStreamController]], value2).
284 if (!unwrappedTeeState->canceled2()) {
285 unwrappedController = unwrappedTeeState->branch2();
286 if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
287 value2)) {
288 return false;
289 }
290 }
291
292 args.rval().setUndefined();
293 return true;
294 }
295
296 /**
297 * Streams spec, 3.4.10. ReadableStreamTee step 12, "Let pullAlgorithm be the
298 * following steps:"
299 */
ReadableStreamTee_Pull(JSContext * cx,JS::Handle<TeeState * > unwrappedTeeState)300 MOZ_MUST_USE PromiseObject* js::ReadableStreamTee_Pull(
301 JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState) {
302 // Combine step 12.a/12.e far below, and handle steps 12.b-12.d after
303 // inverting step 12.a's "If reading is true" condition.
304 if (!unwrappedTeeState->reading()) {
305 // Step 12.b: Set reading to true.
306 unwrappedTeeState->setReading();
307
308 // Implicit in the spec: Unpack `reader` from the TeeState (by way of the
309 // stream stored in one of its slots).
310 Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
311 {
312 Rooted<ReadableStream*> unwrappedStream(
313 cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
314 TeeState::Slot_Stream));
315 if (!unwrappedStream) {
316 return nullptr;
317 }
318 ReadableStreamReader* unwrappedReaderObj =
319 UnwrapReaderFromStream(cx, unwrappedStream);
320 if (!unwrappedReaderObj) {
321 return nullptr;
322 }
323
324 unwrappedReader = &unwrappedReaderObj->as<ReadableStreamDefaultReader>();
325 }
326
327 // Step 12.c: Let readPromise be the result of reacting to
328 // ! ReadableStreamDefaultReaderRead(reader) with the following
329 // fulfillment steps given the argument result: [...]
330 // Step 12.d: Set readPromise.[[PromiseIsHandled]] to true.
331
332 // First, perform |ReadableStreamDefaultReaderRead(reader)|.
333 Rooted<PromiseObject*> readerReadResultPromise(
334 cx, js::ReadableStreamDefaultReaderRead(cx, unwrappedReader));
335 if (!readerReadResultPromise) {
336 return nullptr;
337 }
338
339 // Next, create a function to perform the fulfillment steps under step 12.c
340 // (implemented in the |TeeReaderReadHandler| C++ function).
341 Rooted<JSObject*> teeState(cx, unwrappedTeeState);
342 if (!cx->compartment()->wrap(cx, &teeState)) {
343 return nullptr;
344 }
345
346 Rooted<JSObject*> onFulfilled(
347 cx, NewHandler(cx, TeeReaderReadHandler, teeState));
348 if (!onFulfilled) {
349 return nullptr;
350 }
351
352 // Finally, perform those fulfillment steps when |readerReadResultPromise|
353 // fulfills. (Step 12.c doesn't provide rejection steps, so don't handle
354 // rejection.)
355 //
356 // The spec's |readPromise| promise is unobservable, so implement this using
357 // a JSAPI function that acts as if it created |readPromise| but doesn't
358 // actually do so.
359 //
360 // Step 12.d causes |readPromise| to be treated as handled, even if it
361 // rejects. Use |JS::AddPromiseReactionsIgnoringUnhandledRejection|, not
362 // |JS::AddPromiseReactions|, to avoid reporting a freshly-consed-up promise
363 // as rejected if |readerReadResultPromise| rejects.
364 if (!JS::AddPromiseReactionsIgnoringUnhandledRejection(
365 cx, readerReadResultPromise, onFulfilled, nullptr)) {
366 return nullptr;
367 }
368 }
369
370 // Step 12.a: (If reading is true,) return a promise resolved with undefined.
371 // Step 12.e: Return a promise resolved with undefined.
372 return PromiseResolvedWithUndefined(cx);
373 }
374
375 /**
376 * Cancel one branch of a tee'd stream with the given |reason_|.
377 *
378 * Streams spec, 3.4.10. ReadableStreamTee steps 13 and 14: "Let
379 * cancel1Algorithm/cancel2Algorithm be the following steps, taking a reason
380 * argument:"
381 */
ReadableStreamTee_Cancel(JSContext * cx,JS::Handle<TeeState * > unwrappedTeeState,JS::Handle<ReadableStreamDefaultController * > unwrappedBranch,JS::Handle<Value> reason)382 MOZ_MUST_USE JSObject* js::ReadableStreamTee_Cancel(
383 JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState,
384 JS::Handle<ReadableStreamDefaultController*> unwrappedBranch,
385 JS::Handle<Value> reason) {
386 Rooted<ReadableStream*> unwrappedStream(
387 cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
388 TeeState::Slot_Stream));
389 if (!unwrappedStream) {
390 return nullptr;
391 }
392
393 bool bothBranchesCanceled = false;
394
395 // Step 13/14.a: Set canceled1/canceled2 to true.
396 // Step 13/14.b: Set reason1/reason2 to reason.
397 {
398 AutoRealm ar(cx, unwrappedTeeState);
399
400 Rooted<Value> unwrappedReason(cx, reason);
401 if (!cx->compartment()->wrap(cx, &unwrappedReason)) {
402 return nullptr;
403 }
404
405 if (unwrappedBranch->isTeeBranch1()) {
406 unwrappedTeeState->setCanceled1(unwrappedReason);
407 bothBranchesCanceled = unwrappedTeeState->canceled2();
408 } else {
409 MOZ_ASSERT(unwrappedBranch->isTeeBranch2());
410 unwrappedTeeState->setCanceled2(unwrappedReason);
411 bothBranchesCanceled = unwrappedTeeState->canceled1();
412 }
413 }
414
415 Rooted<PromiseObject*> unwrappedCancelPromise(
416 cx, unwrappedTeeState->cancelPromise());
417 MOZ_ASSERT(unwrappedCancelPromise != nullptr);
418
419 // Step 13/14.c: If canceled2/canceled1 is true,
420 if (bothBranchesCanceled) {
421 // Step 13/14.c.i: Let compositeReason be
422 // ! CreateArrayFromList(« reason1, reason2 »).
423 Rooted<Value> compositeReason(cx);
424 {
425 Rooted<Value> reason1(cx, unwrappedTeeState->reason1());
426 Rooted<Value> reason2(cx, unwrappedTeeState->reason2());
427 if (!cx->compartment()->wrap(cx, &reason1) ||
428 !cx->compartment()->wrap(cx, &reason2)) {
429 return nullptr;
430 }
431
432 ArrayObject* reasonArray = NewDenseFullyAllocatedArray(cx, 2);
433 if (!reasonArray) {
434 return nullptr;
435 }
436 reasonArray->setDenseInitializedLength(2);
437 reasonArray->initDenseElement(0, reason1);
438 reasonArray->initDenseElement(1, reason2);
439
440 compositeReason = ObjectValue(*reasonArray);
441 }
442
443 // Step 13/14.c.ii: Let cancelResult be
444 // ! ReadableStreamCancel(stream, compositeReason).
445 // In our implementation, this can fail with OOM. The best course then
446 // is to reject cancelPromise with an OOM error.
447 Rooted<JSObject*> cancelResult(
448 cx, js::ReadableStreamCancel(cx, unwrappedStream, compositeReason));
449 if (!cancelResult) {
450 // Handle the OOM case mentioned above.
451 AutoRealm ar(cx, unwrappedCancelPromise);
452 if (!RejectPromiseWithPendingError(cx, unwrappedCancelPromise)) {
453 return nullptr;
454 }
455 } else {
456 // Step 13/14.c.iii: Resolve cancelPromise with cancelResult.
457 Rooted<Value> cancelResultVal(cx, ObjectValue(*cancelResult));
458 if (!ResolveUnwrappedPromiseWithValue(cx, unwrappedCancelPromise,
459 cancelResultVal)) {
460 return nullptr;
461 }
462 }
463 }
464
465 // Step 13/14.d: Return cancelPromise.
466 Rooted<JSObject*> cancelPromise(cx, unwrappedCancelPromise);
467 if (!cx->compartment()->wrap(cx, &cancelPromise)) {
468 return nullptr;
469 }
470
471 return cancelPromise;
472 }
473
474 /**
475 * Streams spec, 3.4.10. step 18:
476 * Upon rejection of reader.[[closedPromise]] with reason r,
477 */
TeeReaderErroredHandler(JSContext * cx,unsigned argc,JS::Value * vp)478 static bool TeeReaderErroredHandler(JSContext* cx, unsigned argc,
479 JS::Value* vp) {
480 CallArgs args = CallArgsFromVp(argc, vp);
481
482 Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args));
483 Handle<Value> reason = args.get(0);
484
485 Rooted<ReadableStreamDefaultController*> unwrappedBranchController(cx);
486
487 // Step 18.a.i: Perform
488 // ! ReadableStreamDefaultControllerError(
489 // branch1.[[readableStreamController]], r).
490 unwrappedBranchController = teeState->branch1();
491 if (!ReadableStreamControllerError(cx, unwrappedBranchController, reason)) {
492 return false;
493 }
494
495 // Step a.ii: Perform
496 // ! ReadableStreamDefaultControllerError(
497 // branch2.[[readableStreamController]], r).
498 unwrappedBranchController = teeState->branch2();
499 if (!ReadableStreamControllerError(cx, unwrappedBranchController, reason)) {
500 return false;
501 }
502
503 args.rval().setUndefined();
504 return true;
505 }
506
507 /**
508 * Streams spec, 3.4.10. ReadableStreamTee ( stream, cloneForBranch2 )
509 */
ReadableStreamTee(JSContext * cx,JS::Handle<ReadableStream * > unwrappedStream,bool cloneForBranch2,JS::MutableHandle<ReadableStream * > branch1Stream,JS::MutableHandle<ReadableStream * > branch2Stream)510 MOZ_MUST_USE bool js::ReadableStreamTee(
511 JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
512 bool cloneForBranch2, JS::MutableHandle<ReadableStream*> branch1Stream,
513 JS::MutableHandle<ReadableStream*> branch2Stream) {
514 // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
515
516 // Step 2: Assert: Type(cloneForBranch2) is Boolean (implicit).
517 //
518 // The streams spec only ever passes |cloneForBranch2 = false|. It's expected
519 // that external specs that pass |cloneForBranch2 = true| will at some point
520 // come into existence, but we don't presently implement any such specs.
521 MOZ_ASSERT(!cloneForBranch2,
522 "support for cloneForBranch2=true is not yet implemented");
523
524 // Step 3: Let reader be ? AcquireReadableStreamDefaultReader(stream).
525 Rooted<ReadableStreamDefaultReader*> reader(
526 cx, CreateReadableStreamDefaultReader(cx, unwrappedStream,
527 ForAuthorCodeBool::No));
528 if (!reader) {
529 return false;
530 }
531
532 // Several algorithms close over the variables initialized in the next few
533 // steps, so we allocate them in an object, the TeeState. The algorithms
534 // also close over `stream` and `reader`, so TeeState gets a reference to
535 // the stream.
536 //
537 // Step 4: Let reading be false.
538 // Step 5: Let canceled1 be false.
539 // Step 6: Let canceled2 be false.
540 // Step 7: Let reason1 be undefined.
541 // Step 8: Let reason2 be undefined.
542 // Step 9: Let branch1 be undefined.
543 // Step 10: Let branch2 be undefined.
544 // Step 11: Let cancelPromise be a new promise.
545 Rooted<TeeState*> teeState(cx, TeeState::create(cx, unwrappedStream));
546 if (!teeState) {
547 return false;
548 }
549
550 MOZ_ASSERT(!teeState->reading());
551 MOZ_ASSERT(!teeState->canceled1());
552 MOZ_ASSERT(!teeState->canceled2());
553
554 // Step 12: Let pullAlgorithm be the following steps: [...]
555 // Step 13: Let cancel1Algorithm be the following steps: [...]
556 // Step 14: Let cancel2Algorithm be the following steps: [...]
557 // Step 15: Let startAlgorithm be an algorithm that returns undefined.
558 //
559 // Implicit. Our implementation does not use objects to represent
560 // [[pullAlgorithm]], [[cancelAlgorithm]], and so on. Instead, we decide
561 // which one to perform based on class checks. For example, our
562 // implementation of ReadableStreamControllerCallPullIfNeeded checks
563 // whether the stream's underlyingSource is a TeeState object.
564
565 // Step 16: Set branch1 to
566 // ! CreateReadableStream(startAlgorithm, pullAlgorithm,
567 // cancel1Algorithm).
568 Rooted<Value> underlyingSource(cx, ObjectValue(*teeState));
569 branch1Stream.set(
570 CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
571 if (!branch1Stream) {
572 return false;
573 }
574
575 Rooted<ReadableStreamDefaultController*> branch1(cx);
576 branch1 = &branch1Stream->controller()->as<ReadableStreamDefaultController>();
577 branch1->setTeeBranch1();
578 teeState->setBranch1(branch1);
579
580 // Step 17: Set branch2 to
581 // ! CreateReadableStream(startAlgorithm, pullAlgorithm,
582 // cancel2Algorithm).
583 branch2Stream.set(
584 CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
585 if (!branch2Stream) {
586 return false;
587 }
588
589 Rooted<ReadableStreamDefaultController*> branch2(cx);
590 branch2 = &branch2Stream->controller()->as<ReadableStreamDefaultController>();
591 branch2->setTeeBranch2();
592 teeState->setBranch2(branch2);
593
594 // Step 18: Upon rejection of reader.[[closedPromise]] with reason r, [...]
595 Rooted<JSObject*> closedPromise(cx, reader->closedPromise());
596
597 Rooted<JSObject*> onRejected(
598 cx, NewHandler(cx, TeeReaderErroredHandler, teeState));
599 if (!onRejected) {
600 return false;
601 }
602
603 if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected)) {
604 return false;
605 }
606
607 // Step 19: Return « branch1, branch2 ».
608 return true;
609 }
610
611 /**
612 * Streams spec, 3.4.10.
613 * ReadableStreamPipeTo ( source, dest, preventClose, preventAbort,
614 * preventCancel, signal )
615 */
ReadableStreamPipeTo(JSContext * cx,Handle<ReadableStream * > unwrappedSource,Handle<WritableStream * > unwrappedDest,bool preventClose,bool preventAbort,bool preventCancel,Handle<JSObject * > signal)616 PromiseObject* js::ReadableStreamPipeTo(JSContext* cx,
617 Handle<ReadableStream*> unwrappedSource,
618 Handle<WritableStream*> unwrappedDest,
619 bool preventClose, bool preventAbort,
620 bool preventCancel,
621 Handle<JSObject*> signal) {
622 // Step 1. Assert: ! IsReadableStream(source) is true.
623 // Step 2. Assert: ! IsWritableStream(dest) is true.
624 // Step 3. Assert: Type(preventClose) is Boolean, Type(preventAbort) is
625 // Boolean, and Type(preventCancel) is Boolean.
626 // (These are guaranteed by the type system.)
627
628 // Step 12: Let promise be a new promise.
629 //
630 // We reorder this so that this promise can be rejected and returned in case
631 // of internal error.
632 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
633 if (!promise) {
634 return nullptr;
635 }
636
637 // Steps 4-11, 13-14.
638 Rooted<PipeToState*> pipeToState(
639 cx,
640 PipeToState::create(cx, promise, unwrappedSource, unwrappedDest,
641 preventClose, preventAbort, preventCancel, signal));
642 if (!pipeToState) {
643 if (!RejectPromiseWithPendingError(cx, promise)) {
644 return nullptr;
645 }
646
647 return promise;
648 }
649
650 // Step 15.
651 return promise;
652 }
653