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/UnderlyingSinkCallbackHelpers.h"
8 #include "StreamUtils.h"
9 #include "mozilla/dom/UnionTypes.h"
10 #include "mozilla/dom/WebTransportError.h"
13 using namespace mozilla::dom
;
15 NS_IMPL_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase
)
16 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithmsBase
)
17 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithmsBase
)
18 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase
)
19 NS_INTERFACE_MAP_ENTRY(nsISupports
)
22 NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(
23 UnderlyingSinkAlgorithms
, UnderlyingSinkAlgorithmsBase
,
24 (mGlobal
, mStartCallback
, mWriteCallback
, mCloseCallback
, mAbortCallback
),
26 NS_IMPL_ADDREF_INHERITED(UnderlyingSinkAlgorithms
, UnderlyingSinkAlgorithmsBase
)
27 NS_IMPL_RELEASE_INHERITED(UnderlyingSinkAlgorithms
,
28 UnderlyingSinkAlgorithmsBase
)
29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms
)
30 NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase
)
32 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
33 void UnderlyingSinkAlgorithms::StartCallback(
34 JSContext
* aCx
, WritableStreamDefaultController
& aController
,
35 JS::MutableHandle
<JS::Value
> aRetVal
, ErrorResult
& aRv
) {
36 if (!mStartCallback
) {
37 // Step 2: Let startAlgorithm be an algorithm that returns undefined.
38 aRetVal
.setUndefined();
42 // Step 6: If underlyingSinkDict["start"] exists, then set startAlgorithm to
43 // an algorithm which returns the result of invoking
44 // underlyingSinkDict["start"] with argument list « controller » and callback
45 // this value underlyingSink.
46 JS::Rooted
<JSObject
*> thisObj(aCx
, mUnderlyingSink
);
47 return mStartCallback
->Call(thisObj
, aController
, aRetVal
, aRv
,
48 "UnderlyingSink.start",
49 CallbackFunction::eRethrowExceptions
);
52 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
53 already_AddRefed
<Promise
> UnderlyingSinkAlgorithms::WriteCallback(
54 JSContext
* aCx
, JS::Handle
<JS::Value
> aChunk
,
55 WritableStreamDefaultController
& aController
, ErrorResult
& aRv
) {
56 if (!mWriteCallback
) {
57 // Step 3: Let writeAlgorithm be an algorithm that returns a promise
58 // resolved with undefined.
59 return Promise::CreateResolvedWithUndefined(mGlobal
, aRv
);
62 // Step 7: If underlyingSinkDict["write"] exists, then set writeAlgorithm to
63 // an algorithm which takes an argument chunk and returns the result of
64 // invoking underlyingSinkDict["write"] with argument list « chunk, controller
65 // » and callback this value underlyingSink.
66 JS::Rooted
<JSObject
*> thisObj(aCx
, mUnderlyingSink
);
67 RefPtr
<Promise
> promise
= mWriteCallback
->Call(
68 thisObj
, aChunk
, aController
, aRv
, "UnderlyingSink.write",
69 CallbackFunction::eRethrowExceptions
);
70 return promise
.forget();
73 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
74 already_AddRefed
<Promise
> UnderlyingSinkAlgorithms::CloseCallback(
75 JSContext
* aCx
, ErrorResult
& aRv
) {
76 if (!mCloseCallback
) {
77 // Step 4: Let closeAlgorithm be an algorithm that returns a promise
78 // resolved with undefined.
79 return Promise::CreateResolvedWithUndefined(mGlobal
, aRv
);
82 // Step 8: If underlyingSinkDict["close"] exists, then set closeAlgorithm to
83 // an algorithm which returns the result of invoking
84 // underlyingSinkDict["close"] with argument list «» and callback this value
86 JS::Rooted
<JSObject
*> thisObj(aCx
, mUnderlyingSink
);
87 RefPtr
<Promise
> promise
=
88 mCloseCallback
->Call(thisObj
, aRv
, "UnderlyingSink.close",
89 CallbackFunction::eRethrowExceptions
);
90 return promise
.forget();
93 // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
94 already_AddRefed
<Promise
> UnderlyingSinkAlgorithms::AbortCallback(
95 JSContext
* aCx
, const Optional
<JS::Handle
<JS::Value
>>& aReason
,
97 if (!mAbortCallback
) {
98 // Step 5: Let abortAlgorithm be an algorithm that returns a promise
99 // resolved with undefined.
100 return Promise::CreateResolvedWithUndefined(mGlobal
, aRv
);
103 // Step 9: Let abortAlgorithm be an algorithm that returns a promise resolved
105 JS::Rooted
<JSObject
*> thisObj(aCx
, mUnderlyingSink
);
106 RefPtr
<Promise
> promise
=
107 mAbortCallback
->Call(thisObj
, aReason
, aRv
, "UnderlyingSink.abort",
108 CallbackFunction::eRethrowExceptions
);
110 return promise
.forget();
113 // https://streams.spec.whatwg.org/#writable-set-up
114 // Step 2.1: Let closeAlgorithmWrapper be an algorithm that runs these steps:
115 already_AddRefed
<Promise
> UnderlyingSinkAlgorithmsWrapper::CloseCallback(
116 JSContext
* aCx
, ErrorResult
& aRv
) {
117 nsCOMPtr
<nsIGlobalObject
> global
= xpc::CurrentNativeGlobal(aCx
);
118 return PromisifyAlgorithm(
119 global
, [&](ErrorResult
& aRv
) { return CloseCallbackImpl(aCx
, aRv
); },
123 // https://streams.spec.whatwg.org/#writable-set-up
124 // Step 3.1: Let abortAlgorithmWrapper be an algorithm that runs these steps:
125 already_AddRefed
<Promise
> UnderlyingSinkAlgorithmsWrapper::AbortCallback(
126 JSContext
* aCx
, const Optional
<JS::Handle
<JS::Value
>>& aReason
,
128 nsCOMPtr
<nsIGlobalObject
> global
= xpc::CurrentNativeGlobal(aCx
);
129 return PromisifyAlgorithm(
131 [&](ErrorResult
& aRv
) { return AbortCallbackImpl(aCx
, aReason
, aRv
); },
135 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(WritableStreamToOutput
,
136 UnderlyingSinkAlgorithmsBase
,
137 nsIOutputStreamCallback
)
138 NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableStreamToOutput
,
139 UnderlyingSinkAlgorithmsBase
, mParent
,
143 WritableStreamToOutput::OnOutputStreamReady(nsIAsyncOutputStream
* aStream
) {
147 MOZ_ASSERT(mPromise
);
148 uint32_t written
= 0;
149 nsresult rv
= mOutput
->Write(
150 reinterpret_cast<const char*>(mData
->Elements() + mWritten
),
151 mData
->Length() - mWritten
, &written
);
152 if (NS_FAILED(rv
) && rv
!= NS_BASE_STREAM_WOULD_BLOCK
) {
153 mPromise
->MaybeRejectWithAbortError("Error writing to stream"_ns
);
155 // XXX should we add mErrored and fail future calls immediately?
156 // I presume new calls to Write() will fail though, too
159 if (NS_SUCCEEDED(rv
)) {
161 MOZ_ASSERT(mWritten
<= mData
->Length());
162 if (mWritten
>= mData
->Length()) {
163 mPromise
->MaybeResolveWithUndefined();
169 // wrote partial or nothing
171 nsCOMPtr
<nsIEventTarget
> target
= mozilla::GetCurrentSerialEventTarget();
172 rv
= mOutput
->AsyncWait(this, 0, 0, target
);
174 mPromise
->MaybeRejectWithUnknownError("error waiting to write data");
176 // XXX should we add mErrored and fail future calls immediately?
177 // New calls to Write() will fail, note
179 // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write.
185 already_AddRefed
<Promise
> WritableStreamToOutput::WriteCallback(
186 JSContext
* aCx
, JS::Handle
<JS::Value
> aChunk
,
187 WritableStreamDefaultController
& aController
, ErrorResult
& aError
) {
188 ArrayBufferViewOrArrayBuffer data
;
189 if (!data
.Init(aCx
, aChunk
)) {
190 aError
.StealExceptionFromJSContext(aCx
);
194 MOZ_ASSERT(data
.IsArrayBuffer() || data
.IsArrayBufferView());
196 RefPtr
<Promise
> promise
= Promise::Create(mParent
, aError
);
197 if (NS_WARN_IF(aError
.Failed())) {
201 // Try to write first, and only enqueue data if we were already blocked
202 // or the write didn't write it all. This avoids allocations and copies
204 MOZ_ASSERT(!mPromise
);
205 MOZ_ASSERT(mWritten
== 0);
206 uint32_t written
= 0;
207 ProcessTypedArraysFixed(data
, [&](const Span
<uint8_t>& aData
) {
208 Span
<uint8_t> dataSpan
= aData
;
209 nsresult rv
= mOutput
->Write(mozilla::AsChars(dataSpan
).Elements(),
210 dataSpan
.Length(), &written
);
211 if (NS_FAILED(rv
) && rv
!= NS_BASE_STREAM_WOULD_BLOCK
) {
212 promise
->MaybeRejectWithAbortError("error writing data");
215 if (NS_SUCCEEDED(rv
)) {
216 if (written
== dataSpan
.Length()) {
217 promise
->MaybeResolveWithUndefined();
220 dataSpan
= dataSpan
.From(written
);
223 auto buffer
= Buffer
<uint8_t>::CopyFrom(dataSpan
);
224 if (buffer
.isNothing()) {
225 promise
->MaybeReject(NS_ERROR_OUT_OF_MEMORY
);
228 mData
= std::move(buffer
);
231 if (promise
->State() != Promise::PromiseState::Pending
) {
232 return promise
.forget();
237 nsCOMPtr
<nsIEventTarget
> target
= mozilla::GetCurrentSerialEventTarget();
238 nsresult rv
= mOutput
->AsyncWait(this, 0, 0, target
);
241 promise
->MaybeRejectWithUnknownError("error waiting to write data");
243 return promise
.forget();
246 already_AddRefed
<Promise
> WritableStreamToOutput::AbortCallbackImpl(
247 JSContext
* aCx
, const Optional
<JS::Handle
<JS::Value
>>& aReason
,
249 // https://streams.spec.whatwg.org/#writablestream-set-up
250 // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps:
252 if (aReason
.WasPassed() && aReason
.Value().isObject()) {
253 JS::Rooted
<JSObject
*> obj(aCx
, &aReason
.Value().toObject());
254 RefPtr
<WebTransportError
> error
;
255 UnwrapObject
<prototypes::id::WebTransportError
, WebTransportError
>(
256 obj
, error
, nullptr);
258 mOutput
->CloseWithStatus(net::GetNSResultFromWebTransportError(
259 error
->GetStreamErrorCode().Value()));
264 // XXX The close or rather a dedicated abort should be async. For now we have
265 // to always fall back to the Step 3.3 below.
266 // XXX how do we know this stream is used by webtransport?
267 mOutput
->CloseWithStatus(NS_ERROR_WEBTRANSPORT_CODE_BASE
);
269 // Step 3.3. Return a promise resolved with undefined.
270 // Wrapper handles this
274 void WritableStreamToOutput::ReleaseObjects() { mOutput
->Close(); }