summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/method/core.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/method/core.js')
-rw-r--r--toolkit/jetpack/method/core.js225
1 files changed, 225 insertions, 0 deletions
diff --git a/toolkit/jetpack/method/core.js b/toolkit/jetpack/method/core.js
new file mode 100644
index 000000000..a6a5261e6
--- /dev/null
+++ b/toolkit/jetpack/method/core.js
@@ -0,0 +1,225 @@
+"use strict";
+
+var defineProperty = Object.defineProperty || function(object, name, property) {
+ object[name] = property.value
+ return object
+}
+
+// Shortcut for `Object.prototype.toString` for faster access.
+var typefy = Object.prototype.toString
+
+// Map to for jumping from typeof(value) to associated type prefix used
+// as a hash in the map of builtin implementations.
+var types = { "function": "Object", "object": "Object" }
+
+// Array is used to save method implementations for the host objects in order
+// to avoid extending them with non-primitive values that could cause leaks.
+var host = []
+// Hash map is used to save method implementations for builtin types in order
+// to avoid extending their prototypes. This also allows to share method
+// implementations for types across diff contexts / frames / compartments.
+var builtin = {}
+
+function Primitive() {}
+function ObjectType() {}
+ObjectType.prototype = new Primitive()
+function ErrorType() {}
+ErrorType.prototype = new ObjectType()
+
+var Default = builtin.Default = Primitive.prototype
+var Null = builtin.Null = new Primitive()
+var Void = builtin.Void = new Primitive()
+builtin.String = new Primitive()
+builtin.Number = new Primitive()
+builtin.Boolean = new Primitive()
+
+builtin.Object = ObjectType.prototype
+builtin.Error = ErrorType.prototype
+
+builtin.EvalError = new ErrorType()
+builtin.InternalError = new ErrorType()
+builtin.RangeError = new ErrorType()
+builtin.ReferenceError = new ErrorType()
+builtin.StopIteration = new ErrorType()
+builtin.SyntaxError = new ErrorType()
+builtin.TypeError = new ErrorType()
+builtin.URIError = new ErrorType()
+
+
+function Method(hint) {
+ /**
+ Private Method is a callable private name that dispatches on the first
+ arguments same named Method:
+
+ method(object, ...rest) => object[method](...rest)
+
+ Optionally hint string may be provided that will be used in generated names
+ to ease debugging.
+
+ ## Example
+
+ var foo = Method()
+
+ // Implementation for any types
+ foo.define(function(value, arg1, arg2) {
+ // ...
+ })
+
+ // Implementation for a specific type
+ foo.define(BarType, function(bar, arg1, arg2) {
+ // ...
+ })
+ **/
+
+ // Create an internal unique name if `hint` is provided it is used to
+ // prefix name to ease debugging.
+ var name = (hint || "") + "#" + Math.random().toString(32).substr(2)
+
+ function dispatch(value) {
+ // Method dispatches on type of the first argument.
+ // If first argument is `null` or `void` associated implementation is
+ // looked up in the `builtin` hash where implementations for built-ins
+ // are stored.
+ var type = null
+ var method = value === null ? Null[name] :
+ value === void(0) ? Void[name] :
+ // Otherwise attempt to use method with a generated private
+ // `name` that is supposedly in the prototype chain of the
+ // `target`.
+ value[name] ||
+ // Otherwise assume it's one of the built-in type instances,
+ // in which case implementation is stored in a `builtin` hash.
+ // Attempt to find a implementation for the given built-in
+ // via constructor name and method name.
+ ((type = builtin[(value.constructor || "").name]) &&
+ type[name]) ||
+ // Otherwise assume it's a host object. For host objects
+ // actual method implementations are stored in the `host`
+ // array and only index for the implementation is stored
+ // in the host object's prototype chain. This avoids memory
+ // leaks that otherwise could happen when saving JS objects
+ // on host object.
+ host[value["!" + name] || void(0)] ||
+ // Otherwise attempt to lookup implementation for builtins by
+ // a type of the value. This basically makes sure that all
+ // non primitive values will delegate to an `Object`.
+ ((type = builtin[types[typeof(value)]]) && type[name])
+
+
+ // If method implementation for the type is still not found then
+ // just fallback for default implementation.
+ method = method || Default[name]
+
+
+ // If implementation is still not found (which also means there is no
+ // default) just throw an error with a descriptive message.
+ if (!method) throw TypeError("Type does not implements method: " + name)
+
+ // If implementation was found then just delegate.
+ return method.apply(method, arguments)
+ }
+
+ // Make `toString` of the dispatch return a private name, this enables
+ // method definition without sugar:
+ //
+ // var method = Method()
+ // object[method] = function() { /***/ }
+ dispatch.toString = function toString() { return name }
+
+ // Copy utility methods for convenient API.
+ dispatch.implement = implementMethod
+ dispatch.define = defineMethod
+
+ return dispatch
+}
+
+// Create method shortcuts form functions.
+var defineMethod = function defineMethod(Type, lambda) {
+ return define(this, Type, lambda)
+}
+var implementMethod = function implementMethod(object, lambda) {
+ return implement(this, object, lambda)
+}
+
+// Define `implement` and `define` polymorphic methods to allow definitions
+// and implementations through them.
+var implement = Method("implement")
+var define = Method("define")
+
+
+function _implement(method, object, lambda) {
+ /**
+ Implements `Method` for the given `object` with a provided `implementation`.
+ Calling `Method` with `object` as a first argument will dispatch on provided
+ implementation.
+ **/
+ return defineProperty(object, method.toString(), {
+ enumerable: false,
+ configurable: false,
+ writable: false,
+ value: lambda
+ })
+}
+
+function _define(method, Type, lambda) {
+ /**
+ Defines `Method` for the given `Type` with a provided `implementation`.
+ Calling `Method` with a first argument of this `Type` will dispatch on
+ provided `implementation`. If `Type` is a `Method` default implementation
+ is defined. If `Type` is a `null` or `undefined` `Method` is implemented
+ for that value type.
+ **/
+
+ // Attempt to guess a type via `Object.prototype.toString.call` hack.
+ var type = Type && typefy.call(Type.prototype)
+
+ // If only two arguments are passed then `Type` is actually an implementation
+ // for a default type.
+ if (!lambda) Default[method] = Type
+ // If `Type` is `null` or `void` store implementation accordingly.
+ else if (Type === null) Null[method] = lambda
+ else if (Type === void(0)) Void[method] = lambda
+ // If `type` hack indicates built-in type and type has a name us it to
+ // store a implementation into associated hash. If hash for this type does
+ // not exists yet create one.
+ else if (type !== "[object Object]" && Type.name) {
+ var Bulitin = builtin[Type.name] || (builtin[Type.name] = new ObjectType())
+ Bulitin[method] = lambda
+ }
+ // If `type` hack indicates an object, that may be either object or any
+ // JS defined "Class". If name of the constructor is `Object`, assume it's
+ // built-in `Object` and store implementation accordingly.
+ else if (Type.name === "Object")
+ builtin.Object[method] = lambda
+ // Host objects are pain!!! Every browser does some crazy stuff for them
+ // So far all browser seem to not implement `call` method for host object
+ // constructors. If that is a case here, assume it's a host object and
+ // store implementation in a `host` array and store `index` in the array
+ // in a `Type.prototype` itself. This avoids memory leaks that could be
+ // caused by storing JS objects on a host objects.
+ else if (Type.call === void(0)) {
+ var index = host.indexOf(lambda)
+ if (index < 0) index = host.push(lambda) - 1
+ // Prefix private name with `!` so it can be dispatched from the method
+ // without type checks.
+ implement("!" + method, Type.prototype, index)
+ }
+ // If Got that far `Type` is user defined JS `Class`. Define private name
+ // as hidden property on it's prototype.
+ else
+ implement(method, Type.prototype, lambda)
+}
+
+// And provided implementations for a polymorphic equivalents.
+_define(define, _define)
+_define(implement, _implement)
+
+// Define exports on `Method` as it's only thing being exported.
+Method.implement = implement
+Method.define = define
+Method.Method = Method
+Method.method = Method
+Method.builtin = builtin
+Method.host = host
+
+module.exports = Method