]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/base.jsm
Import r6948 from upstream hg supporting Firefox up to 24.*
[dactyl.git] / common / modules / base.jsm
index 13bd9a03e93fcd3b88a5400ba38061e5b25bf08a..a9318eb414119a78cb118446827cf1f377a93379 100644 (file)
-// Copyright (c) 2009-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2009-2013 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";
 
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cr = Components.results;
-var Cu = Components.utils;
+var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 try {
     var ctypes;
-    Components.utils.import("resource://gre/modules/ctypes.jsm");
+    Cu.import("resource://gre/modules/ctypes.jsm");
 }
 catch (e) {}
 
 let objproto = Object.prototype;
-let { __lookupGetter__, __lookupSetter__, hasOwnProperty, propertyIsEnumerable } = objproto;
+let { __lookupGetter__, __lookupSetter__, __defineGetter__, __defineSetter__,
+      hasOwnProperty, propertyIsEnumerable } = objproto;
 
 if (typeof XPCSafeJSObjectWrapper === "undefined")
     this.XPCSafeJSObjectWrapper = XPCNativeWrapper;
 
-if (!XPCNativeWrapper.unwrap)
-    XPCNativeWrapper.unwrap = function unwrap(obj) {
-        if (hasOwnProperty.call(obj, "wrappedJSObject"))
-            return obj.wrappedJSObject;
-        return obj;
-    };
-if (!Object.create)
-    Object.create = function create(proto, props) {
-        let obj = { __proto__: proto };
-        for (let k in properties(props || {}))
-            Object.defineProperty(obj, k, props[k]);
-        return obj;
-    };
-if (!Object.defineProperty)
-    Object.defineProperty = function defineProperty(obj, prop, desc) {
-        let value = desc.value;
-        if ("value" in desc)
-            if (desc.writable && !__lookupGetter__.call(obj, prop)
-                              && !__lookupSetter__.call(obj, prop))
-                try {
-                    obj[prop] = value;
-                }
-                catch (e if e instanceof TypeError) {}
-            else {
-                objproto.__defineGetter__.call(obj, prop, function () value);
-                if (desc.writable)
-                    objproto.__defineSetter__.call(obj, prop, function (val) { value = val; });
-            }
+let getGlobalForObject = Cu.getGlobalForObject || function (obj) obj.__parent__;
 
-        if ("get" in desc)
-            objproto.__defineGetter__.call(obj, prop, desc.get);
-        if ("set" in desc)
-            objproto.__defineSetter__.call(obj, prop, desc.set);
-    };
-if (!Object.defineProperties)
-    Object.defineProperties = function defineProperties(obj, props) {
-        for (let [k, v] in Iterator(props))
-            Object.defineProperty(obj, k, v);
-    }
-if (!Object.freeze)
-    Object.freeze = function freeze(obj) {};
-if (!Object.getOwnPropertyDescriptor)
-    Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(obj, prop) {
-        if (!hasOwnProperty.call(obj, prop))
-            return undefined;
-        let desc = {
-            configurable: true,
-            enumerable: propertyIsEnumerable.call(obj, prop)
-        };
-        var get = __lookupGetter__.call(obj, prop),
-            set = __lookupSetter__.call(obj, prop);
-        if (!get && !set) {
-            desc.value = obj[prop];
-            desc.writable = true;
-        }
-        if (get)
-            desc.get = get;
-        if (set)
-            desc.set = set;
-        return desc;
-    };
-if (!Object.getOwnPropertyNames)
-    Object.getOwnPropertyNames = function getOwnPropertyNames(obj, _debugger) {
-        // This is an ugly and unfortunately necessary hack.
-        if (hasOwnProperty.call(obj, "__iterator__")) {
-            var oldIter = obj.__iterator__;
-            delete obj.__iterator__;
-        }
-        let res = [k for (k in obj) if (hasOwnProperty.call(obj, k))];
-        if (oldIter !== undefined) {
-            obj.__iterator__ = oldIter;
-            res.push("__iterator__");
-        }
-        return res;
-    };
-if (!Object.getPrototypeOf)
-    Object.getPrototypeOf = function getPrototypeOf(obj) obj.__proto__;
-if (!Object.keys)
-    Object.keys = function keys(obj)
-        Object.getOwnPropertyNames(obj).filter(function (k) propertyIsEnumerable.call(obj, k));
+function require(module, target) JSMLoader.load(module, target);
 
-let getGlobalForObject = Cu.getGlobalForObject || function (obj) obj.__parent__;
+function lazyRequire(module, names, target) {
+    for each (let name in names)
+        memoize(target || this, name, function (name) require(module)[name]);
+}
+
+let jsmodules = { lazyRequire: lazyRequire };
+jsmodules.jsmodules = jsmodules;
+
+function toString() "[module-global " + this.NAME + "]";
 
 let use = {};
 let loaded = {};
@@ -111,84 +40,85 @@ let currentModule;
 let global = this;
 function defineModule(name, params, module) {
     if (!module)
-        module = getGlobalForObject(params);
+        module = this;
 
     module.NAME = name;
     module.EXPORTED_SYMBOLS = params.exports || [];
-    defineModule.loadLog.push("defineModule " + name);
+    if (!~module.EXPORTED_SYMBOLS.indexOf("File"))
+        delete module.File;
+
+    defineModule.loadLog.push("[Begin " + name + "]");
+    defineModule.prefix += "  ";
+
     for (let [, mod] in Iterator(params.require || []))
-        require(module, mod);
+        require(mod, module);
 
-    for (let [, mod] in Iterator(params.use || []))
-        if (loaded.hasOwnProperty(mod))
-            require(module, mod, "use");
-        else {
-            use[mod] = use[mod] || [];
-            use[mod].push(module);
-        }
+    module._lastModule = currentModule;
     currentModule = module;
+    module.startTime = Date.now();
 }
 
 defineModule.loadLog = [];
 Object.defineProperty(defineModule.loadLog, "push", {
     value: function (val) {
-        if (false)
+        val = defineModule.prefix + val;
+        if (true)
             defineModule.dump(val + "\n");
         this[this.length] = Date.now() + " " + val;
     }
 });
