summaryrefslogtreecommitdiffstats
path: root/dom/bindings/IterableIterator.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/bindings/IterableIterator.cpp338
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