diff --git a/common/content/base.js b/common/content/base.js index 686009be..7f34dddc 100644 --- a/common/content/base.js +++ b/common/content/base.js @@ -134,18 +134,41 @@ function isobject(obj) { return typeof obj === "object" && obj != null; } -function isarray(obj) { - return Object.prototype.toString.call(obj) == "[object Array]"; +/** + * Returns true if and only if its sole argument is an + * instance of the builtin Array type. The array may come from + * any window, frame, namespace, or execution context, which + * is not the case when using (obj instanceof Array). + */ +function isarray(val) { + return Object.prototype.toString.call(val) == "[object Array]"; } +/** + * Returns true if and only if its sole argument is an + * instance of the builtin Generator type. This includes + * functions containing the 'yield' statement and generator + * statements such as (x for (x in obj)). + */ function isgenerator(val) { return Object.prototype.toString.call(val) == "[object Generator]"; } +/** + * Returns true if and only if its sole argument is a String, + * as defined by the builtin type. May be constructed via + * String(foo) or new String(foo) from any window, frame, + * namespace, or execution context, which is not the case when + * using (obj instanceof String) or (typeof obj == "string"). + */ function isstring(val) { - return typeof val === "string" || val instanceof String; + return Object.prototype.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) { return typeof val === "function"; } @@ -155,7 +178,28 @@ function call(fn) { return fn; } -function curry(fn, length, acc) { +/** + * Curries a function to the given number of arguments. Each + * call of the resulting function returns a new function. When + * a call does not contain enough arguments to satisfy the + * required number, the resulting function is another curried + * function with previous arguments accumulated. + * + * function foo(a, b, c) [a, b, c].join(" "); + * curry(foo)(1, 2, 3) -> "1 2 3"; + * curry(foo)(4)(5, 6) -> "4 5 6"; + * curry(foo)(4)(8)(9) -> "7 8 9"; + * + * @param {function} fn The function to curry. + * @param {integer} length The number of arguments expected. + * @default fn.length + * @optional + * @param {object} self The 'this' value for the returned + * function. When omitted, the value of 'this' from the + * first call to the function is preserved. + * @optional + */ +function curry(fn, length, self, acc) { if (length == null) length = fn.length; if (length == 0) @@ -164,7 +208,6 @@ function curry(fn, length, acc) { /* Close over function with 'this' */ function close(self, fn) function () fn.apply(self, Array.slice(arguments)); - let first = (arguments.length < 3); if (acc == null) acc = []; @@ -173,18 +216,35 @@ function curry(fn, length, acc) { /* The curried result should preserve 'this' */ if (arguments.length == 0) - return close(this, arguments.callee); + return close(self || this, arguments.callee); if (args.length >= length) - return fn.apply(this, args); + return fn.apply(self || this, args); - if (first) - fn = close(this, fn); - return curry(fn, length, args); + return curry(fn, length, self || this, args); } } -function update(targ) { +/** + * Updates an object with the properties of another object. Getters + * and setters are copied as expected. Moreover, any function + * properties receive new 'supercall' and 'superapply' properties, + * which will call the identically named function in target's + * prototype. + * + * let a = { foo: function (arg) "bar " + arg } + * let b = { __proto__: a } + * update(b, { foo: function () arguments.callee.supercall(this, "baz") }); + * + * a.foo("foo") -> "bar foo" + * b.foo() -> "bar baz" + * + * @param {Object} target The object to update. + * @param {Object} src The source object from which to update target. + * May be provided multiple times. + * @returns {Object} Returns its updated first argument. + */ +function update(target) { for (let i=1; i < arguments.length; i++) { let src = arguments[i]; foreach(keys(src || {}), function(k) { @@ -192,10 +252,10 @@ function update(targ) { set = src.__lookupSetter__(k); if (!get && !set) { var v = src[k]; - targ[k] = v; - if (targ.__proto__ && callable(v)) { + target[k] = v; + if (target.__proto__ && callable(v)) { v.superapply = function(self, args) { - return targ.__proto__[k].apply(self, args); + return target.__proto__[k].apply(self, args); } v.supercall = function(self) { return v.superapply(self, Array.slice(arguments, 1)); @@ -203,28 +263,64 @@ function update(targ) { } } if (get) - targ.__defineGetter__(k, get); + target.__defineGetter__(k, get); if (set) - targ.__defineSetter__(k, set); + target.__defineSetter__(k, set); }); } - return targ; + return target; } -function extend(subc, superc, overrides) { - subc.prototype = {}; - update(subc.prototype, overrides); - // This is unduly expensive. - subc.prototype.__proto__ = superc.prototype; +/** + * Extends a subclass with a superclass. The subclass's + * prototype is replaced with a new object, which inherits + * from the super class's prototype, {@see update}d with the + * members of 'overrides'. + * + * @param {function} subclass + * @param {function} superclass + * @param {Object} overrides @optional + */ +function extend(subclass, superclass, overrides) { + subclass.prototype = {}; + update(subclass.prototype, overrides); + // This is unduly expensive. Unfortunately necessary since + // we apparently can't rely on the presence of the + // debugger to enumerate properties when we have + // __iterators__ attached to prototypes. + subclass.prototype.__proto__ = superclass.prototype; - subc.superclass = superc.prototype; - subc.prototype.constructor = subc; - subc.prototype.__class__ = subc; + subclass.superclass = superclass.prototype; + subclass.prototype.constructor = subclass; + subclass.prototype.__class__ = subclass; - if (superc.prototype.constructor === Object.prototype.constructor) - superc.prototype.constructor = superc; + if (superclass.prototype.constructor === Object.prototype.constructor) + superclass.prototype.constructor = superclass; } +/** + * @constructor Class + * + * Constructs a new Class. Arguments marked as optional must be + * either entirely elided, or they must have the exact type + * specified. + * + * @param {string} name The class's as it will appear when toString + * is called, as well as in stack traces. + * @optional + * @param {function} base The base class for this module. May be any + * callable object. + * @optional + * @default Class + * @param {Object} prototype The prototype for instances of this + * object. The object itself is copied and not used as a + * prototype directly. + * @param {Object} classProperties The class properties for the new + * module constructor. More than one may be provided. + * @optional + * + * @returns {function} The constructor for the resulting class. + */ function Class() { function constructor() { let self = { @@ -274,10 +370,24 @@ function Class() { } Class.toString = function () "[class " + this.constructor.name + "]", Class.prototype = { + /** + * Initializes new instances of this class. Called automatically + * when new instances are created. + */ init: function() {}, toString: function () "[instance " + this.constructor.name + "]", + /** + * Exactly like {@see nsIDOMWindow#setTimeout}, except that it + * preserves the value of 'this' on invocation of 'callback'. + * + * @param {function} callback The function to call after 'timeout' + * @param {number} timeout The timeout, in seconds, to wait + * before calling 'callback'. + * @returns {integer} The ID of this timeout, to be passed to + * {@see nsIDOMWindow#clearTimeout}. + */ setTimeout: function (callback, timeout) { const self = this; function target() callback.call(self); @@ -285,9 +395,24 @@ Class.prototype = { } }; +/** + * @class Struct + * + * Creates a new Struct constructor, used for creating objects with + * a fixed set of named members. Each argument should be the name of + * a member in the resulting objects. These names will correspond to + * the arguments passed to the resultant constructor. Instances of + * the new struct may be treated vary much like arrays, and provide + * many of the same methods. + * + * const Point = Struct("x", "y", "z"); + * let p1 = Point(x, y, z); + * + * @returns {function} The constructor for the new Struct. + */ const Struct = Class("Struct", { init: function () { - let args = Array.slice(arguments); + let args = this._args = Array.slice(arguments); this.__defineGetter__("length", function () args.length); this.__defineGetter__("members", function () args.slice()); for (let arg in Iterator(args)) { @@ -305,6 +430,16 @@ const Struct = Class("Struct", { return self; } Struct.prototype = this; + /** + * Sets a lazily constructed default value for a member of + * the struct. The value is constructed once, the first time + * it is accesed and memoized thereafter. + * + * @param {string} key The name of the member for which to + * provide the default value. + * @param {function} val The function which is to generate + * the default value. + */ Struct.defaultValue = function (key, val) { let i = args.indexOf(key); Struct.prototype.__defineGetter__(i, function () (this[i] = val.call(this), this[i])); // Kludge for FF 3.0 diff --git a/common/content/modules.js b/common/content/modules.js index d3e661d9..ef2940fe 100644 --- a/common/content/modules.js +++ b/common/content/modules.js @@ -3,12 +3,60 @@ // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. -const ModuleBase = Class("ModuleBase", { requires: [] }); -function Module(name, inst, clas, moduleInit) { +/** + * @class ModuleBase + * The base class for all modules. + */ +const ModuleBase = Class("ModuleBase", { + /** + * @property {[string]} A list of module prerequisites which + * must be initialized before this module is loaded. + */ + requires: [], + + toString: function () "[module " + this.constructor.name + "]", +}); + +/** + * @constructor Module + * + * Constructs a new ModuleBase class and makes arrangements for its + * initialization. Arguments marked as optional must be either + * entirely elided, or they must have the exact type specified. + * Loading semantics are as follows: + * + * - A module is garunteed not to be initialized before any of its + * prerequisites as listed in its {@see ModuleBase#requires} member. + * - A module is considered initialized once its been instantiated, + * it's {@see Class#init} method has been called, and its + * instance has been installed into the top-level {@see modules} + * object. + * - Once the module has been initialized, its module-dependent + * initialization functions will be called as described hereafter. + * @param {string} name The module's name as it will appear in the + * top-level {@see modules} object. + * @param {ModuleBase} base The base class for this module. + * @optional + * @param {Object} prototype The prototype for instances of this + * object. The object itself is copied and not used as a + * prototype directly. + * @param {Object} classProperties The class properties for the new + * module constructor. + * @optional + * @param {Object} moduleInit The module initialization functions + * for the new module. Each function is called as soon as the + * named module has been initialized, but after the module + * itself. The constructors are garunteed to be called in the + * same order that the dependent modules were initialized. + * @optional + * + * @returns {function} The constructor for the resulting module. + */ +function Module(name, prototype, classProperties, moduleInit) { var base = ModuleBase; - if (callable(inst)) + if (callable(prototype)) base = Array.splice(arguments, 1, 1)[0] - const module = Class(name, base, inst, clas); + const module = Class(name, base, prototype, classProperties); module.INIT = moduleInit || {}; module.requires = inst.requires || []; Module.list.push(module);