/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/IterableIterator.h" #include "mozilla/dom/Promise-inl.h" namespace mozilla::dom { // Due to IterableIterator being a templated class, we implement the necessary // CC bits in a superclass that IterableIterator then inherits from. This allows // us to put the macros outside of the header. The base class has pure virtual // functions for Traverse/Unlink that the templated subclasses will override. NS_IMPL_CYCLE_COLLECTION_CLASS(IterableIteratorBase) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IterableIteratorBase) tmp->TraverseHelper(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IterableIteratorBase) tmp->UnlinkHelper(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END namespace iterator_utils { void DictReturn(JSContext* aCx, JS::MutableHandle aResult, bool aDone, JS::Handle aValue, ErrorResult& aRv) { RootedDictionary dict(aCx); dict.mDone = aDone; dict.mValue = aValue; JS::Rooted dictValue(aCx); if (!ToJSValue(aCx, dict, &dictValue)) { aRv.Throw(NS_ERROR_FAILURE); return; } aResult.set(dictValue); } void DictReturn(JSContext* aCx, JS::MutableHandle aResult, bool aDone, JS::Handle aValue, ErrorResult& aRv) { JS::Rooted dictValue(aCx); DictReturn(aCx, &dictValue, aDone, aValue, aRv); if (aRv.Failed()) { return; } aResult.set(&dictValue.toObject()); } void KeyAndValueReturn(JSContext* aCx, JS::Handle aKey, JS::Handle aValue, JS::MutableHandle aResult, ErrorResult& aRv) { RootedDictionary dict(aCx); dict.mDone = false; // Dictionary values are a Sequence, which is a FallibleTArray, so we need // to check returns when appending. if (!dict.mValue.AppendElement(aKey, mozilla::fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } if (!dict.mValue.AppendElement(aValue, mozilla::fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } JS::Rooted dictValue(aCx); if (!ToJSValue(aCx, dict, &dictValue)) { aRv.Throw(NS_ERROR_FAILURE); return; } aResult.set(&dictValue.toObject()); } } // namespace iterator_utils namespace binding_detail { static already_AddRefed PromiseOrErr( Result, nsresult>&& aResult, ErrorResult& aError) { if (aResult.isErr()) { aError.Throw(aResult.unwrapErr()); return nullptr; } return aResult.unwrap().forget(); } already_AddRefed AsyncIterableNextImpl::NextSteps( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsIGlobalObject* aGlobalObject, ErrorResult& aRv) { // 2. If object’s is finished is true, then: if (aObject->mIsFinished) { // 1. Let result be CreateIterResultObject(undefined, true). JS::Rooted dict(aCx); iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue, aRv); if (aRv.Failed()) { return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv); } // 2. Perform ! Call(nextPromiseCapability.[[Resolve]], undefined, // «result»). // 3. Return nextPromiseCapability.[[Promise]]. return Promise::Resolve(aGlobalObject, aCx, dict, aRv); } // 4. Let nextPromise be the result of getting the next iteration result with // object’s target and object. RefPtr nextPromise; { ErrorResult error; nextPromise = GetNextResult(error); error.WouldReportJSException(); if (error.Failed()) { nextPromise = Promise::Reject(aGlobalObject, std::move(error), aRv); } } // 5. Let fulfillSteps be the following steps, given next: auto fulfillSteps = [](JSContext* aCx, JS::Handle aNext, ErrorResult& aRv, const RefPtr& aObject, const nsCOMPtr& aGlobalObject) -> already_AddRefed { // 1. Set object’s ongoing promise to null. aObject->mOngoingPromise = nullptr; // 2. If next is end of iteration, then: JS::Rooted dict(aCx); if (aNext.isMagic(binding_details::END_OF_ITERATION)) { // 1. Set object’s is finished to true. aObject->mIsFinished = true; // 2. Return CreateIterResultObject(undefined, true). iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue, aRv); if (aRv.Failed()) { return nullptr; } } else { // 3. Otherwise, if interface has a pair asynchronously iterable // declaration: // 1. Assert: next is a value pair. // 2. Return the iterator result for next and kind. // 4. Otherwise: // 1. Assert: interface has a value asynchronously iterable declaration. // 2. Assert: next is a value of the type that appears in the // declaration. // 3. Let value be next, converted to an ECMAScript value. // 4. Return CreateIterResultObject(value, false). iterator_utils::DictReturn(aCx, &dict, false, aNext, aRv); if (aRv.Failed()) { return nullptr; } } // Note that ThenCatchWithCycleCollectedArgs expects a Promise, so // we use Promise::Resolve here. The specs do convert this to a // promise too at another point, but the end result should be the // same. return Promise::Resolve(aGlobalObject, aCx, dict, aRv); }; // 7. Let rejectSteps be the following steps, given reason: auto rejectSteps = [](JSContext* aCx, JS::Handle aReason, ErrorResult& aRv, const RefPtr& aObject, const nsCOMPtr& aGlobalObject) { // 1. Set object’s ongoing promise to null. aObject->mOngoingPromise = nullptr; // 2. Set object’s is finished to true. aObject->mIsFinished = true; // 3. Throw reason. return Promise::Reject(aGlobalObject, aCx, aReason, aRv); }; // 9. Perform PerformPromiseThen(nextPromise, onFulfilled, onRejected, // nextPromiseCapability). Result, nsresult> result = nextPromise->ThenCatchWithCycleCollectedArgs( std::move(fulfillSteps), std::move(rejectSteps), RefPtr{aObject}, nsCOMPtr{aGlobalObject}); // 10. Return nextPromiseCapability.[[Promise]]. return PromiseOrErr(std::move(result), aRv); } already_AddRefed AsyncIterableNextImpl::Next( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsISupports* aGlobalObject, ErrorResult& aRv) { nsCOMPtr globalObject = do_QueryInterface(aGlobalObject); // 3.7.10.2. Asynchronous iterator prototype object // … // 10. If ongoingPromise is not null, then: if (aObject->mOngoingPromise) { // 1. Let afterOngoingPromiseCapability be // ! NewPromiseCapability(%Promise%). // 2. Let onSettled be CreateBuiltinFunction(nextSteps, « »). // aObject is the same object as 'this', so it's fine to capture 'this' // without taking a strong reference, because we already take a strong // reference to it through aObject. auto onSettled = [this](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, const RefPtr& aObject, const nsCOMPtr& aGlobalObject) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { return NextSteps(aCx, aObject, aGlobalObject, aRv); }; // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled, // afterOngoingPromiseCapability). Result, nsresult> afterOngoingPromise = aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgs( onSettled, onSettled, RefPtr{aObject}, std::move(globalObject)); if (afterOngoingPromise.isErr()) { aRv.Throw(afterOngoingPromise.unwrapErr()); return nullptr; } // 4. Set object’s ongoing promise to // afterOngoingPromiseCapability.[[Promise]]. aObject->mOngoingPromise = afterOngoingPromise.unwrap().forget(); } else { // 11. Otherwise: // 1. Set object’s ongoing promise to the result of running nextSteps. aObject->mOngoingPromise = NextSteps(aCx, aObject, globalObject, aRv); } // 12. Return object’s ongoing promise. return do_AddRef(aObject->mOngoingPromise); } already_AddRefed AsyncIterableReturnImpl::ReturnSteps( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsIGlobalObject* aGlobalObject, JS::Handle aValue, ErrorResult& aRv) { // 2. If object’s is finished is true, then: if (aObject->mIsFinished) { // 1. Let result be CreateIterResultObject(value, true). JS::Rooted dict(aCx); iterator_utils::DictReturn(aCx, &dict, true, aValue, aRv); if (aRv.Failed()) { return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv); } // 2. Perform ! Call(returnPromiseCapability.[[Resolve]], undefined, // «result»). // 3. Return returnPromiseCapability.[[Promise]]. return Promise::Resolve(aGlobalObject, aCx, dict, aRv); } // 3. Set object’s is finished to true. aObject->mIsFinished = true; // 4. Return the result of running the asynchronous iterator return algorithm // for interface, given object’s target, object, and value. ErrorResult error; RefPtr returnPromise = GetReturnPromise(aCx, aValue, error); error.WouldReportJSException(); if (error.Failed()) { return Promise::Reject(aGlobalObject, std::move(error), aRv); } return returnPromise.forget(); } already_AddRefed AsyncIterableReturnImpl::Return( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsISupports* aGlobalObject, JS::Handle aValue, ErrorResult& aRv) { nsCOMPtr globalObject = do_QueryInterface(aGlobalObject); // 3.7.10.2. Asynchronous iterator prototype object // … RefPtr returnStepsPromise; // 11. If ongoingPromise is not null, then: if (aObject->mOngoingPromise) { // 1. Let afterOngoingPromiseCapability be // ! NewPromiseCapability(%Promise%). // 2. Let onSettled be CreateBuiltinFunction(returnSteps, « »). // aObject is the same object as 'this', so it's fine to capture 'this' // without taking a strong reference, because we already take a strong // reference to it through aObject. auto onSettled = [this](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, const RefPtr& aObject, const nsCOMPtr& aGlobalObject, JS::Handle aVal) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { return ReturnSteps(aCx, aObject, aGlobalObject, aVal, aRv); }; // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled, // afterOngoingPromiseCapability). Result, nsresult> afterOngoingPromise = aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgsJS( onSettled, onSettled, std::make_tuple(RefPtr{aObject}, nsCOMPtr{globalObject}), std::make_tuple(aValue)); if (afterOngoingPromise.isErr()) { aRv.Throw(afterOngoingPromise.unwrapErr()); return nullptr; } // 4. Set returnStepsPromise to afterOngoingPromiseCapability.[[Promise]]. returnStepsPromise = afterOngoingPromise.unwrap().forget(); } else { // 12. Otherwise: // 1. Set returnStepsPromise to the result of running returnSteps. returnStepsPromise = ReturnSteps(aCx, aObject, globalObject, aValue, aRv); } // 13. Let fulfillSteps be the following steps: auto onFullFilled = [](JSContext* aCx, JS::Handle, ErrorResult& aRv, const nsCOMPtr& aGlobalObject, JS::Handle aVal) { // 1. Return CreateIterResultObject(value, true). JS::Rooted dict(aCx); iterator_utils::DictReturn(aCx, &dict, true, aVal, aRv); return Promise::Resolve(aGlobalObject, aCx, dict, aRv); }; // 14. Let onFulfilled be CreateBuiltinFunction(fulfillSteps, « »). // 15. Perform PerformPromiseThen(returnStepsPromise, onFulfilled, undefined, // returnPromiseCapability). Result, nsresult> returnPromise = returnStepsPromise->ThenWithCycleCollectedArgsJS( onFullFilled, std::make_tuple(std::move(globalObject)), std::make_tuple(aValue)); // 16. Return returnPromiseCapability.[[Promise]]. return PromiseOrErr(std::move(returnPromise), aRv); } } // namespace binding_detail } // namespace mozilla::dom