summaryrefslogtreecommitdiffstats
path: root/mailnews/base/util/iteratorUtils.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/util/iteratorUtils.jsm')
-rw-r--r--mailnews/base/util/iteratorUtils.jsm166
1 files changed, 166 insertions, 0 deletions
diff --git a/mailnews/base/util/iteratorUtils.jsm b/mailnews/base/util/iteratorUtils.jsm
new file mode 100644
index 000000000..266c4d7b6
--- /dev/null
+++ b/mailnews/base/util/iteratorUtils.jsm
@@ -0,0 +1,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);
+}