]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/util.jsm
Imported Upstream version 1.1+hg7904
[dactyl.git] / common / modules / util.jsm
index c5cd610fbcf8ecf6fabc31dea584a43331857e59..a0d0f090f2b06df38c674cce543b1170e1e46901 100644 (file)
@@ -1,20 +1,31 @@
 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2014 Kris Maglione <maglione.k@gmail.com>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
-/* use strict */
+"use strict";
 
 try {
 
-Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("util", {
     exports: ["DOM", "$", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
-    require: ["dom", "services"]
-}, this);
+    require: ["dom", "promises", "services"]
+});
+
+lazyRequire("overlay", ["overlay"]);
+lazyRequire("storage", ["File", "storage"]);
+lazyRequire("template", ["template"]);
+
+var Magic = Class("Magic", {
+    init: function init(str) {
+        this.str = str;
+    },
+
+    get message() this.str,
 
-this.lazyRequire("overlay", ["overlay"]);
+    toString: function () this.str
+});
 
 var FailedAssertion = Class("FailedAssertion", ErrorBase, {
     init: function init(message, level, noTrace) {
@@ -51,7 +62,9 @@ var wrapCallback = function wrapCallback(fn, isEvent) {
 }
 
 var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
-    init: function () {
+    Magic: Magic,
+
+    init: function init() {
         this.Array = array;
 
         this.addObserver(this);
@@ -59,8 +72,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     },
 
     activeWindow: deprecated("overlay.activeWindow", { get: function activeWindow() overlay.activeWindow }),
-    overlayObject: deprecated("overlay.overlayObject", { get: function overlayObject() overlay.closure.overlayObject }),
-    overlayWindow: deprecated("overlay.overlayWindow", { get: function overlayWindow() overlay.closure.overlayWindow }),
+    overlayObject: deprecated("overlay.overlayObject", { get: function overlayObject() overlay.bound.overlayObject }),
+    overlayWindow: deprecated("overlay.overlayWindow", { get: function overlayWindow() overlay.bound.overlayWindow }),
 
     compileMatcher: deprecated("DOM.compileMatcher", { get: function compileMatcher() DOM.compileMatcher }),
     computedStyle: deprecated("DOM#style", function computedStyle(elem) DOM(elem).style),
@@ -82,7 +95,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     readFromClipboard: deprecated("dactyl.clipboardRead", function readFromClipboard() util.dactyl.clipboardRead(false)),
 
     chromePackages: deprecated("config.chromePackages", { get: function chromePackages() config.chromePackages }),
-    haveGecko: deprecated("config.haveGecko", { get: function haveGecko() config.closure.haveGecko }),
+    haveGecko: deprecated("config.haveGecko", { get: function haveGecko() config.bound.haveGecko }),
     OS: deprecated("config.OS", { get: function OS() config.OS }),
 
     dactyl: update(function dactyl(obj) {
@@ -90,7 +103,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             var global = Class.objectGlobal(obj);
 
         return {
-            __noSuchMethod__: function (meth, args) {
+            __noSuchMethod__: function __noSuchMethod__(meth, args) {
                 let win = overlay.activeWindow;
 
                 var dactyl = global && global.dactyl || win && win.dactyl;
@@ -104,7 +117,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             }
         };
     }, {
-        __noSuchMethod__: function () this().__noSuchMethod__.apply(null, arguments)
+        __noSuchMethod__: function __noSuchMethod__() this().__noSuchMethod__.apply(null, arguments)
     }),
 
     /**
@@ -124,7 +137,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         let cleanup = ["dactyl-cleanup-modules", "quit-application"];
 
         function register(meth) {
-            for (let target in Set(cleanup.concat(Object.keys(obj.observers))))
+            for (let target of RealSet(cleanup.concat(Object.keys(obj.observers))))
                 try {
                     services.observer[meth](obj, target, true);
                 }
@@ -147,7 +160,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 }
             });
 
-        obj.observe.unregister = function () register("removeObserver");
+        obj.observe.unregister = () => register("removeObserver");
         register("addObserver");
     }, { dump: dump, Error: Error }),
 
@@ -159,7 +172,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {string} message The message to present to the
      *     user on failure.
      */
-    assert: function (condition, message, quiet) {
+    assert: function assert(condition, message, quiet) {
         if (!condition)
             throw FailedAssertion(message, 1, quiet === undefined ? true : quiet);
         return condition;
@@ -171,7 +184,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {string} name The name to mangle.
      * @returns {string} The mangled name.
      */
-    camelCase: function camelCase(name) String.replace(name, /-(.)/g, function (m, m1) m1.toUpperCase()),
+    camelCase: function camelCase(name) String.replace(name, /-(.)/g,
+                                                       (m, m1) => m1.toUpperCase()),
 
     /**
      * Capitalizes the first character of the given string.
@@ -247,12 +261,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
         function frame() update(
             function _frame(obj)
-                _frame === stack.top || _frame.valid(obj) ?
-                    _frame.elements.map(function (e) callable(e) ? e(obj) : e).join("") : "",
+                _frame === stack.top || _frame.valid(obj)
+                    ? _frame.elements.map(e => callable(e) ? e(obj) : e)
+                                     .join("")
+                    : "",
             {
                 elements: [],
                 seen: {},
-                valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj))
+                valid: function valid(obj) this.elements.every(e => !e.test || e.test(obj))
             });
 
         let end = 0;
@@ -281,8 +297,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 char = char.toLowerCase();
 
                 stack.top.elements.push(update(
-                    function (obj) obj[char] != null ? quote(obj, char) : "",
-                    { test: function (obj) obj[char] != null }));
+                    function (obj) obj[char] != null ? quote(obj, char)
+                                                     : "",
+                    { test: function test(obj) obj[char] != null }));
 
                 for (let elem in array.iterValues(stack))
                     elem.seen[char] = true;
@@ -326,28 +343,30 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
         let unknown = util.identity;
         if (!keepUnknown)
-            unknown = function () "";
+            unknown = () => "";
 
         function frame() update(
             function _frame(obj)
-                _frame === stack.top || _frame.valid(obj) ?
-                    _frame.elements.map(function (e) callable(e) ? e(obj) : e).join("") : "",
+                _frame === stack.top || _frame.valid(obj)
+                    ? _frame.elements.map(e => callable(e) ? e(obj) : e)
+                            .join("")
+                    : "",
             {
                 elements: [],
-                seen: {},
-                valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj))
+                seen: RealSet(),
+                valid: function valid(obj) this.elements.every(e => (!e.test || e.test(obj)))
             });
 
         let defaults = { lt: "<", gt: ">" };
 
-        let re = util.regexp(<![CDATA[
+        let re = util.regexp(literal(/*
             ([^]*?) // 1
             (?:
                 (<\{) | // 2
                 (< ((?:[a-z]-)?[a-z-]+?) (?:\[([0-9]+)\])? >) | // 3 4 5
                 (\}>) // 6
             )
-        ]]>, "gixy");
+        */), "gixy");
         macro = String(macro);
         let end = 0;
         for (let match in re.iterate(macro)) {
@@ -363,46 +382,46 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             }
             else if (close) {
                 stack.pop();
-                util.assert(stack.length, /*L*/"Unmatched %] in macro");
+                util.assert(stack.length, /*L*/"Unmatched }> in macro");
             }
             else {
                 let [, flags, name] = /^((?:[a-z]-)*)(.*)/.exec(macro);
-                flags = Set(flags);
+                flags = RealSet(flags);
 
                 let quote = util.identity;
-                if (flags.q)
+                if (flags.has("q"))
                     quote = function quote(obj) typeof obj === "number" ? obj : String.quote(obj);
-                if (flags.e)
+                if (flags.has("e"))
                     quote = function quote(obj) "";
 
-                if (Set.has(defaults, name))
+                if (hasOwnProperty(defaults, name))
                     stack.top.elements.push(quote(defaults[name]));
                 else {
                     let index = idx;
                     if (idx) {
                         idx = Number(idx) - 1;
                         stack.top.elements.push(update(
-                            function (obj) obj[name] != null && idx in obj[name] ? quote(obj[name][idx])
-                                                                                 : Set.has(obj, name) ? "" : unknown(full),
+                            obj => obj[name] != null && idx in obj[name] ? quote(obj[name][idx])
+                                                                         : hasOwnProperty(obj, name) ? "" : unknown(full),
                             {
-                                test: function (obj) obj[name] != null && idx in obj[name]
-                                                  && obj[name][idx] !== false
-                                                  && (!flags.e || obj[name][idx] != "")
+                                test: function test(obj) obj[name] != null && idx in obj[name]
+                                                      && obj[name][idx] !== false
+                                                      && (!flags.e || obj[name][idx] != "")
                             }));
                     }
                     else {
                         stack.top.elements.push(update(
-                            function (obj) obj[name] != null ? quote(obj[name])
-                                                             : Set.has(obj, name) ? "" : unknown(full),
+                            obj => obj[name] != null ? quote(obj[name])
+                                                     : hasOwnProperty(obj, name) ? "" : unknown(full),
                             {
-                                test: function (obj) obj[name] != null
-                                                  && obj[name] !== false
-                                                  && (!flags.e || obj[name] != "")
+                                test: function test(obj) obj[name] != null
+                                                      && obj[name] !== false
+                                                      && (!flags.e || obj[name] != "")
                             }));
                     }
 
                     for (let elem in array.iterValues(stack))
-                        elem.seen[name] = true;
+                        elem.seen.add(name);
                 }
             }
         }
@@ -453,7 +472,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                             acc.push(vals);
 
                         if (acc.length == pattern.length)
-                            this.res.push(acc.join(""))
+                            this.res.push(acc.join(""));
                         else
                             for (let val in values(vals))
                                 this.rec(acc.concat(val));
@@ -463,7 +482,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 return obj.res;
             }
 
-            if (pattern.indexOf("{") == -1)
+            if (!pattern.contains("{"))
                 return [pattern];
 
             let res = [];
@@ -477,8 +496,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                         fn(match);
                 }
                 res.push(pattern.substr(end));
-                return res.map(function (s) util.dequote(s, dequote));
-            }
+                return res.map(s => util.dequote(s, dequote));
+            };
 
             let patterns = [];
             let substrings = split(pattern, /((?:[^\\{]|\\.)*)\{((?:[^\\}]|\\.)*)\}/gy,
@@ -493,11 +512,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 else
                     for (let [, pattern] in Iterator(patterns[acc.length]))
                         rec(acc.concat(pattern));
-            }
+            };
             rec([]);
             return res;
         }
