Bug 1856663 - Add more chunks for Android mochitest-plain. r=jmaher,taskgraph-reviewe...
[gecko.git] / dom / bindings / IterableIterator.cpp
blobd0f2e5d2271a0047c9546f575e932b577374e27d
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)
24 tmp->UnlinkHelper();
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);
32 dict.mDone = aDone;
33 dict.mValue = aValue;
34 JS::Rooted<JS::Value> dictValue(aCx);
35 if (!ToJSValue(aCx, dict, &dictValue)) {
36 aRv.Throw(NS_ERROR_FAILURE);
37 return;
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);
46 if (aRv.Failed()) {
47 return;
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);
56 dict.mDone = false;
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);
61 return;
63 if (!dict.mValue.AppendElement(aValue, mozilla::fallible)) {
64 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
65 return;
67 JS::Rooted<JS::Value> dictValue(aCx);
68 if (!ToJSValue(aCx, dict, &dictValue)) {
69 aRv.Throw(NS_ERROR_FAILURE);
70 return;
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());
83 return nullptr;
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);
97 if (aRv.Failed()) {
98 return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
101 // 2. Perform ! Call(nextPromiseCapability.[[Resolve]], undefined,
102 // «result»).
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;
111 ErrorResult error;
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,
122 ErrorResult& aRv,
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,
136 aRv);
137 if (aRv.Failed()) {
138 return nullptr;
140 } else {
141 // 3. Otherwise, if interface has a pair asynchronously iterable
142 // declaration:
143 // 1. Assert: next is a value pair.
144 // 2. Return the iterator result for next and kind.
145 // 4. Otherwise:
146 // 1. Assert: interface has a value asynchronously iterable declaration.
147 // 2. Assert: next is a value of the type that appears in the
148 // declaration.
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);
152 if (aRv.Failed()) {
153 return nullptr;
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
159 // same.
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,
164 ErrorResult& aRv,
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;
171 // 3. Throw reason.
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
191 // …
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,
202 ErrorResult& aRv,
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());
216 return nullptr;
219 // 4. Set object’s ongoing promise to
220 // afterOngoingPromiseCapability.[[Promise]].
221 aObject->mOngoingPromise = afterOngoingPromise.unwrap().forget();
222 } else {
223 // 11. Otherwise:
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,
235 ErrorResult& aRv) {
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);
241 if (aRv.Failed()) {
242 return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
245 // 2. Perform ! Call(returnPromiseCapability.[[Resolve]], undefined,
246 // «result»).
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.
256 ErrorResult error;
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,
270 ErrorResult& aRv) {
271 nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject);
273 // 3.7.10.2. Asynchronous iterator prototype object
274 // …
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.
285 auto onSettled =
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());
302 return nullptr;
305 // 4. Set returnStepsPromise to afterOngoingPromiseCapability.[[Promise]].
306 returnStepsPromise = afterOngoingPromise.unwrap().forget();
307 } else {
308 // 12. Otherwise:
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>,
315 ErrorResult& aRv,
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