1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 #include "js/Exception.h"
8 #include "js/TypeDecls.h"
9 #include "js/Value.h"
10 #include "mozilla/AlreadyAddRefed.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/dom/AbortSignal.h"
13 #include "mozilla/dom/Promise.h"
14 #include "mozilla/dom/PromiseNativeHandler.h"
15 #include "mozilla/dom/WritableStream.h"
16 #include "mozilla/dom/WritableStreamDefaultController.h"
17 #include "mozilla/dom/WritableStreamDefaultControllerBinding.h"
18 // #include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
19 #include "mozilla/dom/UnderlyingSinkBinding.h"
20 #include "nsCycleCollectionParticipant.h"
21 #include "nsDebug.h"
22 #include "nsISupports.h"
23
24 namespace mozilla::dom {
25
26 // Note: Using the individual macros vs NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE
27 // because I need to specificy a manual implementation of
28 // NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN.
29 NS_IMPL_CYCLE_COLLECTION_CLASS(WritableStreamDefaultController)
30 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WritableStreamDefaultController)
31 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal, mSignal, mStrategySizeAlgorithm,
32 mWriteAlgorithm, mCloseAlgorithm,
33 mAbortAlgorithm, mStream)
34 tmp->mQueue.clear();
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
36 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WritableStreamDefaultController)
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal, mSignal, mStrategySizeAlgorithm,
39 mWriteAlgorithm, mCloseAlgorithm,
40 mAbortAlgorithm, mStream)
41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
42
43 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WritableStreamDefaultController)
44 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
45 // Trace the associated queue.
46 for (const auto& queueEntry : tmp->mQueue) {
47 aCallbacks.Trace(&queueEntry->mValue, "mQueue.mValue", aClosure);
48 }
49 NS_IMPL_CYCLE_COLLECTION_TRACE_END
50
NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStreamDefaultController)51 NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStreamDefaultController)
52 NS_IMPL_CYCLE_COLLECTING_RELEASE(WritableStreamDefaultController)
53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamDefaultController)
54 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
55 NS_INTERFACE_MAP_ENTRY(nsISupports)
56 NS_INTERFACE_MAP_END
57
58 WritableStreamDefaultController::WritableStreamDefaultController(
59 nsISupports* aGlobal, WritableStream& aStream)
60 : mGlobal(do_QueryInterface(aGlobal)), mStream(&aStream) {}
61
~WritableStreamDefaultController()62 WritableStreamDefaultController::~WritableStreamDefaultController() {
63 // MG:XXX: LinkedLists are required to be empty at destruction, but it seems
64 // it is possible to have a controller be destructed while still
65 // having entries in its queue.
66 //
67 // This needs to be verified as not indicating some other issue.
68 mQueue.clear();
69 }
70
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)71 JSObject* WritableStreamDefaultController::WrapObject(
72 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
73 return WritableStreamDefaultController_Binding::Wrap(aCx, this, aGivenProto);
74 }
75
76 // https://streams.spec.whatwg.org/#ws-default-controller-error
Error(JSContext * aCx,JS::Handle<JS::Value> aError,ErrorResult & aRv)77 void WritableStreamDefaultController::Error(JSContext* aCx,
78 JS::Handle<JS::Value> aError,
79 ErrorResult& aRv) {
80 // Step 1. Let state be this.[[stream]].[[state]].
81 // Step 2. If state is not "writable", return.
82 if (mStream->State() != WritableStream::WriterState::Writable) {
83 return;
84 }
85 // Step 3. Perform ! WritableStreamDefaultControllerError(this, e).
86 RefPtr<WritableStreamDefaultController> thisRefPtr = this;
87 WritableStreamDefaultControllerError(aCx, thisRefPtr, aError, aRv);
88 }
89
90 // https://streams.spec.whatwg.org/#ws-default-controller-private-abort
AbortSteps(JSContext * aCx,JS::Handle<JS::Value> aReason,ErrorResult & aRv)91 already_AddRefed<Promise> WritableStreamDefaultController::AbortSteps(
92 JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
93 // Step 1. Let result be the result of performing this.[[abortAlgorithm]],
94 // passing reason.
95 RefPtr<UnderlyingSinkAbortCallbackHelper> abortAlgorithm(mAbortAlgorithm);
96 Optional<JS::Handle<JS::Value>> optionalReason(aCx, aReason);
97 RefPtr<Promise> abortPromise =
98 abortAlgorithm
99 ? abortAlgorithm->AbortCallback(aCx, optionalReason, aRv)
100 : Promise::CreateResolvedWithUndefined(GetParentObject(), aRv);
101 if (aRv.Failed()) {
102 return nullptr;
103 }
104
105 // Step 2. Perform ! WritableStreamDefaultControllerClearAlgorithms(this).
106 ClearAlgorithms();
107
108 // Step 3. Return result.
109 return abortPromise.forget();
110 }
111
112 // https://streams.spec.whatwg.org/#ws-default-controller-private-error
ErrorSteps()113 void WritableStreamDefaultController::ErrorSteps() {
114 // Step 1. Perform ! ResetQueue(this).
115 ResetQueue(this);
116 }
117
SetSignal(AbortSignal * aSignal)118 void WritableStreamDefaultController::SetSignal(AbortSignal* aSignal) {
119 MOZ_ASSERT(aSignal);
120 mSignal = aSignal;
121 }
122
123 MOZ_CAN_RUN_SCRIPT static void
124 WritableStreamDefaultControllerAdvanceQueueIfNeeded(
125 JSContext* aCx, WritableStreamDefaultController* aController,
126 ErrorResult& aRv);
127
128 class WritableStartPromiseNativeHandler final : public PromiseNativeHandler {
129 ~WritableStartPromiseNativeHandler() override = default;
130
131 // Virtually const, but cycle collected
132 RefPtr<WritableStreamDefaultController> mController;
133
134 public:
135 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(WritableStartPromiseNativeHandler)136 NS_DECL_CYCLE_COLLECTION_CLASS(WritableStartPromiseNativeHandler)
137
138 explicit WritableStartPromiseNativeHandler(
139 WritableStreamDefaultController* aController)
140 : PromiseNativeHandler(), mController(aController) {}
141
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)142 MOZ_CAN_RUN_SCRIPT void ResolvedCallback(JSContext* aCx,
143 JS::Handle<JS::Value> aValue,
144 ErrorResult& aRv) override {
145 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller
146 // Step 17. Upon fulfillment of startPromise,
147 // Step 17.1. Assert: stream.[[state]] is "writable" or "erroring".
148 MOZ_ASSERT(mController->Stream()->State() ==
149 WritableStream::WriterState::Writable ||
150 mController->Stream()->State() ==
151 WritableStream::WriterState::Erroring);
152 // Step 17.2. Set controller.[[started]] to true.
153 mController->SetStarted(true);
154 // Step 17.3 Perform
155 // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
156 WritableStreamDefaultControllerAdvanceQueueIfNeeded(
157 aCx, MOZ_KnownLive(mController), aRv);
158 }
159
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)160 MOZ_CAN_RUN_SCRIPT void RejectedCallback(JSContext* aCx,
161 JS::Handle<JS::Value> aValue,
162 ErrorResult& aRv) override {
163 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller
164 RefPtr<WritableStream> stream = mController->Stream();
165 // Step 18. Upon rejection of startPromise with reason r,
166 // Step 18.1. Assert: stream.[[state]] is "writable" or "erroring".
167 MOZ_ASSERT(stream->State() == WritableStream::WriterState::Writable ||
168 stream->State() == WritableStream::WriterState::Erroring);
169 // Step 18.2. Set controller.[[started]] to true.
170 mController->SetStarted(true);
171 // Step 18.3. Perform ! WritableStreamDealWithRejection(stream, r).
172 stream->DealWithRejection(aCx, aValue, aRv);
173 }
174 };
175
NS_IMPL_CYCLE_COLLECTION(WritableStartPromiseNativeHandler,mController)176 NS_IMPL_CYCLE_COLLECTION(WritableStartPromiseNativeHandler, mController)
177 NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStartPromiseNativeHandler)
178 NS_IMPL_CYCLE_COLLECTING_RELEASE(WritableStartPromiseNativeHandler)
179 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStartPromiseNativeHandler)
180 NS_INTERFACE_MAP_ENTRY(nsISupports)
181 NS_INTERFACE_MAP_END
182
183 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller
184 void SetUpWritableStreamDefaultController(
185 JSContext* aCx, WritableStream* aStream,
186 WritableStreamDefaultController* aController,
187 UnderlyingSinkStartCallbackHelper* aStartAlgorithm,
188 UnderlyingSinkWriteCallbackHelper* aWriteAlgorithm,
189 UnderlyingSinkCloseCallbackHelper* aCloseAlgorithm,
190 UnderlyingSinkAbortCallbackHelper* aAbortAlgorithm, double aHighWaterMark,
191 QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
192 // Step 1. Assert: stream implements WritableStream.
193 // Step 2. Assert: stream.[[controller]] is undefined.
194 MOZ_ASSERT(!aStream->Controller());
195
196 // Step 3. Set controller.[[stream]] to stream.
197 // Note: Already set in
198 // SetUpWritableStreamDefaultControllerFromUnderlyingSink.
199 MOZ_ASSERT(aController->Stream() == aStream);
200
201 // Step 4. Set stream.[[controller]] to controller.
202 aStream->SetController(aController);
203
204 // Step 5. Perform ! ResetQueue(controller).
205 ResetQueue(aController);
206
207 // Step 6. Set controller.[[signal]] to a new AbortSignal.
208 RefPtr<AbortSignal> signal = new AbortSignal(aController->GetParentObject(),
209 false, JS::UndefinedHandleValue);
210 aController->SetSignal(signal);
211
212 // Step 7. Set controller.[[started]] to false.
213 aController->SetStarted(false);
214
215 // Step 8. Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm.
216 aController->SetStrategySizeAlgorithm(aSizeAlgorithm);
217
218 // Step 9. Set controller.[[strategyHWM]] to highWaterMark.
219 aController->SetStrategyHWM(aHighWaterMark);
220
221 // Step 10. Set controller.[[writeAlgorithm]] to writeAlgorithm.
222 aController->SetWriteAlgorithm(aWriteAlgorithm);
223
224 // Step 11. Set controller.[[closeAlgorithm]] to closeAlgorithm.
225 aController->SetCloseAlgorithm(aCloseAlgorithm);
226
227 // Step 12. Set controller.[[abortAlgorithm]] to abortAlgorithm.
228 aController->SetAbortAlgorithm(aAbortAlgorithm);
229
230 // Step 13. Let backpressure be !
231 // WritableStreamDefaultControllerGetBackpressure(controller).
232 bool backpressure = aController->GetBackpressure();
233
234 // Step 14. Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
235 aStream->UpdateBackpressure(backpressure, aRv);
236 if (aRv.Failed()) {
237 return;
238 }
239
240 // Step 15. Let startResult be the result of performing startAlgorithm. (This
241 // may throw an exception.)
242 JS::Rooted<JS::Value> startResult(aCx, JS::UndefinedValue());
243 if (aStartAlgorithm) {
244 // Strong Refs:
245 RefPtr<UnderlyingSinkStartCallbackHelper> startAlgorithm(aStartAlgorithm);
246 RefPtr<WritableStreamDefaultController> controller(aController);
247
248 startAlgorithm->StartCallback(aCx, *controller, &startResult, aRv);
249 if (aRv.Failed()) {
250 return;
251 }
252 }
253
254 // Step 16. Let startPromise be a promise resolved with startResult.
255 RefPtr<Promise> startPromise = Promise::Create(GetIncumbentGlobal(), aRv);
256 if (aRv.Failed()) {
257 return;
258 }
259 startPromise->MaybeResolve(startResult);
260
261 // Step 17/18.
262 RefPtr<WritableStartPromiseNativeHandler> startPromiseHandler =
263 new WritableStartPromiseNativeHandler(aController);
264 startPromise->AppendNativeHandler(startPromiseHandler);
265 }
266
267 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
SetUpWritableStreamDefaultControllerFromUnderlyingSink(JSContext * aCx,WritableStream * aStream,JS::HandleObject aUnderlyingSink,UnderlyingSink & aUnderlyingSinkDict,double aHighWaterMark,QueuingStrategySize * aSizeAlgorithm,ErrorResult & aRv)268 void SetUpWritableStreamDefaultControllerFromUnderlyingSink(
269 JSContext* aCx, WritableStream* aStream, JS::HandleObject aUnderlyingSink,
270 UnderlyingSink& aUnderlyingSinkDict, double aHighWaterMark,
271 QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
272 // Step 1.
273 RefPtr<WritableStreamDefaultController> controller =
274 new WritableStreamDefaultController(aStream->GetParentObject(), *aStream);
275
276 // Step 6. (implicit Step 2.)
277 RefPtr<UnderlyingSinkStartCallbackHelper> startAlgorithm =
278 aUnderlyingSinkDict.mStart.WasPassed()
279 ? new UnderlyingSinkStartCallbackHelper(
280 aUnderlyingSinkDict.mStart.Value(), aUnderlyingSink)
281 : nullptr;
282
283 // Step 7. (implicit Step 3.)
284 RefPtr<UnderlyingSinkWriteCallbackHelper> writeAlgorithm =
285 aUnderlyingSinkDict.mWrite.WasPassed()
286 ? new UnderlyingSinkWriteCallbackHelper(
287 aUnderlyingSinkDict.mWrite.Value(), aUnderlyingSink)
288 : nullptr;
289
290 // Step 8. (implicit Step 4.)
291 RefPtr<UnderlyingSinkCloseCallbackHelper> closeAlgorithm =
292 aUnderlyingSinkDict.mClose.WasPassed()
293 ? new UnderlyingSinkCloseCallbackHelper(
294 aUnderlyingSinkDict.mClose.Value(), aUnderlyingSink)
295 : nullptr;
296
297 // Step 9. (implicit Step 5.)
298 RefPtr<UnderlyingSinkAbortCallbackHelper> abortAlgorithm =
299 aUnderlyingSinkDict.mAbort.WasPassed()
300 ? new UnderlyingSinkAbortCallbackHelper(
301 aUnderlyingSinkDict.mAbort.Value(), aUnderlyingSink)
302 : nullptr;
303
304 // Step 10.
305 SetUpWritableStreamDefaultController(
306 aCx, aStream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm,
307 abortAlgorithm, aHighWaterMark, aSizeAlgorithm, aRv);
308 }
309
310 // MG:XXX: Probably can find base class between this and
311 // StartPromiseNativeHandler
312 class SinkCloseNativePromiseHandler final : public PromiseNativeHandler {
313 ~SinkCloseNativePromiseHandler() override = default;
314
315 public:
316 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SinkCloseNativePromiseHandler)317 NS_DECL_CYCLE_COLLECTION_CLASS(SinkCloseNativePromiseHandler)
318
319 explicit SinkCloseNativePromiseHandler(
320 WritableStreamDefaultController* aController)
321 : PromiseNativeHandler(), mController(aController) {}
322
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)323 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
324 ErrorResult& aRv) override {
325 // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close
326 RefPtr<WritableStream> stream = mController->Stream();
327 // Step 7. Upon fulfillment of sinkClosePromise,
328 // Step 7.1. Perform ! WritableStreamFinishInFlightClose(stream).
329 stream->FinishInFlightClose();
330 }
331
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)332 MOZ_CAN_RUN_SCRIPT void RejectedCallback(JSContext* aCx,
333 JS::Handle<JS::Value> aValue,
334 ErrorResult& aRv) override {
335 // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close
336 RefPtr<WritableStream> stream = mController->Stream();
337 // Step 8. Upon rejection of sinkClosePromise with reason reason,
338 // Step 8.1. Perform ! WritableStreamFinishInFlightCloseWithError(stream,
339 // reason).
340
341 stream->FinishInFlightCloseWithError(aCx, aValue, aRv);
342 }
343
344 private:
345 RefPtr<WritableStreamDefaultController> mController;
346 };
347
348 // Cycle collection methods for promise handler.
NS_IMPL_CYCLE_COLLECTION(SinkCloseNativePromiseHandler,mController)349 NS_IMPL_CYCLE_COLLECTION(SinkCloseNativePromiseHandler, mController)
350 NS_IMPL_CYCLE_COLLECTING_ADDREF(SinkCloseNativePromiseHandler)
351 NS_IMPL_CYCLE_COLLECTING_RELEASE(SinkCloseNativePromiseHandler)
352 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SinkCloseNativePromiseHandler)
353 NS_INTERFACE_MAP_ENTRY(nsISupports)
354 NS_INTERFACE_MAP_END
355
356 // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close
357 MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessClose(
358 JSContext* aCx, WritableStreamDefaultController* aController,
359 ErrorResult& aRv) {
360 // Step 1. Let stream be controller.[[stream]].
361 RefPtr<WritableStream> stream = aController->Stream();
362
363 // Step 2. Perform ! WritableStreamMarkCloseRequestInFlight(stream).
364 stream->MarkCloseRequestInFlight();
365
366 // Step 3. Perform ! DequeueValue(controller).
367 JS::Rooted<JS::Value> value(aCx);
368 DequeueValue(aController, &value);
369
370 // Step 4. Assert: controller.[[queue]] is empty.
371 MOZ_ASSERT(aController->Queue().isEmpty());
372
373 // Step 5. Let sinkClosePromise be the result of performing
374 // controller.[[closeAlgorithm]].
375 RefPtr<UnderlyingSinkCloseCallbackHelper> closeAlgorithm(
376 aController->GetCloseAlgorithm());
377
378 RefPtr<Promise> sinkClosePromise =
379 closeAlgorithm ? closeAlgorithm->CloseCallback(aCx, aRv)
380 : Promise::CreateResolvedWithUndefined(
381 aController->GetParentObject(), aRv);
382
383 if (aRv.Failed()) {
384 return;
385 }
386
387 // Step 6. Perform !
388 // WritableStreamDefaultControllerClearAlgorithms(controller).
389 aController->ClearAlgorithms();
390
391 // Step 7 + 8.
392 sinkClosePromise->AppendNativeHandler(
393 new SinkCloseNativePromiseHandler(aController));
394 }
395
396 // MG:XXX: Probably can find base class between this and
397 // StartPromiseNativeHandler
398 class SinkWriteNativePromiseHandler final : public PromiseNativeHandler {
399 ~SinkWriteNativePromiseHandler() override = default;
400
401 // Virtually const, but is cycle collected
402 RefPtr<WritableStreamDefaultController> mController;
403
404 public:
405 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SinkWriteNativePromiseHandler)406 NS_DECL_CYCLE_COLLECTION_CLASS(SinkWriteNativePromiseHandler)
407
408 explicit SinkWriteNativePromiseHandler(
409 WritableStreamDefaultController* aController)
410 : PromiseNativeHandler(), mController(aController) {}
411
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)412 MOZ_CAN_RUN_SCRIPT void ResolvedCallback(JSContext* aCx,
413 JS::Handle<JS::Value> aValue,
414 ErrorResult& aRv) override {
415 // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write
416 RefPtr<WritableStream> stream = mController->Stream();
417
418 // Step 4.1. Perform ! WritableStreamFinishInFlightWrite(stream).
419 stream->FinishInFlightWrite();
420
421 // Step 4.2. Let state be stream.[[state]].
422 WritableStream::WriterState state = stream->State();
423
424 // Step 4.3. Assert: state is "writable" or "erroring".
425 MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
426 state == WritableStream::WriterState::Erroring);
427
428 // Step 4.4. Perform ! DequeueValue(controller).
429 JS::Rooted<JS::Value> value(aCx);
430 DequeueValue(mController, &value);
431
432 // Step 4.5. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
433 // state is "writable",
434 if (!stream->CloseQueuedOrInFlight() &&
435 state == WritableStream::WriterState::Writable) {
436 // Step 4.5.1. Let backpressure be !
437 // WritableStreamDefaultControllerGetBackpressure(controller).
438 bool backpressure = mController->GetBackpressure();
439 // Step 4.5.2. Perform ! WritableStreamUpdateBackpressure(stream,
440 // backpressure).
441 stream->UpdateBackpressure(backpressure, aRv);
442 if (aRv.Failed()) {
443 return;
444 }
445 }
446
447 // Step 4.6. Perform !
448 // WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
449 WritableStreamDefaultControllerAdvanceQueueIfNeeded(
450 aCx, MOZ_KnownLive(mController), aRv);
451 }
452
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)453 MOZ_CAN_RUN_SCRIPT void RejectedCallback(JSContext* aCx,
454 JS::Handle<JS::Value> aValue,
455 ErrorResult& aRv) override {
456 // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write
457 RefPtr<WritableStream> stream = mController->Stream();
458
459 // Step 5.1. If stream.[[state]] is "writable", perform !
460 // WritableStreamDefaultControllerClearAlgorithms(controller).
461 if (stream->State() == WritableStream::WriterState::Writable) {
462 mController->ClearAlgorithms();
463 }
464
465 // Step 5.2. Perform ! WritableStreamFinishInFlightWriteWithError(stream,
466 // reason)
467
468 stream->FinishInFlightWriteWithError(aCx, aValue, aRv);
469 }
470 };
471
472 // Cycle collection methods for promise handler.
NS_IMPL_CYCLE_COLLECTION(SinkWriteNativePromiseHandler,mController)473 NS_IMPL_CYCLE_COLLECTION(SinkWriteNativePromiseHandler, mController)
474 NS_IMPL_CYCLE_COLLECTING_ADDREF(SinkWriteNativePromiseHandler)
475 NS_IMPL_CYCLE_COLLECTING_RELEASE(SinkWriteNativePromiseHandler)
476 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SinkWriteNativePromiseHandler)
477 NS_INTERFACE_MAP_ENTRY(nsISupports)
478 NS_INTERFACE_MAP_END
479
480 // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write
481 MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessWrite(
482 JSContext* aCx, WritableStreamDefaultController* aController,
483 JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
484 // Step 1. Let stream be controller.[[stream]].
485 RefPtr<WritableStream> stream = aController->Stream();
486
487 // Step 2. Perform ! WritableStreamMarkFirstWriteRequestInFlight(stream).
488 stream->MarkFirstWriteRequestInFlight();
489
490 // Step 3. Let sinkWritePromise be the result of performing
491 // controller.[[writeAlgorithm]], passing in chunk.
492 RefPtr<UnderlyingSinkWriteCallbackHelper> writeAlgorithm(
493 aController->GetWriteAlgorithm());
494
495 RefPtr<Promise> sinkWritePromise =
496 writeAlgorithm
497 ? writeAlgorithm->WriteCallback(aCx, aChunk, *aController, aRv)
498 : Promise::CreateResolvedWithUndefined(aController->GetParentObject(),
499 aRv);
500 if (aRv.Failed()) {
501 return;
502 }
503
504 // Step 4 + 5:
505 sinkWritePromise->AppendNativeHandler(
506 new SinkWriteNativePromiseHandler(aController));
507 }
508
509 // We use a JS::MagicValue to represent the close sentinel required by the spec.
510 // Normal JavaScript code can not generate magic values, so we can use this
511 // as a special value. However care has to be taken to not leak the magic value
512 // to other code.
513 constexpr JSWhyMagic CLOSE_SENTINEL = JS_GENERIC_MAGIC;
514
515 // https://streams.spec.whatwg.org/#writable-stream-default-controller-advance-queue-if-needed
WritableStreamDefaultControllerAdvanceQueueIfNeeded(JSContext * aCx,WritableStreamDefaultController * aController,ErrorResult & aRv)516 static void WritableStreamDefaultControllerAdvanceQueueIfNeeded(
517 JSContext* aCx, WritableStreamDefaultController* aController,
518 ErrorResult& aRv) {
519 // Step 1. Let stream be controller.[[stream]].
520 RefPtr<WritableStream> stream = aController->Stream();
521
522 // Step 2. If controller.[[started]] is false, return.
523 if (!aController->Started()) {
524 return;
525 }
526
527 // Step 3. If stream.[[inFlightWriteRequest]] is not undefined, return.
528 if (stream->GetInFlightWriteRequest()) {
529 return;
530 }
531
532 // Step 4. Let state be stream.[[state]].
533 WritableStream::WriterState state = stream->State();
534
535 // Step 5. Assert: state is not "closed" or "errored".
536 MOZ_ASSERT(state != WritableStream::WriterState::Closed &&
537 state != WritableStream::WriterState::Errored);
538
539 // Step 6. If state is "erroring",
540 if (state == WritableStream::WriterState::Erroring) {
541 // Step 6.1. Perform ! WritableStreamFinishErroring(stream).
542 stream->FinishErroring(aCx, aRv);
543
544 // Step 6.2. Return.
545 return;
546 }
547
548 // Step 7. If controller.[[queue]] is empty, return.
549 if (aController->Queue().isEmpty()) {
550 return;
551 }
552
553 // Step 8. Let value be ! PeekQueueValue(controller).
554 JS::Rooted<JS::Value> value(aCx);
555 PeekQueueValue(aController, &value);
556
557 // Step 9. If value is the close sentinel, perform !
558 // WritableStreamDefaultControllerProcessClose(controller).
559 if (value.isMagic(CLOSE_SENTINEL)) {
560 WritableStreamDefaultControllerProcessClose(aCx, aController, aRv);
561 return;
562 }
563
564 // Step 10. Otherwise, perform !
565 // WritableStreamDefaultControllerProcessWrite(controller, value).
566 WritableStreamDefaultControllerProcessWrite(aCx, aController, value, aRv);
567 }
568
569 // https://streams.spec.whatwg.org/#writable-stream-default-controller-close
WritableStreamDefaultControllerClose(JSContext * aCx,WritableStreamDefaultController * aController,ErrorResult & aRv)570 void WritableStreamDefaultControllerClose(
571 JSContext* aCx, WritableStreamDefaultController* aController,
572 ErrorResult& aRv) {
573 // Step 1. Perform ! EnqueueValueWithSize(controller, close sentinel, 0).
574 JS::Rooted<JS::Value> aCloseSentinel(aCx, JS::MagicValue(CLOSE_SENTINEL));
575 EnqueueValueWithSize(aController, aCloseSentinel, 0, aRv);
576 MOZ_ASSERT(!aRv.Failed());
577
578 // Step 2. Perform !
579 // WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
580 WritableStreamDefaultControllerAdvanceQueueIfNeeded(aCx, aController, aRv);
581 }
582
583 // https://streams.spec.whatwg.org/#writable-stream-default-controller-write
WritableStreamDefaultControllerWrite(JSContext * aCx,WritableStreamDefaultController * aController,JS::Handle<JS::Value> aChunk,double chunkSize,ErrorResult & aRv)584 void WritableStreamDefaultControllerWrite(
585 JSContext* aCx, WritableStreamDefaultController* aController,
586 JS::Handle<JS::Value> aChunk, double chunkSize, ErrorResult& aRv) {
587 // Step 1. Let enqueueResult be EnqueueValueWithSize(controller, chunk,
588 // chunkSize).
589 IgnoredErrorResult rv;
590 EnqueueValueWithSize(aController, aChunk, chunkSize, rv);
591
592 // Step 2. If enqueueResult is an abrupt completion,
593 if (rv.MaybeSetPendingException(aCx,
594 "WritableStreamDefaultController.write")) {
595 JS::Rooted<JS::Value> error(aCx);
596 JS_GetPendingException(aCx, &error);
597 JS_ClearPendingException(aCx);
598
599 // Step 2.1. Perform !
600 // WritableStreamDefaultControllerErrorIfNeeded(controller,
601 // enqueueResult.[[Value]]).
602 WritableStreamDefaultControllerErrorIfNeeded(aCx, aController, error, aRv);
603
604 // Step 2.2. Return.
605 return;
606 }
607
608 // Step 3. Let stream be controller.[[stream]].
609 RefPtr<WritableStream> stream = aController->Stream();
610
611 // Step 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
612 // stream.[[state]] is "writable",
613 if (!stream->CloseQueuedOrInFlight() &&
614 stream->State() == WritableStream::WriterState::Writable) {
615 // Step 4.1. Let backpressure be
616 // !WritableStreamDefaultControllerGetBackpressure(controller).
617 bool backpressure = aController->GetBackpressure();
618
619 // Step 4.2. Perform ! WritableStreamUpdateBackpressure(stream,
620 // backpressure).
621 stream->UpdateBackpressure(backpressure, aRv);
622 if (aRv.Failed()) {
623 return;
624 }
625 }
626
627 // Step 5. Perform
628 // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
629 WritableStreamDefaultControllerAdvanceQueueIfNeeded(aCx, aController, aRv);
630 }
631
WritableStreamDefaultControllerError(JSContext * aCx,WritableStreamDefaultController * aController,JS::Handle<JS::Value> aError,ErrorResult & aRv)632 void WritableStreamDefaultControllerError(
633 JSContext* aCx, WritableStreamDefaultController* aController,
634 JS::Handle<JS::Value> aError, ErrorResult& aRv) {
635 // Step 1. Let stream be controller.[[stream]].
636 RefPtr<WritableStream> stream = aController->Stream();
637
638 // Step 2. Assert: stream.[[state]] is "writable".
639 MOZ_ASSERT(stream->State() == WritableStream::WriterState::Writable);
640
641 // Step 3. Perform
642 // ! WritableStreamDefaultControllerClearAlgorithms(controller).
643 aController->ClearAlgorithms();
644
645 // Step 4.Perform ! WritableStreamStartErroring(stream, error).
646 stream->StartErroring(aCx, aError, aRv);
647 }
648
649 // https://streams.spec.whatwg.org/#writable-stream-default-controller-error-if-needed
WritableStreamDefaultControllerErrorIfNeeded(JSContext * aCx,WritableStreamDefaultController * aController,JS::Handle<JS::Value> aError,ErrorResult & aRv)650 void WritableStreamDefaultControllerErrorIfNeeded(
651 JSContext* aCx, WritableStreamDefaultController* aController,
652 JS::Handle<JS::Value> aError, ErrorResult& aRv) {
653 // Step 1. If controller.[[stream]].[[state]] is "writable", perform
654 // !WritableStreamDefaultControllerError(controller, error).
655 if (aController->Stream()->State() == WritableStream::WriterState::Writable) {
656 WritableStreamDefaultControllerError(aCx, aController, aError, aRv);
657 }
658 }
659
660 // https://streams.spec.whatwg.org/#writable-stream-default-controller-get-chunk-size
WritableStreamDefaultControllerGetChunkSize(JSContext * aCx,WritableStreamDefaultController * aController,JS::Handle<JS::Value> aChunk,ErrorResult & aRv)661 double WritableStreamDefaultControllerGetChunkSize(
662 JSContext* aCx, WritableStreamDefaultController* aController,
663 JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
664 // Step 1. Let returnValue be the result of performing
665 // controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting
666 // the result as a completion record.
667 RefPtr<QueuingStrategySize> sizeAlgorithm(
668 aController->StrategySizeAlgorithm());
669
670 // If !sizeAlgorithm, we return 1, which is inlined from
671 // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function
672 Optional<JS::Handle<JS::Value>> optionalChunk(aCx, aChunk);
673
674 double chunkSize =
675 sizeAlgorithm
676 ? sizeAlgorithm->Call(
677 optionalChunk, aRv,
678 "WritableStreamDefaultController.[[strategySizeAlgorithm]]",
679 CallbackObject::eRethrowExceptions)
680 : 1.0;
681
682 // Step 2. If returnValue is an abrupt completion,
683 if (aRv.MaybeSetPendingException(
684 aCx, "WritableStreamDefaultController.[[strategySizeAlgorithm]]")) {
685 JS::Rooted<JS::Value> error(aCx);
686 JS_GetPendingException(aCx, &error);
687 JS_ClearPendingException(aCx);
688
689 // Step 2.1. Perform !
690 // WritableStreamDefaultControllerErrorIfNeeded(controller,
691 // returnValue.[[Value]]).
692 WritableStreamDefaultControllerErrorIfNeeded(aCx, aController, error, aRv);
693
694 // Step 2.2. Return 1.
695 return 1.0;
696 }
697
698 // Step 3. Return returnValue.[[Value]].
699 return chunkSize;
700 }
701
702 } // namespace mozilla::dom
703