/* -*- 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/TypeDecls.h" #include "js/Value.h" #include "nsISupports.h" #include "mozilla/dom/IterableIteratorBinding.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/ToJSValue.h" namespace mozilla { namespace dom { class IterableIteratorBase : public nsISupports { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(IterableIteratorBase) typedef enum { Keys = 0, Values, Entries } IterableIteratorType; 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 final : public IterableIteratorBase { public: typedef bool (*WrapFunc)(JSContext* aCx, IterableIterator* aObject, JS::Handle aGivenProto, JS::MutableHandle aReflector); explicit IterableIterator(T* aIterableObj, IterableIteratorType aIteratorType, WrapFunc aWrapFunc) : mIterableObj(aIterableObj), mIteratorType(aIteratorType), mWrapFunc(aWrapFunc), mIndex(0) { MOZ_ASSERT(mIterableObj); MOZ_ASSERT(mWrapFunc); } 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()) { DictReturn(aCx, aResult, true, value, aRv); return; } switch (mIteratorType) { case IterableIteratorType::Keys: { if (!GetKeyAtIndex(aCx, mIndex, &value)) { aRv.Throw(NS_ERROR_FAILURE); return; } DictReturn(aCx, aResult, false, value, aRv); break; } case IterableIteratorType::Values: { if (!GetValueAtIndex(aCx, mIndex, &value)) { aRv.Throw(NS_ERROR_FAILURE); return; } DictReturn(aCx, aResult, false, value, aRv); break; } case IterableIteratorType::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; } KeyAndValueReturn(aCx, key, value, aResult, aRv); break; } default: MOZ_CRASH("Invalid iterator type!"); } ++mIndex; } bool WrapObject(JSContext* aCx, JS::Handle aGivenProto, JS::MutableHandle aObj) { return (*mWrapFunc)(aCx, this, aGivenProto, aObj); } protected: static 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.toObject()); } static 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()); } 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. IterableIteratorType mIteratorType; // Function pointer to binding-type-specific Wrap() call for this iterator. WrapFunc mWrapFunc; // Current index of iteration. uint32_t mIndex; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_IterableIterator_h