mirror of
https://github.com/tuna/mirror-web.git
synced 2025-12-25 20:32:46 +00:00
496 lines
12 KiB
JavaScript
496 lines
12 KiB
JavaScript
// Constant variables
|
|
var UNDEFINED; // => undefined
|
|
var PROXY_TARGET = "[[ProxyTarget]]";
|
|
var PROXY_HANDLER = "[[ProxyHandler]]";
|
|
var GET = "[[Get]]";
|
|
var SET = "[[Set]]";
|
|
var CALL = "[[Call]]";
|
|
var CONSTRUCT = "[[Construct]]";
|
|
var PROTOTYPE = "__proto__";
|
|
var PROXY_FLAG = "__PROXY__";
|
|
var REVOKED_FLAG = "REVOKED";
|
|
|
|
var defineProperty = Object.defineProperty;
|
|
var defineProperties = Object.defineProperties;
|
|
var getPrototypeOf = Object.getPrototypeOf;
|
|
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
var supportES5 = defineProperties ? isNativeFn(defineProperties) : false;
|
|
|
|
/**
|
|
* Return the prototype of proxy object
|
|
* @param {object} obj
|
|
* @returns {object}
|
|
*/
|
|
var getProxyProto = supportES5
|
|
? Object[PROTOTYPE]
|
|
? getPrototypeOf
|
|
: function (obj) {
|
|
return typeof obj === "function"
|
|
? obj[PROTOTYPE] || {}
|
|
: getPrototypeOf(obj);
|
|
}
|
|
: function (obj) {
|
|
return (_isVbObject(obj) && _getVbInternalOf(obj)[PROTOTYPE]) || {};
|
|
};
|
|
|
|
/**
|
|
* Check if `value` is a pristine native function
|
|
* @param {any} value
|
|
* @returns {boolean}
|
|
*/
|
|
function isNativeFn(value) {
|
|
return (
|
|
typeof value === "function" && /\[native code\]/.test(value.toString())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The Proxy constructor
|
|
* @constructor
|
|
* @param {object} target
|
|
* @param {object} handler
|
|
*/
|
|
function ProxyPolyfill(target, handler) {
|
|
if (this instanceof ProxyPolyfill) {
|
|
return createProxy(new Internal(target, handler));
|
|
} else {
|
|
throwTypeError("Constructor Proxy requires 'new'");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a revocable Proxy object
|
|
* @param {object} target
|
|
* @param {object} handler
|
|
* @returns {{proxy: object, revoke: function}}
|
|
*/
|
|
ProxyPolyfill.revocable = function (target, handler) {
|
|
if (this instanceof ProxyPolyfill.revocable) {
|
|
throwTypeError("Proxy.revocable is not a constructor");
|
|
}
|
|
var internal = new Internal(target, handler);
|
|
var proxy = createProxy(internal);
|
|
return {
|
|
proxy: proxy,
|
|
revoke: function () {
|
|
internal[PROXY_TARGET] = UNDEFINED;
|
|
internal[PROXY_HANDLER] = UNDEFINED;
|
|
if (!supportES5) {
|
|
getProxyProto(proxy)[PROXY_FLAG] = REVOKED_FLAG;
|
|
}
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Create a Proxy object
|
|
* @param {Internal} internal
|
|
* @returns {object}
|
|
*/
|
|
function createProxy(internal) {
|
|
var target = internal[PROXY_TARGET];
|
|
var proxy;
|
|
if (typeof target === "function") {
|
|
proxy = proxyFunction(internal);
|
|
} else if (target instanceof Array) {
|
|
proxy = proxyArray(internal);
|
|
} else {
|
|
proxy = proxyObject(internal);
|
|
}
|
|
return proxy;
|
|
}
|
|
|
|
/**
|
|
* Internal data
|
|
* @constructor
|
|
* @param {object} target
|
|
* @param {object} handler
|
|
*/
|
|
function Internal(target, handler) {
|
|
if (!isObject(target) || !isObject(handler)) {
|
|
throwTypeError(
|
|
"Cannot create proxy with a non-object as target or handler",
|
|
);
|
|
}
|
|
if (
|
|
(getProxyProto(target) && getProxyProto(target)[PROXY_FLAG]) ||
|
|
(getProxyProto(handler) && getProxyProto(handler)[PROXY_FLAG]) ===
|
|
REVOKED_FLAG
|
|
) {
|
|
throwTypeError(
|
|
"Cannot create proxy with a revoked proxy as target or handler",
|
|
);
|
|
}
|
|
this[PROXY_TARGET] = target;
|
|
this[PROXY_HANDLER] = handler;
|
|
}
|
|
|
|
/**
|
|
* The implementation of internal method [[Get]]
|
|
* @param {string} property
|
|
* @param {object} receiver
|
|
* @returns {any}
|
|
*/
|
|
Internal.prototype[GET] = function (property, receiver) {
|
|
var handler = this[PROXY_HANDLER];
|
|
validateProxyHanler(handler, "get");
|
|
if (handler.get == UNDEFINED) {
|
|
return this[PROXY_TARGET][property];
|
|
}
|
|
if (typeof handler.get === "function") {
|
|
return handler.get(this[PROXY_TARGET], property, receiver);
|
|
}
|
|
throwTypeError("Trap 'get' is not a function: " + handler.get);
|
|
};
|
|
|
|
/**
|
|
* The implementation of internal method [[Set]]
|
|
* @param {string} property
|
|
* @param {any} value
|
|
* @param {object} receiver
|
|
*/
|
|
Internal.prototype[SET] = function (property, value, receiver) {
|
|
var handler = this[PROXY_HANDLER];
|
|
validateProxyHanler(handler, "set");
|
|
if (handler.set == UNDEFINED) {
|
|
this[PROXY_TARGET][property] = value;
|
|
} else if (typeof handler.set === "function") {
|
|
var result = handler.set(this[PROXY_TARGET], property, value, receiver);
|
|
if (!result) {
|
|
// If the set() method returns false in strict-mode code, a TypeError will be thrown.
|
|
// throwTypeError("Trap 'set' returned false for property '" + property + "'");
|
|
console.warn("Trap 'set' returned false for property '" + property + "'");
|
|
}
|
|
return Boolean(result);
|
|
} else {
|
|
throwTypeError("Trap 'set' is not a function: " + handler.set);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The implementation of internal method [[Call]]
|
|
* @param {object} thisArg
|
|
* @param {any[]} argList
|
|
* @returns {any}
|
|
*/
|
|
Internal.prototype[CALL] = function (thisArg, argList) {
|
|
var handler = this[PROXY_HANDLER];
|
|
validateProxyHanler(handler, "apply");
|
|
if (handler.apply == UNDEFINED) {
|
|
return this[PROXY_TARGET].apply(thisArg, argList);
|
|
}
|
|
if (typeof handler.apply === "function") {
|
|
return handler.apply(this[PROXY_TARGET], thisArg, argList);
|
|
}
|
|
throwTypeError("Trap 'apply' is not a function: " + handler.apply);
|
|
};
|
|
|
|
/**
|
|
* The implementation of internal method [[Construct]]
|
|
* @param {any[]} argList
|
|
* @param {object} newTarget
|
|
* @returns {object}
|
|
*/
|
|
Internal.prototype[CONSTRUCT] = function (argList, newTarget) {
|
|
var handler = this[PROXY_HANDLER];
|
|
validateProxyHanler(handler, "construct");
|
|
|
|
var newObj;
|
|
if (handler.construct == UNDEFINED) {
|
|
newObj = evaluateNew(this[PROXY_TARGET], argList);
|
|
} else if (typeof handler.construct === "function") {
|
|
newObj = handler.construct(this[PROXY_TARGET], argList, newTarget);
|
|
} else {
|
|
throwTypeError("Trap 'construct' is not a function: " + handler.construct);
|
|
}
|
|
|
|
if (isObject(newObj)) {
|
|
return newObj;
|
|
} else {
|
|
throwTypeError("Trap 'construct' returned non-object: " + newObj);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Validate the proxy hanler
|
|
* @param {object} handler
|
|
* @param {string} trap
|
|
*/
|
|
function validateProxyHanler(handler, trap) {
|
|
if (!handler) {
|
|
throwTypeError(
|
|
"Cannot perform '" + trap + "' on a proxy that has been revoked",
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call constructor with 'new'
|
|
* @param {function} F constructor
|
|
* @param {any[]} argList
|
|
* @returns {object}
|
|
*/
|
|
function evaluateNew(F, argList) {
|
|
var params = [];
|
|
for (var i = 0, len = argList.length; i < len; ++i) {
|
|
params.push("args[" + i + "]");
|
|
}
|
|
var executor = new Function(
|
|
"Ctor",
|
|
"args",
|
|
"return new Ctor(" + params.join(", ") + ")",
|
|
);
|
|
return executor(F, argList);
|
|
}
|
|
|
|
/**
|
|
* Throw a type error
|
|
* @param {string} message
|
|
*/
|
|
function throwTypeError(message) {
|
|
throw new TypeError(message);
|
|
}
|
|
|
|
/**
|
|
* Check if value is the language type of Object
|
|
* @param {any} value
|
|
* @returns {boolean}
|
|
*/
|
|
function isObject(value) {
|
|
return value
|
|
? typeof value === "object" || typeof value === "function"
|
|
: false;
|
|
}
|
|
|
|
/**
|
|
* Check if key is an own property of object
|
|
* @param {object} obj
|
|
* @param {string} key
|
|
* @returns {boolean}
|
|
*/
|
|
function hasOwnProperty(obj, key) {
|
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
}
|
|
|
|
/**
|
|
* Hack `Object.getOwnPropertyNames`
|
|
*/
|
|
var getOwnPropertyNames =
|
|
Object.getOwnPropertyNames ||
|
|
function (obj) {
|
|
var names = [];
|
|
for (var key in obj) {
|
|
if (hasOwnProperty(obj, key)) {
|
|
names.push(key);
|
|
}
|
|
}
|
|
return names;
|
|
};
|
|
|
|
/**
|
|
* Set the prototype of function
|
|
* @param {function} fn
|
|
* @param {object} proto
|
|
* @returns {object}
|
|
*/
|
|
var setFuncProto = isNativeFn(Object.setPrototypeOf)
|
|
? Object.setPrototypeOf
|
|
: Object[PROTOTYPE]
|
|
? function (fn, proto) {
|
|
fn[PROTOTYPE] = proto;
|
|
return fn;
|
|
}
|
|
: function (fn, proto) {
|
|
return defineProperty(fn, PROTOTYPE, { value: proto });
|
|
};
|
|
|
|
/**
|
|
* Hack `Object.create`
|
|
*/
|
|
var objectCreate = supportES5
|
|
? Object.create
|
|
: function (_, props) {
|
|
var obj = defineProperties({}, props);
|
|
if (_isVbObject(obj)) {
|
|
var proto = {};
|
|
proto[PROXY_FLAG] = UNDEFINED;
|
|
_getVbInternalOf(obj)[PROTOTYPE] = proto;
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
/**
|
|
* Hack `Object.assign`
|
|
*/
|
|
var objectAssign =
|
|
Object.assign ||
|
|
function (target, source) {
|
|
for (var key in source) {
|
|
if (hasOwnProperty(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Proxy function
|
|
* @param {Internal} internal
|
|
* @returns {function}
|
|
*/
|
|
function proxyFunction(internal) {
|
|
var target = internal[PROXY_TARGET];
|
|
|
|
function P() {
|
|
return this instanceof P
|
|
? internal[CONSTRUCT](arguments, P)
|
|
: internal[CALL](this, arguments);
|
|
}
|
|
P.prototype = target.prototype; // `prototype` is not configurable
|
|
|
|
if (supportES5) {
|
|
var descMap = observeProto(internal);
|
|
var newProto = objectCreate(getPrototypeOf(target), descMap);
|
|
setFuncProto(P, newProto);
|
|
|
|
descMap = observeProperties(target, internal);
|
|
for (var key in descMap) {
|
|
if (hasOwnProperty(P, key)) delete descMap[key];
|
|
}
|
|
defineProperties(P, descMap);
|
|
} else {
|
|
objectAssign(P, target);
|
|
}
|
|
|
|
return P;
|
|
}
|
|
|
|
/**
|
|
* Proxy array
|
|
* @param {Internal} internal
|
|
* @returns {object} array-like object
|
|
*/
|
|
function proxyArray(internal) {
|
|
var target = internal[PROXY_TARGET];
|
|
|
|
var descMap, newProto;
|
|
if (supportES5) {
|
|
descMap = observeProto(internal);
|
|
// Fix: `concat` does not work correctly on array-like object
|
|
descMap.concat.get = function () {
|
|
var val = internal[GET]("concat", this);
|
|
return val === Array.prototype.concat ? val.bind(target) : val;
|
|
};
|
|
newProto = objectCreate(getPrototypeOf(target), descMap);
|
|
}
|
|
|
|
descMap = observeProperties(target, internal);
|
|
// Observe the change of `length`, and synchronize
|
|
// the properties of Proxy object to target array
|
|
descMap.length.set = function (value) {
|
|
var lenDiff = value - target.length;
|
|
internal[SET]("length", value, this);
|
|
if (lenDiff) syncArrayElement(lenDiff, this, internal);
|
|
};
|
|
|
|
return objectCreate(newProto, descMap);
|
|
}
|
|
|
|
/**
|
|
* Proxy object
|
|
* @param {Internal} internal
|
|
* @returns {object}
|
|
*/
|
|
function proxyObject(internal) {
|
|
var target = internal[PROXY_TARGET];
|
|
var descMap, newProto;
|
|
if (supportES5) {
|
|
descMap = observeProto(internal);
|
|
newProto = objectCreate(getPrototypeOf(target), descMap);
|
|
}
|
|
|
|
descMap = observeProperties(target, internal);
|
|
return objectCreate(newProto, descMap);
|
|
}
|
|
|
|
/**
|
|
* Observe [[Prototype]]
|
|
* @param {Internal} internal
|
|
* @returns {object} descriptors
|
|
*/
|
|
function observeProto(internal) {
|
|
var descMap = {};
|
|
var proto = internal[PROXY_TARGET];
|
|
while ((proto = getPrototypeOf(proto))) {
|
|
var props = observeProperties(proto, internal);
|
|
objectAssign(descMap, props);
|
|
}
|
|
descMap[PROXY_FLAG] = {
|
|
get: function () {
|
|
return internal[PROXY_TARGET] ? UNDEFINED : REVOKED_FLAG;
|
|
},
|
|
};
|
|
return descMap;
|
|
}
|
|
|
|
/**
|
|
* Observe properties
|
|
* @param {object} obj
|
|
* @param {Internal} internal
|
|
* @returns {object} descriptors
|
|
*/
|
|
function observeProperties(obj, internal) {
|
|
var names = getOwnPropertyNames(obj);
|
|
var descMap = {};
|
|
for (var i = names.length - 1; i >= 0; --i) {
|
|
descMap[names[i]] = observeProperty(obj, names[i], internal);
|
|
}
|
|
return descMap;
|
|
}
|
|
|
|
/**
|
|
* Observe property
|
|
* @param {object} obj
|
|
* @param {string} prop
|
|
* @param {Internal} internal
|
|
* @returns {{get: function, set: function, enumerable: boolean, configurable: boolean}}
|
|
*/
|
|
function observeProperty(obj, prop, internal) {
|
|
var desc = getOwnPropertyDescriptor(obj, prop);
|
|
return {
|
|
get: function () {
|
|
return internal[GET](prop, this);
|
|
},
|
|
set: function (value) {
|
|
internal[SET](prop, value, this);
|
|
},
|
|
enumerable: desc.enumerable,
|
|
configurable: desc.configurable,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Sync array element from P to target
|
|
* @param {number} lenDiff
|
|
* @param {object} P
|
|
* @param {Internal} internal
|
|
*/
|
|
function syncArrayElement(lenDiff, P, internal) {
|
|
var target = internal[PROXY_TARGET];
|
|
if (lenDiff > 0) {
|
|
for (var tLen = target.length, i = tLen - lenDiff; i < tLen; ++i) {
|
|
var desc = getOwnPropertyDescriptor(P, i);
|
|
if (desc) defineProperty(target, i, desc);
|
|
else target[i] = UNDEFINED;
|
|
desc = observeProperty(target, i, internal);
|
|
defineProperty(P, i, desc);
|
|
}
|
|
} else {
|
|
for (var i = target.length, pLen = i - lenDiff; i < pLen; ++i) {
|
|
delete P[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
export default typeof Proxy === "undefined" ? ProxyPolyfill : Proxy;
|