]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/util.jsm
Import r6923 from upstream hg supporting Firefox up to 22.0a1
[dactyl.git] / common / modules / util.jsm
index c5cd610fbcf8ecf6fabc31dea584a43331857e59..0e83efe7a15a6ea7e81fb1111181360e06d296c0 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-2012 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);
+});
+
+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);
@@ -76,6 +89,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     parseForm: deprecated("DOM#formData", function parseForm(elem) values(DOM(elem).formData).toArray()),
     scrollIntoView: deprecated("DOM#scrollIntoView", function scrollIntoView(elem, alignWithTop) DOM(elem).scrollIntoView(alignWithTop)),
     validateMatcher: deprecated("DOM.validateMatcher", { get: function validateMatcher() DOM.validateMatcher }),
+    xmlToDom: deprecated("DOM.fromJSON", function xmlToDom() DOM.fromXML.apply(DOM, arguments)),
 
     map: deprecated("iter.map", function map(obj, fn, self) iter(obj).map(fn, self).toArray()),
     writeToClipboard: deprecated("dactyl.clipboardWrite", function writeToClipboard(str, verbose) util.dactyl.clipboardWrite(str, verbose)),
@@ -90,7 +104,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 +118,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)
     }),
 
     /**
@@ -159,7 +173,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;
@@ -252,7 +266,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             {
                 elements: [],
                 seen: {},
-                valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj))
+                valid: function valid(obj) this.elements.every(function (e) !e.test || e.test(obj))
             });
 
         let end = 0;
@@ -282,7 +296,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
                 stack.top.elements.push(update(
                     function (obj) obj[char] != null ? quote(obj, char) : "",
-                    { test: function (obj) obj[char] != null }));
+                    { test: function test(obj) obj[char] != null }));
 
                 for (let elem in array.iterValues(stack))
                     elem.seen[char] = true;
@@ -335,19 +349,19 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             {
                 elements: [],
                 seen: {},
-                valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj))
+                valid: function valid(obj) this.elements.every(function (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,7 +377,7 @@ 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);
@@ -385,9 +399,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                             function (obj) obj[name] != null && idx in obj[name] ? quote(obj[name][idx])
                                                                                  : Set.has(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 {
@@ -395,9 +409,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                             function (obj) obj[name] != null ? quote(obj[name])
                                                              : Set.has(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] != "")
                             }));
                     }
 
@@ -505,6 +519,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.
@@ -542,7 +567,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))
@@ -674,7 +699,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;
         }
@@ -795,7 +820,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),
@@ -863,7 +888,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     },
 
     // ripped from Firefox; modified
-    unsafeURI: Class.Memoize(function () util.regexp(String.replace(<![CDATA[
+    unsafeURI: Class.Memoize(function () util.regexp(String.replace(literal(/*
             [
                 \s
                 // Invisible characters (bug 452979)
@@ -877,7 +902,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 +926,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,
+                                     function (m) map[m]);
+            }
+
+            return iter(obj).map(function ([k, v])
+                                 ["<!ENTITY ", k, " '", escape(v), "'>"].join(""))
+                            .join("\n");
+        },
 
     /**
      * Converts a URI string into a URI object.
@@ -943,13 +977,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,8 +997,14 @@ 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",
+                                           function () ["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 = [];
 
@@ -987,7 +1020,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 keyIter = keys(object)
 
             for (let i in keyIter) {
-                let value = <![CDATA[<no value>]]>;
+                let value = Magic("<no value>");
                 try {
                     value = object[i];
                 }
@@ -1001,13 +1034,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 +1066,75 @@ 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), function (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)) {
+                if (~seen.indexOf(data))
+                    throw Error("Recursive object passed");
+                seen = seen.concat([data]);
+            }
+
+            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, "", []);
+        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))
@@ -1038,7 +1148,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             if (!this.rehashing)
                 services.observer.addObserver(this, "dactyl-rehash", true);
         },
-        "dactyl-rehash": function () {
+        "dactyl-rehash": function dactylRehash() {
             services.observer.removeObserver(this, "dactyl-rehash");
 
             defineModule.loadLog.push("dactyl: util: observe: dactyl-rehash");
@@ -1051,7 +1161,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                         module.init();
                 }
         },
-        "dactyl-purge": function () {
+        "dactyl-purge": function dactylPurge() {
             this.rehashing = 1;
         },
     },
@@ -1135,7 +1245,11 @@ 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,
+                                  function (m, n1, n2) !n1 && Set.has(tokens, n2) ?    tokens[n2].dactylSource
+                                                                                    || tokens[n2].source
+                                                                                    || tokens[n2]
+                                                                                  : m);
 
         // Strip comments and white space.
         if (/x/.test(flags))
@@ -1157,7 +1271,7 @@ 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")),
             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
@@ -1220,8 +1334,8 @@ 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;
@@ -1253,7 +1367,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);
@@ -1307,7 +1421,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 +1440,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 +1461,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 +1521,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;
@@ -1546,7 +1660,7 @@ 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) {
+    visibleHosts: function visibleHosts(win) {
         let res = [], seen = {};
         (function rec(frame) {
             try {
@@ -1566,7 +1680,7 @@ 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) {
+    visibleURIs: function visibleURIs(win) {
         let res = [], seen = {};
         (function rec(frame) {
             try {
@@ -1602,9 +1716,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         catch (e) {
             throw e.stack ? e : Error(e);
         }
-    },
-
-    xmlToDom: function () DOM.fromXML.apply(DOM, arguments)
+    }
 }, {
     Array: array
 });
@@ -1615,7 +1727,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
  * @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*.
      *