-        catch (e if e.message && ~e.message.indexOf("res is undefined")) {
+        catch (e if e.message && e.message.contains("res is undefined")) {
             // prefs.safeSet() would be reset on :rehash
             prefs.set("javascript.options.methodjit.chrome", false);
             util.dactyl.warn(_(UTF8("error.damnYouJägermonkey")));
@@ -505,6 +524,17 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }
     },
 
+    /**
+     * Briefly delay the execution of the passed function.
+     *
+     * @param {function} callback The function to delay.
+     */
+    delay: function delay(callback) {
+        let { mainThread } = services.threading;
+        mainThread.dispatch(callback,
+                            mainThread.DISPATCH_NORMAL);
+    },
+
     /**
      * Removes certain backslash-quoted characters while leaving other
      * backslash-quoting sequences untouched.
@@ -514,7 +544,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @returns {string}
      */
     dequote: function dequote(pattern, chars)
-        pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0),
+        pattern.replace(/\\(.)/, (m0, m1) => chars.contains(m1) ? m1 : m0),
 
     /**
      * Returns the nsIDocShell for the given window.
@@ -542,7 +572,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {string} stack The stack trace from an Error.
      * @returns {[string]} The stack frames.
      */
-    stackLines: function (stack) {
+    stackLines: function stackLines(stack) {
         let lines = [];
         let match, re = /([^]*?)@([^@\n]*)(?:\n|$)/g;
         while (match = re.exec(stack))
@@ -557,10 +587,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {string} msg The trace message.
      * @param {number} frames The number of frames to print.
      */
-    dumpStack: function dumpStack(msg, frames) {
+    dumpStack: function dumpStack(msg="Stack", frames=null) {
         let stack = util.stackLines(Error().stack);
         stack = stack.slice(1, 1 + (frames || stack.length)).join("\n").replace(/^/gm, "    ");
-        util.dump((arguments.length == 0 ? "Stack" : msg) + "\n" + stack + "\n");
+        util.dump(msg + "\n" + stack + "\n");
     },
 
     /**
@@ -634,7 +664,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         [hours, minutes]   = div(minutes, 60);
         [days, hours]      = div(hours,   24);
         if (days)
-            return /*L*/days + " days " + hours + " hours"
+            return /*L*/days + " days " + hours + " hours";
         if (hours)
             return /*L*/hours + "h " + minutes + "m";
         if (minutes)
@@ -674,7 +704,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {string} url
      * @returns {string|null}
      */
-    getHost: function (url) {
+    getHost: function getHost(url) {
         try {
             return util.createURI(url).host;
         }
@@ -719,22 +749,21 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      *
      * @returns {XMLHttpRequest}
      */
-    httpGet: function httpGet(url, callback, self) {
-        let params = callback;
-        if (!isObject(params))
-            params = { callback: params && function () callback.apply(self, arguments) };
+    httpGet: function httpGet(url, params={}, self) {
+        if (callable(params))
+            // Deprecated.
+            params = { callback: params.bind(self) };
 
         try {
             let xmlhttp = services.Xmlhttp();
-            xmlhttp.mozBackgroundRequest = Set.has(params, "background") ? params.background : true;
+            xmlhttp.mozBackgroundRequest = hasOwnProperty(params, "background") ? params.background : true;
 
             let async = params.callback || params.onload || params.onerror;
             if (async) {
-                xmlhttp.addEventListener("load",  function handler(event) { util.trapErrors(params.onload  || params.callback, params, xmlhttp, event) }, false);
-                xmlhttp.addEventListener("error", function handler(event) { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event) }, false);
+                xmlhttp.addEventListener("load",  event => { util.trapErrors(params.onload  || params.callback, params, xmlhttp, event); }, false);
+                xmlhttp.addEventListener("error", event => { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event); }, false);
             }
 
-
             if (isObject(params.params)) {
                 let data = [encodeURIComponent(k) + "=" + encodeURIComponent(v)
                             for ([k, v] in iter(params.params))];
@@ -780,6 +809,22 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }
     },
 
+    /**
+     * Like #httpGet, but returns a promise rather than accepting
+     * callbacks.
+     *
+     * @param {string} url The URL to fetch.
+     * @param {object} params Parameter object, as in #httpGet.
+     */
+    fetchUrl: promises.withCallbacks(function fetchUrl([accept, reject, deferred], url, params) {
+        params = update({}, params);
+        params.onload = accept;
+        params.onerror = reject;
+
+        let req = this.httpGet(url, params);
+        promises.oncancel(deferred, req.cancel);
+    }),
+
     /**
      * The identity function.
      *
@@ -795,7 +840,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {Object} r2
      * @returns {Object}
      */
-    intersection: function (r1, r2) ({
+    intersection: function intersection(r1, r2) ({
         get width()  this.right - this.left,
         get height() this.bottom - this.top,
         left: Math.max(r1.left, r2.left),
@@ -844,26 +889,26 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * top-level window and sub-frames thereof.
      */
     iterDocuments: function iterDocuments(types) {
-        types = types ? types.map(function (s) "type" + util.capitalize(s))
+        types = types ? types.map(s => "type" + util.capitalize(s))
                       : ["typeChrome", "typeContent"];
 
         let windows = services.windowMediator.getXULWindowEnumerator(null);
         while (windows.hasMoreElements()) {
             let window = windows.getNext().QueryInterface(Ci.nsIXULWindow);
-            for each (let type in types) {
+            for (let type of types) {
                 let docShells = window.docShell.getDocShellEnumerator(Ci.nsIDocShellTreeItem[type],
                                                                       Ci.nsIDocShell.ENUMERATE_FORWARDS);
                 while (docShells.hasMoreElements())
                     let (viewer = docShells.getNext().QueryInterface(Ci.nsIDocShell).contentViewer) {
                         if (viewer)
                             yield viewer.DOMDocument;
-                    }
+                    };
             }
         }
     },
 
     // ripped from Firefox; modified
-    unsafeURI: Class.Memoize(function () util.regexp(String.replace(<![CDATA[
+    unsafeURI: Class.Memoize(() => util.regexp(String.replace(literal(/*
             [
                 \s
                 // Invisible characters (bug 452979)
@@ -877,7 +922,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 // Bidi formatting characters. (RFC 3987 sections 3.2 and 4.1 paragraph 6)
                 U200E U200F U202A U202B U202C U202D U202E
             ]
-        ]]>, /U/g, "\\u"),
+        */), /U/g, "\\u"),
         "gx")),
     losslessDecodeURI: function losslessDecodeURI(url) {
         return url.split("%25").map(function (url) {
@@ -901,12 +946,21 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      *      for *obj*.
      */
     makeDTD: let (map = { "'": "&apos;", '"': "&quot;", "%": "&#x25;", "&": "&amp;", "<": "&lt;", ">": "&gt;" })
-        function makeDTD(obj) iter(obj)
-          .map(function ([k, v]) ["<!ENTITY ", k, " '", String.replace(v == null ? "null" : typeof v == "xml" ? v.toXMLString() : v,
-                                                                       typeof v == "xml" ? /['%]/g : /['"%&<>]/g,
-                                                                       function (m) map[m]),
-                                  "'>"].join(""))
-          .join("\n"),
+        function makeDTD(obj) {
+            function escape(val) {
+               let isDOM = DOM.isJSONXML(val);
+               return String.replace(val == null ? "null" :
+                                     isDOM       ? DOM.toXML(val)
+                                                 : val,
+                                     isDOM ? /['%]/g
+                                           : /['"%&<>]/g,
+                                     m => map[m]);
+            }
+
+            return iter(obj).map(([k, v]) =>
+                                 ["<!ENTITY ", k, " '", escape(v), "'>"].join(""))
+                            .join("\n");
+        },
 
     /**
      * Converts a URI string into a URI object.
@@ -943,13 +997,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @returns {string}
      */
     objectToString: function objectToString(object, color) {
-        // Use E4X literals so html is automatically quoted
-        // only when it's asked for. No one wants to see &lt;
-        // on their console or :map :foo in their buffer
-        // when they expect :map <C-f> :foo.
-        XML.prettyPrinting = false;
-        XML.ignoreWhitespace = false;
-
         if (object == null)
             return object + "\n";
 
@@ -970,28 +1017,43 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         catch (e) {
             obj = Object.prototype.toString.call(obj);
         }
-        obj = template.highlightFilter(util.clip(obj, 150), "\n", !color ? function () "^J" : function () <span highlight="NonText">^J</span>);
-        let string = <><span highlight="Title Object">{obj}</span>::&#x0a;</>;
+
+        if (color) {
+            obj = template.highlightFilter(util.clip(obj, 150), "\n",
+                                           () => ["span", { highlight: "NonText" },
+                                                      "^J"]);
+
+            var head = ["span", { highlight: "Title Object" }, obj, "::\n"];
+        }
+        else
+            head = util.clip(obj, 150).replace(/\n/g, "^J") + "::\n";
 
         let keys = [];
 
         // window.content often does not want to be queried with "var i in object"
         try {
             let hasValue = !("__iterator__" in object || isinstance(object, ["Generator", "Iterator"]));
+
             if (object.dactyl && object.modules && object.modules.modules == object.modules) {
                 object = Iterator(object);
                 hasValue = false;
             }
+
             let keyIter = object;
-            if ("__iterator__" in object && !callable(object.__iterator__))
-                keyIter = keys(object)
+            if (iter.iteratorProp in object) {
+                keyIter = (k for (k of object));
+                hasValue = false;
+            }
+            else if ("__iterator__" in object && !callable(object.__iterator__))
+                keyIter = keys(object);
 
             for (let i in keyIter) {
-                let value = <![CDATA[<no value>]]>;
+                let value = Magic("<no value>");
                 try {
                     value = object[i];
                 }
                 catch (e) {}
+
                 if (!hasValue) {
                     if (isArray(i) && i.length == 2)
                         [i, value] = i;
@@ -1001,13 +1063,27 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                     }
                 }
 
-                value = template.highlight(value, true, 150, !color);
-                let key = <span highlight="Key">{i}</span>;
+                let key = i;
                 if (!isNaN(i))
                     i = parseInt(i);
                 else if (/^[A-Z_]+$/.test(i))
                     i = "";
-                keys.push([i, <>{noVal ? value : <>{key}: {value}</>}&#x0a;</>]);
+
+                if (color)
+                    value = template.highlight(value, true, 150, !color);
+                else if (value instanceof Magic)
+                    value = String(value);
+                else
+                    value = util.clip(String(value).replace(/\n/g, "^J"), 150);
+
+                if (noVal)
+                    var val = value;
+                else if (color)
+                    val = [["span", { highlight: "Key" }, key], ": ", value];
+                else
+                    val = key + ": " + value;
+
+                keys.push([i, val]);
             }
         }
         catch (e) {
@@ -1019,12 +1095,77 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 return a[0] - b[0];
             return String.localeCompare(a[0], b[0]);
         }
-        string += template.map(keys.sort(compare), function (f) f[1]);
-        return color ? <div style="white-space: pre-wrap;">{string}</div> : [s for each (s in string)].join("");
+
+        let vals = template.map(keys.sort(compare), f => f[1],
+                                "\n");
+
+        if (color) {
+            return ["div", { style: "white-space: pre-wrap" }, head, vals];
+        }
+        return head + vals.join("");
+    },
+
+    prettifyJSON: function prettifyJSON(data, indent, invalidOK) {
+        const INDENT = indent || "    ";
+
+        function rec(data, level, seen) {
+            if (isObject(data)) {
+                seen = RealSet(seen);
+                if (seen.add(data))
+                    throw Error("Recursive object passed");
+            }
+
+            let prefix = level + INDENT;
+
+            if (data === undefined)
+                data = null;
+
+            if (~["boolean", "number"].indexOf(typeof data) || data === null)
+                res.push(String(data));
+            else if (isinstance(data, ["String", _]))
+                res.push(JSON.stringify(String(data)));
+            else if (isArray(data)) {
+                if (data.length == 0)
+                    res.push("[]");
+                else {
+                    res.push("[\n");
+                    for (let [i, val] in Iterator(data)) {
+                        if (i)
+                            res.push(",\n");
+                        res.push(prefix);
+                        rec(val, prefix, seen);
+                    }
+                    res.push("\n", level, "]");
+                }
+            }
+            else if (isObject(data)) {
+                res.push("{\n");
+
+                let i = 0;
+                for (let [key, val] in Iterator(data)) {
+                    if (i++)
+                        res.push(",\n");
+                    res.push(prefix, JSON.stringify(key), ": ");
+                    rec(val, prefix, seen);
+                }
+                if (i > 0)
+                    res.push("\n", level, "}");
+                else
+                    res[res.length - 1] = "{}";
+            }
+            else if (invalidOK)
+                res.push({}.toString.call(data));
+            else
+                throw Error("Invalid JSON object");
+        }
+
+        let res = [];
+        rec(data, "", RealSet());
+        return res.join("");
     },
 
     observers: {
-        "dactyl-cleanup-modules": function (subject, reason) {
+        "dactyl-cleanup-modules": function cleanupModules(subject, reason) {
             defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules " + reason);
 
             for (let module in values(defineModule.modules))
@@ -1032,28 +1173,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                     util.dump("cleanup: " + module.constructor.className);
                     util.trapErrors(module.cleanup, module, reason);
                 }
-
-            JSMLoader.cleanup();
-
-            if (!this.rehashing)
-                services.observer.addObserver(this, "dactyl-rehash", true);
-        },
-        "dactyl-rehash": function () {
-            services.observer.removeObserver(this, "dactyl-rehash");
-
-            defineModule.loadLog.push("dactyl: util: observe: dactyl-rehash");
-            if (!this.rehashing)
-                for (let module in values(defineModule.modules)) {
-                    defineModule.loadLog.push("dactyl: util: init(" + module + ")");
-                    if (module.reinit)
-                        module.reinit();
-                    else
-                        module.init();
-                }
-        },
-        "dactyl-purge": function () {
-            this.rehashing = 1;
-        },
+        }
     },
 
     /**
@@ -1111,7 +1231,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      *
      * This is similar to Perl's extended regular expression format.
      *
-     * @param {string|XML} expr The expression to compile into a RegExp.
+     * @param {string} expr The expression to compile into a RegExp.
      * @param {string} flags Flags to apply to the new RegExp.
      * @param {object} tokens The tokens to substitute. @optional
      * @returns {RegExp} A custom regexp object.
@@ -1135,11 +1255,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
         // Replace replacement <tokens>.
         if (tokens)
-            expr = String.replace(expr, /(\(?P)?<(\w+)>/g, function (m, n1, n2) !n1 && Set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m);
+            expr = String.replace(expr, /(\(?P)?<(\w+)>/g,
+                                  (m, n1, n2) => !n1 && hasOwnProperty(tokens, n2) ?    tokens[n2].dactylSource
+                                                                                     || tokens[n2].source
+                                                                                     || tokens[n2]
+                                                                                   : m);
 
         // Strip comments and white space.
         if (/x/.test(flags))
-            expr = String.replace(expr, /(\\.)|\/\/[^\n]*|\/\*[^]*?\*\/|\s+/gm, function (m, m1) m1 || "");
+            expr = String.replace(expr, /(\\.)|\/\/[^\n]*|\/\*[^]*?\*\/|\s+/gm,
+                                  (m, m1) => m1 || "");
 
         // Replace (?P<named> parameters)
         if (/\(\?P</.test(expr)) {
@@ -1155,9 +1280,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }
 
         let res = update(RegExp(expr, flags.replace("x", "")), {
-            closure: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "closure")),
+            bound: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "bound")),
+            closure: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "bound")),
             dactylPropertyNames: ["exec", "match", "test", "toSource", "toString", "global", "ignoreCase", "lastIndex", "multiLine", "source", "sticky"],
-            iterate: function (str, idx) util.regexp.iterate(this, str, idx)
+            iterate: function iterate(str, idx) util.regexp.iterate(this, str, idx)
         });
 
         // Return a struct with properties for named parameters if we
@@ -1183,7 +1309,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
          * @param {RegExp} re The regexp showable source of which is to be returned.
          * @returns {string}
          */
-        getSource: function regexp_getSource(re) re.source.replace(/\\(.)/g, function (m0, m1) m1 === "/" ? "/" : m0),
+        getSource: function regexp_getSource(re) re.source.replace(/\\(.)/g,
+                                                                   (m0, m1) => m1 === "/" ? m1
+                                                                                          : m0),
 
         /**
          * Iterates over all matches of the given regexp in the given
@@ -1220,11 +1348,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * Reloads dactyl in entirety by disabling the add-on and
      * re-enabling it.
      */
-    rehash: function (args) {
-        storage.session.commandlineArgs = args;
+    rehash: function rehash(args) {
+        storage.storeForSession("commandlineArgs", args);
         this.timeout(function () {
             this.flushCache();
-            this.rehashing = true;
+            cache.flush(bind("test", /^literal:/));
             let addon = config.addon;
             addon.userDisabled = true;
             addon.userDisabled = false;
@@ -1232,7 +1360,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     },
 
     errorCount: 0,
-    errors: Class.Memoize(function () []),
+    errors: Class.Memoize(() => []),
     maxErrors: 15,
     /**
      * Reports an error to the Error Console and the standard output,
@@ -1253,7 +1381,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
             let obj = update({}, error, {
                 toString: function () String(error),
-                stack: <>{util.stackLines(String(error.stack || Error().stack)).join("\n").replace(/^/mg, "\t")}</>
+                stack: Magic(util.stackLines(String(error.stack || Error().stack)).join("\n").replace(/^/mg, "\t"))
             });
 
             services.console.logStringMessage(obj.stack);
@@ -1298,7 +1426,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
         let ary = host.split(".");
         ary = [ary.slice(i).join(".") for (i in util.range(ary.length, 0, -1))];
-        return ary.filter(function (h) h.length >= base.length);
+        return ary.filter(h => h.length >= base.length);
     },
 
     /**
@@ -1307,7 +1435,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {Window} window
      * @returns {nsISelectionController}
      */
-    selectionController: function (win)
+    selectionController: function selectionController(win)
         win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
            .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay)
            .QueryInterface(Ci.nsISelectionController),
@@ -1326,7 +1454,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      *
      * @param {number} delay The time period for which to sleep in milliseconds.
      */
-    sleep: function (delay) {
+    sleep: function sleep(delay) {
         let mainThread = services.threading.mainThread;
 
         let end = Date.now() + delay;
@@ -1347,7 +1475,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {number} limit The maximum number of elements to return.
      * @returns {[string]}
      */
-    split: function (str, re, limit) {
+    split: function split(str, re, limit) {
         re.lastIndex = 0;
         if (!re.global)
             re = RegExp(re.source || re, "g");
@@ -1407,7 +1535,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      *      interrupted by pressing <C-c>, in which case,
      *      Error("Interrupted") will be thrown.
      */
-    threadYield: function (flush, interruptable) {
+    threadYield: function threadYield(flush, interruptable) {
         this.yielders++;
         try {
             let mainThread = services.threading.mainThread;
@@ -1429,7 +1557,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * Waits for the function *test* to return true, or *timeout*
      * milliseconds to expire.
      *
-     * @param {function} test The predicate on which to wait.
+     * @param {function|Promise} test The predicate on which to wait.
      * @param {object} self The 'this' object for *test*.
      * @param {Number} timeout The maximum number of milliseconds to
      *      wait.
@@ -1439,6 +1567,15 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      *      thrown.
      */
     waitFor: function waitFor(test, self, timeout, interruptable) {
+        if (!callable(test)) {
+            let done = false;
+            var promise = test,
+                retVal;
+            promise.then((arg) => { retVal = arg; done = true; },
+                         (arg) => { retVal = arg; done = true; });
+            test = () => done;
+        }
+
         let end = timeout && Date.now() + timeout, result;
 
         let timer = services.Timer(function () {}, 10, services.Timer.TYPE_REPEATING_SLACK);
@@ -1449,7 +1586,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         finally {
             timer.cancel();
         }
-        return result;
+        return promise ? retVal: result;
     },
 
     /**
@@ -1469,7 +1606,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @returns {function} A new function which may not execute
      *      synchronously.
      */
-    yieldable: function yieldable(func)
+    yieldable: deprecated("Task.spawn", function yieldable(func)
         function magic() {
             let gen = func.apply(this, arguments);
             (function next() {
@@ -1478,7 +1615,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 }
                 catch (e if e instanceof StopIteration) {};
             })();
-        },
+        }),
 
     /**
      * Wraps a callback function such that its errors are not lost. This
@@ -1509,11 +1646,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {function} func The function to call
      * @param {object} self The 'this' object for the function.
      */
-    trapErrors: function trapErrors(func, self) {
+    trapErrors: function trapErrors(func, self, ...args) {
         try {
             if (!callable(func))
                 func = self[func];
-            return func.apply(self || this, Array.slice(arguments, 2));
+            return func.apply(self || this, args);
         }
         catch (e) {
             this.reportError(e);
@@ -1546,8 +1683,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {nsIDOMWindow} win The window for which to find domains.
      * @returns {[string]} The visible domains.
      */
-    visibleHosts: function (win) {
-        let res = [], seen = {};
+    visibleHosts: function visibleHosts(win) {
+        let res = [],
+            seen = RealSet();
         (function rec(frame) {
             try {
                 if (frame.location.hostname)
@@ -1556,7 +1694,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             catch (e) {}
             Array.forEach(frame.frames, rec);
         })(win);
-        return res.filter(function (h) !Set.add(seen, h));
+        return res.filter(h => !seen.add(h));
     },
 
     /**
@@ -1566,8 +1704,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {nsIDOMWindow} win The window for which to find URIs.
      * @returns {[nsIURI]} The visible URIs.
      */
-    visibleURIs: function (win) {
-        let res = [], seen = {};
+    visibleURIs: function visibleURIs(win) {
+        let res = [],
+            seen = RealSet();
         (function rec(frame) {
             try {
                 res = res.concat(util.newURI(frame.location.href));
@@ -1575,7 +1714,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             catch (e) {}
             Array.forEach(frame.frames, rec);
         })(win);
-        return res.filter(function (h) !Set.add(seen, h.spec));
+        return res.filter(h => !seen.add(h.spec));
     },
 
     /**
@@ -1595,27 +1734,24 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {object} self The 'this' object of the method.
      * @param ... Arguments to pass to *meth*.
      */
-    withProperErrors: function withProperErrors(meth, self) {
+    withProperErrors: function withProperErrors(meth, self, ...args) {
         try {
-            return (callable(meth) ? meth : self[meth]).apply(self, Array.slice(arguments, withProperErrors.length));
+            return (callable(meth) ? meth : self[meth]).apply(self, args);
         }
         catch (e) {
             throw e.stack ? e : Error(e);
         }
-    },
-
-    xmlToDom: function () DOM.fromXML.apply(DOM, arguments)
+    }
 }, {
     Array: array
 });
 
-
 /**
  * Math utility methods.
  * @singleton
  */
 var GlobalMath = Math;
-var Math = update(Object.create(GlobalMath), {
+this.Math = update(Object.create(GlobalMath), {
     /**
      * Returns the specified *value* constrained to the range *min* - *max*.
      *
@@ -1631,4 +1767,4 @@ endModule();
 
 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
 
-// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: