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