зеркало из https://github.com/mozilla/gecko-dev.git
339 строки
13 KiB
C++
339 строки
13 KiB
C++
/* -*- 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
|