IterableIterator.h (15189B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /** 8 * The IterableIterator class is used for WebIDL interfaces that have a 9 * iterable<> member defined with two types (so a pair iterator). It handles 10 * the ES6 Iterator-like functions that are generated for the iterable 11 * interface. 12 * 13 * For iterable interfaces with a pair iterator, the implementation class will 14 * need to implement these two functions: 15 * 16 * - size_t GetIterableLength() 17 * - Returns the number of elements available to iterate over 18 * - [type] GetValueAtIndex(size_t index) 19 * - Returns the value at the requested index. 20 * - [type] GetKeyAtIndex(size_t index) 21 * - Returns the key at the requested index 22 * 23 * Examples of iterable interface implementations can be found in the bindings 24 * test directory. 25 */ 26 27 #ifndef mozilla_dom_IterableIterator_h 28 #define mozilla_dom_IterableIterator_h 29 30 #include "js/RootingAPI.h" 31 #include "js/TypeDecls.h" 32 #include "js/Value.h" 33 #include "mozilla/AlreadyAddRefed.h" 34 #include "mozilla/dom/IterableIteratorBinding.h" 35 #include "mozilla/dom/Promise.h" 36 #include "mozilla/dom/RootedDictionary.h" 37 #include "mozilla/dom/ToJSValue.h" 38 #include "nsISupports.h" 39 40 namespace mozilla::dom { 41 42 namespace binding_details { 43 44 // JS::MagicValue(END_OF_ITERATION) is the value we use for 45 // https://webidl.spec.whatwg.org/#end-of-iteration. It shouldn't be returned to 46 // JS, because AsyncIterableIteratorBase::NextSteps will detect it and will 47 // return the result of CreateIterResultObject(undefined, true) instead 48 // (discarding the magic value). 49 static const JSWhyMagic END_OF_ITERATION = JS_GENERIC_MAGIC; 50 51 } // namespace binding_details 52 53 namespace iterator_utils { 54 55 void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, 56 bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv); 57 58 void KeyAndValueReturn(JSContext* aCx, JS::Handle<JS::Value> aKey, 59 JS::Handle<JS::Value> aValue, 60 JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv); 61 62 inline void ResolvePromiseForFinished(Promise* aPromise) { 63 aPromise->MaybeResolve(JS::MagicValue(binding_details::END_OF_ITERATION)); 64 } 65 66 template <typename Key, typename Value> 67 void ResolvePromiseWithKeyAndValue(Promise* aPromise, const Key& aKey, 68 const Value& aValue) { 69 aPromise->MaybeResolve(std::make_tuple(aKey, aValue)); 70 } 71 72 } // namespace iterator_utils 73 74 class IterableIteratorBase { 75 public: 76 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(IterableIteratorBase) 77 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(IterableIteratorBase) 78 79 typedef enum { Keys = 0, Values, Entries } IteratorType; 80 81 IterableIteratorBase() = default; 82 83 protected: 84 virtual ~IterableIteratorBase() = default; 85 virtual void UnlinkHelper() = 0; 86 virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) = 0; 87 }; 88 89 // Helpers to call iterator getter methods with the correct arguments, depending 90 // on the types they return, and convert the result to JS::Values. 91 92 // Helper for Get[Key,Value]AtIndex(uint32_t) methods, which accept an index and 93 // return a type supported by ToJSValue. 94 template <typename T, typename U> 95 bool CallIterableGetter(JSContext* aCx, U (T::*aMethod)(uint32_t), T* aInst, 96 uint32_t aIndex, JS::MutableHandle<JS::Value> aResult) { 97 return ToJSValue(aCx, (aInst->*aMethod)(aIndex), aResult); 98 } 99 100 template <typename T, typename U> 101 bool CallIterableGetter(JSContext* aCx, U (T::*aMethod)(uint32_t) const, 102 const T* aInst, uint32_t aIndex, 103 JS::MutableHandle<JS::Value> aResult) { 104 return ToJSValue(aCx, (aInst->*aMethod)(aIndex), aResult); 105 } 106 107 // Helper for Get[Key,Value]AtIndex(JSContext*, uint32_t, MutableHandleValue) 108 // methods, which accept a JS context, index, and mutable result value handle, 109 // and return true on success or false on failure. 110 template <typename T> 111 bool CallIterableGetter(JSContext* aCx, 112 bool (T::*aMethod)(JSContext*, uint32_t, 113 JS::MutableHandle<JS::Value>), 114 T* aInst, uint32_t aIndex, 115 JS::MutableHandle<JS::Value> aResult) { 116 return (aInst->*aMethod)(aCx, aIndex, aResult); 117 } 118 119 template <typename T> 120 bool CallIterableGetter(JSContext* aCx, 121 bool (T::*aMethod)(JSContext*, uint32_t, 122 JS::MutableHandle<JS::Value>) const, 123 const T* aInst, uint32_t aIndex, 124 JS::MutableHandle<JS::Value> aResult) { 125 return (aInst->*aMethod)(aCx, aIndex, aResult); 126 } 127 128 template <typename T> 129 class IterableIterator : public IterableIteratorBase { 130 public: 131 IterableIterator(T* aIterableObj, IteratorType aIteratorType) 132 : mIterableObj(aIterableObj), mIteratorType(aIteratorType), mIndex(0) { 133 MOZ_ASSERT(mIterableObj); 134 } 135 136 bool GetKeyAtIndex(JSContext* aCx, uint32_t aIndex, 137 JS::MutableHandle<JS::Value> aResult) { 138 return CallIterableGetter(aCx, &T::GetKeyAtIndex, mIterableObj.get(), 139 aIndex, aResult); 140 } 141 142 bool GetValueAtIndex(JSContext* aCx, uint32_t aIndex, 143 JS::MutableHandle<JS::Value> aResult) { 144 return CallIterableGetter(aCx, &T::GetValueAtIndex, mIterableObj.get(), 145 aIndex, aResult); 146 } 147 148 void Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, 149 ErrorResult& aRv) { 150 JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue()); 151 if (mIndex >= this->mIterableObj->GetIterableLength()) { 152 iterator_utils::DictReturn(aCx, aResult, true, value, aRv); 153 return; 154 } 155 switch (mIteratorType) { 156 case IteratorType::Keys: { 157 if (!GetKeyAtIndex(aCx, mIndex, &value)) { 158 aRv.Throw(NS_ERROR_FAILURE); 159 return; 160 } 161 iterator_utils::DictReturn(aCx, aResult, false, value, aRv); 162 break; 163 } 164 case IteratorType::Values: { 165 if (!GetValueAtIndex(aCx, mIndex, &value)) { 166 aRv.Throw(NS_ERROR_FAILURE); 167 return; 168 } 169 iterator_utils::DictReturn(aCx, aResult, false, value, aRv); 170 break; 171 } 172 case IteratorType::Entries: { 173 JS::Rooted<JS::Value> key(aCx); 174 if (!GetKeyAtIndex(aCx, mIndex, &key)) { 175 aRv.Throw(NS_ERROR_FAILURE); 176 return; 177 } 178 if (!GetValueAtIndex(aCx, mIndex, &value)) { 179 aRv.Throw(NS_ERROR_FAILURE); 180 return; 181 } 182 iterator_utils::KeyAndValueReturn(aCx, key, value, aResult, aRv); 183 break; 184 } 185 default: 186 MOZ_CRASH("Invalid iterator type!"); 187 } 188 ++mIndex; 189 } 190 191 protected: 192 virtual ~IterableIterator() = default; 193 194 // Since we're templated on a binding, we need to possibly CC it, but can't do 195 // that through macros. So it happens here. 196 void UnlinkHelper() final { mIterableObj = nullptr; } 197 198 virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override { 199 IterableIterator<T>* tmp = this; 200 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj); 201 } 202 203 // Binding Implementation object that we're iterating over. 204 RefPtr<T> mIterableObj; 205 // Tells whether this is a key, value, or entries iterator. 206 IteratorType mIteratorType; 207 // Current index of iteration. 208 uint32_t mIndex; 209 }; 210 211 namespace binding_detail { 212 213 class AsyncIterableNextImpl; 214 class AsyncIterableReturnImpl; 215 216 } // namespace binding_detail 217 218 class AsyncIterableIteratorBase : public IterableIteratorBase { 219 public: 220 IteratorType GetIteratorType() { return mIteratorType; } 221 222 protected: 223 explicit AsyncIterableIteratorBase(IteratorType aIteratorType) 224 : mIteratorType(aIteratorType) {} 225 226 void UnlinkHelper() override { 227 AsyncIterableIteratorBase* tmp = this; 228 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOngoingPromise); 229 } 230 231 void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override { 232 AsyncIterableIteratorBase* tmp = this; 233 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOngoingPromise); 234 } 235 236 private: 237 friend class binding_detail::AsyncIterableNextImpl; 238 friend class binding_detail::AsyncIterableReturnImpl; 239 240 // 3.7.10.1. Default asynchronous iterator objects 241 // Target is in AsyncIterableIterator 242 // Kind 243 IteratorType mIteratorType; 244 // Ongoing promise 245 RefPtr<Promise> mOngoingPromise; 246 // Is finished 247 bool mIsFinished = false; 248 }; 249 250 template <typename T> 251 class AsyncIterableIterator : public AsyncIterableIteratorBase { 252 private: 253 using IteratorData = typename T::IteratorData; 254 255 public: 256 AsyncIterableIterator(T* aIterableObj, IteratorType aIteratorType) 257 : AsyncIterableIteratorBase(aIteratorType), mIterableObj(aIterableObj) { 258 MOZ_ASSERT(mIterableObj); 259 } 260 261 IteratorData& Data() { return mData; } 262 263 protected: 264 // We'd prefer to use ImplCycleCollectionTraverse/ImplCycleCollectionUnlink on 265 // the iterator data, but unfortunately that doesn't work because it's 266 // dependent on the template parameter. Instead we detect if the data 267 // structure has Traverse and Unlink functions and call those. 268 template <typename Data> 269 auto TraverseData(Data& aData, nsCycleCollectionTraversalCallback& aCallback, 270 int) -> decltype(aData.Traverse(aCallback)) { 271 return aData.Traverse(aCallback); 272 } 273 template <typename Data> 274 void TraverseData(Data& aData, nsCycleCollectionTraversalCallback& aCallback, 275 double) {} 276 277 template <typename Data> 278 auto UnlinkData(Data& aData, int) -> decltype(aData.Unlink()) { 279 return aData.Unlink(); 280 } 281 template <typename Data> 282 void UnlinkData(Data& aData, double) {} 283 284 // Since we're templated on a binding, we need to possibly CC it, but can't do 285 // that through macros. So it happens here. 286 void UnlinkHelper() final { 287 AsyncIterableIteratorBase::UnlinkHelper(); 288 289 AsyncIterableIterator<T>* tmp = this; 290 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIterableObj); 291 UnlinkData(tmp->mData, 0); 292 } 293 294 void TraverseHelper(nsCycleCollectionTraversalCallback& cb) final { 295 AsyncIterableIteratorBase::TraverseHelper(cb); 296 297 AsyncIterableIterator<T>* tmp = this; 298 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj); 299 TraverseData(tmp->mData, cb, 0); 300 } 301 302 // 3.7.10.1. Default asynchronous iterator objects 303 // Target 304 RefPtr<T> mIterableObj; 305 // Kind 306 // Ongoing promise 307 // Is finished 308 // See AsyncIterableIteratorBase 309 310 // Opaque data of the backing object. 311 IteratorData mData; 312 }; 313 314 namespace binding_detail { 315 316 template <typename T> 317 using IterableIteratorWrapFunc = 318 bool (*)(JSContext* aCx, IterableIterator<T>* aObject, 319 JS::MutableHandle<JSObject*> aReflector); 320 321 template <typename T, IterableIteratorWrapFunc<T> WrapFunc> 322 class WrappableIterableIterator final : public IterableIterator<T> { 323 public: 324 using IterableIterator<T>::IterableIterator; 325 326 bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, 327 JS::MutableHandle<JSObject*> aObj) { 328 MOZ_ASSERT(!aGivenProto); 329 return (*WrapFunc)(aCx, this, aObj); 330 } 331 }; 332 333 class AsyncIterableNextImpl { 334 protected: 335 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Next( 336 JSContext* aCx, AsyncIterableIteratorBase* aObject, 337 nsISupports* aGlobalObject, ErrorResult& aRv); 338 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> GetNextResult( 339 ErrorResult& aRv) = 0; 340 341 private: 342 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> NextSteps( 343 JSContext* aCx, AsyncIterableIteratorBase* aObject, 344 nsIGlobalObject* aGlobalObject, ErrorResult& aRv); 345 }; 346 347 class AsyncIterableReturnImpl { 348 protected: 349 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Return( 350 JSContext* aCx, AsyncIterableIteratorBase* aObject, 351 nsISupports* aGlobalObject, JS::Handle<JS::Value> aValue, 352 ErrorResult& aRv); 353 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> GetReturnPromise( 354 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) = 0; 355 356 private: 357 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> ReturnSteps( 358 JSContext* aCx, AsyncIterableIteratorBase* aObject, 359 nsIGlobalObject* aGlobalObject, JS::Handle<JS::Value> aValue, 360 ErrorResult& aRv); 361 }; 362 363 template <typename T> 364 class AsyncIterableIteratorNoReturn : public AsyncIterableIterator<T>, 365 public AsyncIterableNextImpl { 366 public: 367 using AsyncIterableIterator<T>::AsyncIterableIterator; 368 369 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Next(JSContext* aCx, 370 ErrorResult& aRv) { 371 nsCOMPtr<nsISupports> parentObject = this->mIterableObj->GetParentObject(); 372 return AsyncIterableNextImpl::Next(aCx, this, parentObject, aRv); 373 } 374 375 protected: 376 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> GetNextResult( 377 ErrorResult& aRv) override { 378 RefPtr<T> iterableObj(this->mIterableObj); 379 return iterableObj->GetNextIterationResult( 380 static_cast<AsyncIterableIterator<T>*>(this), aRv); 381 } 382 }; 383 384 template <typename T> 385 class AsyncIterableIteratorWithReturn : public AsyncIterableIteratorNoReturn<T>, 386 public AsyncIterableReturnImpl { 387 public: 388 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Return( 389 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { 390 nsCOMPtr<nsISupports> parentObject = this->mIterableObj->GetParentObject(); 391 return AsyncIterableReturnImpl::Return(aCx, this, parentObject, aValue, 392 aRv); 393 } 394 395 protected: 396 using AsyncIterableIteratorNoReturn<T>::AsyncIterableIteratorNoReturn; 397 398 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> GetReturnPromise( 399 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) override { 400 RefPtr<T> iterableObj(this->mIterableObj); 401 return iterableObj->IteratorReturn( 402 aCx, static_cast<AsyncIterableIterator<T>*>(this), aValue, aRv); 403 } 404 }; 405 406 template <typename T, bool NeedReturnMethod> 407 using AsyncIterableIteratorNative = 408 std::conditional_t<NeedReturnMethod, AsyncIterableIteratorWithReturn<T>, 409 AsyncIterableIteratorNoReturn<T>>; 410 411 template <typename T, bool NeedReturnMethod> 412 using AsyncIterableIteratorWrapFunc = bool (*)( 413 JSContext* aCx, AsyncIterableIteratorNative<T, NeedReturnMethod>* aObject, 414 JS::MutableHandle<JSObject*> aReflector); 415 416 template <typename T, bool NeedReturnMethod, 417 AsyncIterableIteratorWrapFunc<T, NeedReturnMethod> WrapFunc, 418 typename Base = AsyncIterableIteratorNative<T, NeedReturnMethod>> 419 class WrappableAsyncIterableIterator final : public Base { 420 public: 421 using Base::Base; 422 423 bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, 424 JS::MutableHandle<JSObject*> aObj) { 425 MOZ_ASSERT(!aGivenProto); 426 return (*WrapFunc)(aCx, this, aObj); 427 } 428 }; 429 430 } // namespace binding_detail 431 432 } // namespace mozilla::dom 433 434 #endif // mozilla_dom_IterableIterator_h