dom/bindings/IterableIterator.h
author Sylvestre Ledru <sledru@mozilla.com>
Fri, 24 Feb 2017 17:04:50 +0100
changeset 489671 05d9746016f47666c00390aacc9f9d62c8ffffb4
parent 489668 cbb8fdf1daf98a15f7d57f6b08d273bdf96aa1a0
permissions -rw-r--r--
Move to 99 chars instead of 80 MozReview-Commit-ID: 6NxbMuFVI7e

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "nsISupports.h"
#include "nsWrapperCache.h"
#include "nsPIDOMWindow.h"
#include "nsCOMPtr.h"
#include "mozilla/dom/ToJSValue.h"
#include "jswrapper.h"
#include "mozilla/dom/IterableIteratorBinding.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() {}

protected:
  virtual ~IterableIteratorBase() {}
  virtual void UnlinkHelper() = 0;
  virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) = 0;
};

template<typename T>
class IterableIterator final : public IterableIteratorBase
{
public:
  typedef bool (*WrapFunc)(JSContext* aCx,
                           IterableIterator<T>* aObject,
                           JS::Handle<JSObject*> aGivenProto,
                           JS::MutableHandle<JSObject*> aReflector);

  explicit IterableIterator(T* aIterableObj,
                            IterableIteratorType aIteratorType,
                            WrapFunc aWrapFunc)
    : mIterableObj(aIterableObj)
    , mIteratorType(aIteratorType)
    , mWrapFunc(aWrapFunc)
    , mIndex(0)
  {
    MOZ_ASSERT(mIterableObj);
    MOZ_ASSERT(mWrapFunc);
  }

  void Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
  {
    JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
    if (mIndex >= this->mIterableObj->GetIterableLength()) {
      DictReturn(aCx, aResult, true, value, aRv);
      return;
    }
    switch (mIteratorType) {
      case IterableIteratorType::Keys: {
        if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) {
          aRv.Throw(NS_ERROR_FAILURE);
          return;
        }
        DictReturn(aCx, aResult, false, value, aRv);
        break;
      }
      case IterableIteratorType::Values: {
        if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
          aRv.Throw(NS_ERROR_FAILURE);
          return;
        }
        DictReturn(aCx, aResult, false, value, aRv);
        break;
      }
      case IterableIteratorType::Entries: {
        JS::Rooted<JS::Value> key(aCx);
        if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) {
          aRv.Throw(NS_ERROR_FAILURE);
          return;
        }
        if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(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<JSObject*> aGivenProto,
                  JS::MutableHandle<JSObject*> aObj)
  {
    return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
  }

protected:
  static void DictReturn(JSContext* aCx,
                         JS::MutableHandle<JSObject*> 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.toObject());
  }

  static 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());
  }

protected:
  virtual ~IterableIterator() {}

  // Since we're templated on a binding, we need to possibly CC it, but can't do
  // that through macros. So it happens here.
  virtual void UnlinkHelper() final { mIterableObj = nullptr; }

  virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override
  {
    IterableIterator<T>* tmp = this;
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj);
  }

  // Binding Implementation object that we're iterating over.
  RefPtr<T> 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