mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
226 lines
8.3 KiB
JavaScript
226 lines
8.3 KiB
JavaScript
"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
|