diff --git a/proxy.min.js b/proxy.min.js index 292e1d6..e1c3656 100644 --- a/proxy.min.js +++ b/proxy.min.js @@ -1,4 +1,5 @@ -(function(){function k(){function p(a){return a?"object"===typeof a||"function"===typeof a:!1}var l=null;var n=function(a,c){function g(){}if(!p(a)||!p(c))throw new TypeError("Cannot create proxy with a non-object as target or handler");l=function(){a=null;g=function(b){throw new TypeError("Cannot perform '"+b+"' on a proxy that has been revoked");}};setTimeout(function(){l=null},0);var f=c;c={get:null,set:null,apply:null,construct:null};for(var h in f){if(!(h in c))throw new TypeError("Proxy polyfill does not support trap '"+ -h+"'");c[h]=f[h]}"function"===typeof f&&(c.apply=f.apply.bind(f));var d=this,q=!1,r=!1;"function"===typeof a?(d=function(){var b=this&&this.constructor===d,e=Array.prototype.slice.call(arguments);g(b?"construct":"apply");return b&&c.construct?c.construct.call(this,a,e):!b&&c.apply?c.apply(a,this,e):b?(e.unshift(a),new (a.bind.apply(a,e))):a.apply(this,e)},q=!0):a instanceof Array&&(d=[],r=!0);var t=c.get?function(b){g("get");return c.get(this,b,d)}:function(b){g("get");return this[b]},w=c.set?function(b, -e){g("set");c.set(this,b,e,d)}:function(b,e){g("set");this[b]=e},u={};Object.getOwnPropertyNames(a).forEach(function(b){if(!((q||r)&&b in d)){var e={enumerable:!!Object.getOwnPropertyDescriptor(a,b).enumerable,get:t.bind(a,b),set:w.bind(a,b)};Object.defineProperty(d,b,e);u[b]=!0}});f=!0;Object.setPrototypeOf?Object.setPrototypeOf(d,Object.getPrototypeOf(a)):d.__proto__?d.__proto__=a.__proto__:f=!1;if(c.get||!f)for(var m in a)u[m]||Object.defineProperty(d,m,{get:t.bind(a,m)});Object.seal(a);Object.seal(d); -return d};n.revocable=function(a,c){return{proxy:new n(a,c),revoke:l}};return n};var v="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)||"undefined"!==typeof navigator&&"ReactNative"===navigator.product?global:self;v.Proxy||(v.Proxy=k(),v.Proxy.revocable=v.Proxy.revocable);})(); +(function(){function n(){function l(a){return a?"object"===typeof a||"function"===typeof a:!1}function p(a){if(null!==a&&!l(a))throw new TypeError("Object prototype may only be an Object or null: "+a);}var q=null,d=Object,v=!!d.create||!({__proto__:null}instanceof d),B=d.create||(v?function(a){p(a);return{__proto__:a}}:function(a){function b(){}p(a);if(null===a)throw new SyntaxError("Native Object.create is required to create objects with null prototype");b.prototype=a;return new b}),w=d.getPrototypeOf||([].__proto__=== +Array.prototype?function(a){a=a.__proto__;return l(a)?a:null}:null),x=d.setPrototypeOf||([].__proto__===Array.prototype?function(a,b){p(b);a.__proto__=b;return a}:null);var m=function(a,b){function k(){}if(void 0===(this&&this instanceof m?this.constructor:void 0))throw new TypeError("Constructor Proxy requires 'new'");if(!l(a)||!l(b))throw new TypeError("Cannot create proxy with a non-object as target or handler");q=function(){a=null;k=function(c){throw new TypeError("Cannot perform '"+c+"' on a proxy that has been revoked"); +}};setTimeout(function(){q=null},0);var g=b;b={get:null,set:null,apply:null,construct:null};for(var h in g){if(!(h in b))throw new TypeError("Proxy polyfill does not support trap '"+h+"'");b[h]=g[h]}"function"===typeof g&&(b.apply=g.apply.bind(g));g=w?w(a):null;var r=!1,t=!1;if("function"===typeof a){var e=function(){var c=this&&this.constructor===e,f=Array.prototype.slice.call(arguments);k(c?"construct":"apply");return c&&b.construct?b.construct.call(this,a,f):!c&&b.apply?b.apply(a,this,f):c?(f.unshift(a), +new (a.bind.apply(a,f))):a.apply(this,f)};r=!0}else a instanceof Array?(e=[],t=!0):e=v||null!==g?B(g):{};var y=b.get?function(c){k("get");return b.get(this,c,e)}:function(c){k("get");return this[c]},C=b.set?function(c,f){k("set");b.set(this,c,f,e)}:function(c,f){k("set");this[c]=f},z={};d.getOwnPropertyNames(a).forEach(function(c){if(!((r||t)&&c in e)){var f={enumerable:!!d.getOwnPropertyDescriptor(a,c).enumerable,get:y.bind(a,c),set:C.bind(a,c)};d.defineProperty(e,c,f);z[c]=!0}});h=!0;if(r||t)x&& +void 0!==g?x(e,g):h=!1;if(b.get||!h)for(var u in a)z[u]||d.defineProperty(e,u,{get:y.bind(a,u)});d.seal(a);d.seal(e);return e};m.revocable=function(a,b){return{proxy:new m(a,b),revoke:q}};return m};var A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)||"undefined"!==typeof navigator&&"ReactNative"===navigator.product?global:self;A.Proxy||(A.Proxy=n(),A.Proxy.revocable=A.Proxy.revocable);})(); diff --git a/src/proxy.js b/src/proxy.js index 0316395..819fe4e 100644 --- a/src/proxy.js +++ b/src/proxy.js @@ -26,12 +26,69 @@ module.exports = function proxyPolyfill() { return o ? (typeof o === 'object' || typeof o === 'function') : false; } + function validateProto(proto) { + if (proto !== null && !isObject(proto)) { + throw new TypeError('Object prototype may only be an Object or null: ' + proto); + } + } + + const $Object = Object; + + // Closure assumes that `{__proto__: null} instanceof Object` is always true, hence why we check against a different name. + const canCreateNullProtoObjects = !!$Object.create || !({ __proto__: null } instanceof $Object); + const objectCreate = + $Object.create || + (canCreateNullProtoObjects + ? function create(proto) { + validateProto(proto); + return { __proto__: proto }; + } + : function create(proto) { + validateProto(proto); + if (proto === null) { + throw new SyntaxError('Native Object.create is required to create objects with null prototype'); + } + + // nb. cast to convince Closure compiler that this is a constructor + var T = /** @type {!Function} */ (function T() {}); + T.prototype = proto; + return new T(); + }); + + const getProto = + $Object.getPrototypeOf || + ([].__proto__ === Array.prototype + ? function getPrototypeOf(O) { + // If O.[[Prototype]] === null, then the __proto__ accessor won't exist, + // as it's inherited from `Object.prototype` + const proto = O.__proto__; + return isObject(proto) ? proto : null; + } + : null); + + // Some old engines support Object.getPrototypeOf but not Object.setPrototypeOf, + // because Object.setPrototypeOf was standardized later. + const setProto = + $Object.setPrototypeOf || + ([].__proto__ === Array.prototype + ? function setPrototypeOf(O, proto) { + validateProto(proto); + O.__proto__ = proto; + return O; + } + : null); + /** * @constructor * @param {!Object} target * @param {{apply, construct, get, set}} handler */ ProxyPolyfill = function(target, handler) { + const newTarget = this && this instanceof ProxyPolyfill ? this.constructor : undefined; + if (newTarget === undefined) { + throw new TypeError("Constructor Proxy requires 'new'"); + } + if (!isObject(target) || !isObject(handler)) { throw new TypeError('Cannot create proxy with a non-object as target or handler'); } @@ -67,9 +124,10 @@ module.exports = function proxyPolyfill() { handler.apply = unsafeHandler.apply.bind(unsafeHandler); } - // Define proxy as this, or a Function (if either it's callable, or apply is set). - // TODO(samthor): Closure compiler doesn't know about 'construct', attempts to rename it. - let proxy = this; + // Define proxy as an object that extends target.[[Prototype]], + // or a Function (if either it's callable, or apply is set). + const proto = getProto ? getProto(target) : null; + let proxy; let isMethod = false; let isArray = false; if (typeof target === 'function') { @@ -78,6 +136,7 @@ module.exports = function proxyPolyfill() { const args = Array.prototype.slice.call(arguments); throwRevoked(usingNew ? 'construct' : 'apply'); + // TODO(samthor): Closure compiler doesn't know about 'construct', attempts to rename it. if (usingNew && handler['construct']) { return handler['construct'].call(this, target, args); } else if (!usingNew && handler.apply) { @@ -98,6 +157,8 @@ module.exports = function proxyPolyfill() { } else if (target instanceof Array) { proxy = []; isArray = true; + } else { + proxy = canCreateNullProtoObjects || proto !== null ? objectCreate(proto) : {}; } // Create default getters/setters. Create different code paths as handler.get/handler.set can't @@ -123,19 +184,19 @@ module.exports = function proxyPolyfill() { }; // Clone direct properties (i.e., not part of a prototype). - const propertyNames = Object.getOwnPropertyNames(target); + const propertyNames = $Object.getOwnPropertyNames(target); const propertyMap = {}; propertyNames.forEach(function(prop) { if ((isMethod || isArray) && prop in proxy) { return; // ignore properties already here, e.g. 'bind', 'prototype' etc } - const real = Object.getOwnPropertyDescriptor(target, prop); + const real = $Object.getOwnPropertyDescriptor(target, prop); const desc = { enumerable: !!real.enumerable, get: getter.bind(target, prop), set: setter.bind(target, prop), }; - Object.defineProperty(proxy, prop, desc); + $Object.defineProperty(proxy, prop, desc); propertyMap[prop] = true; }); @@ -143,25 +204,25 @@ module.exports = function proxyPolyfill() { // TODO(samthor): We don't allow prototype methods to be set. It's (even more) awkward. // An alternative here would be to _just_ clone methods to keep behavior consistent. let prototypeOk = true; - if (Object.setPrototypeOf) { - Object.setPrototypeOf(proxy, Object.getPrototypeOf(target)); - } else if (proxy.__proto__) { - proxy.__proto__ = target.__proto__; - } else { - prototypeOk = false; + if (isMethod || isArray) { + if (setProto && proto !== undefined) { + setProto(proxy, proto); + } else { + prototypeOk = false; + } } if (handler.get || !prototypeOk) { for (let k in target) { if (propertyMap[k]) { continue; } - Object.defineProperty(proxy, k, { get: getter.bind(target, k) }); + $Object.defineProperty(proxy, k, { get: getter.bind(target, k) }); } } // The Proxy polyfill cannot handle adding new properties. Seal the target and proxy. - Object.seal(target); - Object.seal(proxy); + $Object.seal(target); + $Object.seal(proxy); return proxy; // nb. if isMethod is true, proxy != this }; @@ -172,4 +233,4 @@ module.exports = function proxyPolyfill() { }; return ProxyPolyfill; -} \ No newline at end of file +}