diff options
Diffstat (limited to '')
-rw-r--r-- | dom/bindings/IterableIterator.cpp | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/dom/bindings/IterableIterator.cpp b/dom/bindings/IterableIterator.cpp new file mode 100644 index 0000000000..d0f2e5d227 --- /dev/null +++ b/dom/bindings/IterableIterator.cpp @@ -0,0 +1,338 @@ +/* -*- 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<JS::Value> aResult, + bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + RootedDictionary<IterableKeyOrValueResult> dict(aCx); + dict.mDone = aDone; + dict.mValue = aValue; + JS::Rooted<JS::Value> dictValue(aCx); + if (!ToJSValue(aCx, dict, &dictValue)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + aResult.set(dictValue); +} + +void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, + bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + JS::Rooted<JS::Value> dictValue(aCx); + DictReturn(aCx, &dictValue, aDone, aValue, aRv); + if (aRv.Failed()) { + return; + } + aResult.set(&dictValue.toObject()); +} + +void KeyAndValueReturn(JSContext* aCx, JS::Handle<JS::Value> aKey, + JS::Handle<JS::Value> aValue, + JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv) { + RootedDictionary<IterableKeyAndValueResult> 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<JS::Value> 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<Promise> PromiseOrErr( + Result<RefPtr<Promise>, nsresult>&& aResult, ErrorResult& aError) { + if (aResult.isErr()) { + aError.Throw(aResult.unwrapErr()); + return nullptr; + } + + return aResult.unwrap().forget(); +} + +already_AddRefed<Promise> 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<JS::Value> 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<Promise> 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<JS::Value> aNext, + ErrorResult& aRv, + const RefPtr<AsyncIterableIteratorBase>& aObject, + const nsCOMPtr<nsIGlobalObject>& aGlobalObject) + -> already_AddRefed<Promise> { + // 1. Set object’s ongoing promise to null. + aObject->mOngoingPromise = nullptr; + + // 2. If next is end of iteration, then: + JS::Rooted<JS::Value> 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<JS::Value> aReason, + ErrorResult& aRv, + const RefPtr<AsyncIterableIteratorBase>& aObject, + const nsCOMPtr<nsIGlobalObject>& 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<RefPtr<Promise>, 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<Promise> AsyncIterableNextImpl::Next( + JSContext* aCx, AsyncIterableIteratorBase* aObject, + nsISupports* aGlobalObject, ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> 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<JS::Value> aValue, + ErrorResult& aRv, + const RefPtr<AsyncIterableIteratorBase>& aObject, + const nsCOMPtr<nsIGlobalObject>& aGlobalObject) + MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + return NextSteps(aCx, aObject, aGlobalObject, aRv); + }; + + // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled, + // afterOngoingPromiseCapability). + Result<RefPtr<Promise>, 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<Promise> AsyncIterableReturnImpl::ReturnSteps( + JSContext* aCx, AsyncIterableIteratorBase* aObject, + nsIGlobalObject* aGlobalObject, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) { + // 2. If object’s is finished is true, then: + if (aObject->mIsFinished) { + // 1. Let result be CreateIterResultObject(value, true). + JS::Rooted<JS::Value> 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<Promise> returnPromise = GetReturnPromise(aCx, aValue, error); + + error.WouldReportJSException(); + if (error.Failed()) { + return Promise::Reject(aGlobalObject, std::move(error), aRv); + } + + return returnPromise.forget(); +} + +already_AddRefed<Promise> AsyncIterableReturnImpl::Return( + JSContext* aCx, AsyncIterableIteratorBase* aObject, + nsISupports* aGlobalObject, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject); + + // 3.7.10.2. Asynchronous iterator prototype object + // … + RefPtr<Promise> 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<JS::Value> aValue, ErrorResult& aRv, + const RefPtr<AsyncIterableIteratorBase>& aObject, + const nsCOMPtr<nsIGlobalObject>& aGlobalObject, + JS::Handle<JS::Value> aVal) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + return ReturnSteps(aCx, aObject, aGlobalObject, aVal, aRv); + }; + + // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled, + // afterOngoingPromiseCapability). + Result<RefPtr<Promise>, 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<JS::Value>, + ErrorResult& aRv, + const nsCOMPtr<nsIGlobalObject>& aGlobalObject, + JS::Handle<JS::Value> aVal) { + // 1. Return CreateIterResultObject(value, true). + JS::Rooted<JS::Value> 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<RefPtr<Promise>, 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 |