]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/base.jsm
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / modules / base.jsm
index d07ba1230732b78e0f69eb944b84e8017c91f3fc..bc767f68887cf45147ac8e9787c6a21b5c2e33d0 100644 (file)
@@ -2,22 +2,21 @@
 //
 // 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 */
 
-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://dactyl/bootstrap.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 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;
@@ -47,15 +46,15 @@ if (!Object.defineProperty)
                     }
                     catch (e if e instanceof TypeError) {}
                 else {
-                    objproto.__defineGetter__.call(obj, prop, function () value);
+                    __defineGetter__.call(obj, prop, function () value);
                     if (desc.writable)
-                        objproto.__defineSetter__.call(obj, prop, function (val) { value = val; });
+                        __defineSetter__.call(obj, prop, function (val) { value = val; });
                 }
 
             if ("get" in desc)
-                objproto.__defineGetter__.call(obj, prop, desc.get);
+                __defineGetter__.call(obj, prop, desc.get);
             if ("set" in desc)
-                objproto.__defineSetter__.call(obj, prop, desc.set);
+                __defineSetter__.call(obj, prop, desc.set);
         }
         catch (e) {
             throw e.stack ? e : Error(e);
@@ -123,6 +122,14 @@ if (!Object.keys)
 
 let getGlobalForObject = Cu.getGlobalForObject || function (obj) obj.__parent__;
 
+let jsmodules = {
+    lazyRequire: function lazyRequire(module, names, target) {
+        for each (let name in names)
+            memoize(target || this, name, function (name) require(module)[name]);
+    }
+};
+jsmodules.jsmodules = jsmodules;
+
 let use = {};
 let loaded = {};
 let currentModule;
@@ -133,17 +140,17 @@ function defineModule(name, params, module) {
 
     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(module, mod, null, name);
+    module.__proto__ = jsmodules;
 
-    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();
 }
@@ -151,20 +158,21 @@ function defineModule(name, params, module) {
 defineModule.loadLog = [];
 Object.defineProperty(defineModule.loadLog, "push", {
     value: function (val) {
+        val = defineModule.prefix + val;
         if (false)
             defineModule.dump(val + "\n");
         this[this.length] = Date.now() + " " + val;
     }
 });
+defineModule.prefix = "";
 defineModule.dump = function dump_() {
     let msg = Array.map(arguments, 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.time = function time(major, minor, func, self) {
@@ -184,15 +192,15 @@ defineModule.time = function time(major, minor, func, self) {
 }
 
 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(jsmodules, currentModule.NAME);
+    currentModule = currentModule._lastModule;
 }
 
-function require(obj, name, from) {
+function require(obj, name, from, targetName) {
     try {
         if (arguments.length === 1)
             [obj, name] = [{}, obj];
@@ -200,9 +208,14 @@ function require(obj, name, from) {
         let caller = Components.stack.caller;
 
         if (!loaded[name])
-            defineModule.loadLog.push((from || "require") + ": loading " + name + " into " + (obj.NAME || caller.filename + ":" + caller.lineNumber));
+            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) {
@@ -215,25 +228,20 @@ function require(obj, name, from) {
 }
 
 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",
-        "Set", "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"]
+        "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Module", "JSMLoader", "Object",
+        "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", "iter", "iterAll",
+        "iterOwnProperties", "keys", "memoize", "octal", "properties", "require", "set", "update",
+        "values", "withCallerGlobal"
+    ]
 }, this);
 
-function Runnable(self, func, args) {
-    return {
-        __proto__: Runnable.prototype,
-        run: function () { func.apply(self, args || []); }
-    };
-}
-Runnable.prototype.QueryInterface = XPCOMUtils.generateQI([Ci.nsIRunnable]);
+this.lazyRequire("messages", ["_", "Messages"]);
+this.lazyRequire("util", ["util"]);
 
 /**
  * Returns a list of all of the top-level properties of an object, by
@@ -331,7 +339,7 @@ deprecated.warn = function warn(func, name, alternative, frame) {
     let filename = util.fixURI(frame.filename || "unknown");
     if (!Set.add(func.seenCaller, filename))
         util.dactyl(func).warn([util.urlPath(filename), frame.lineNumber, " "].join(":")
-                                   + require("messages")._("warn.deprecated", name, alternative));
+                                   + _("warn.deprecated", name, alternative));
 }
 
 /**
@@ -346,6 +354,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.
@@ -594,7 +603,7 @@ 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));
@@ -611,13 +620,13 @@ function call(fn) {
  */
 function memoize(obj, key, getter) {
     if (arguments.length == 1) {
-        obj = update({ __proto__: obj.__proto__ }, 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;
     }
 
     try {
@@ -625,9 +634,15 @@ function memoize(obj, key, getter) {
             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)
@@ -680,18 +695,18 @@ 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;
-                if (!func.superapply) {
-                    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 () 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));
+                    }
+                }
                 Object.defineProperty(target, k, desc);
             }
             catch (e) {}
@@ -732,22 +747,26 @@ function Class() {
     if (callable(args[0]))
         superclass = args.shift();
 
-    if (loaded.util && util.haveGecko("6.0a1")) // Bug 657418.
+    if (loaded.config && (config.haveGecko("5.*", "6.0") || config.haveGecko("6.*"))) // Bug 657418.
         var Constructor = function Constructor() {
-            var self = Object.create(Constructor.prototype, {
-                constructor: { value: Constructor },
-            });
+            var self = Object.create(Constructor.prototype);
             self.instance = self;
+
+            if ("_metaInit_" in self && self._metaInit_)
+                self._metaInit_.apply(self, arguments);
+
             var res = self.init.apply(self, arguments);
             return res !== undefined ? res : self;
         };
     else
         var Constructor = eval(String.replace(<![CDATA[
             (function constructor(PARAMS) {
-                var self = Object.create(Constructor.prototype, {
-                    constructor: { value: Constructor },
-                });
+                var self = Object.create(Constructor.prototype);
                 self.instance = self;
+
+                if ("_metaInit_" in self && self._metaInit_)
+                    self._metaInit_.apply(self, arguments);
+
                 var res = self.init.apply(self, arguments);
                 return res !== undefined ? res : self;
             })]]>,
@@ -769,10 +788,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);
@@ -839,7 +860,7 @@ Class.extend = function extend(subclass, superclass, overrides) {
  *      property's value.
  * @returns {Class.Property}
  */
-Class.memoize = function memoize(getter, wait)
+Class.Memoize = function Memoize(getter, wait)
     Class.Property({
         configurable: true,
         enumerable: true,
@@ -847,6 +868,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,  {
@@ -871,13 +893,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);
         }
     });
 
@@ -893,6 +936,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 {
@@ -926,10 +972,14 @@ Class.prototype = {
             if (self.stale ||
                     util.rehashing && !isinstance(Cu.getGlobalForObject(callback), ["BackstagePass"]))
                 return;
+            self.timeouts.splice(self.timeouts.indexOf(timer), 1);
             util.trapErrors(callback, self);
         }
-        return services.Timer(timeout_notify, timeout || 0, services.Timer.TYPE_ONE_SHOT);
+        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.
@@ -968,10 +1018,18 @@ Class.prototype = {
                 catch (e) {}
             }, this);
         }
+        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) {
@@ -1015,16 +1073,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.
@@ -1059,17 +1136,32 @@ var ErrorBase = Class("ErrorBase", Error, {
  * @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();
+    try {
+        let init = callable(prototype) ? 4 : 3;
+        let proto = arguments[callable(prototype) ? 2 : 1];
 
-    instance.INIT = update(Object.create(Module.INIT),
-                           arguments[init] || {});
+        proto._metaInit_ = function () {
+            delete module.prototype._metaInit_;
+            currentModule[name.toLowerCase()] = this;
+        };
 
-    currentModule[module.className] = instance;
-    defineModule.modules.push(instance);
-    return module;
+        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;
+    }
+    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) {
@@ -1133,13 +1225,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")),
@@ -1181,7 +1275,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;
     }
 });
@@ -1211,7 +1305,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);
         }
@@ -1297,9 +1391,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);