summaryrefslogtreecommitdiffstats
path: root/mailnews/base/util/iteratorUtils.jsm
blob: 266c4d7b63df3bd386a0595f2b739a00c84cffd0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/* 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/. */

/**
 * This file contains helper methods for dealing with XPCOM iterators (arrays
 * and enumerators) in JS-friendly ways.
 */

this.EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMArray", "toArray"];

Components.utils.import("resource://gre/modules/Deprecated.jsm");

var Ci = Components.interfaces;

var JS_HAS_SYMBOLS = typeof Symbol === "function";
var ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";

/**
 * This function will take a number of objects and convert them to an array.
 *
 * Currently, we support the following objects:
 *   Anything you can for (let x of aObj) on
 *                (e.g. toArray(fixIterator(enum))[4],
 *                 also a NodeList from element.childNodes)
 *
 * @param aObj        The object to convert
 */
function toArray(aObj) {
  if (ITERATOR_SYMBOL in aObj) {
    return Array.from(aObj);
  }

  // We got something unexpected, notify the caller loudly.
  throw new Error("An unsupported object sent to toArray: " +
                  (("toString" in aObj) ? aObj.toString() : aObj));
}

/**
 * Given a JS array, JS iterator, or one of a variety of XPCOM collections or
 * iterators, return a JS iterator suitable for use in a for...of expression.
 *
 * Currently, we support the following types of XPCOM iterators:
 *   nsIArray
 *   nsISupportsArray
 *   nsISimpleEnumerator
 *
 * This intentionally does not support nsIEnumerator as it is obsolete and
 * no longer used in the base code.
 *
 * Note that old-style JS iterators are explicitly not supported in this
 * method, as they are going away. For a limited time, the resulting iterator
 * can be used in a for...in loop, but this is a legacy compatibility shim that
 * will not work forever. See bug 1098412.
 *
 *   @param aEnum  the enumerator to convert
 *   @param aIface (optional) an interface to QI each object to prior to
 *                 returning
 *
 *   @note This returns an object that can be used in 'for...of' loops.
 *         Do not use 'for each...in'. 'for...in' may be used, but only as a
 *         legacy feature.
 *         This does *not* return an Array object. To create such an array, use
 *         let array = toArray(fixIterator(xpcomEnumerator));
 */
function fixIterator(aEnum, aIface) {
  // Minor internal details: to support both for (let x of fixIterator()) and
  // for (let x in fixIterator()), we need to add in a __iterator__ kludge
  // property. __iterator__ is to go away in bug 1098412; we could theoretically
  // make it work beyond that by using Proxies, but that's far to go for
  // something we want to get rid of anyways.
  // Note that the new-style iterator uses Symbol.iterator to work, and anything
  // that has Symbol.iterator works with for-of.
  function makeDualIterator(newStyle) {
    newStyle.__iterator__ = function() {
      for (let item of newStyle)
        yield item;
    };
    return newStyle;
  }

  // If the input is an array or something that sports Symbol.iterator, then
  // the original input is sufficient to directly return. However, if we want
  // to support the aIface parameter, we need to do a lazy version of Array.map.
  if (Array.isArray(aEnum) || ITERATOR_SYMBOL in aEnum) {
    if (!aIface) {
      return makeDualIterator(aEnum);
    } else {
      return makeDualIterator((function*() {
        for (let o of aEnum)
          yield o.QueryInterface(aIface);
      })());
    }
  }

  let face = aIface || Ci.nsISupports;
  // Figure out which kind of array object we have.
  // First try nsIArray (covers nsIMutableArray too).
  if (aEnum instanceof Ci.nsIArray) {
    return makeDualIterator((function*() {
      let count = aEnum.length;
      for (let i = 0; i < count; i++)
        yield aEnum.queryElementAt(i, face);
    })());
  }

  // Try an nsISupportsArray.
  // This object is deprecated, but we need to keep supporting it
  // while anything in the base code (including mozilla-central) produces it.
  if (aEnum instanceof Ci.nsISupportsArray) {
    return makeDualIterator((function*() {
      let count = aEnum.Count();
      for (let i = 0; i < count; i++)
        yield aEnum.QueryElementAt(i, face);
    })());
  }

  // How about nsISimpleEnumerator? This one is nice and simple.
  if (aEnum instanceof Ci.nsISimpleEnumerator) {
    return makeDualIterator((function*() {
      while (aEnum.hasMoreElements())
        yield aEnum.getNext().QueryInterface(face);
    })());
  }

  // We got something unexpected, notify the caller loudly.
  throw new Error("An unsupported object sent to fixIterator: " +
                  (("toString" in aEnum) ? aEnum.toString() : aEnum));
}

/**
 * This function takes an Array object and returns an XPCOM array
 * of the desired type. It will *not* work if you extend Array.prototype.
 *
 * @param aArray      the array (anything fixIterator supports) to convert to an XPCOM array
 * @param aInterface  the type of XPCOM array to convert
 *
 * @note The returned array is *not* dynamically updated.  Changes made to the
 *       JS array after a call to this function will not be reflected in the
 *       XPCOM array.
 */
function toXPCOMArray(aArray, aInterface) {
  if (aInterface.equals(Ci.nsISupportsArray)) {
    Deprecated.warning("nsISupportsArray object is deprecated, avoid creating new ones.",
                       "https://developer.mozilla.org/en-US/docs/XPCOM_array_guide");
    let supportsArray = Components.classes["@mozilla.org/supports-array;1"]
                                  .createInstance(Ci.nsISupportsArray);
    for (let item of fixIterator(aArray)) {
      supportsArray.AppendElement(item);
    }
    return supportsArray;
  }

  if (aInterface.equals(Ci.nsIMutableArray)) {
    let mutableArray = Components.classes["@mozilla.org/array;1"]
                                 .createInstance(Ci.nsIMutableArray);
    for (let item of fixIterator(aArray)) {
      mutableArray.appendElement(item, false);
    }
    return mutableArray;
  }

  // We got something unexpected, notify the caller loudly.
  throw new Error("An unsupported interface requested from toXPCOMArray: " +
                  aInterface);
}