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/. */
7 #include "js/Exception.h"
8 #include "js/TypeDecls.h"
10 #include "mozilla/AlreadyAddRefed.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/HoldDropJSObjects.h"
13 #include "mozilla/dom/Promise.h"
14 #include "mozilla/dom/Promise-inl.h"
15 #include "mozilla/dom/PromiseNativeHandler.h"
16 #include "mozilla/dom/ReadableStream.h"
17 #include "mozilla/dom/ReadableStreamController.h"
18 #include "mozilla/dom/ReadableStreamDefaultController.h"
19 #include "mozilla/dom/ReadableStreamDefaultControllerBinding.h"
20 #include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
21 #include "mozilla/dom/UnderlyingSourceBinding.h"
22 #include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
23 #include "nsCycleCollectionParticipant.h"
24 #include "nsISupports.h"
26 namespace mozilla::dom
{
28 NS_IMPL_CYCLE_COLLECTION(ReadableStreamController
, mGlobal
)
29 NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadableStreamController
)
30 NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadableStreamController
)
32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamController
)
33 NS_INTERFACE_MAP_ENTRY(nsISupports
)
36 // Note: Using the individual macros vs NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE
37 // because I need to specificy a manual implementation of
38 // NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN.
39 NS_IMPL_CYCLE_COLLECTION_CLASS(ReadableStreamDefaultController
)
41 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ReadableStreamDefaultController
)
42 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAlgorithms
, mStrategySizeAlgorithm
, mStream
)
44 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
45 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
48 ReadableStreamDefaultController
, ReadableStreamController
)
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAlgorithms
, mStrategySizeAlgorithm
,
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
53 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ReadableStreamDefaultController
,
54 ReadableStreamController
)
55 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
56 // Trace the associated queue.
57 for (const auto& queueEntry
: tmp
->mQueue
) {
58 aCallbacks
.Trace(&queueEntry
->mValue
, "mQueue.mValue", aClosure
);
60 NS_IMPL_CYCLE_COLLECTION_TRACE_END
62 NS_IMPL_ADDREF_INHERITED(ReadableStreamDefaultController
,
63 ReadableStreamController
)
64 NS_IMPL_RELEASE_INHERITED(ReadableStreamDefaultController
,
65 ReadableStreamController
)
67 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamDefaultController
)
68 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
69 NS_INTERFACE_MAP_END_INHERITING(ReadableStreamController
)
71 ReadableStreamDefaultController::ReadableStreamDefaultController(
72 nsIGlobalObject
* aGlobal
)
73 : ReadableStreamController(aGlobal
) {
74 mozilla::HoldJSObjects(this);
77 ReadableStreamDefaultController::~ReadableStreamDefaultController() {
78 // MG:XXX: LinkedLists are required to be empty at destruction, but it seems
79 // it is possible to have a controller be destructed while still
80 // having entries in its queue.
82 // This needs to be verified as not indicating some other issue.
83 mozilla::DropJSObjects(this);
87 JSObject
* ReadableStreamDefaultController::WrapObject(
88 JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
) {
89 return ReadableStreamDefaultController_Binding::Wrap(aCx
, this, aGivenProto
);
92 void ReadableStreamDefaultController::SetStream(ReadableStream
* aStream
) {
96 // https://streams.spec.whatwg.org/#readable-stream-default-controller-can-close-or-enqueue
97 static bool ReadableStreamDefaultControllerCanCloseOrEnqueue(
98 ReadableStreamDefaultController
* aController
) {
99 // Step 1. Let state be controller.[[stream]].[[state]].
100 ReadableStream::ReaderState state
= aController
->GetStream()->State();
102 // Step 2. If controller.[[closeRequested]] is false and state is "readable",
104 // Step 3. Return false.
105 return !aController
->CloseRequested() &&
106 state
== ReadableStream::ReaderState::Readable
;
109 // https://streams.spec.whatwg.org/#readable-stream-default-controller-can-close-or-enqueue
110 // This is a variant of ReadableStreamDefaultControllerCanCloseOrEnqueue
111 // that also throws when the function would return false to improve error
113 bool ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
114 ReadableStreamDefaultController
* aController
,
115 CloseOrEnqueue aCloseOrEnqueue
, ErrorResult
& aRv
) {
116 // Step 1. Let state be controller.[[stream]].[[state]].
117 ReadableStream::ReaderState state
= aController
->GetStream()->State();
120 if (aCloseOrEnqueue
== CloseOrEnqueue::Close
) {
121 prefix
= "Cannot close a stream that "_ns
;
123 prefix
= "Cannot enqueue into a stream that "_ns
;
127 case ReadableStream::ReaderState::Readable
:
128 // Step 2. If controller.[[closeRequested]] is false and
129 // state is "readable", return true.
130 // Note: We don't error/check for [[closeRequest]] first, because
131 // [[closedRequest]] is still true even after the state is "closed".
132 // This doesn't cause any spec observable difference.
133 if (!aController
->CloseRequested()) {
137 // Step 3. Return false.
138 aRv
.ThrowTypeError(prefix
+ "has already been requested to close."_ns
);
141 case ReadableStream::ReaderState::Closed
:
142 aRv
.ThrowTypeError(prefix
+ "is already closed."_ns
);
145 case ReadableStream::ReaderState::Errored
:
146 aRv
.ThrowTypeError(prefix
+ "has errored."_ns
);
150 MOZ_ASSERT_UNREACHABLE("Unknown ReaderState");
155 Nullable
<double> ReadableStreamDefaultControllerGetDesiredSize(
156 ReadableStreamDefaultController
* aController
) {
157 ReadableStream::ReaderState state
= aController
->GetStream()->State();
158 if (state
== ReadableStream::ReaderState::Errored
) {
162 if (state
== ReadableStream::ReaderState::Closed
) {
166 return aController
->StrategyHWM() - aController
->QueueTotalSize();
169 // https://streams.spec.whatwg.org/#rs-default-controller-desired-size
170 Nullable
<double> ReadableStreamDefaultController::GetDesiredSize() {
172 return ReadableStreamDefaultControllerGetDesiredSize(this);
175 // https://streams.spec.whatwg.org/#readable-stream-default-controller-clear-algorithms
177 // Note: nullptr is used to indicate we run the default algorithm at the
179 // so the below doesn't quite match the spec, but serves the correct
180 // purpose for disconnecting the algorithms from the object graph to allow
183 // As far as I know, this isn't currently visible, but we need to keep
184 // this in mind. This is a weakness of this current implementation, and
185 // I'd prefer to have a better answer here eventually.
186 void ReadableStreamDefaultControllerClearAlgorithms(
187 ReadableStreamDefaultController
* aController
) {
190 aController
->SetAlgorithms(nullptr);
193 aController
->setStrategySizeAlgorithm(nullptr);
196 // https://streams.spec.whatwg.org/#readable-stream-default-controller-close
197 void ReadableStreamDefaultControllerClose(
198 JSContext
* aCx
, ReadableStreamDefaultController
* aController
,
201 if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(aController
)) {
206 RefPtr
<ReadableStream
> stream
= aController
->GetStream();
209 aController
->SetCloseRequested(true);
212 if (aController
->Queue().isEmpty()) {
214 ReadableStreamDefaultControllerClearAlgorithms(aController
);
217 ReadableStreamClose(aCx
, stream
, aRv
);
221 // https://streams.spec.whatwg.org/#rs-default-controller-close
222 void ReadableStreamDefaultController::Close(JSContext
* aCx
, ErrorResult
& aRv
) {
224 if (!ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
225 this, CloseOrEnqueue::Close
, aRv
)) {
230 ReadableStreamDefaultControllerClose(aCx
, this, aRv
);
233 MOZ_CAN_RUN_SCRIPT
static void ReadableStreamDefaultControllerCallPullIfNeeded(
234 JSContext
* aCx
, ReadableStreamDefaultController
* aController
,
237 // https://streams.spec.whatwg.org/#readable-stream-default-controller-enqueue
238 void ReadableStreamDefaultControllerEnqueue(
239 JSContext
* aCx
, ReadableStreamDefaultController
* aController
,
240 JS::Handle
<JS::Value
> aChunk
, ErrorResult
& aRv
) {
242 if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(aController
)) {
247 RefPtr
<ReadableStream
> stream
= aController
->GetStream();
250 if (IsReadableStreamLocked(stream
) &&
251 ReadableStreamGetNumReadRequests(stream
) > 0) {
252 ReadableStreamFulfillReadRequest(aCx
, stream
, aChunk
, false, aRv
);
255 Optional
<JS::Handle
<JS::Value
>> optionalChunk(aCx
, aChunk
);
257 // Step 4.3 (Re-ordered);
258 RefPtr
<QueuingStrategySize
> sizeAlgorithm(
259 aController
->StrategySizeAlgorithm());
261 // If !sizeAlgorithm, we return 1, which is inlined from
262 // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function
265 ? sizeAlgorithm
->Call(
267 "ReadableStreamDefaultController.[[strategySizeAlgorithm]]",
268 CallbackObject::eRethrowExceptions
)
271 // If this is an uncatchable exception we can't continue.
272 if (aRv
.IsUncatchableException()) {
277 if (aRv
.MaybeSetPendingException(
278 aCx
, "ReadableStreamDefaultController.enqueue")) {
279 JS::Rooted
<JS::Value
> errorValue(aCx
);
281 JS_GetPendingException(aCx
, &errorValue
);
285 ReadableStreamDefaultControllerError(aCx
, aController
, errorValue
, aRv
);
290 // Step 4.2.2 Caller must treat aRv as if it were a completion
292 aRv
.MightThrowJSException();
293 aRv
.ThrowJSException(aCx
, errorValue
);
298 EnqueueValueWithSize(aController
, aChunk
, chunkSize
, aRv
);
301 // Note we convert the pending exception to a JS value here, and then
302 // re-throw it because we save this exception and re-expose it elsewhere
303 // and there are tests to ensure the identity of these errors are the same.
304 if (aRv
.MaybeSetPendingException(
305 aCx
, "ReadableStreamDefaultController.enqueue")) {
306 JS::Rooted
<JS::Value
> errorValue(aCx
);
308 JS_GetPendingException(aCx
, &errorValue
);
311 ReadableStreamDefaultControllerError(aCx
, aController
, errorValue
, aRv
);
316 // Step 4.5.2 Caller must treat aRv as if it were a completion
318 aRv
.MightThrowJSException();
319 aRv
.ThrowJSException(aCx
, errorValue
);
325 ReadableStreamDefaultControllerCallPullIfNeeded(aCx
, aController
, aRv
);
328 // https://streams.spec.whatwg.org/#rs-default-controller-close
329 void ReadableStreamDefaultController::Enqueue(JSContext
* aCx
,
330 JS::Handle
<JS::Value
> aChunk
,
333 if (!ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
334 this, CloseOrEnqueue::Enqueue
, aRv
)) {
339 ReadableStreamDefaultControllerEnqueue(aCx
, this, aChunk
, aRv
);
342 void ReadableStreamDefaultController::Error(JSContext
* aCx
,
343 JS::Handle
<JS::Value
> aError
,
345 ReadableStreamDefaultControllerError(aCx
, this, aError
, aRv
);
348 // https://streams.spec.whatwg.org/#readable-stream-default-controller-should-call-pull
349 bool ReadableStreamDefaultControllerShouldCallPull(
350 ReadableStreamDefaultController
* aController
) {
352 ReadableStream
* stream
= aController
->GetStream();
355 if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(aController
)) {
360 if (!aController
->Started()) {
365 if (IsReadableStreamLocked(stream
) &&
366 ReadableStreamGetNumReadRequests(stream
) > 0) {
371 Nullable
<double> desiredSize
=
372 ReadableStreamDefaultControllerGetDesiredSize(aController
);
375 MOZ_ASSERT(!desiredSize
.IsNull());
378 return desiredSize
.Value() > 0;
381 // https://streams.spec.whatwg.org/#readable-stream-default-controller-error
382 void ReadableStreamDefaultControllerError(
383 JSContext
* aCx
, ReadableStreamDefaultController
* aController
,
384 JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
) {
386 ReadableStream
* stream
= aController
->GetStream();
389 if (stream
->State() != ReadableStream::ReaderState::Readable
) {
394 ResetQueue(aController
);
397 ReadableStreamDefaultControllerClearAlgorithms(aController
);
400 ReadableStreamError(aCx
, stream
, aValue
, aRv
);
403 // https://streams.spec.whatwg.org/#readable-stream-default-controller-call-pull-if-needed
404 static void ReadableStreamDefaultControllerCallPullIfNeeded(
405 JSContext
* aCx
, ReadableStreamDefaultController
* aController
,
408 bool shouldPull
= ReadableStreamDefaultControllerShouldCallPull(aController
);
416 if (aController
->Pulling()) {
418 aController
->SetPullAgain(true);
424 MOZ_ASSERT(!aController
->PullAgain());
427 aController
->SetPulling(true);
430 RefPtr
<UnderlyingSourceAlgorithmsBase
> algorithms
=
431 aController
->GetAlgorithms();
432 RefPtr
<Promise
> pullPromise
=
433 algorithms
->PullCallback(aCx
, *aController
, aRv
);
439 pullPromise
->AddCallbacksWithCycleCollectedArgs(
440 [](JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
,
441 ReadableStreamDefaultController
* mController
)
442 MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
444 mController
->SetPulling(false);
446 if (mController
->PullAgain()) {
448 mController
->SetPullAgain(false);
452 ReadableStreamDefaultControllerCallPullIfNeeded(
453 aCx
, MOZ_KnownLive(mController
), aRv
);
456 [](JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
,
457 ReadableStreamDefaultController
* mController
) {
459 ReadableStreamDefaultControllerError(aCx
, mController
, aValue
, aRv
);
461 RefPtr(aController
));
464 // https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller
465 void SetUpReadableStreamDefaultController(
466 JSContext
* aCx
, ReadableStream
* aStream
,
467 ReadableStreamDefaultController
* aController
,
468 UnderlyingSourceAlgorithmsBase
* aAlgorithms
, double aHighWaterMark
,
469 QueuingStrategySize
* aSizeAlgorithm
, ErrorResult
& aRv
) {
471 MOZ_ASSERT(!aStream
->Controller());
474 aController
->SetStream(aStream
);
477 ResetQueue(aController
);
480 aController
->SetStarted(false);
481 aController
->SetCloseRequested(false);
482 aController
->SetPullAgain(false);
483 aController
->SetPulling(false);
486 aController
->setStrategySizeAlgorithm(aSizeAlgorithm
);
487 aController
->SetStrategyHWM(aHighWaterMark
);
491 aController
->SetAlgorithms(aAlgorithms
);
494 aStream
->SetController(*aController
);
496 // Step 9. Default algorithm returns undefined. See Step 2 of
497 // https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller
498 JS::Rooted
<JS::Value
> startResult(aCx
, JS::UndefinedValue());
499 RefPtr
<ReadableStreamDefaultController
> controller
= aController
;
500 aAlgorithms
->StartCallback(aCx
, *controller
, &startResult
, aRv
);
506 RefPtr
<Promise
> startPromise
= Promise::Create(GetIncumbentGlobal(), aRv
);
510 startPromise
->MaybeResolve(startResult
);
513 startPromise
->AddCallbacksWithCycleCollectedArgs(
514 [](JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
,
515 ReadableStreamDefaultController
* aController
)
516 MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
517 MOZ_ASSERT(aController
);
520 aController
->SetStarted(true);
523 aController
->SetPulling(false);
526 aController
->SetPullAgain(false);
529 ReadableStreamDefaultControllerCallPullIfNeeded(
530 aCx
, MOZ_KnownLive(aController
), aRv
);
533 [](JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
,
534 ReadableStreamDefaultController
* aController
) {
536 ReadableStreamDefaultControllerError(aCx
, aController
, aValue
, aRv
);
538 RefPtr(aController
));
541 // https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller-from-underlying-source
542 void SetupReadableStreamDefaultControllerFromUnderlyingSource(
543 JSContext
* aCx
, ReadableStream
* aStream
,
544 JS::Handle
<JSObject
*> aUnderlyingSource
,
545 UnderlyingSource
& aUnderlyingSourceDict
, double aHighWaterMark
,
546 QueuingStrategySize
* aSizeAlgorithm
, ErrorResult
& aRv
) {
548 RefPtr
<ReadableStreamDefaultController
> controller
=
549 new ReadableStreamDefaultController(aStream
->GetParentObject());
552 RefPtr
<UnderlyingSourceAlgorithms
> algorithms
=
553 new UnderlyingSourceAlgorithms(aStream
->GetParentObject(),
554 aUnderlyingSource
, aUnderlyingSourceDict
);
557 SetUpReadableStreamDefaultController(aCx
, aStream
, controller
, algorithms
,
558 aHighWaterMark
, aSizeAlgorithm
, aRv
);
561 // https://streams.spec.whatwg.org/#rs-default-controller-private-cancel
562 already_AddRefed
<Promise
> ReadableStreamDefaultController::CancelSteps(
563 JSContext
* aCx
, JS::Handle
<JS::Value
> aReason
, ErrorResult
& aRv
) {
568 Optional
<JS::Handle
<JS::Value
>> errorOption(aCx
, aReason
);
569 RefPtr
<UnderlyingSourceAlgorithmsBase
> algorithms
= mAlgorithms
;
570 RefPtr
<Promise
> result
= algorithms
->CancelCallback(aCx
, errorOption
, aRv
);
576 ReadableStreamDefaultControllerClearAlgorithms(this);
579 return result
.forget();
582 // https://streams.spec.whatwg.org/#rs-default-controller-private-pull
583 void ReadableStreamDefaultController::PullSteps(JSContext
* aCx
,
584 ReadRequest
* aReadRequest
,
587 RefPtr
<ReadableStream
> stream
= mStream
;
590 if (!mQueue
.isEmpty()) {
592 JS::Rooted
<JS::Value
> chunk(aCx
);
593 DequeueValue(this, &chunk
);
596 if (CloseRequested() && mQueue
.isEmpty()) {
598 ReadableStreamDefaultControllerClearAlgorithms(this);
600 ReadableStreamClose(aCx
, stream
, aRv
);
606 ReadableStreamDefaultControllerCallPullIfNeeded(aCx
, this, aRv
);
613 aReadRequest
->ChunkSteps(aCx
, chunk
, aRv
);
617 ReadableStreamAddReadRequest(stream
, aReadRequest
);
619 ReadableStreamDefaultControllerCallPullIfNeeded(aCx
, this, aRv
);
623 // https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaultcontroller-releasesteps
624 void ReadableStreamDefaultController::ReleaseSteps() {
628 } // namespace mozilla::dom