/* -*- 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/. */ /** * The IterableIterator class is used for WebIDL interfaces that have a * iterable<> member defined with two types (so a pair iterator). It handles * the ES6 Iterator-like functions that are generated for the iterable * interface. * * For iterable interfaces with a pair iterator, the implementation class will * need to implement these two functions: * * - size_t GetIterableLength() * - Returns the number of elements available to iterate over * - [type] GetValueAtIndex(size_t index) * - Returns the value at the requested index. * - [type] GetKeyAtIndex(size_t index) * - Returns the key at the requested index * * Examples of iterable interface implementations can be found in the bindings * test directory. */ #ifndef mozilla_dom_IterableIterator_h #define mozilla_dom_IterableIterator_h #include "js/RootingAPI.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "nsISupports.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/dom/IterableIteratorBinding.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/WeakPtr.h" namespace mozilla::dom { namespace binding_details { // JS::MagicValue(END_OF_ITERATION) is the value we use for // https://webidl.spec.whatwg.org/#end-of-iteration. It shouldn't be returned to // JS, because AsyncIterableIteratorBase::NextSteps will detect it and will // return the result of CreateIterResultObject(undefined, true) instead // (discarding the magic value). static const JSWhyMagic END_OF_ITERATION = JS_GENERIC_MAGIC; } // namespace binding_details namespace iterator_utils { void DictReturn(JSContext* aCx, JS::MutableHandle aResult, bool aDone, JS::Handle aValue, ErrorResult& aRv); void KeyAndValueReturn(JSContext* aCx, JS::Handle aKey, JS::Handle aValue, JS::MutableHandle aResult, ErrorResult& aRv); inline void ResolvePromiseForFinished(Promise* aPromise) { aPromise->MaybeResolve(JS::MagicValue(binding_details::END_OF_ITERATION)); } template void ResolvePromiseWithKeyAndValue(Promise* aPromise, const Key& aKey, const Value& aValue) { aPromise->MaybeResolve(MakeTuple(aKey, aValue)); } } // namespace iterator_utils class IterableIteratorBase { public: NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(IterableIteratorBase) NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(IterableIteratorBase) typedef enum { Keys = 0, Values, Entries } IteratorType; IterableIteratorBase() = default; protected: virtual ~IterableIteratorBase() = default; virtual void UnlinkHelper() = 0; virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) = 0; }; // Helpers to call iterator getter methods with the correct arguments, depending // on the types they return, and convert the result to JS::Values. // Helper for Get[Key,Value]AtIndex(uint32_t) methods, which accept an index and // return a type supported by ToJSValue. template bool CallIterableGetter(JSContext* aCx, U (T::*aMethod)(uint32_t), T* aInst, uint32_t aIndex, JS::MutableHandle aResult) { return ToJSValue(aCx, (aInst->*aMethod)(aIndex), aResult); } template bool CallIterableGetter(JSContext* aCx, U (T::*aMethod)(uint32_t) const, const T* aInst, uint32_t aIndex, JS::MutableHandle aResult) { return ToJSValue(aCx, (aInst->*aMethod)(aIndex), aResult); } // Helper for Get[Key,Value]AtIndex(JSContext*, uint32_t, MutableHandleValue) // methods, which accept a JS context, index, and mutable result value handle, // and return true on success or false on failure. template bool CallIterableGetter(JSContext* aCx, bool (T::*aMethod)(JSContext*, uint32_t, JS::MutableHandle), T* aInst, uint32_t aIndex, JS::MutableHandle aResult) { return (aInst->*aMethod)(aCx, aIndex, aResult); } template bool CallIterableGetter(JSContext* aCx, bool (T::*aMethod)(JSContext*, uint32_t, JS::MutableHandle) const, const T* aInst, uint32_t aIndex, JS::MutableHandle aResult) { return (aInst->*aMethod)(aCx, aIndex, aResult); } template class IterableIterator : public IterableIteratorBase { public: IterableIterator(T* aIterableObj, IteratorType aIteratorType) : mIterableObj(aIterableObj), mIteratorType(aIteratorType), mIndex(0) { MOZ_ASSERT(mIterableObj); } bool GetKeyAtIndex(JSContext* aCx, uint32_t aIndex, JS::MutableHandle aResult) { return CallIterableGetter(aCx, &T::GetKeyAtIndex, mIterableObj.get(), aIndex, aResult); } bool GetValueAtIndex(JSContext* aCx, uint32_t aIndex, JS::MutableHandle aResult) { return CallIterableGetter(aCx, &T::GetValueAtIndex, mIterableObj.get(), aIndex, aResult); } void Next(JSContext* aCx, JS::MutableHandle aResult, ErrorResult& aRv) { JS::Rooted value(aCx, JS::UndefinedValue()); if (mIndex >= this->mIterableObj->GetIterableLength()) { iterator_utils::DictReturn(aCx, aResult, true, value, aRv); return; } switch (mIteratorType) { case IteratorType::Keys: { if (!GetKeyAtIndex(aCx, mIndex, &value)) { aRv.Throw(NS_ERROR_FAILURE); return; } iterator_utils::DictReturn(aCx, aResult, false, value, aRv); break; } case IteratorType::Values: { if (!GetValueAtIndex(aCx, mIndex, &value)) { aRv.Throw(NS_ERROR_FAILURE); return; } iterator_utils::DictReturn(aCx, aResult, false, value, aRv); break; } case IteratorType::Entries: { JS::Rooted key(aCx); if (!GetKeyAtIndex(aCx, mIndex, &key)) { aRv.Throw(NS_ERROR_FAILURE); return; } if (!GetValueAtIndex(aCx, mIndex, &value)) { aRv.Throw(NS_ERROR_FAILURE); return; } iterator_utils::KeyAndValueReturn(aCx, key, value, aResult, aRv); break; } default: MOZ_CRASH("Invalid iterator type!"); } ++mIndex; } protected: virtual ~IterableIterator() = default; // Since we're templated on a binding, we need to possibly CC it, but can't do // that through macros. So it happens here. void UnlinkHelper() final { mIterableObj = nullptr; } virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override { IterableIterator* tmp = this; NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj); } // Binding Implementation object that we're iterating over. RefPtr mIterableObj; // Tells whether this is a key, value, or entries iterator. IteratorType mIteratorType; // Current index of iteration. uint32_t mIndex; }; namespace binding_detail { class AsyncIterableNextImpl; class AsyncIterableReturnImpl; } // namespace binding_detail class AsyncIterableIteratorBase : public IterableIteratorBase { public: IteratorType GetIteratorType() { return mIteratorType; } protected: explicit AsyncIterableIteratorBase(IteratorType aIteratorType) : mIteratorType(aIteratorType) {} void UnlinkHelper() override { AsyncIterableIteratorBase* tmp = this; NS_IMPL_CYCLE_COLLECTION_UNLINK(mOngoingPromise); } void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override { AsyncIterableIteratorBase* tmp = this; NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOngoingPromise); } private: friend class binding_detail::AsyncIterableNextImpl; friend class binding_detail::AsyncIterableReturnImpl; // 3.7.10.1. Default asynchronous iterator objects // Target is in AsyncIterableIterator // Kind IteratorType mIteratorType; // Ongoing promise RefPtr mOngoingPromise; // Is finished bool mIsFinished = false; }; template class AsyncIterableIterator : public AsyncIterableIteratorBase { private: using IteratorData = typename T::IteratorData; public: AsyncIterableIterator(T* aIterableObj, IteratorType aIteratorType) : AsyncIterableIteratorBase(aIteratorType), mIterableObj(aIterableObj) { MOZ_ASSERT(mIterableObj); } IteratorData& Data() { return mData; } protected: // We'd prefer to use ImplCycleCollectionTraverse/ImplCycleCollectionUnlink on // the iterator data, but unfortunately that doesn't work because it's // dependent on the template parameter. Instead we detect if the data // structure has Traverse and Unlink functions and call those. template auto TraverseData(Data& aData, nsCycleCollectionTraversalCallback& aCallback, int) -> decltype(aData.Traverse(aCallback)) { return aData.Traverse(aCallback); } template void TraverseData(Data& aData, nsCycleCollectionTraversalCallback& aCallback, double) {} template auto UnlinkData(Data& aData, int) -> decltype(aData.Unlink()) { return aData.Unlink(); } template void UnlinkData(Data& aData, double) {} // Since we're templated on a binding, we need to possibly CC it, but can't do // that through macros. So it happens here. void UnlinkHelper() final { AsyncIterableIteratorBase::UnlinkHelper(); AsyncIterableIterator* tmp = this; NS_IMPL_CYCLE_COLLECTION_UNLINK(mIterableObj); UnlinkData(tmp->mData, 0); } void TraverseHelper(nsCycleCollectionTraversalCallback& cb) final { AsyncIterableIteratorBase::TraverseHelper(cb); AsyncIterableIterator* tmp = this; NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj); TraverseData(tmp->mData, cb, 0); } // 3.7.10.1. Default asynchronous iterator objects // Target RefPtr mIterableObj; // Kind // Ongoing promise // Is finished // See AsyncIterableIteratorBase // Opaque data of the backing object. IteratorData mData; }; namespace binding_detail { template using IterableIteratorWrapFunc = bool (*)(JSContext* aCx, IterableIterator* aObject, JS::MutableHandle aReflector); template WrapFunc> class WrappableIterableIterator final : public IterableIterator { public: using IterableIterator::IterableIterator; bool WrapObject(JSContext* aCx, JS::Handle aGivenProto, JS::MutableHandle aObj) { MOZ_ASSERT(!aGivenProto); return (*WrapFunc)(aCx, this, aObj); } }; class AsyncIterableNextImpl { protected: MOZ_CAN_RUN_SCRIPT already_AddRefed Next( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsISupports* aGlobalObject, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT virtual already_AddRefed GetNextResult( ErrorResult& aRv) = 0; private: MOZ_CAN_RUN_SCRIPT already_AddRefed NextSteps( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsIGlobalObject* aGlobalObject, ErrorResult& aRv); }; class AsyncIterableReturnImpl { protected: MOZ_CAN_RUN_SCRIPT already_AddRefed Return( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsISupports* aGlobalObject, JS::Handle aValue, ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT virtual already_AddRefed GetReturnPromise( JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) = 0; private: MOZ_CAN_RUN_SCRIPT already_AddRefed ReturnSteps( JSContext* aCx, AsyncIterableIteratorBase* aObject, nsIGlobalObject* aGlobalObject, JS::Handle aValue, ErrorResult& aRv); }; template class AsyncIterableIteratorNoReturn : public AsyncIterableIterator, public AsyncIterableNextImpl { public: using AsyncIterableIterator::AsyncIterableIterator; MOZ_CAN_RUN_SCRIPT already_AddRefed Next(JSContext* aCx, ErrorResult& aRv) { nsCOMPtr parentObject = this->mIterableObj->GetParentObject(); return AsyncIterableNextImpl::Next(aCx, this, parentObject, aRv); } protected: MOZ_CAN_RUN_SCRIPT already_AddRefed GetNextResult( ErrorResult& aRv) override { RefPtr iterableObj(this->mIterableObj); return iterableObj->GetNextIterationResult( static_cast*>(this), aRv); } }; template class AsyncIterableIteratorWithReturn : public AsyncIterableIteratorNoReturn, public AsyncIterableReturnImpl { public: MOZ_CAN_RUN_SCRIPT already_AddRefed Return( JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { nsCOMPtr parentObject = this->mIterableObj->GetParentObject(); return AsyncIterableReturnImpl::Return(aCx, this, parentObject, aValue, aRv); } protected: using AsyncIterableIteratorNoReturn::AsyncIterableIteratorNoReturn; MOZ_CAN_RUN_SCRIPT already_AddRefed GetReturnPromise( JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) override { RefPtr iterableObj(this->mIterableObj); return iterableObj->IteratorReturn( aCx, static_cast*>(this), aValue, aRv); } }; template using AsyncIterableIteratorNative = std::conditional_t, AsyncIterableIteratorNoReturn>; template using AsyncIterableIteratorWrapFunc = bool (*)( JSContext* aCx, AsyncIterableIteratorNative* aObject, JS::MutableHandle aReflector); template WrapFunc, typename Base = AsyncIterableIteratorNative> class WrappableAsyncIterableIterator final : public Base { public: using Base::Base; bool WrapObject(JSContext* aCx, JS::Handle aGivenProto, JS::MutableHandle aObj) { MOZ_ASSERT(!aGivenProto); return (*WrapFunc)(aCx, this, aObj); } }; } // namespace binding_detail } // namespace mozilla::dom #endif // mozilla_dom_IterableIterator_h