-defineModule.dump = function dump_() {
-    let msg = Array.map(arguments, function (msg) {
+defineModule.prefix = "";
+defineModule.dump = function dump_(...args) {
+    let msg = args.map(function (msg) {
         if (loaded.util && typeof msg == "object")
             msg = util.objectToString(msg);
         return msg;
     }).join(", ");
-    let name = loaded.config ? config.name : "dactyl";
     dump(String.replace(msg, /\n?$/, "\n")
-               .replace(/^./gm, name + ": $&"));
+               .replace(/^./gm, JSMLoader.name + ": $&"));
 }
 defineModule.modules = [];
-defineModule.times = { all: 0 };
-defineModule.time = function time(major, minor, func, self) {
+defineModule.time = function time(major, minor, func, self, ...args) {
     let time = Date.now();
     if (typeof func !== "function")
         func = self[func];
 
     try {
-        var res = func.apply(self, Array.slice(arguments, 4));
+        var res = func.apply(self, args);
     }
     catch (e) {
         loaded.util && util.reportError(e);
     }
 
-    let delta = Date.now() - time;
-    defineModule.times.all += delta;
-    defineModule.times[major] = (defineModule.times[major] || 0) + delta;
-    if (minor) {
-        defineModule.times[":" + minor] = (defineModule.times[":" + minor] || 0) + delta;
-        defineModule.times[major + ":" + minor] = (defineModule.times[major + ":" + minor] || 0) + delta;
-    }
+    JSMLoader.times.add(major, minor, Date.now() - time);
     return res;
 }
 
 function endModule() {
-    defineModule.loadLog.push("endModule " + currentModule.NAME);
-
-    for (let [, mod] in Iterator(use[currentModule.NAME] || []))
-        require(mod, currentModule.NAME, "use");
+    defineModule.prefix = defineModule.prefix.slice(0, -2);
+    defineModule.loadLog.push("(End   " + currentModule.NAME + ")");
 
     loaded[currentModule.NAME] = 1;
+    require(currentModule.NAME, jsmodules);
+    currentModule = currentModule._lastModule;
 }
 
-function require(obj, name, from) {
+function require_(obj, name, from, targetName) {
     try {
         if (arguments.length === 1)
             [obj, name] = [{}, obj];
 
+        let caller = Components.stack.caller;
+
         if (!loaded[name])
-            defineModule.loadLog.push((from || "require") + ": loading " + name + (obj.NAME ? " into " + obj.NAME : ""));
+            defineModule.loadLog.push((from || "require") + ": loading " + name +
+                                      " into " + (targetName || obj.NAME || caller.filename + ":" + caller.lineNumber));
 
         JSMLoader.load(name + ".jsm", obj);
+
+        if (!loaded[name] && obj != jsmodules)
+            JSMLoader.load(name + ".jsm", jsmodules);
+
         return obj;
     }
     catch (e) {
@@ -196,30 +126,54 @@ function require(obj, name, from) {
         if (loaded.util)
             util.reportError(e);
         else
-            defineModule.dump("    " + (e.filename || e.fileName) + ":" + e.lineNumber + ": " + e +"\n");
+            defineModule.dump("    " + (e.filename || e.fileName) + ":" + e.lineNumber + ": " + e + "\n");
     }
 }
 
 defineModule("base", {
-    // sed -n 's/^(const|function) ([a-zA-Z0-9_]+).*/  "\2",/p' base.jsm | sort | fmt
+    // sed -n 's/^(const|var|function) ([a-zA-Z0-9_]+).*/      "\2",/p' base.jsm | sort | fmt
     exports: [
-        "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Module", "JSMLoader", "Object", "Runnable",
-        "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMUtils", "XPCSafeJSObjectWrapper",
-        "array", "bind", "call", "callable", "ctypes", "curry", "debuggerProperties", "defineModule",
-        "deprecated", "endModule", "forEach", "isArray", "isGenerator", "isinstance", "isObject",
-        "isString", "isSubclass", "iter", "iterAll", "iterOwnProperties","keys", "memoize", "octal",
-        "properties", "require", "set", "update", "values", "withCallerGlobal"
-    ],
-    use: ["config", "services", "util"]
-}, this);
-
-function Runnable(self, func, args) {
-    return {
-        __proto__: Runnable.prototype,
-        run: function () { func.apply(self, args || []); }
-    };
+        "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Finished", "Module", "JSMLoader",
+        "Set", "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMShim", "XPCOMUtils",
+        "XPCSafeJSObjectWrapper", "array", "bind", "call", "callable", "ctypes", "curry",
+        "debuggerProperties", "defineModule", "deprecated", "endModule", "forEach", "isArray",
+        "isGenerator", "isinstance", "isObject", "isString", "isSubclass", "isXML", "iter",
+        "iterAll", "iterOwnProperties", "keys", "literal", "memoize", "octal", "properties",
+        "require", "set", "update", "values", "update_"
+    ]
+});
+
+this.lazyRequire("cache", ["cache"]);
+this.lazyRequire("config", ["config"]);
+this.lazyRequire("messages", ["_", "Messages"]);
+this.lazyRequire("services", ["services"]);
+this.lazyRequire("storage", ["File"]);
+this.lazyRequire("util", ["FailedAssertion", "util"]);
+
+function literal(/* comment */) {
+    let { caller } = Components.stack;
+    while (caller && caller.language != 2)
+        caller = caller.caller;
+
+    let file = caller.filename.replace(/.* -> /, "");
+    let key = "literal:" + file + ":" + caller.line;
+
+    let source = File.readURL(file);
+
+    let match = RegExp("(?:.*\\n){" + (caller.lineNumber - 1) + "}" +
+                       ".*literal\\(/\\*([^]*?)\\*/\\)").exec(source);
+    return match[1];
+
+    // Later...
+    return cache.get(key, function () {
+        let source = cache.get("literal:" + file,
+                               function () util.httpGet(file).responseText);
+
+        let match = RegExp("(?:.*\\n){" + (caller.lineNumber - 1) + "}" +
+                           ".*literal\\(/\\*([^]*?)\\*/\\)").exec(source);
+        return match[1];
+    });
 }
-Runnable.prototype.QueryInterface = XPCOMUtils.generateQI([Ci.nsIRunnable]);
 
 /**
  * Returns a list of all of the top-level properties of an object, by
@@ -247,7 +201,6 @@ function debuggerProperties(obj) {
  * @returns {Generator}
  */
 function prototype(obj)
-    /* Temporary hack: */ typeof obj === "xml" || obj.__proto__ !== obj.__proto__ ? null :
     obj.__proto__ || Object.getPrototypeOf(obj) ||
     XPCNativeWrapper.unwrap(obj).__proto__ ||
     Object.getPrototypeOf(XPCNativeWrapper.unwrap(obj));
@@ -259,22 +212,54 @@ function properties(obj, prototypes, debugger_) {
     try {
         if ("dactylPropertyNames" in obj && !prototypes)
             for (let key in values(obj.dactylPropertyNames))
-                if (key in obj && !set.add(seen, key))
+                if (key in obj && !Set.add(seen, key))
                     yield key;
     }
     catch (e) {}
 
+    function props(obj) {
+        // Grr.
+        try {
+            return Object.getOwnPropertyNames(obj);
+        }
+        catch (e) {
+            if (e.result === Cr.NS_ERROR_FAILURE) {
+                // This is being thrown for PDF.js content windows,
+                // currently.
+                let filter = function filter(prop) {
+                    try {
+                        return prop in obj;
+                    }
+                    catch (e) {
+                        util.reportError("Filtering properties for " +
+                                         String.quote(obj) + ", " +
+                                         "error checking presence of " +
+                                         String.quote(prop) + ": " + e);
+                    }
+                    return false;
+                };
+                return array.uniq([k for (k in obj)].concat(
+                    Object.getOwnPropertyNames(
+                              XPCNativeWrapper.unwrap(obj))
+                          .filter(filter)));
+            }
+            else if (!e.stack) {
+                throw Error(e);
+            }
+        }
+    }
+
     for (; obj; obj = prototypes && prototype(obj)) {
         try {
-            if (sandbox.Object.getOwnPropertyNames || !debugger_ || !services.debugger.isOn)
-                var iter = values(Object.getOwnPropertyNames(obj));
+            if (!debugger_ || !services.debugger.isOn)
+                var iter = (v for each (v in props(obj)));
         }
         catch (e) {}
         if (!iter)
             iter = (prop.name.stringValue for (prop in values(debuggerProperties(obj))));
 
         for (let key in iter)
-            if (!prototypes || !set.add(seen, key) && obj != orig)
+            if (!prototypes || !Set.add(seen, key) && obj != orig)
                 yield key;
     }
 }
@@ -292,7 +277,8 @@ function deprecated(alternative, fn) {
     let name, func = callable(fn) ? fn : function () this[fn].apply(this, arguments);
 
     function deprecatedMethod() {
-        let obj = this.className             ? this.className + "#" :
+        let obj = !this                      ? "" :
+                  this.className             ? this.className + "#" :
                   this.constructor.className ? this.constructor.className + "#" :
                       "";
 
@@ -307,17 +293,16 @@ function deprecated(alternative, fn) {
 }
 deprecated.warn = function warn(func, name, alternative, frame) {
     if (!func.seenCaller)
-        func.seenCaller = set([
-            "resource://dactyl" + JSMLoader.suffix + "/javascript.jsm",
-            "resource://dactyl" + JSMLoader.suffix + "/util.jsm"
+        func.seenCaller = Set([
+            "resource://dactyl/javascript.jsm",
+            "resource://dactyl/util.jsm"
         ]);
 
     frame = frame || Components.stack.caller.caller;
     let filename = util.fixURI(frame.filename || "unknown");
-    if (!set.add(func.seenCaller, filename))
-        util.dactyl(func).warn(
-            util.urlPath(filename) + ":" + frame.lineNumber + ": " +
-            name + " is deprecated: Please use " + alternative + " instead");
+    if (!Set.add(func.seenCaller, filename))
+        util.dactyl(func).warn([util.urlPath(filename), frame.lineNumber, " "].join(":")
+                                   + _("warn.deprecated", name, alternative));
 }
 
 /**
@@ -332,6 +317,7 @@ function keys(obj) iter(function keys() {
         if (hasOwnProperty.call(obj, k))
             yield k;
 }());
+
 /**
  * Iterates over all of the top-level, iterable property values of an
  * object.
@@ -340,7 +326,7 @@ function keys(obj) iter(function keys() {
  * @returns {Generator}
  */
 function values(obj) iter(function values() {
-    if (isinstance(obj, ["Generator", "Iterator"]))
+    if (isinstance(obj, ["Generator", "Iterator", Iter]))
         for (let k in obj)
             yield k;
     else
@@ -359,7 +345,7 @@ var iterAll = deprecated("iter", function iterAll() iter.apply(null, arguments))
  * @param {[string]} ary @optional
  * @returns {object}
  */
-function set(ary) {
+function Set(ary) {
     let obj = {};
     if (ary)
         for (let val in values(ary))
@@ -374,7 +360,7 @@ function set(ary) {
  * @param {string} key The key to add.
  * @returns boolean
  */
-set.add = curry(function set_add(set, key) {
+Set.add = curry(function set_add(set, key) {
     let res = this.has(set, key);
     set[key] = true;
     return res;
@@ -386,7 +372,7 @@ set.add = curry(function set_add(set, key) {
  * @param {string} key The key to check.
  * @returns {boolean}
  */
-set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) &&
+Set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) &&
                                            propertyIsEnumerable.call(set, key));
 /**
  * Returns a new set containing the members of the first argument which
@@ -395,7 +381,7 @@ set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) &&
  * @param {object} set The set.
  * @returns {object}
  */
-set.subtract = function set_subtract(set) {
+Set.subtract = function set_subtract(set) {
     set = update({}, set);
     for (let i = 1; i < arguments.length; i++)
         for (let k in keys(arguments[i]))
@@ -410,12 +396,23 @@ set.subtract = function set_subtract(set) {
  * @param {string} key The key to remove.
  * @returns boolean
  */
-set.remove = curry(function set_remove(set, key) {
+Set.remove = curry(function set_remove(set, key) {
     let res = set.has(set, key);
     delete set[key];
     return res;
 });
 
+function set() {
+    deprecated.warn(set, "set", "Set");
+    return Set.apply(this, arguments);
+}
+Object.keys(Set).forEach(function (meth) {
+    set[meth] = function proxy() {
+        deprecated.warn(proxy, "set." + meth, "Set." + meth);
+        return Set[meth].apply(Set, arguments);
+    };
+});
+
 /**
  * Curries a function to the given number of arguments. Each
  * call of the resulting function returns a new function. When
@@ -444,18 +441,18 @@ function curry(fn, length, self, acc) {
         return fn;
 
     // Close over function with 'this'
-    function close(self, fn) function () fn.apply(self, Array.slice(arguments));
+    function close(self, fn) function () fn.apply(self, arguments);
 
     if (acc == null)
         acc = [];
 
-    return function curried() {
-        let args = acc.concat(Array.slice(arguments));
-
+    return function curried(...args) {
         // The curried result should preserve 'this'
-        if (arguments.length == 0)
+        if (args.length == 0)
             return close(self || this, curried);
 
+        let args = acc.concat(args);
+
         if (args.length >= length)
             return fn.apply(self || this, args);
 
@@ -464,11 +461,14 @@ function curry(fn, length, self, acc) {
 }
 
 if (curry.bind)
-    var bind = function bind(func) func.bind.apply(func, Array.slice(arguments, bind.length));
+    var bind = function bind(meth, self, ...args) let (func = callable(meth) ? meth : self[meth])
+        func.bind.apply(func, [self].concat(args));
 else
-    var bind = function bind(func, self) {
-        let args = Array.slice(arguments, bind.length);
-        return function bound() func.apply(self, args.concat(Array.slice(arguments)));
+    var bind = function bind(func, self, ...args) {
+        if (!callable(func))
+            func = self[func];
+
+        return function bound(...args2) func.apply(self, args.concat(args2));
     };
 
 /**
@@ -532,6 +532,12 @@ function isinstance(object, interfaces) {
  */
 function isObject(obj) typeof obj === "object" && obj != null || obj instanceof Ci.nsISupports;
 
+/**
+ * Returns true if obje is an E4X XML object.
+ * @deprecated
+ */
+function isXML(obj) typeof obj === "xml";
+
 /**
  * Returns true if and only if its sole argument is an
  * instance of the builtin Array type. The array may come from
@@ -565,10 +571,10 @@ function isString(val) objproto.toString.call(val) == "[object String]";
  * Returns true if and only if its sole argument may be called
  * as a function. This includes classes and function objects.
  */
-function callable(val) typeof val === "function";
+function callable(val) typeof val === "function" && !(val instanceof Ci.nsIDOMElement);
 
-function call(fn) {
-    fn.apply(arguments[1], Array.slice(arguments, 2));
+function call(fn, self, ...args) {
+    fn.apply(self, args);
     return fn;
 }
 
@@ -582,42 +588,41 @@ function call(fn) {
  */
 function memoize(obj, key, getter) {
     if (arguments.length == 1) {
-        obj = update({}, obj);
-        for (let prop in Object.getOwnPropertyNames(obj)) {
+        let res = update(Object.create(obj), obj);
+        for each (let prop in Object.getOwnPropertyNames(obj)) {
             let get = __lookupGetter__.call(obj, prop);
             if (get)
-                memoize(obj, prop, get);
+                memoize(res, prop, get);
         }
-        return obj;
+        return res;
     }
 
-    Object.defineProperty(obj, key, {
-        configurable: true,
-        enumerable: true,
+    try {
+        Object.defineProperty(obj, key, {
+            configurable: true,
+            enumerable: true,
 
-        get: function g_replaceProperty() (
-            Class.replaceProperty(this.instance || this, key, null),
-            Class.replaceProperty(this.instance || this, key, getter.call(this, key))),
+            get: function g_replaceProperty() {
+                try {
+                    Class.replaceProperty(this.instance || this, key, null);
+                    return Class.replaceProperty(this.instance || this, key, getter.call(this, key));
+                }
+                catch (e) {
+                    util.reportError(e);
+                }
+            },
 
-        set: function s_replaceProperty(val)
-            Class.replaceProperty(this.instance || this, key, val)
-    });
+            set: function s_replaceProperty(val)
+                Class.replaceProperty(this.instance || this, key, val)
+        });
+    }
+    catch (e) {
+        obj[key] = getter.call(obj, key);
+    }
 }
 
-let sandbox = Cu.Sandbox(this);
+let sandbox = Cu.Sandbox(Cc["@mozilla.org/systemprincipal;1"].createInstance());
 sandbox.__proto__ = this;
-/**
- * Wraps a function so that when called, the global object of the caller
- * is prepended to its arguments.
- */
-// Hack to get around lack of access to caller in strict mode.
-var withCallerGlobal = Cu.evalInSandbox(<![CDATA[
-    (function withCallerGlobal(fn)
-        function withCallerGlobal_wrapped()
-            fn.apply(this,
-                     [Class.objectGlobal(withCallerGlobal_wrapped.caller)]
-                        .concat(Array.slice(arguments))))
-]]>, Cu.Sandbox(this), "1.8");
 
 /**
  * Updates an object with the properties of another object. Getters
@@ -646,16 +651,45 @@ function update(target) {
             if (desc.value instanceof Class.Property)
                 desc = desc.value.init(k, target) || desc.value;
 
-            if (typeof desc.value === "function" && target.__proto__) {
-                let func = desc.value.wrapped || desc.value;
-                func.__defineGetter__("super", function () Object.getPrototypeOf(target)[k]);
-                func.superapply = function superapply(self, args)
-                    let (meth = Object.getPrototypeOf(target)[k])
-                        meth && meth.apply(self, args);
-                func.supercall = function supercall(self)
-                    func.superapply(self, Array.slice(arguments, 1));
+            try {
+                if (typeof desc.value === "function" && target.__proto__ && !(desc.value instanceof Ci.nsIDOMElement /* wtf? */)) {
+                    let func = desc.value.wrapped || desc.value;
+                    if (!func.superapply) {
+                        func.__defineGetter__("super", function get_super() Object.getPrototypeOf(target)[k]);
+                        func.superapply = function superapply(self, args)
+                            let (meth = Object.getPrototypeOf(target)[k])
+                                meth && meth.apply(self, args);
+                        func.supercall = function supercall(self, ...args)
+                            func.superapply(self, args);
+                    }
+                }
+                Object.defineProperty(target, k, desc);
             }
+            catch (e) {}
+        });
+    }
+    return target;
+}
+function update_(target) {
+    for (let i = 1; i < arguments.length; i++) {
+        let src = arguments[i];
+        Object.getOwnPropertyNames(src || {}).forEach(function (k) {
+            let desc = Object.getOwnPropertyDescriptor(src, k);
+            if (desc.value instanceof Class.Property)
+                desc = desc.value.init(k, target) || desc.value;
+
             try {
+                if (typeof desc.value === "function" && target.__proto__ && !(desc.value instanceof Ci.nsIDOMElement /* wtf? */)) {
+                    let func = desc.value.wrapped || desc.value;
+                    if (!func.superapply) {
+                        func.__defineGetter__("super", function get_super_() Object.getPrototypeOf(target)[k]);
+                        func.superapply = function super_apply(self, args)
+                            let (meth = Object.getPrototypeOf(target)[k])
+                                meth && meth.apply(self, args);
+                        func.supercall = function super_call(self, ...args)
+                            func.superapply(self, args);
+                    }
+                }
                 Object.defineProperty(target, k, desc);
             }
             catch (e) {}
@@ -687,27 +721,42 @@ function update(target) {
  *
  * @returns {function} The constructor for the resulting class.
  */
-function Class() {
+function Class(...args) {
 
-    var args = Array.slice(arguments);
     if (isString(args[0]))
         var name = args.shift();
     var superclass = Class;
     if (callable(args[0]))
         superclass = args.shift();
 
-    var Constructor = eval(String.replace(<![CDATA[
-        (function constructor(PARAMS) {
-            var self = Object.create(Constructor.prototype, {
-                constructor: { value: Constructor },
-            });
+    if (loaded.config && (config.haveGecko("5.*", "6.0") || config.haveGecko("6.*"))) // Bug 657418.
+        var Constructor = function Constructor() {
+            var self = Object.create(Constructor.prototype);
             self.instance = self;
+            self.globalInstance = self;
+
+            if ("_metaInit_" in self && self._metaInit_)
+                self._metaInit_.apply(self, arguments);
+
             var res = self.init.apply(self, arguments);
             return res !== undefined ? res : self;
-        })]]>,
-        "constructor", (name || superclass.className).replace(/\W/g, "_"))
-            .replace("PARAMS", /^function .*?\((.*?)\)/.exec(args[0] && args[0].init || Class.prototype.init)[1]
-                                                       .replace(/\b(self|res|Constructor)\b/g, "$1_")));
+        };
+    else
+        var Constructor = eval(String.replace('\n\
+            (function constructor(PARAMS) {                      \n\
+                var self = Object.create(Constructor.prototype); \n\
+                self.instance = self;                            \n\
+                self.globalInstance = self;                      \n\
+                                                                 \n\
+                if ("_metaInit_" in self && self._metaInit_)     \n\
+                    self._metaInit_.apply(self, arguments);      \n\
+                                                                 \n\
+                var res = self.init.apply(self, arguments);      \n\
+                return res !== undefined ? res : self;           \n\
+            })',
+            "constructor", (name || superclass.className).replace(/\W/g, "_"))
+                .replace("PARAMS", /^function .*?\((.*?)\)/.exec(args[0] && args[0].init || Class.prototype.init)[1]
+                                                           .replace(/\b(self|res|Constructor)\b/g, "$1_")));
 
     Constructor.className = name || superclass.className || superclass.name;
 
@@ -723,10 +772,12 @@ function Class() {
     }
 
     Class.extend(Constructor, superclass, args[0]);
+    memoize(Constructor, "closure", Class.makeClosure);
     update(Constructor, args[1]);
+
     Constructor.__proto__ = superclass;
-    args = args.slice(2);
-    Array.forEach(args, function (obj) {
+
+    args.slice(2).forEach(function (obj) {
         if (callable(obj))
             obj = obj.prototype;
         update(Constructor.prototype, obj);
@@ -735,19 +786,19 @@ function Class() {
 }
 
 if (Cu.getGlobalForObject)
-    Class.objectGlobal = function (caller) {
+    Class.objectGlobal = function (object) {
         try {
-            return Cu.getGlobalForObject(caller);
+            return Cu.getGlobalForObject(object);
         }
         catch (e) {
             return null;
         }
     };
 else
-    Class.objectGlobal = function (caller) {
-        while (caller.__parent__)
-            caller = caller.__parent__;
-        return caller;
+    Class.objectGlobal = function (object) {
+        while (object.__parent__)
+            object = object.__parent__;
+        return object;
     };
 
 /**
@@ -791,9 +842,9 @@ Class.extend = function extend(subclass, superclass, overrides) {
  *
  * @param {function(string)} getter The function which returns the
  *      property's value.
- * @return {Class.Property}
+ * @returns {Class.Property}
  */
-Class.memoize = function memoize(getter, wait)
+Class.Memoize = function Memoize(getter, wait)
     Class.Property({
         configurable: true,
         enumerable: true,
@@ -801,6 +852,7 @@ Class.memoize = function memoize(getter, wait)
             let done = false;
 
             if (wait)
+                // Crazy, yeah, I know. -- Kris
                 this.get = function replace() {
                     let obj = this.instance || this;
                     Object.defineProperty(obj, key,  {
@@ -825,13 +877,34 @@ Class.memoize = function memoize(getter, wait)
                     return this[key];
                 };
             else
-                this.get = function replace() {
+                this.get = function g_Memoize() {
                     let obj = this.instance || this;
-                    Class.replaceProperty(obj, key, null);
-                    return Class.replaceProperty(obj, key, getter.call(this, key));
+                    try {
+                        Class.replaceProperty(obj, key, null);
+                        return Class.replaceProperty(obj, key, getter.call(this, key));
+                    }
+                    catch (e) {
+                        util.reportError(e);
+                    }
                 };
 
-            this.set = function replace(val) Class.replaceProperty(this.instance || this, val);
+            this.set = function s_Memoize(val) Class.replaceProperty(this.instance || this, key, val);
+        }
+    });
+
+Class.memoize = deprecated("Class.Memoize", function memoize() Class.Memoize.apply(this, arguments));
+
+/**
+ * Updates the given object with the object in the target class's
+ * prototype.
+ */
+Class.Update = function Update(obj)
+    Class.Property({
+        configurable: true,
+        enumerable: true,
+        writable: true,
+        init: function (key, target) {
+            this.value = update({}, target[key], obj);
         }
     });
 
@@ -847,6 +920,9 @@ Class.prototype = {
      */
     init: function c_init() {},
 
+    get instance() ({}),
+    set instance(val) Class.replaceProperty(this, "instance", val),
+
     withSavedValues: function withSavedValues(names, callback, self) {
         let vals = names.map(function (name) this[name], this);
         try {
@@ -875,16 +951,71 @@ Class.prototype = {
      * @returns {nsITimer} The timer which backs this timeout.
      */
     timeout: function timeout(callback, timeout) {
-        const self = this;
-        function timeout_notify(timer) {
-            if (self.stale ||
+        let timeout_notify = (timer) => {
+            if (this.stale ||
                     util.rehashing && !isinstance(Cu.getGlobalForObject(callback), ["BackstagePass"]))
                 return;
-            util.trapErrors(callback, self);
+            this.timeouts.splice(this.timeouts.indexOf(timer), 1);
+            util.trapErrors(callback, this);
+        };
+        let timer = services.Timer(timeout_notify, timeout || 0, services.Timer.TYPE_ONE_SHOT);
+        this.timeouts.push(timer);
+        return timer;
+    },
+    timeouts: [],
+
+    /**
+     * Updates this instance with the properties of the given objects.
+     * Like the update function, but with special semantics for
+     * localized properties.
+     */
+    update: function update() {
+        // XXX: Duplication.
+
+        for (let i = 0; i < arguments.length; i++) {
+            let src = arguments[i];
+            Object.getOwnPropertyNames(src || {}).forEach((k) => {
+                let desc = Object.getOwnPropertyDescriptor(src, k);
+                if (desc.value instanceof Class.Property)
+                    desc = desc.value.init(k, this) || desc.value;
+
+                if (typeof desc.value === "function") {
+                    let func = desc.value.wrapped || desc.value;
+                    if (!func.superapply) {
+                        func.__defineGetter__("super", () => Object.getPrototypeOf(this)[k]);
+
+                        func.superapply = function superapply(self, args) {
+                            let meth = Object.getPrototypeOf(self)[k];
+                            return meth && meth.apply(self, args);
+                        };
+
+                        func.supercall = function supercall(self, ...args) {
+                            return func.superapply(self, args);
+                        }
+                    }
+                }
+
+                try {
+                    if ("value" in desc && (k in this.localizedProperties || k in this.magicalProperties))
+                        this[k] = desc.value;
+                    else
+                        Object.defineProperty(this, k, desc);
+                }
+                catch (e) {}
+            }, this);
         }
-        return services.Timer(timeout_notify, timeout || 0, services.Timer.TYPE_ONE_SHOT);
-    }
+        return this;
+    },
+
+    localizedProperties: {},
+    magicalProperties: {}
 };
+for (let name in properties(Class.prototype)) {
+    let desc = Object.getOwnPropertyDescriptor(Class.prototype, name);
+    desc.enumerable = false;
+    Object.defineProperty(Class.prototype, name, desc);
+}
+
 Class.makeClosure = function makeClosure() {
     const self = this;
     function closure(fn) {
@@ -928,16 +1059,35 @@ memoize(Class.prototype, "closure", Class.makeClosure);
 function XPCOM(interfaces, superClass) {
     interfaces = Array.concat(interfaces);
 
-    let shim = interfaces.reduce(function (shim, iface) shim.QueryInterface(iface),
-                                 Cc["@dactyl.googlecode.com/base/xpc-interface-shim"].createInstance());
+    let shim = XPCOMShim(interfaces);
+
+    let res = Class("XPCOM(" + interfaces + ")", superClass || Class,
+        update(iter([k,
+                     v === undefined || callable(v) ? stub : v]
+                     for ([k, v] in Iterator(shim))).toObject(),
+               { QueryInterface: XPCOMUtils.generateQI(interfaces) }));
 
-    let res = Class("XPCOM(" + interfaces + ")", superClass || Class, update(
-        iter.toObject([k, v === undefined || callable(v) ? function stub() null : v]
-                      for ([k, v] in Iterator(shim))),
-        { QueryInterface: XPCOMUtils.generateQI(interfaces) }));
-    shim = interfaces = null;
     return res;
 }
+function XPCOMShim(interfaces) {
+    let ip = services.InterfacePointer({
+        QueryInterface: function (iid) {
+            if (iid.equals(Ci.nsISecurityCheckedComponent))
+                throw Cr.NS_ERROR_NO_INTERFACE;
+            return this;
+        },
+        getHelperForLanguage: function () null,
+        getInterfaces: function (count) { count.value = 0; }
+    });
+    return (interfaces || []).reduce(function (shim, iface) shim.QueryInterface(Ci[iface]),
+                                     ip.data);
+};
+let stub = Class.Property({
+    configurable: true,
+    enumerable: false,
+    value: function stub() null,
+    writable: true
+});
 
 /**
  * An abstract base class for classes that wish to inherit from Error.
@@ -946,7 +1096,9 @@ var ErrorBase = Class("ErrorBase", Error, {
     level: 2,
     init: function EB_init(message, level) {
         level = level || 0;
-        update(this, Error(message))
+        let error = Error(message);
+        update(this, error);
+        this.stack = error.stack;
         this.message = message;
 
         let frame = Components.stack;
@@ -956,9 +1108,16 @@ var ErrorBase = Class("ErrorBase", Error, {
         }
         this.fileName = frame.filename;
         this.lineNumber = frame.lineNumber;
-    }
+    },
+    toString: function () String(this.message)
 });
 
+/**
+ * An Error subclass to throw in order to stop sourcing a plugin without
+ * printing a stack trace.
+ */
+var Finished = Class("Finished", ErrorBase);
+
 /**
  * Constructs a new Module class and instantiates an instance into the current
  * module global object.
@@ -968,18 +1127,33 @@ var ErrorBase = Class("ErrorBase", Error, {
  * @param {Object} classProperties Properties to be applied to the class constructor.
  * @returns {Class}
  */
-function Module(name, prototype) {
-    let init = callable(prototype) ? 4 : 3;
-    const module = Class.apply(Class, Array.slice(arguments, 0, init));
-    let instance = module();
-    module.className = name.toLowerCase();
-
-    instance.INIT = update(Object.create(Module.INIT),
-                           arguments[init] || {});
-
-    currentModule[module.className] = instance;
-    defineModule.modules.push(instance);
-    return module;
+function Module(name, prototype, ...args) {
+    try {
+        let init = callable(prototype) ? 2 : 1;
+        let proto = callable(prototype) ? args[0] : prototype;
+
+        proto._metaInit_ = function () {
+            delete module.prototype._metaInit_;
+            currentModule[name.toLowerCase()] = this;
+        };
+
+        const module = Class.apply(Class, [name, prototype, ...args.slice(0, init)]);
+        let instance = module();
+        module.className = name.toLowerCase();
+
+        instance.INIT = update(Object.create(Module.INIT),
+                               args[init] || {});
+
+        currentModule[module.className] = instance;
+        defineModule.modules.push(instance);
+        return module;
+    }
+    catch (e) {
+        if (typeof e === "string")
+            e = Error(e);
+
+        dump(e.fileName + ":" + e.lineNumber + ": " + e + "\n" + (e.stack || Error().stack));
+    }
 }
 Module.INIT = {
     init: function Module_INIT_init(dactyl, modules, window) {
@@ -998,7 +1172,7 @@ Module.INIT = {
             module.isLocalModule = true;
 
             modules.jsmodules[this.constructor.className] = module;
-            locals.reverse().forEach(function (fn, i) update(objs[i], fn.apply(module, args)))
+            locals.reverse().forEach(function (fn, i) update(objs[i], fn.apply(module, args)));
 
             memoize(module, "closure", Class.makeClosure);
             module.instance = module;
@@ -1025,13 +1199,9 @@ Module.INIT = {
  *
  * @returns {function} The constructor for the new Struct.
  */
-function Struct() {
-    if (!/^[A-Z]/.test(arguments[0]))
-        var args = Array.slice(arguments, 0);
-    else {
-        var className = arguments[0];
-        args = Array.slice(arguments, 1);
-    }
+function Struct(...args) {
+    if (/^[A-Z]/.test(args[0]))
+        var className = args.shift();
 
     const Struct = Class(className || "Struct", StructBase, {
         length: args.length,
@@ -1043,13 +1213,15 @@ function Struct() {
     });
     return Struct;
 }
-let StructBase = Class("StructBase", Array, {
+var StructBase = Class("StructBase", Array, {
     init: function struct_init() {
         for (let i = 0; i < arguments.length; i++)
             if (arguments[i] != undefined)
                 this[i] = arguments[i];
     },
 
+    get toStringParams() this,
+
     clone: function struct_clone() this.constructor.apply(null, this.slice()),
 
     closure: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "closure")),
@@ -1091,7 +1263,7 @@ let StructBase = Class("StructBase", Array, {
 
     localize: function localize(key, defaultValue) {
         let i = this.prototype.members[key];
-        Object.defineProperty(this.prototype, i, require("messages").Messages.Localized(defaultValue).init(key, this.prototype));
+        Object.defineProperty(this.prototype, i, Messages.Localized(defaultValue).init(key, this.prototype));
         return this;
     }
 });
@@ -1121,7 +1293,7 @@ var Timer = Class("Timer", {
         }
         catch (e) {
             if (typeof util === "undefined")
-                dump("dactyl: " + e + "\n" + (e.stack || Error().stack));
+                dump(JSMLoader.name + ": " + e + "\n" + (e.stack || Error().stack));
             else
                 util.reportError(e);
         }
@@ -1207,9 +1379,13 @@ function octal(decimal) parseInt(decimal, 8);
  * function.
  *
  * @param {object} obj
+ * @param {nsIJSIID} iface The interface to which to query all elements.
  * @returns {Generator}
  */
-function iter(obj) {
+function iter(obj, iface) {
+    if (arguments.length == 2 && iface instanceof Ci.nsIJSIID)
+        return iter(obj).map(function (item) item.QueryInterface(iface));
+
     let args = arguments;
     let res = Iterator(obj);
 
@@ -1236,7 +1412,8 @@ function iter(obj) {
     }
     else if (isinstance(obj, [Ci.nsIDOMHTMLCollection, Ci.nsIDOMNodeList]))
         res = array.iterItems(obj);
-    else if (obj instanceof Ci.nsIDOMNamedNodeMap)
+    else if (Ci.nsIDOMNamedNodeMap && obj instanceof Ci.nsIDOMNamedNodeMap ||
+             Ci.nsIDOMMozNamedAttrMap && obj instanceof Ci.nsIDOMMozNamedAttrMap)
         res = (function () {
             for (let i = 0; i < obj.length; i++)
                 yield [obj.name, obj];
@@ -1264,17 +1441,7 @@ function iter(obj) {
             return iter(obj.enumerator());
         return iter(obj.enumerator);
     }
-    res.__noSuchMethod__ = function __noSuchMethod__(meth, args) {
-        if (meth in iter)
-            var res = iter[meth].apply(iter, [this].concat(args));
-        else
-            res = let (ary = array(this))
-                ary[meth] ? ary[meth].apply(ary, args) : ary.__noSuchMethod__(meth, args);
-        if (isinstance(res, ["Iterator", "Generator"]))
-            return iter(res);
-        return res;
-    };
-    return res;
+    return Iter(res);
 }
 update(iter, {
     toArray: function toArray(iter) array(iter).array,
@@ -1368,7 +1535,7 @@ update(iter, {
     uniq: function uniq(iter) {
         let seen = {};
         for (let item in iter)
-            if (!set.add(seen, item))
+            if (!Set.add(seen, item))
                 yield item;
     },
 
@@ -1388,6 +1555,23 @@ update(iter, {
     }
 });
 
+const Iter = Class("Iter", {
+    init: function init(iter) {
+        this.iter = iter;
+        if ("__iterator__" in iter)
+            this.iter = iter.__iterator__();
+
+        if (this.iter.finalize)
+            this.finalize = function finalize() this.iter.finalize.apply(this.iter, arguments);
+    },
+
+    next: function next() this.iter.next(),
+
+    send: function send() this.iter.send.apply(this.iter, arguments),
+
+    __iterator__: function () this.iter
+});
+
 /**
  * Array utility methods.
  */
@@ -1411,9 +1595,9 @@ var array = Class("array", Array, {
             },
             array: ary,
             toString: function () this.array.toString(),
-            concat: function () this.__noSuchMethod__("concat", Array.slice(arguments)),
-            filter: function () this.__noSuchMethod__("filter", Array.slice(arguments)),
-            map: function () this.__noSuchMethod__("map", Array.slice(arguments))
+            concat: function (...args) this.__noSuchMethod__("concat", args),
+            filter: function (...args) this.__noSuchMethod__("filter", args),
+            map: function (...args) this.__noSuchMethod__("map", args)
         };
     }
 }, {
@@ -1423,7 +1607,7 @@ var array = Class("array", Array, {
      * as such:
      *    [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" }
      *
-     * @param {Array[]} assoc
+     * @param {[Array]} assoc
      * @... {string} 0 - Key
      * @...          1 - Value
      */
@@ -1543,8 +1727,42 @@ var array = Class("array", Array, {
     }
 });
 
+/* Make Minefield not explode, because Minefield exploding is not fun. */
+let iterProto = Iter.prototype;
+Object.keys(iter).forEach(function (k) {
+    iterProto[k] = function (...args) {
+        let res = iter[k].apply(iter, [this].concat(args));
+        if (isinstance(res, ["Iterator", "Generator"]))
+            return Iter(res);
+        return res;
+    };
+});
+
+Object.keys(array).forEach(function (k) {
+    if (!(k in iterProto))
+        iterProto[k] = function (...args) {
+            let res = array[k].apply(array, [this.toArray()].concat(args));
+            if (isinstance(res, ["Iterator", "Generator"]))
+                return Iter(res);
+            if (isArray(res))
+                return array(res);
+            return res;
+        };
+});
+
+Object.getOwnPropertyNames(Array.prototype).forEach(function (k) {
+    if (!(k in iterProto) && callable(Array.prototype[k]))
+        iterProto[k] = function () {
+            let ary = iter(this).toArray();
+            let res = ary[k].apply(ary, arguments);
+            if (isArray(res))
+                return array(res);
+            return res;
+        };
+});
+
 endModule();
 
 // catch(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: