|
var FN = (function(Object, String) { |
|
|
|
'use strict'; |
|
|
|
var undefined, |
|
|
|
create = Object.create, |
|
keys = Object.keys, |
|
getOwnPropertyNames = Object.getOwnPropertyNames, |
|
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, |
|
isExtensible = Object.isExtensible, |
|
defineProperty = Object.defineProperty, |
|
fromCharCode = String.fromCharCode, |
|
|
|
// A property name can be prepped to be exposed when object[SECRET_KEY] is accessed. |
|
preppedName, |
|
|
|
// Determines whether object[SECRET_KEY] should expose the secret map. |
|
locked = true, |
|
|
|
random = getRandomGenerator(), |
|
// idNum will ensure identifiers are unique. |
|
idNum = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], |
|
preIdentifier = randStr(7) + '0', |
|
SECRET_KEY = '!S:' + getIdentifier(); |
|
|
|
// Override Object.create |
|
Object.create = function create_(proto, props) { |
|
var obj = create(proto, props); |
|
Secrets(obj); |
|
return obj; |
|
}; |
|
|
|
// Override getOwnPropertyNames |
|
Object.getOwnPropertyNames = (function() { |
|
var original = Object.getOwnPropertyNames; |
|
return function getOwnPropertyNames(obj) { |
|
var ret = [ ], |
|
names = original(obj); |
|
for (var i = 0, j = 0; i < names.length; i++) |
|
if (names[i] != SECRET_KEY) |
|
ret[j++] = names[i]; |
|
return ret; |
|
}; |
|
})(); |
|
|
|
// Override functions which prevent extensions on objects to go ahead and add a secret map first. |
|
[ 'preventExtensions', 'seal', 'freeze' ].forEach(function(u) { |
|
var original = Object[u]; |
|
Object[u] = function(obj) { |
|
// Define the secret map. |
|
Secrets(obj); |
|
return original(obj); |
|
}; |
|
}); |
|
|
|
var methods = { |
|
|
|
set: function setSecretProperty(O, name, value) { |
|
locked = false; |
|
O[SECRET_KEY][name] = value; |
|
return value; |
|
}, |
|
|
|
getOwn: function getOwnSecretProperty(O, name) { |
|
locked = false; |
|
return O[SECRET_KEY][name]; |
|
}, |
|
|
|
delete: function deleteSecretProperty(O, name) { |
|
locked = false; |
|
return delete O[SECRET_KEY][name]; |
|
} |
|
|
|
}; |
|
|
|
function Symbol(/* params */) { |
|
|
|
Secrets(this).set('id', '!Y:' + getIdentifier()); |
|
|
|
} |
|
|
|
Object.defineProperties(Symbol.prototype, { |
|
|
|
toString: { |
|
value: function() { |
|
preppedName = Secrets(this).getOwn('id'); |
|
return SECRET_KEY; |
|
}, |
|
enumerable: false, |
|
writable: true, |
|
configurable: true |
|
}, |
|
|
|
// We can't simulate "delete obj[symbol]" in ES5. So we'll have to resort to |
|
// "symbol.deleteFrom(obj)" in this situation. |
|
deleteFrom: { |
|
value: function(obj) { |
|
var S = Secrets(obj), T = Secrets(this); |
|
if (!S || !T) return false; |
|
return S.delete(T.getOwn('id')); |
|
}, |
|
enumerable: false, |
|
writable: true, |
|
configurable: true |
|
} |
|
|
|
}); |
|
|
|
var contextualCall = contextualize(emulateCall), |
|
contextualApply = contextualize(emulateApply), |
|
contextualBind = contextualize(emulateBind); |
|
|
|
return { |
|
|
|
call: emulateCall, |
|
apply: emulateApply, |
|
bind: emulateBind, |
|
|
|
lazyBind: emulateBind(contextualBind, contextualCall), |
|
|
|
instance: { |
|
|
|
call: contextualCall, |
|
apply: contextualApply, |
|
bind: contextualBind, |
|
|
|
lazyBind: contextualize(emulateBind(contextualBind, contextualCall)) |
|
|
|
} |
|
|
|
}; |
|
|
|
function emulateApply(f, context, args) { |
|
|
|
var argsClass = getClassOf(args); |
|
|
|
if (argsClass != 'Array' && argsClass != 'Arguments') |
|
throw new TypeError('Arguments list has wrong type.'); |
|
|
|
var $method = new Symbol(), |
|
argStrs = [ ]; |
|
|
|
for (var i = 0; i < args.length; i++) |
|
argStrs[argStrs.length] = 'args[' + i + ']'; |
|
|
|
// If context is null or undefined, evaluate without a context. |
|
if (context == null) |
|
return eval('f(' + join(argStrs, ', ') + ');'); |
|
|
|
// Coerce context to an object. |
|
context = Object(context); |
|
|
|
// Make sure the context object has a Secrets map defined on it. |
|
if (!Secrets(context)) |
|
throw new Error('Cannot use object as a context object.'); |
|
|
|
context[$method] = f; |
|
|
|
// Use eval to call the method with the right number of arguments without using the built-in apply. |
|
var ret = eval('context[$method](' + join(argStrs, ', ') + ');'); |
|
|
|
$method.deleteFrom(context); |
|
|
|
return ret; |
|
|
|
} |
|
|
|
function emulateCall(f, context/*, ...args */) { |
|
// Although emulateCall could in a sense be implemented using emulateApply, we need an independent |
|
// implementation here because emulateApply relies on getClassOf which relies on emulateCall. |
|
|
|
var $method = new Symbol(), |
|
args = arguments, |
|
argStrs = [ ]; |
|
|
|
for (var i = 2; i < arguments.length; i++) |
|
argStrs[argStrs.length] = 'args[' + i + ']'; |
|
|
|
// If context is null or undefined, evaluate without a context. |
|
if (context == null) |
|
return eval('f(' + join(argStrs, ', ') + ');'); |
|
|
|
// Coerce context to an object. |
|
context = Object(context); |
|
|
|
// Make sure the context object has a Secrets map defined on it. |
|
if (!Secrets(context)) |
|
throw new Error('Cannot use object as a context object.'); |
|
|
|
context[$method] = f; |
|
|
|
// Use eval to call the method with the right number of arguments without using the built-in apply. |
|
var ret = eval('context[$method](' + join(argStrs, ', ') + ');'); |
|
|
|
$method.deleteFrom(context); |
|
|
|
return ret; |
|
|
|
} |
|
|
|
function emulateBind(f, context/*, ...preArgs */) { |
|
|
|
var preArgs = [ ]; |
|
|
|
for (var i = 2; i < arguments.length; i++) |
|
preArgs[preArgs.length] = arguments[i]; |
|
|
|
return function bound(/* ...args */) { |
|
|
|
// Copy the preArgs array with concat. |
|
var args = preArgs.concat([ ]); |
|
|
|
for (var i = 0; i < arguments.length; i++) |
|
args[args.length] = arguments[i]; |
|
|
|
return emulateApply(f, context, args); |
|
|
|
}; |
|
|
|
} |
|
|
|
function Secrets(O, name) { |
|
|
|
if(O === Object.prototype) return; |
|
if (O !== Object(O)) |
|
throw new TypeError('Not an object: ' + O); |
|
|
|
if (!(SECRET_KEY in O)) { |
|
|
|
if (!isExtensible(O)) |
|
throw new Error('Object is not extensible.'); |
|
|
|
defineProperty(O, SECRET_KEY, own({ |
|
|
|
get: (function() { |
|
var secretMap = create( |
|
// Prevent the secret map from having a prototype chain. |
|
null, |
|
own({ |
|
Secrets: { value: preloadMethods(methods, O) } |
|
}) |
|
); |
|
return function getSecret() { |
|
var value; |
|
// The lock protects against retrieval in the event that the SECRET_KEY is discovered. |
|
if (locked) { |
|
if (!preppedName) return; |
|
var name = preppedName; |
|
preppedName = undefined; |
|
value = secretMap.Secrets.getOwn(name); |
|
return value; |
|
} |
|
locked = true; |
|
return secretMap; |
|
}; |
|
})(), |
|
|
|
set: function setSecret(value) { |
|
// Weird Chrome behavior where getOwnPropertyNames seems to call object[key] = true... |
|
// Let's ignore it. |
|
if(preppedName === undefined) return; |
|
var ret; |
|
locked = false; |
|
var name = preppedName; |
|
preppedName = undefined; |
|
ret = this[SECRET_KEY].Secrets.set(name, value); |
|
return ret; |
|
}, |
|
|
|
enumerable: false, |
|
configurable: false |
|
|
|
})); |
|
|
|
} |
|
|
|
locked = false; |
|
|
|
if (name) return O[SECRET_KEY].Secrets.getOwn(name); |
|
return O[SECRET_KEY].Secrets; |
|
|
|
} |
|
|
|
function getIdentifier() { |
|
var range = 125 - 65, idS = ''; |
|
idNum[0]++; |
|
for(var i = 0; i < idNum.length; i++) { |
|
if (idNum[i] > range) { |
|
idNum[i] = 0; |
|
if (i < idNum.length) idNum[i + 1]++; |
|
else idNum = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; |
|
} |
|
idS += encodeStr(idNum[i]); |
|
} |
|
return preIdentifier + ':' + join(getRandStrs(8, 11), '/') + ':' + idS; |
|
} |
|
|
|
function encodeStr(num) { |
|
return fromCharCode(num + 65); |
|
} |
|
|
|
function getRandStrs(count, length) { |
|
var r = [ ]; |
|
for(var i = 0; i < count; i++) |
|
r[i] = randStr(length); |
|
return r; |
|
} |
|
|
|
function randStr(length) { |
|
var s = ''; |
|
for (var i = 0; i < length; i++) |
|
s += encodeStr(random() * (125 - 65 + 1)); |
|
return s; |
|
} |
|
|
|
function getRandomGenerator() { |
|
var getRandomValues |
|
= typeof crypto != 'undefined' && crypto != null |
|
? (function() { |
|
var f = crypto.random || crypto.getRandomValues; |
|
if (f) return f; |
|
return undefined; |
|
})() |
|
: undefined; |
|
if (getRandomValues) { |
|
// Firefox (15 & 16) seems to be throwing a weird "not implemented" error on getRandomValues. |
|
// Not sure why? |
|
try { getRandomValues(new Uint8Array(4)); } |
|
catch(x) { getRandomValues = undefined } |
|
} |
|
if (typeof getRandomValues == 'function' && typeof Uint8Array == 'function') { |
|
return (function() { |
|
var values = new Uint8Array(4), index = 4; |
|
return function random() { |
|
if (index >= values.length) { |
|
getRandomValues(values); |
|
index = 0; |
|
} |
|
return values[index++] / 256; |
|
}; |
|
})(); |
|
} else return Math.random; |
|
} |
|
|
|
function join(array, glue) { |
|
var s = String(array[0]); |
|
for (var i = 1; i < array.length; i++) { |
|
s += glue + array[i]; |
|
} |
|
return s; |
|
} |
|
|
|
function preloadMethods(methods, arg) { |
|
|
|
var loaded = create(null), |
|
ks = keys(methods); |
|
|
|
for (var i = 0, method; i < ks.length; i++) { |
|
method = ks[i]; |
|
loaded[method] = (function(method) { |
|
return function loadedMethod($0, $1) { |
|
return methods[method](arg, $0, $1); |
|
}; |
|
})(method); |
|
} |
|
|
|
return loaded; |
|
|
|
} |
|
|
|
function getClassOf(obj) { |
|
return emulateCall(Object.prototype.toString, obj).slice(8, -1); |
|
} |
|
|
|
function contextualize(f) { |
|
return function contextualized() { |
|
|
|
var args = arguments, |
|
argStrs = [ 'this' ]; |
|
|
|
for (var i = 0; i < arguments.length; i++) |
|
argStrs[argStrs.length] = 'args[' + i + ']'; |
|
|
|
return eval('f(' + join(argStrs, ', ') + ');'); |
|
|
|
}; |
|
} |
|
|
|
function own(obj) { |
|
|
|
var O = create(null); |
|
|
|
var keys = getOwnPropertyNames(obj); |
|
|
|
for (var i = 0, key; i < keys.length; i++) { |
|
key = keys[i]; |
|
defineProperty(O, key, |
|
getOwnPropertyDescriptor(obj, key)); |
|
} |
|
|
|
return O; |
|
|
|
} |
|
|
|
})(Object, String); |