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/. */
7 #include "mozilla/dom/IterableIterator.h"
8 #include "mozilla/dom/Promise-inl.h"
10 namespace mozilla::dom
{
12 // Due to IterableIterator being a templated class, we implement the necessary
13 // CC bits in a superclass that IterableIterator then inherits from. This allows
14 // us to put the macros outside of the header. The base class has pure virtual
15 // functions for Traverse/Unlink that the templated subclasses will override.
17 NS_IMPL_CYCLE_COLLECTION_CLASS(IterableIteratorBase
)
19 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IterableIteratorBase
)
20 tmp
->TraverseHelper(cb
);
21 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
23 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IterableIteratorBase
)
25 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
27 namespace iterator_utils
{
29 void DictReturn(JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
,
30 bool aDone
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
) {
31 RootedDictionary
<IterableKeyOrValueResult
> dict(aCx
);
34 JS::Rooted
<JS::Value
> dictValue(aCx
);
35 if (!ToJSValue(aCx
, dict
, &dictValue
)) {
36 aRv
.Throw(NS_ERROR_FAILURE
);
39 aResult
.set(dictValue
);
42 void DictReturn(JSContext
* aCx
, JS::MutableHandle
<JSObject
*> aResult
,
43 bool aDone
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
) {
44 JS::Rooted
<JS::Value
> dictValue(aCx
);
45 DictReturn(aCx
, &dictValue
, aDone
, aValue
, aRv
);
49 aResult
.set(&dictValue
.toObject());
52 void KeyAndValueReturn(JSContext
* aCx
, JS::Handle
<JS::Value
> aKey
,
53 JS::Handle
<JS::Value
> aValue
,
54 JS::MutableHandle
<JSObject
*> aResult
, ErrorResult
& aRv
) {
55 RootedDictionary
<IterableKeyAndValueResult
> dict(aCx
);
57 // Dictionary values are a Sequence, which is a FallibleTArray, so we need
58 // to check returns when appending.
59 if (!dict
.mValue
.AppendElement(aKey
, mozilla::fallible
)) {
60 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
63 if (!dict
.mValue
.AppendElement(aValue
, mozilla::fallible
)) {
64 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
67 JS::Rooted
<JS::Value
> dictValue(aCx
);
68 if (!ToJSValue(aCx
, dict
, &dictValue
)) {
69 aRv
.Throw(NS_ERROR_FAILURE
);
72 aResult
.set(&dictValue
.toObject());
75 } // namespace iterator_utils
77 namespace binding_detail
{
79 static already_AddRefed
<Promise
> PromiseOrErr(
80 Result
<RefPtr
<Promise
>, nsresult
>&& aResult
, ErrorResult
& aError
) {
81 if (aResult
.isErr()) {
82 aError
.Throw(aResult
.unwrapErr());
86 return aResult
.unwrap().forget();
89 already_AddRefed
<Promise
> AsyncIterableNextImpl::NextSteps(
90 JSContext
* aCx
, AsyncIterableIteratorBase
* aObject
,
91 nsIGlobalObject
* aGlobalObject
, ErrorResult
& aRv
) {
92 // 2. If object’s is finished is true, then:
93 if (aObject
->mIsFinished
) {
94 // 1. Let result be CreateIterResultObject(undefined, true).
95 JS::Rooted
<JS::Value
> dict(aCx
);
96 iterator_utils::DictReturn(aCx
, &dict
, true, JS::UndefinedHandleValue
, aRv
);
98 return Promise::CreateRejectedWithErrorResult(aGlobalObject
, aRv
);
101 // 2. Perform ! Call(nextPromiseCapability.[[Resolve]], undefined,
103 // 3. Return nextPromiseCapability.[[Promise]].
104 return Promise::Resolve(aGlobalObject
, aCx
, dict
, aRv
);
107 // 4. Let nextPromise be the result of getting the next iteration result with
108 // object’s target and object.
109 RefPtr
<Promise
> nextPromise
;
112 nextPromise
= GetNextResult(error
);
114 error
.WouldReportJSException();
115 if (error
.Failed()) {
116 nextPromise
= Promise::Reject(aGlobalObject
, std::move(error
), aRv
);
120 // 5. Let fulfillSteps be the following steps, given next:
121 auto fulfillSteps
= [](JSContext
* aCx
, JS::Handle
<JS::Value
> aNext
,
123 const RefPtr
<AsyncIterableIteratorBase
>& aObject
,
124 const nsCOMPtr
<nsIGlobalObject
>& aGlobalObject
)
125 -> already_AddRefed
<Promise
> {
126 // 1. Set object’s ongoing promise to null.
127 aObject
->mOngoingPromise
= nullptr;
129 // 2. If next is end of iteration, then:
130 JS::Rooted
<JS::Value
> dict(aCx
);
131 if (aNext
.isMagic(binding_details::END_OF_ITERATION
)) {
132 // 1. Set object’s is finished to true.
133 aObject
->mIsFinished
= true;
134 // 2. Return CreateIterResultObject(undefined, true).
135 iterator_utils::DictReturn(aCx
, &dict
, true, JS::UndefinedHandleValue
,
141 // 3. Otherwise, if interface has a pair asynchronously iterable
143 // 1. Assert: next is a value pair.
144 // 2. Return the iterator result for next and kind.
146 // 1. Assert: interface has a value asynchronously iterable declaration.
147 // 2. Assert: next is a value of the type that appears in the
149 // 3. Let value be next, converted to an ECMAScript value.
150 // 4. Return CreateIterResultObject(value, false).
151 iterator_utils::DictReturn(aCx
, &dict
, false, aNext
, aRv
);
156 // Note that ThenCatchWithCycleCollectedArgs expects a Promise, so
157 // we use Promise::Resolve here. The specs do convert this to a
158 // promise too at another point, but the end result should be the
160 return Promise::Resolve(aGlobalObject
, aCx
, dict
, aRv
);
162 // 7. Let rejectSteps be the following steps, given reason:
163 auto rejectSteps
= [](JSContext
* aCx
, JS::Handle
<JS::Value
> aReason
,
165 const RefPtr
<AsyncIterableIteratorBase
>& aObject
,
166 const nsCOMPtr
<nsIGlobalObject
>& aGlobalObject
) {
167 // 1. Set object’s ongoing promise to null.
168 aObject
->mOngoingPromise
= nullptr;
169 // 2. Set object’s is finished to true.
170 aObject
->mIsFinished
= true;
172 return Promise::Reject(aGlobalObject
, aCx
, aReason
, aRv
);
174 // 9. Perform PerformPromiseThen(nextPromise, onFulfilled, onRejected,
175 // nextPromiseCapability).
176 Result
<RefPtr
<Promise
>, nsresult
> result
=
177 nextPromise
->ThenCatchWithCycleCollectedArgs(
178 std::move(fulfillSteps
), std::move(rejectSteps
), RefPtr
{aObject
},
179 nsCOMPtr
{aGlobalObject
});
181 // 10. Return nextPromiseCapability.[[Promise]].
182 return PromiseOrErr(std::move(result
), aRv
);
185 already_AddRefed
<Promise
> AsyncIterableNextImpl::Next(
186 JSContext
* aCx
, AsyncIterableIteratorBase
* aObject
,
187 nsISupports
* aGlobalObject
, ErrorResult
& aRv
) {
188 nsCOMPtr
<nsIGlobalObject
> globalObject
= do_QueryInterface(aGlobalObject
);
190 // 3.7.10.2. Asynchronous iterator prototype object
192 // 10. If ongoingPromise is not null, then:
193 if (aObject
->mOngoingPromise
) {
194 // 1. Let afterOngoingPromiseCapability be
195 // ! NewPromiseCapability(%Promise%).
196 // 2. Let onSettled be CreateBuiltinFunction(nextSteps, « »).
198 // aObject is the same object as 'this', so it's fine to capture 'this'
199 // without taking a strong reference, because we already take a strong
200 // reference to it through aObject.
201 auto onSettled
= [this](JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
203 const RefPtr
<AsyncIterableIteratorBase
>& aObject
,
204 const nsCOMPtr
<nsIGlobalObject
>& aGlobalObject
)
205 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION
{
206 return NextSteps(aCx
, aObject
, aGlobalObject
, aRv
);
209 // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
210 // afterOngoingPromiseCapability).
211 Result
<RefPtr
<Promise
>, nsresult
> afterOngoingPromise
=
212 aObject
->mOngoingPromise
->ThenCatchWithCycleCollectedArgs(
213 onSettled
, onSettled
, RefPtr
{aObject
}, std::move(globalObject
));
214 if (afterOngoingPromise
.isErr()) {
215 aRv
.Throw(afterOngoingPromise
.unwrapErr());
219 // 4. Set object’s ongoing promise to
220 // afterOngoingPromiseCapability.[[Promise]].
221 aObject
->mOngoingPromise
= afterOngoingPromise
.unwrap().forget();
224 // 1. Set object’s ongoing promise to the result of running nextSteps.
225 aObject
->mOngoingPromise
= NextSteps(aCx
, aObject
, globalObject
, aRv
);
228 // 12. Return object’s ongoing promise.
229 return do_AddRef(aObject
->mOngoingPromise
);
232 already_AddRefed
<Promise
> AsyncIterableReturnImpl::ReturnSteps(
233 JSContext
* aCx
, AsyncIterableIteratorBase
* aObject
,
234 nsIGlobalObject
* aGlobalObject
, JS::Handle
<JS::Value
> aValue
,
236 // 2. If object’s is finished is true, then:
237 if (aObject
->mIsFinished
) {
238 // 1. Let result be CreateIterResultObject(value, true).
239 JS::Rooted
<JS::Value
> dict(aCx
);
240 iterator_utils::DictReturn(aCx
, &dict
, true, aValue
, aRv
);
242 return Promise::CreateRejectedWithErrorResult(aGlobalObject
, aRv
);
245 // 2. Perform ! Call(returnPromiseCapability.[[Resolve]], undefined,
247 // 3. Return returnPromiseCapability.[[Promise]].
248 return Promise::Resolve(aGlobalObject
, aCx
, dict
, aRv
);
251 // 3. Set object’s is finished to true.
252 aObject
->mIsFinished
= true;
254 // 4. Return the result of running the asynchronous iterator return algorithm
255 // for interface, given object’s target, object, and value.
257 RefPtr
<Promise
> returnPromise
= GetReturnPromise(aCx
, aValue
, error
);
259 error
.WouldReportJSException();
260 if (error
.Failed()) {
261 return Promise::Reject(aGlobalObject
, std::move(error
), aRv
);
264 return returnPromise
.forget();
267 already_AddRefed
<Promise
> AsyncIterableReturnImpl::Return(
268 JSContext
* aCx
, AsyncIterableIteratorBase
* aObject
,
269 nsISupports
* aGlobalObject
, JS::Handle
<JS::Value
> aValue
,
271 nsCOMPtr
<nsIGlobalObject
> globalObject
= do_QueryInterface(aGlobalObject
);
273 // 3.7.10.2. Asynchronous iterator prototype object
275 RefPtr
<Promise
> returnStepsPromise
;
276 // 11. If ongoingPromise is not null, then:
277 if (aObject
->mOngoingPromise
) {
278 // 1. Let afterOngoingPromiseCapability be
279 // ! NewPromiseCapability(%Promise%).
280 // 2. Let onSettled be CreateBuiltinFunction(returnSteps, « »).
282 // aObject is the same object as 'this', so it's fine to capture 'this'
283 // without taking a strong reference, because we already take a strong
284 // reference to it through aObject.
286 [this](JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
,
287 const RefPtr
<AsyncIterableIteratorBase
>& aObject
,
288 const nsCOMPtr
<nsIGlobalObject
>& aGlobalObject
,
289 JS::Handle
<JS::Value
> aVal
) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION
{
290 return ReturnSteps(aCx
, aObject
, aGlobalObject
, aVal
, aRv
);
293 // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
294 // afterOngoingPromiseCapability).
295 Result
<RefPtr
<Promise
>, nsresult
> afterOngoingPromise
=
296 aObject
->mOngoingPromise
->ThenCatchWithCycleCollectedArgsJS(
297 onSettled
, onSettled
,
298 std::make_tuple(RefPtr
{aObject
}, nsCOMPtr
{globalObject
}),
299 std::make_tuple(aValue
));
300 if (afterOngoingPromise
.isErr()) {
301 aRv
.Throw(afterOngoingPromise
.unwrapErr());
305 // 4. Set returnStepsPromise to afterOngoingPromiseCapability.[[Promise]].
306 returnStepsPromise
= afterOngoingPromise
.unwrap().forget();
309 // 1. Set returnStepsPromise to the result of running returnSteps.
310 returnStepsPromise
= ReturnSteps(aCx
, aObject
, globalObject
, aValue
, aRv
);
313 // 13. Let fulfillSteps be the following steps:
314 auto onFullFilled
= [](JSContext
* aCx
, JS::Handle
<JS::Value
>,
316 const nsCOMPtr
<nsIGlobalObject
>& aGlobalObject
,
317 JS::Handle
<JS::Value
> aVal
) {
318 // 1. Return CreateIterResultObject(value, true).
319 JS::Rooted
<JS::Value
> dict(aCx
);
320 iterator_utils::DictReturn(aCx
, &dict
, true, aVal
, aRv
);
321 return Promise::Resolve(aGlobalObject
, aCx
, dict
, aRv
);
324 // 14. Let onFulfilled be CreateBuiltinFunction(fulfillSteps, « »).
325 // 15. Perform PerformPromiseThen(returnStepsPromise, onFulfilled, undefined,
326 // returnPromiseCapability).
327 Result
<RefPtr
<Promise
>, nsresult
> returnPromise
=
328 returnStepsPromise
->ThenWithCycleCollectedArgsJS(
329 onFullFilled
, std::make_tuple(std::move(globalObject
)),
330 std::make_tuple(aValue
));
332 // 16. Return returnPromiseCapability.[[Promise]].
333 return PromiseOrErr(std::move(returnPromise
), aRv
);
336 } // namespace binding_detail
338 } // namespace mozilla::dom