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 "mozilla/dom/WritableStreamDefaultWriter.h"
9 #include "js/TypeDecls.h"
11 #include "mozilla/AlreadyAddRefed.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/CycleCollectedJSContext.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/dom/WritableStream.h"
17 #include "mozilla/dom/WritableStreamDefaultWriterBinding.h"
20 #include "mozilla/dom/Promise-inl.h"
21 #include "nsIGlobalObject.h"
22 #include "nsISupports.h"
24 namespace mozilla::dom
{
26 using namespace streams_abstract
;
28 NS_IMPL_CYCLE_COLLECTION_CLASS(WritableStreamDefaultWriter
)
29 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WritableStreamDefaultWriter
)
30 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal
, mStream
, mReadyPromise
,
32 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WritableStreamDefaultWriter
)
36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal
, mStream
, mReadyPromise
,
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
40 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WritableStreamDefaultWriter
)
41 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
42 NS_IMPL_CYCLE_COLLECTION_TRACE_END
44 NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStreamDefaultWriter
)
45 NS_IMPL_CYCLE_COLLECTING_RELEASE(WritableStreamDefaultWriter
)
46 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamDefaultWriter
)
47 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
48 NS_INTERFACE_MAP_ENTRY(nsISupports
)
51 WritableStreamDefaultWriter::WritableStreamDefaultWriter(
52 nsIGlobalObject
* aGlobal
)
54 mozilla::HoldJSObjects(this);
57 WritableStreamDefaultWriter::~WritableStreamDefaultWriter() {
58 mozilla::DropJSObjects(this);
61 void WritableStreamDefaultWriter::SetReadyPromise(Promise
* aPromise
) {
63 mReadyPromise
= aPromise
;
66 void WritableStreamDefaultWriter::SetClosedPromise(Promise
* aPromise
) {
68 mClosedPromise
= aPromise
;
71 JSObject
* WritableStreamDefaultWriter::WrapObject(
72 JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
) {
73 return WritableStreamDefaultWriter_Binding::Wrap(aCx
, this, aGivenProto
);
77 already_AddRefed
<WritableStreamDefaultWriter
>
78 WritableStreamDefaultWriter::Constructor(const GlobalObject
& aGlobal
,
79 WritableStream
& aStream
,
81 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
82 RefPtr
<WritableStreamDefaultWriter
> writer
=
83 new WritableStreamDefaultWriter(global
);
84 SetUpWritableStreamDefaultWriter(writer
, &aStream
, aRv
);
88 return writer
.forget();
91 already_AddRefed
<Promise
> WritableStreamDefaultWriter::Closed() {
92 RefPtr
<Promise
> closedPromise
= mClosedPromise
;
93 return closedPromise
.forget();
96 already_AddRefed
<Promise
> WritableStreamDefaultWriter::Ready() {
97 RefPtr
<Promise
> readyPromise
= mReadyPromise
;
98 return readyPromise
.forget();
101 namespace streams_abstract
{
102 // https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size
103 Nullable
<double> WritableStreamDefaultWriterGetDesiredSize(
104 WritableStreamDefaultWriter
* aWriter
) {
105 // Step 1. Let stream be writer.[[stream]].
106 RefPtr
<WritableStream
> stream
= aWriter
->GetStream();
108 // Step 2. Let state be stream.[[state]].
109 WritableStream::WriterState state
= stream
->State();
111 // Step 3. If state is "errored" or "erroring", return null.
112 if (state
== WritableStream::WriterState::Errored
||
113 state
== WritableStream::WriterState::Erroring
) {
117 // Step 4. If state is "closed", return 0.
118 if (state
== WritableStream::WriterState::Closed
) {
123 // ! WritableStreamDefaultControllerGetDesiredSize(stream.[[controller]]).
124 return stream
->Controller()->GetDesiredSize();
126 } // namespace streams_abstract
128 // https://streams.spec.whatwg.org/#default-writer-desired-size
129 Nullable
<double> WritableStreamDefaultWriter::GetDesiredSize(ErrorResult
& aRv
) {
130 // Step 1. If this.[[stream]] is undefined, throw a TypeError exception.
132 aRv
.ThrowTypeError("Missing stream");
136 // Step 2. Return ! WritableStreamDefaultWriterGetDesiredSize(this).
137 RefPtr
<WritableStreamDefaultWriter
> thisRefPtr
= this;
138 return WritableStreamDefaultWriterGetDesiredSize(thisRefPtr
);
141 // https://streams.spec.whatwg.org/#writable-stream-default-writer-abort
142 MOZ_CAN_RUN_SCRIPT already_AddRefed
<Promise
> WritableStreamDefaultWriterAbort(
143 JSContext
* aCx
, WritableStreamDefaultWriter
* aWriter
,
144 JS::Handle
<JS::Value
> aReason
, ErrorResult
& aRv
) {
145 // Step 1. Let stream be writer.[[stream]].
146 RefPtr
<WritableStream
> stream
= aWriter
->GetStream();
148 // Step 2. Assert: stream is not undefined.
151 // Step 3. Return ! WritableStreamAbort(stream, reason).
152 return WritableStreamAbort(aCx
, stream
, aReason
, aRv
);
155 // https://streams.spec.whatwg.org/#default-writer-abort
156 already_AddRefed
<Promise
> WritableStreamDefaultWriter::Abort(
157 JSContext
* aCx
, JS::Handle
<JS::Value
> aReason
, ErrorResult
& aRv
) {
158 // Step 1. If this.[[stream]] is undefined, return a promise rejected with a
159 // TypeError exception.
161 aRv
.ThrowTypeError("Missing stream");
165 // Step 2. Return ! WritableStreamDefaultWriterAbort(this, reason).
166 RefPtr
<WritableStreamDefaultWriter
> thisRefPtr
= this;
167 return WritableStreamDefaultWriterAbort(aCx
, thisRefPtr
, aReason
, aRv
);
170 // https://streams.spec.whatwg.org/#writable-stream-default-writer-close
171 MOZ_CAN_RUN_SCRIPT
static already_AddRefed
<Promise
>
172 WritableStreamDefaultWriterClose(JSContext
* aCx
,
173 WritableStreamDefaultWriter
* aWriter
,
175 // Step 1. Let stream be writer.[[stream]].
176 RefPtr
<WritableStream
> stream
= aWriter
->GetStream();
178 // Step 2. Assert: stream is not undefined.
181 // Step 3. Return ! WritableStreamClose(stream).
182 return WritableStreamClose(aCx
, stream
, aRv
);
185 // https://streams.spec.whatwg.org/#default-writer-close
186 already_AddRefed
<Promise
> WritableStreamDefaultWriter::Close(JSContext
* aCx
,
188 // Step 1. Let stream be this.[[stream]].
189 RefPtr
<WritableStream
> stream
= mStream
;
191 // Step 2. If stream is undefined, return a promise rejected with a TypeError
194 aRv
.ThrowTypeError("Missing stream");
198 // Step 3. If ! WritableStreamCloseQueuedOrInFlight(stream) is true,
199 // return a promise rejected with a TypeError exception.
200 if (stream
->CloseQueuedOrInFlight()) {
201 aRv
.ThrowTypeError("Stream is closing");
205 // Step 3. Return ! WritableStreamDefaultWriterClose(this).
206 RefPtr
<WritableStreamDefaultWriter
> thisRefPtr
= this;
207 return WritableStreamDefaultWriterClose(aCx
, thisRefPtr
, aRv
);
210 namespace streams_abstract
{
211 // https://streams.spec.whatwg.org/#writable-stream-default-writer-release
212 void WritableStreamDefaultWriterRelease(JSContext
* aCx
,
213 WritableStreamDefaultWriter
* aWriter
) {
214 // Step 1. Let stream be writer.[[stream]].
215 RefPtr
<WritableStream
> stream
= aWriter
->GetStream();
217 // Step 2. Assert: stream is not undefined.
220 // Step 3. Assert: stream.[[writer]] is writer.
221 MOZ_ASSERT(stream
->GetWriter() == aWriter
);
223 // Step 4. Let releasedError be a new TypeError.
224 JS::Rooted
<JS::Value
> releasedError(RootingCx(), JS::UndefinedValue());
227 rv
.ThrowTypeError("Releasing lock");
228 bool ok
= ToJSValue(aCx
, std::move(rv
), &releasedError
);
229 MOZ_RELEASE_ASSERT(ok
, "must be ok");
233 // WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer,
235 WritableStreamDefaultWriterEnsureReadyPromiseRejected(aWriter
, releasedError
);
238 // WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer,
240 WritableStreamDefaultWriterEnsureClosedPromiseRejected(aWriter
,
243 // Step 7. Set stream.[[writer]] to undefined.
244 stream
->SetWriter(nullptr);
246 // Step 8. Set writer.[[stream]] to undefined.
247 aWriter
->SetStream(nullptr);
249 } // namespace streams_abstract
251 // https://streams.spec.whatwg.org/#default-writer-release-lock
252 void WritableStreamDefaultWriter::ReleaseLock(JSContext
* aCx
) {
253 // Step 1. Let stream be this.[[stream]].
254 RefPtr
<WritableStream
> stream
= mStream
;
256 // Step 2. If stream is undefined, return.
261 // Step 3. Assert: stream.[[writer]] is not undefined.
262 MOZ_ASSERT(stream
->GetWriter());
264 // Step 4. Perform ! WritableStreamDefaultWriterRelease(this).
265 RefPtr
<WritableStreamDefaultWriter
> thisRefPtr
= this;
266 return WritableStreamDefaultWriterRelease(aCx
, thisRefPtr
);
269 namespace streams_abstract
{
270 // https://streams.spec.whatwg.org/#writable-stream-default-writer-write
271 already_AddRefed
<Promise
> WritableStreamDefaultWriterWrite(
272 JSContext
* aCx
, WritableStreamDefaultWriter
* aWriter
,
273 JS::Handle
<JS::Value
> aChunk
, ErrorResult
& aRv
) {
274 // Step 1. Let stream be writer.[[stream]].
275 RefPtr
<WritableStream
> stream
= aWriter
->GetStream();
277 // Step 2. Assert: stream is not undefined.
280 // Step 3. Let controller be stream.[[controller]].
281 RefPtr
<WritableStreamDefaultController
> controller
= stream
->Controller();
283 // Step 4. Let chunkSize be !
284 // WritableStreamDefaultControllerGetChunkSize(controller, chunk).
286 WritableStreamDefaultControllerGetChunkSize(aCx
, controller
, aChunk
, aRv
);
291 // Step 5. If stream is not equal to writer.[[stream]], return a promise
292 // rejected with a TypeError exception.
293 if (stream
!= aWriter
->GetStream()) {
295 "Can not write on WritableStream owned by another writer.");
299 // Step 6. Let state be stream.[[state]].
300 WritableStream::WriterState state
= stream
->State();
302 // Step 7. If state is "errored", return a promise rejected with
303 // stream.[[storedError]].
304 if (state
== WritableStream::WriterState::Errored
) {
305 JS::Rooted
<JS::Value
> error(aCx
, stream
->StoredError());
306 return Promise::CreateRejected(aWriter
->GetParentObject(), error
, aRv
);
309 // Step 8. If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
310 // is "closed", return a promise rejected with a TypeError exception
311 // indicating that the stream is closing or closed.
312 if (stream
->CloseQueuedOrInFlight() ||
313 state
== WritableStream::WriterState::Closed
) {
314 return Promise::CreateRejectedWithTypeError(
315 aWriter
->GetParentObject(), "Stream is closed or closing"_ns
, aRv
);
318 // Step 9. If state is "erroring", return a promise rejected with
319 // stream.[[storedError]].
320 if (state
== WritableStream::WriterState::Erroring
) {
321 JS::Rooted
<JS::Value
> error(aCx
, stream
->StoredError());
322 return Promise::CreateRejected(aWriter
->GetParentObject(), error
, aRv
);
325 // Step 10. Assert: state is "writable".
326 MOZ_ASSERT(state
== WritableStream::WriterState::Writable
);
328 // Step 11. Let promise be ! WritableStreamAddWriteRequest(stream).
329 RefPtr
<Promise
> promise
= WritableStreamAddWriteRequest(stream
);
331 // Step 12. Perform ! WritableStreamDefaultControllerWrite(controller, chunk,
333 WritableStreamDefaultControllerWrite(aCx
, controller
, aChunk
, chunkSize
, aRv
);
338 // Step 13. Return promise.
339 return promise
.forget();
341 } // namespace streams_abstract
343 // https://streams.spec.whatwg.org/#default-writer-write
344 already_AddRefed
<Promise
> WritableStreamDefaultWriter::Write(
345 JSContext
* aCx
, JS::Handle
<JS::Value
> aChunk
, ErrorResult
& aRv
) {
346 // Step 1. If this.[[stream]] is undefined, return a promise rejected with a
347 // TypeError exception.
349 aRv
.ThrowTypeError("Missing stream");
353 // Step 2. Return ! WritableStreamDefaultWriterWrite(this, chunk).
354 return WritableStreamDefaultWriterWrite(aCx
, this, aChunk
, aRv
);
357 namespace streams_abstract
{
359 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-writer
360 void SetUpWritableStreamDefaultWriter(WritableStreamDefaultWriter
* aWriter
,
361 WritableStream
* aStream
,
363 // Step 1. If ! IsWritableStreamLocked(stream) is true, throw a TypeError
365 if (IsWritableStreamLocked(aStream
)) {
366 aRv
.ThrowTypeError("WritableStream is already locked!");
370 // Step 2. Set writer.[[stream]] to stream.
371 aWriter
->SetStream(aStream
);
373 // Step 3. Set stream.[[writer]] to writer.
374 aStream
->SetWriter(aWriter
);
376 // Step 4. Let state be stream.[[state]].
377 WritableStream::WriterState state
= aStream
->State();
379 // Step 5. If state is "writable",
380 if (state
== WritableStream::WriterState::Writable
) {
381 RefPtr
<Promise
> readyPromise
=
382 Promise::CreateInfallible(aWriter
->GetParentObject());
384 // Step 5.1 If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
385 // stream.[[backpressure]] is true, set writer.[[readyPromise]] to a new
387 if (!aStream
->CloseQueuedOrInFlight() && aStream
->Backpressure()) {
388 aWriter
->SetReadyPromise(readyPromise
);
390 // Step 5.2. Otherwise, set writer.[[readyPromise]] to a promise resolved
392 readyPromise
->MaybeResolveWithUndefined();
393 aWriter
->SetReadyPromise(readyPromise
);
396 // Step 5.3. Set writer.[[closedPromise]] to a new promise.
397 RefPtr
<Promise
> closedPromise
=
398 Promise::CreateInfallible(aWriter
->GetParentObject());
399 aWriter
->SetClosedPromise(closedPromise
);
400 } else if (state
== WritableStream::WriterState::Erroring
) {
401 // Step 6. Otherwise, if state is "erroring",
403 // Step 6.1. Set writer.[[readyPromise]] to a promise rejected with
404 // stream.[[storedError]].
405 JS::Rooted
<JS::Value
> storedError(RootingCx(), aStream
->StoredError());
406 RefPtr
<Promise
> readyPromise
=
407 Promise::CreateInfallible(aWriter
->GetParentObject());
408 readyPromise
->MaybeReject(storedError
);
409 aWriter
->SetReadyPromise(readyPromise
);
411 // Step 6.2. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
412 readyPromise
->SetSettledPromiseIsHandled();
414 // Step 6.3. Set writer.[[closedPromise]] to a new promise.
415 RefPtr
<Promise
> closedPromise
=
416 Promise::CreateInfallible(aWriter
->GetParentObject());
417 aWriter
->SetClosedPromise(closedPromise
);
418 } else if (state
== WritableStream::WriterState::Closed
) {
419 // Step 7. Otherwise, if state is "closed",
420 // Step 7.1. Set writer.[[readyPromise]] to a promise resolved with
422 RefPtr
<Promise
> readyPromise
=
423 Promise::CreateResolvedWithUndefined(aWriter
->GetParentObject(), aRv
);
427 aWriter
->SetReadyPromise(readyPromise
);
429 // Step 7.2. Set writer.[[closedPromise]] to a promise resolved with
431 RefPtr
<Promise
> closedPromise
=
432 Promise::CreateResolvedWithUndefined(aWriter
->GetParentObject(), aRv
);
436 aWriter
->SetClosedPromise(closedPromise
);
438 // Step 8. Otherwise,
439 // Step 8.1 Assert: state is "errored".
440 MOZ_ASSERT(state
== WritableStream::WriterState::Errored
);
442 // Step 8.2. Step Let storedError be stream.[[storedError]].
443 JS::Rooted
<JS::Value
> storedError(RootingCx(), aStream
->StoredError());
445 // Step 8.3. Set writer.[[readyPromise]] to a promise rejected with
447 RefPtr
<Promise
> readyPromise
=
448 Promise::CreateInfallible(aWriter
->GetParentObject());
449 readyPromise
->MaybeReject(storedError
);
450 aWriter
->SetReadyPromise(readyPromise
);
452 // Step 8.4. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
453 readyPromise
->SetSettledPromiseIsHandled();
455 // Step 8.5. Set writer.[[closedPromise]] to a promise rejected with
457 RefPtr
<Promise
> closedPromise
=
458 Promise::CreateInfallible(aWriter
->GetParentObject());
459 closedPromise
->MaybeReject(storedError
);
460 aWriter
->SetClosedPromise(closedPromise
);
462 // Step 8.6 Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
463 closedPromise
->SetSettledPromiseIsHandled();
467 // https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-closed-promise-rejected
468 void WritableStreamDefaultWriterEnsureClosedPromiseRejected(
469 WritableStreamDefaultWriter
* aWriter
, JS::Handle
<JS::Value
> aError
) {
470 RefPtr
<Promise
> closedPromise
= aWriter
->ClosedPromise();
471 // Step 1. If writer.[[closedPromise]].[[PromiseState]] is "pending", reject
472 // writer.[[closedPromise]] with error.
473 if (closedPromise
->State() == Promise::PromiseState::Pending
) {
474 closedPromise
->MaybeReject(aError
);
476 // Step 2. Otherwise, set writer.[[closedPromise]] to a promise rejected
478 closedPromise
= Promise::CreateInfallible(aWriter
->GetParentObject());
479 closedPromise
->MaybeReject(aError
);
480 aWriter
->SetClosedPromise(closedPromise
);
483 // Step 3. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
484 closedPromise
->SetSettledPromiseIsHandled();
487 // https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-ready-promise-rejected
488 void WritableStreamDefaultWriterEnsureReadyPromiseRejected(
489 WritableStreamDefaultWriter
* aWriter
, JS::Handle
<JS::Value
> aError
) {
490 RefPtr
<Promise
> readyPromise
= aWriter
->ReadyPromise();
491 // Step 1. If writer.[[readyPromise]].[[PromiseState]] is "pending", reject
492 // writer.[[readyPromise]] with error.
493 if (readyPromise
->State() == Promise::PromiseState::Pending
) {
494 readyPromise
->MaybeReject(aError
);
496 // Step 2. Otherwise, set writer.[[readyPromise]] to a promise rejected with
498 readyPromise
= Promise::CreateInfallible(aWriter
->GetParentObject());
499 readyPromise
->MaybeReject(aError
);
500 aWriter
->SetReadyPromise(readyPromise
);
503 // Step 3. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
504 readyPromise
->SetSettledPromiseIsHandled();
507 // https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation
508 already_AddRefed
<Promise
> WritableStreamDefaultWriterCloseWithErrorPropagation(
509 JSContext
* aCx
, WritableStreamDefaultWriter
* aWriter
, ErrorResult
& aRv
) {
510 // Step 1. Let stream be writer.[[stream]].
511 RefPtr
<WritableStream
> stream
= aWriter
->GetStream();
513 // Step 2. Assert: stream is not undefined.
516 // Step 3. Let state be stream.[[state]].
517 WritableStream::WriterState state
= stream
->State();
519 // Step 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is true
520 // or state is "closed", return a promise resolved with undefined.
521 if (stream
->CloseQueuedOrInFlight() ||
522 state
== WritableStream::WriterState::Closed
) {
523 return Promise::CreateResolvedWithUndefined(aWriter
->GetParentObject(),
527 // Step 5. If state is "errored",
528 // return a promise rejected with stream.[[storedError]].
529 if (state
== WritableStream::WriterState::Errored
) {
530 JS::Rooted
<JS::Value
> error(aCx
, stream
->StoredError());
531 return Promise::CreateRejected(aWriter
->GetParentObject(), error
, aRv
);
534 // Step 6. Assert: state is "writable" or "erroring".
535 MOZ_ASSERT(state
== WritableStream::WriterState::Writable
||
536 state
== WritableStream::WriterState::Erroring
);
538 // Step 7. Return ! WritableStreamDefaultWriterClose(writer).
539 return WritableStreamDefaultWriterClose(aCx
, aWriter
, aRv
);
542 } // namespace streams_abstract
544 } // namespace mozilla::dom