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 /* Readable stream default controller abstract operations. */
8 
9 #include "builtin/streams/ReadableStreamDefaultControllerOperations.h"
10 
11 #include "mozilla/Assertions.h"  // MOZ_ASSERT{,_IF}
12 
13 #include "jsfriendapi.h"  // js::AssertSameCompartment
14 
15 #include "builtin/Stream.h"  // js::ReadableByteStreamControllerClearPendingPullIntos
16 #include "builtin/streams/MiscellaneousOperations.h"  // js::CreateAlgorithmFromUnderlyingMethod, js::InvokeOrNoop, js::IsMaybeWrapped
17 #include "builtin/streams/QueueWithSizes.h"  // js::EnqueueValueWithSize, js::ResetQueue
18 #include "builtin/streams/ReadableStreamController.h"  // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController, js::ReadableStreamControllerStart{,Failed}Handler
19 #include "builtin/streams/ReadableStreamInternals.h"  // js::ReadableStream{CloseInternal,ErrorInternal,FulfillReadOrReadIntoRequest,GetNumReadRequests}
20 #include "builtin/streams/ReadableStreamOperations.h"  // js::ReadableStreamTee_Pull, js::SetUpReadableStreamDefaultController
21 #include "builtin/streams/TeeState.h"  // js::TeeState
22 #include "js/CallAndConstruct.h"       // JS::IsCallable
23 #include "js/CallArgs.h"               // JS::CallArgs{,FromVp}
24 #include "js/Promise.h"                // JS::AddPromiseReactions
25 #include "js/RootingAPI.h"             // JS::Handle, JS::Rooted
26 #include "js/Stream.h"                 // JS::ReadableStreamUnderlyingSource
27 #include "js/Value.h"  // JS::{,Int32,Object}Value, JS::UndefinedHandleValue
28 #include "vm/Compartment.h"  // JS::Compartment
29 #include "vm/Interpreter.h"  // js::Call, js::GetAndClearExceptionAndStack
30 #include "vm/JSContext.h"    // JSContext
31 #include "vm/JSObject.h"     // JSObject
32 #include "vm/List.h"         // js::ListObject
33 #include "vm/PromiseObject.h"  // js::PromiseObject, js::PromiseResolvedWithUndefined
34 #include "vm/Runtime.h"        // JSAtomState
35 #include "vm/SavedFrame.h"  // js::SavedFrame
36 
37 #include "builtin/HandlerFunction-inl.h"                  // js::NewHandler
38 #include "builtin/streams/MiscellaneousOperations-inl.h"  // js::PromiseCall
39 #include "vm/Compartment-inl.h"  // JS::Compartment::wrap, js::UnwrapCalleeSlot
40 #include "vm/JSContext-inl.h"    // JSContext::check
41 #include "vm/JSObject-inl.h"     // js::IsCallable, js::NewBuiltinClassInstance
42 #include "vm/Realm-inl.h"        // js::AutoRealm
43 
44 using js::ReadableByteStreamController;
45 using js::ReadableStream;
46 using js::ReadableStreamController;
47 using js::ReadableStreamControllerCallPullIfNeeded;
48 using js::ReadableStreamControllerError;
49 using js::ReadableStreamGetNumReadRequests;
50 using js::UnwrapCalleeSlot;
51 
52 using JS::CallArgs;
53 using JS::CallArgsFromVp;
54 using JS::Handle;
55 using JS::Rooted;
56 using JS::UndefinedHandleValue;
57 using JS::Value;
58 
59 /*** 3.10. Readable stream default controller abstract operations ***********/
60 
61 // Streams spec, 3.10.1. IsReadableStreamDefaultController ( x )
62 // Implemented via is<ReadableStreamDefaultController>()
63 
64 /**
65  * Streams spec, 3.10.2 and 3.13.3. step 7:
66  *      Upon fulfillment of pullPromise, [...]
67  */
ControllerPullHandler(JSContext * cx,unsigned argc,Value * vp)68 static bool ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp) {
69   CallArgs args = CallArgsFromVp(argc, vp);
70 
71   Rooted<ReadableStreamController*> unwrappedController(
72       cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
73   if (!unwrappedController) {
74     return false;
75   }
76 
77   bool pullAgain = unwrappedController->pullAgain();
78 
79   // Step a: Set controller.[[pulling]] to false.
80   // Step b.i: Set controller.[[pullAgain]] to false.
81   unwrappedController->clearPullFlags();
82 
83   // Step b: If controller.[[pullAgain]] is true,
84   if (pullAgain) {
85     // Step ii: Perform
86     //          ! ReadableStreamDefaultControllerCallPullIfNeeded(controller)
87     //          (or ReadableByteStreamControllerCallPullIfNeeded(controller)).
88     if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
89       return false;
90     }
91   }
92 
93   args.rval().setUndefined();
94   return true;
95 }
96 
97 /**
98  * Streams spec, 3.10.2 and 3.13.3. step 8:
99  * Upon rejection of pullPromise with reason e,
100  */
ControllerPullFailedHandler(JSContext * cx,unsigned argc,Value * vp)101 static bool ControllerPullFailedHandler(JSContext* cx, unsigned argc,
102                                         Value* vp) {
103   CallArgs args = CallArgsFromVp(argc, vp);
104   Handle<Value> e = args.get(0);
105 
106   Rooted<ReadableStreamController*> controller(
107       cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
108   if (!controller) {
109     return false;
110   }
111 
112   // Step a: Perform ! ReadableStreamDefaultControllerError(controller, e).
113   //         (ReadableByteStreamControllerError in 3.12.3.)
114   if (!ReadableStreamControllerError(cx, controller, e)) {
115     return false;
116   }
117 
118   args.rval().setUndefined();
119   return true;
120 }
121 
122 static bool ReadableStreamControllerShouldCallPull(
123     ReadableStreamController* unwrappedController);
124 
125 /**
126  * Streams spec, 3.10.2
127  *      ReadableStreamDefaultControllerCallPullIfNeeded ( controller )
128  * Streams spec, 3.13.3.
129  *      ReadableByteStreamControllerCallPullIfNeeded ( controller )
130  */
ReadableStreamControllerCallPullIfNeeded(JSContext * cx,Handle<ReadableStreamController * > unwrappedController)131 [[nodiscard]] bool js::ReadableStreamControllerCallPullIfNeeded(
132     JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
133   // Step 1: Let shouldPull be
134   //         ! ReadableStreamDefaultControllerShouldCallPull(controller).
135   // (ReadableByteStreamDefaultControllerShouldCallPull in 3.13.3.)
136   bool shouldPull = ReadableStreamControllerShouldCallPull(unwrappedController);
137 
138   // Step 2: If shouldPull is false, return.
139   if (!shouldPull) {
140     return true;
141   }
142 
143   // Step 3: If controller.[[pulling]] is true,
144   if (unwrappedController->pulling()) {
145     // Step a: Set controller.[[pullAgain]] to true.
146     unwrappedController->setPullAgain();
147 
148     // Step b: Return.
149     return true;
150   }
151 
152   // Step 4: Assert: controller.[[pullAgain]] is false.
153   MOZ_ASSERT(!unwrappedController->pullAgain());
154 
155   // Step 5: Set controller.[[pulling]] to true.
156   unwrappedController->setPulling();
157 
158   // We use this variable in step 7. For ease of error-handling, we wrap it
159   // early.
160   Rooted<JSObject*> wrappedController(cx, unwrappedController);
161   if (!cx->compartment()->wrap(cx, &wrappedController)) {
162     return false;
163   }
164 
165   // Step 6: Let pullPromise be the result of performing
166   //         controller.[[pullAlgorithm]].
167   // Our representation of pull algorithms is a bit awkward, for performance,
168   // so we must figure out which algorithm is being invoked.
169   Rooted<JSObject*> pullPromise(cx);
170   Rooted<Value> unwrappedUnderlyingSource(
171       cx, unwrappedController->underlyingSource());
172 
173   if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
174     // The pull algorithm given in ReadableStreamTee step 12.
175     MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
176                "tee streams and controllers are always same-compartment with "
177                "the TeeState object");
178     Rooted<TeeState*> unwrappedTeeState(
179         cx, &unwrappedUnderlyingSource.toObject().as<TeeState>());
180     pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState);
181   } else if (unwrappedController->hasExternalSource()) {
182     // An embedding-provided pull algorithm.
183     {
184       AutoRealm ar(cx, unwrappedController);
185       JS::ReadableStreamUnderlyingSource* source =
186           unwrappedController->externalSource();
187       Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
188       double desiredSize =
189           ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
190       source->requestData(cx, stream, desiredSize);
191     }
192     pullPromise = PromiseResolvedWithUndefined(cx);
193   } else {
194     // The pull algorithm created in
195     // SetUpReadableStreamDefaultControllerFromUnderlyingSource step 4.
196     Rooted<Value> unwrappedPullMethod(cx, unwrappedController->pullMethod());
197     if (unwrappedPullMethod.isUndefined()) {
198       // CreateAlgorithmFromUnderlyingMethod step 7.
199       pullPromise = PromiseResolvedWithUndefined(cx);
200     } else {
201       // CreateAlgorithmFromUnderlyingMethod step 6.b.i.
202       {
203         AutoRealm ar(cx, unwrappedController);
204 
205         // |unwrappedPullMethod| and |unwrappedUnderlyingSource| come directly
206         // from |unwrappedController| slots so must be same-compartment with it.
207         cx->check(unwrappedPullMethod);
208         cx->check(unwrappedUnderlyingSource);
209 
210         Rooted<Value> controller(cx, ObjectValue(*unwrappedController));
211         cx->check(controller);
212 
213         pullPromise = PromiseCall(cx, unwrappedPullMethod,
214                                   unwrappedUnderlyingSource, controller);
215         if (!pullPromise) {
216           return false;
217         }
218       }
219       if (!cx->compartment()->wrap(cx, &pullPromise)) {
220         return false;
221       }
222     }
223   }
224   if (!pullPromise) {
225     return false;
226   }
227 
228   // Step 7: Upon fulfillment of pullPromise, [...]
229   // Step 8. Upon rejection of pullPromise with reason e, [...]
230   Rooted<JSObject*> onPullFulfilled(
231       cx, NewHandler(cx, ControllerPullHandler, wrappedController));
232   if (!onPullFulfilled) {
233     return false;
234   }
235   Rooted<JSObject*> onPullRejected(
236       cx, NewHandler(cx, ControllerPullFailedHandler, wrappedController));
237   if (!onPullRejected) {
238     return false;
239   }
240   return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled,
241                                  onPullRejected);
242 }
243 
244 /**
245  * Streams spec, 3.10.3.
246  *      ReadableStreamDefaultControllerShouldCallPull ( controller )
247  * Streams spec, 3.13.25.
248  *      ReadableByteStreamControllerShouldCallPull ( controller )
249  */
ReadableStreamControllerShouldCallPull(ReadableStreamController * unwrappedController)250 static bool ReadableStreamControllerShouldCallPull(
251     ReadableStreamController* unwrappedController) {
252   // Step 1: Let stream be controller.[[controlledReadableStream]]
253   //         (or [[controlledReadableByteStream]]).
254   ReadableStream* unwrappedStream = unwrappedController->stream();
255 
256   // 3.10.3. Step 2:
257   //      If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
258   //      is false, return false.
259   // This turns out to be the same as 3.13.25 steps 2-3.
260 
261   // 3.13.25 Step 2: If stream.[[state]] is not "readable", return false.
262   if (!unwrappedStream->readable()) {
263     return false;
264   }
265 
266   // 3.13.25 Step 3: If controller.[[closeRequested]] is true, return false.
267   if (unwrappedController->closeRequested()) {
268     return false;
269   }
270 
271   // Step 3 (or 4):
272   //      If controller.[[started]] is false, return false.
273   if (!unwrappedController->started()) {
274     return false;
275   }
276 
277   // 3.10.3.
278   // Step 4: If ! IsReadableStreamLocked(stream) is true and
279   //      ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
280   //
281   // 3.13.25.
282   // Step 5: If ! ReadableStreamHasDefaultReader(stream) is true and
283   //         ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
284   // Step 6: If ! ReadableStreamHasBYOBReader(stream) is true and
285   //         ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true.
286   //
287   // All of these amount to the same thing in this implementation:
288   if (unwrappedStream->locked() &&
289       ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
290     return true;
291   }
292 
293   // Step 5 (or 7):
294   //      Let desiredSize be
295   //      ! ReadableStreamDefaultControllerGetDesiredSize(controller).
296   //      (ReadableByteStreamControllerGetDesiredSize in 3.13.25.)
297   double desiredSize =
298       ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
299 
300   // Step 6 (or 8): Assert: desiredSize is not null (implicit).
301   // Step 7 (or 9): If desiredSize > 0, return true.
302   // Step 8 (or 10): Return false.
303   return desiredSize > 0;
304 }
305 
306 /**
307  * Streams spec, 3.10.4.
308  *      ReadableStreamDefaultControllerClearAlgorithms ( controller )
309  * and 3.13.4.
310  *      ReadableByteStreamControllerClearAlgorithms ( controller )
311  */
ReadableStreamControllerClearAlgorithms(Handle<ReadableStreamController * > controller)312 void js::ReadableStreamControllerClearAlgorithms(
313     Handle<ReadableStreamController*> controller) {
314   // Step 1: Set controller.[[pullAlgorithm]] to undefined.
315   // Step 2: Set controller.[[cancelAlgorithm]] to undefined.
316   // (In this implementation, the UnderlyingSource slot is part of the
317   // representation of these algorithms.)
318   controller->setPullMethod(UndefinedHandleValue);
319   controller->setCancelMethod(UndefinedHandleValue);
320   ReadableStreamController::clearUnderlyingSource(controller);
321 
322   // Step 3 (of 3.10.4 only) : Set controller.[[strategySizeAlgorithm]] to
323   // undefined.
324   if (controller->is<ReadableStreamDefaultController>()) {
325     controller->as<ReadableStreamDefaultController>().setStrategySize(
326         UndefinedHandleValue);
327   }
328 }
329 
330 /**
331  * Streams spec, 3.10.5. ReadableStreamDefaultControllerClose ( controller )
332  */
ReadableStreamDefaultControllerClose(JSContext * cx,Handle<ReadableStreamDefaultController * > unwrappedController)333 [[nodiscard]] bool js::ReadableStreamDefaultControllerClose(
334     JSContext* cx,
335     Handle<ReadableStreamDefaultController*> unwrappedController) {
336   // Step 1: Let stream be controller.[[controlledReadableStream]].
337   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
338 
339   // Step 2: Assert:
340   //         ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
341   //         is true.
342   MOZ_ASSERT(!unwrappedController->closeRequested());
343   MOZ_ASSERT(unwrappedStream->readable());
344 
345   // Step 3: Set controller.[[closeRequested]] to true.
346   unwrappedController->setCloseRequested();
347 
348   // Step 4: If controller.[[queue]] is empty,
349   Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
350   if (unwrappedQueue->length() == 0) {
351     // Step a: Perform
352     //         ! ReadableStreamDefaultControllerClearAlgorithms(controller).
353     ReadableStreamControllerClearAlgorithms(unwrappedController);
354 
355     // Step b: Perform ! ReadableStreamClose(stream).
356     return ReadableStreamCloseInternal(cx, unwrappedStream);
357   }
358 
359   return true;
360 }
361 
362 /**
363  * Streams spec, 3.10.6.
364  *      ReadableStreamDefaultControllerEnqueue ( controller, chunk )
365  */
ReadableStreamDefaultControllerEnqueue(JSContext * cx,Handle<ReadableStreamDefaultController * > unwrappedController,Handle<Value> chunk)366 [[nodiscard]] bool js::ReadableStreamDefaultControllerEnqueue(
367     JSContext* cx, Handle<ReadableStreamDefaultController*> unwrappedController,
368     Handle<Value> chunk) {
369   AssertSameCompartment(cx, chunk);
370 
371   // Step 1: Let stream be controller.[[controlledReadableStream]].
372   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
373 
374   // Step 2: Assert:
375   //      ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is
376   //      true.
377   MOZ_ASSERT(!unwrappedController->closeRequested());
378   MOZ_ASSERT(unwrappedStream->readable());
379 
380   // Step 3: If ! IsReadableStreamLocked(stream) is true and
381   //         ! ReadableStreamGetNumReadRequests(stream) > 0, perform
382   //         ! ReadableStreamFulfillReadRequest(stream, chunk, false).
383   if (unwrappedStream->locked() &&
384       ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
385     if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
386                                                     false)) {
387       return false;
388     }
389   } else {
390     // Step 4: Otherwise,
391     // Step a: Let result be the result of performing
392     //         controller.[[strategySizeAlgorithm]], passing in chunk, and
393     //         interpreting the result as an ECMAScript completion value.
394     // Step c: (on success) Let chunkSize be result.[[Value]].
395     Rooted<Value> chunkSize(cx, Int32Value(1));
396     bool success = true;
397     Rooted<Value> strategySize(cx, unwrappedController->strategySize());
398     if (!strategySize.isUndefined()) {
399       if (!cx->compartment()->wrap(cx, &strategySize)) {
400         return false;
401       }
402       success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
403     }
404 
405     // Step d: Let enqueueResult be
406     //         EnqueueValueWithSize(controller, chunk, chunkSize).
407     if (success) {
408       success = EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize);
409     }
410 
411     // Step b: If result is an abrupt completion,
412     // and
413     // Step e: If enqueueResult is an abrupt completion,
414     if (!success) {
415       Rooted<Value> exn(cx);
416       Rooted<SavedFrame*> stack(cx);
417       if (!cx->isExceptionPending() ||
418           !GetAndClearExceptionAndStack(cx, &exn, &stack)) {
419         // Uncatchable error. Die immediately without erroring the
420         // stream.
421         return false;
422       }
423 
424       // Step b.i: Perform ! ReadableStreamDefaultControllerError(
425       //           controller, result.[[Value]]).
426       // Step e.i: Perform ! ReadableStreamDefaultControllerError(
427       //           controller, enqueueResult.[[Value]]).
428       if (!ReadableStreamControllerError(cx, unwrappedController, exn)) {
429         return false;
430       }
431 
432       // Step b.ii: Return result.
433       // Step e.ii: Return enqueueResult.
434       // (I.e., propagate the exception.)
435       cx->setPendingException(exn, stack);
436       return false;
437     }
438   }
439 
440   // Step 5: Perform
441   //         ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
442   return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
443 }
444 
445 /**
446  * Streams spec, 3.10.7. ReadableStreamDefaultControllerError ( controller, e )
447  * Streams spec, 3.13.11. ReadableByteStreamControllerError ( controller, e )
448  */
ReadableStreamControllerError(JSContext * cx,Handle<ReadableStreamController * > unwrappedController,Handle<Value> e)449 [[nodiscard]] bool js::ReadableStreamControllerError(
450     JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
451     Handle<Value> e) {
452   MOZ_ASSERT(!cx->isExceptionPending());
453   AssertSameCompartment(cx, e);
454 
455   // Step 1: Let stream be controller.[[controlledReadableStream]]
456   //         (or controller.[[controlledReadableByteStream]]).
457   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
458 
459   // Step 2: If stream.[[state]] is not "readable", return.
460   if (!unwrappedStream->readable()) {
461     return true;
462   }
463 
464   // Step 3 of 3.13.10:
465   // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller).
466   if (unwrappedController->is<ReadableByteStreamController>()) {
467     Rooted<ReadableByteStreamController*> unwrappedByteStreamController(
468         cx, &unwrappedController->as<ReadableByteStreamController>());
469     if (!ReadableByteStreamControllerClearPendingPullIntos(
470             cx, unwrappedByteStreamController)) {
471       return false;
472     }
473   }
474 
475   // Step 3 (or 4): Perform ! ResetQueue(controller).
476   if (!ResetQueue(cx, unwrappedController)) {
477     return false;
478   }
479 
480   // Step 4 (or 5):
481   //      Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller)
482   //      (or ReadableByteStreamControllerClearAlgorithms(controller)).
483   ReadableStreamControllerClearAlgorithms(unwrappedController);
484 
485   // Step 5 (or 6): Perform ! ReadableStreamError(stream, e).
486   return ReadableStreamErrorInternal(cx, unwrappedStream, e);
487 }
488 
489 /**
490  * Streams spec, 3.10.8.
491  *      ReadableStreamDefaultControllerGetDesiredSize ( controller )
492  * Streams spec 3.13.14.
493  *      ReadableByteStreamControllerGetDesiredSize ( controller )
494  */
ReadableStreamControllerGetDesiredSizeUnchecked(ReadableStreamController * controller)495 [[nodiscard]] double js::ReadableStreamControllerGetDesiredSizeUnchecked(
496     ReadableStreamController* controller) {
497   // Steps 1-4 done at callsites, so only assert that they have been done.
498 #if DEBUG
499   ReadableStream* stream = controller->stream();
500   MOZ_ASSERT(!(stream->errored() || stream->closed()));
501 #endif  // DEBUG
502 
503   // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
504   return controller->strategyHWM() - controller->queueTotalSize();
505 }
506 
507 /**
508  * Streams spec, 3.10.11.
509  *      SetUpReadableStreamDefaultController(stream, controller,
510  *          startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark,
511  *          sizeAlgorithm )
512  *
513  * The standard algorithm takes a `controller` argument which must be a new,
514  * blank object. This implementation creates a new controller instead.
515  *
516  * In the spec, three algorithms (startAlgorithm, pullAlgorithm,
517  * cancelAlgorithm) are passed as arguments to this routine. This
518  * implementation passes these "algorithms" as data, using four arguments:
519  * sourceAlgorithms, underlyingSource, pullMethod, and cancelMethod. The
520  * sourceAlgorithms argument tells how to interpret the other three:
521  *
522  * -   SourceAlgorithms::Script - We're creating a stream from a JS source.
523  *     The caller is `new ReadableStream(underlyingSource)` or
524  *     `JS::NewReadableDefaultStreamObject`. `underlyingSource` is the
525  *     source; `pullMethod` and `cancelMethod` are its .pull and
526  *     .cancel methods, which the caller has already extracted and
527  *     type-checked: each one must be either a callable JS object or undefined.
528  *
529  *     Script streams use the start/pull/cancel algorithms defined in
530  *     3.10.12. SetUpReadableStreamDefaultControllerFromUnderlyingSource, which
531  *     call JS methods of the underlyingSource.
532  *
533  * -   SourceAlgorithms::Tee - We're creating a tee stream. `underlyingSource`
534  *     is a TeeState object. `pullMethod` and `cancelMethod` are undefined.
535  *
536  *     Tee streams use the start/pull/cancel algorithms given in
537  *     3.4.10. ReadableStreamTee.
538  *
539  * Note: All arguments must be same-compartment with cx. ReadableStream
540  * controllers are always created in the same compartment as the stream.
541  */
SetUpReadableStreamDefaultController(JSContext * cx,Handle<ReadableStream * > stream,SourceAlgorithms sourceAlgorithms,Handle<Value> underlyingSource,Handle<Value> pullMethod,Handle<Value> cancelMethod,double highWaterMark,Handle<Value> size)542 [[nodiscard]] bool js::SetUpReadableStreamDefaultController(
543     JSContext* cx, Handle<ReadableStream*> stream,
544     SourceAlgorithms sourceAlgorithms, Handle<Value> underlyingSource,
545     Handle<Value> pullMethod, Handle<Value> cancelMethod, double highWaterMark,
546     Handle<Value> size) {
547   cx->check(stream, underlyingSource, size);
548   MOZ_ASSERT(pullMethod.isUndefined() || IsCallable(pullMethod));
549   MOZ_ASSERT(cancelMethod.isUndefined() || IsCallable(cancelMethod));
550   MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
551                 pullMethod.isUndefined());
552   MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
553                 cancelMethod.isUndefined());
554   MOZ_ASSERT(highWaterMark >= 0);
555   MOZ_ASSERT(size.isUndefined() || IsCallable(size));
556 
557   // Done elsewhere in the standard: Create the new controller.
558   Rooted<ReadableStreamDefaultController*> controller(
559       cx, NewBuiltinClassInstance<ReadableStreamDefaultController>(cx));
560   if (!controller) {
561     return false;
562   }
563 
564   // Step 1: Assert: stream.[[readableStreamController]] is undefined.
565   MOZ_ASSERT(!stream->hasController());
566 
567   // Step 2: Set controller.[[controlledReadableStream]] to stream.
568   controller->setStream(stream);
569 
570   // Step 3: Set controller.[[queue]] and controller.[[queueTotalSize]] to
571   //         undefined (implicit), then perform ! ResetQueue(controller).
572   if (!ResetQueue(cx, controller)) {
573     return false;
574   }
575 
576   // Step 4: Set controller.[[started]], controller.[[closeRequested]],
577   //         controller.[[pullAgain]], and controller.[[pulling]] to false.
578   controller->setFlags(0);
579 
580   // Step 5: Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm
581   //         and controller.[[strategyHWM]] to highWaterMark.
582   controller->setStrategySize(size);
583   controller->setStrategyHWM(highWaterMark);
584 
585   // Step 6: Set controller.[[pullAlgorithm]] to pullAlgorithm.
586   // (In this implementation, the pullAlgorithm is determined by the
587   // underlyingSource in combination with the pullMethod field.)
588   controller->setUnderlyingSource(underlyingSource);
589   controller->setPullMethod(pullMethod);
590 
591   // Step 7: Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
592   controller->setCancelMethod(cancelMethod);
593 
594   // Step 8: Set stream.[[readableStreamController]] to controller.
595   stream->setController(controller);
596 
597   // Step 9: Let startResult be the result of performing startAlgorithm.
598   Rooted<Value> startResult(cx);
599   if (sourceAlgorithms == SourceAlgorithms::Script) {
600     Rooted<Value> controllerVal(cx, ObjectValue(*controller));
601     if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal,
602                       &startResult)) {
603       return false;
604     }
605   }
606 
607   // Step 10: Let startPromise be a promise resolved with startResult.
608   Rooted<JSObject*> startPromise(
609       cx, PromiseObject::unforgeableResolve(cx, startResult));
610   if (!startPromise) {
611     return false;
612   }
613 
614   // Step 11: Upon fulfillment of startPromise, [...]
615   // Step 12: Upon rejection of startPromise with reason r, [...]
616   Rooted<JSObject*> onStartFulfilled(
617       cx, NewHandler(cx, ReadableStreamControllerStartHandler, controller));
618   if (!onStartFulfilled) {
619     return false;
620   }
621   Rooted<JSObject*> onStartRejected(
622       cx,
623       NewHandler(cx, ReadableStreamControllerStartFailedHandler, controller));
624   if (!onStartRejected) {
625     return false;
626   }
627   if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
628                                onStartRejected)) {
629     return false;
630   }
631 
632   return true;
633 }
634 
635 /**
636  * Streams spec, 3.10.12.
637  *      SetUpReadableStreamDefaultControllerFromUnderlyingSource( stream,
638  *          underlyingSource, highWaterMark, sizeAlgorithm )
639  */
SetUpReadableStreamDefaultControllerFromUnderlyingSource(JSContext * cx,Handle<ReadableStream * > stream,Handle<Value> underlyingSource,double highWaterMark,Handle<Value> sizeAlgorithm)640 [[nodiscard]] bool js::SetUpReadableStreamDefaultControllerFromUnderlyingSource(
641     JSContext* cx, Handle<ReadableStream*> stream,
642     Handle<Value> underlyingSource, double highWaterMark,
643     Handle<Value> sizeAlgorithm) {
644   // Step 1: Assert: underlyingSource is not undefined.
645   MOZ_ASSERT(!underlyingSource.isUndefined());
646 
647   // Step 2: Let controller be ObjectCreate(the original value of
648   //         ReadableStreamDefaultController's prototype property).
649   // (Deferred to SetUpReadableStreamDefaultController.)
650 
651   // Step 3: Let startAlgorithm be the following steps:
652   //         a. Return ? InvokeOrNoop(underlyingSource, "start",
653   //                                  « controller »).
654   SourceAlgorithms sourceAlgorithms = SourceAlgorithms::Script;
655 
656   // Step 4: Let pullAlgorithm be
657   //         ? CreateAlgorithmFromUnderlyingMethod(underlyingSource, "pull",
658   //                                               0, « controller »).
659   Rooted<Value> pullMethod(cx);
660   if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSource,
661                                            "ReadableStream source.pull method",
662                                            cx->names().pull, &pullMethod)) {
663     return false;
664   }
665 
666   // Step 5. Let cancelAlgorithm be
667   //         ? CreateAlgorithmFromUnderlyingMethod(underlyingSource,
668   //                                               "cancel", 1, « »).
669   Rooted<Value> cancelMethod(cx);
670   if (!CreateAlgorithmFromUnderlyingMethod(
671           cx, underlyingSource, "ReadableStream source.cancel method",
672           cx->names().cancel, &cancelMethod)) {
673     return false;
674   }
675 
676   // Step 6. Perform ? SetUpReadableStreamDefaultController(stream,
677   //             controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
678   //             highWaterMark, sizeAlgorithm).
679   return SetUpReadableStreamDefaultController(
680       cx, stream, sourceAlgorithms, underlyingSource, pullMethod, cancelMethod,
681       highWaterMark, sizeAlgorithm);
682 }
683