From c8c1d8b23191bc37cb5af264483ff4f56d60852b Mon Sep 17 00:00:00 2001 From: Sam Thorogood Date: Wed, 1 Jul 2020 10:44:04 +1000 Subject: [PATCH] cleanup --- build.js | 10 ++++++++++ proxy.min.js | 10 +++++----- src/proxy.js | 40 ++++++++++++++++++++-------------------- suite.js | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/build.js b/build.js index 5022ef3..dca1017 100644 --- a/build.js +++ b/build.js @@ -14,6 +14,9 @@ * the License. */ +const fs = require('fs'); +const path = require('path'); + const ClosureCompiler = require('google-closure-compiler').compiler; const closureCompiler = new ClosureCompiler({ @@ -28,6 +31,13 @@ const closureCompiler = new ClosureCompiler({ output_wrapper: '(function(){%output%})();', // don't pollute global scope }); +// FIXME(samthor): There's probably a better way to do this. Avoid Java on macOS. +const checkNativeMac = path.join(__dirname, 'node_modules/google-closure-compiler-osx/compiler'); +if (fs.existsSync(checkNativeMac)) { + closureCompiler.JAR_PATH = undefined; + ClosureCompiler.prototype.javaPath = './node_modules/google-closure-compiler-osx/compiler'; +} + const compilerProcess = closureCompiler.run((code, stdout, stderr) => { if (stderr) { console.error('err!', stderr); diff --git a/proxy.min.js b/proxy.min.js index e1c3656..4188017 100644 --- a/proxy.min.js +++ b/proxy.min.js @@ -1,5 +1,5 @@ -(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);})(); +(function(){function n(){function v(){return null}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,e=Object,w=!!e.create||!({__proto__:null}instanceof e),A=e.create||(w?function(a){p(a);return{__proto__:a}}:function(a){function c(){}p(a);if(null===a)throw new SyntaxError("Native Object.create is required to create objects with null prototype");c.prototype=a;return new c}), +B=e.getPrototypeOf||([].__proto__===Array.prototype?function(a){a=a.__proto__;return l(a)?a:null}:v);var m=function(a,c){function k(){}if(void 0===(this&&this instanceof m?this.constructor:void 0))throw new TypeError("Constructor Proxy requires 'new'");if(!l(a)||!l(c))throw new TypeError("Cannot create proxy with a non-object as target or handler");q=function(){a=null;k=function(b){throw new TypeError("Cannot perform '"+b+"' on a proxy that has been revoked");}};setTimeout(function(){q=null},0);var g= +c;c={get:null,set:null,apply:null,construct:null};for(var h in g){if(!(h in c))throw new TypeError("Proxy polyfill does not support trap '"+h+"'");c[h]=g[h]}"function"===typeof g&&(c.apply=g.apply.bind(g));g=B(a);var r=!1,t=!1;if("function"===typeof a){var f=function(){var b=this&&this.constructor===f,d=Array.prototype.slice.call(arguments);k(b?"construct":"apply");return b&&c.construct?c.construct.call(this,a,d):!b&&c.apply?c.apply(a,this,d):b?(d.unshift(a),new (a.bind.apply(a,d))):a.apply(this, +d)};r=!0}else a instanceof Array?(f=[],t=!0):f=w||null!==g?A(g):{};var x=c.get?function(b){k("get");return c.get(this,b,f)}:function(b){k("get");return this[b]},C=c.set?function(b,d){k("set");c.set(this,b,d,f)}:function(b,d){k("set");this[b]=d},y={};e.getOwnPropertyNames(a).forEach(function(b){if(!((r||t)&&b in f)){var d=e.getOwnPropertyDescriptor(a,b);e.defineProperty(f,b,{enumerable:!!d.enumerable,get:x.bind(a,b),set:C.bind(a,b)});y[b]=!0}});h=!0;if(r||t){var D=e.setPrototypeOf||([].__proto__=== +Array.prototype?function(b,d){p(d);b.__proto__=d;return b}:v);g&&D(f,g)||(h=!1)}if(c.get||!h)for(var u in a)y[u]||e.defineProperty(f,u,{get:x.bind(a,u)});e.seal(a);e.seal(f);return f};m.revocable=function(a,c){return{proxy:new m(a,c),revoke:q}};return m};var z="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)||"undefined"!==typeof navigator&&"ReactNative"===navigator.product?global:self;z.Proxy||(z.Proxy=n(),z.Proxy.revocable=z.Proxy.revocable);})(); diff --git a/src/proxy.js b/src/proxy.js index 819fe4e..01ced4b 100644 --- a/src/proxy.js +++ b/src/proxy.js @@ -35,7 +35,7 @@ module.exports = function proxyPolyfill() { 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 canCreateNullProtoObjects = Boolean($Object.create) || !({ __proto__: null } instanceof $Object); const objectCreate = $Object.create || (canCreateNullProtoObjects @@ -55,6 +55,8 @@ module.exports = function proxyPolyfill() { return new T(); }); + const noop = function() { return null; }; + const getProto = $Object.getPrototypeOf || ([].__proto__ === Array.prototype @@ -64,19 +66,7 @@ module.exports = function proxyPolyfill() { 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); + : noop); /** * @constructor @@ -126,7 +116,7 @@ module.exports = function proxyPolyfill() { // 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; + const proto = getProto(target); // can return null in old browsers let proxy; let isMethod = false; let isArray = false; @@ -158,7 +148,7 @@ module.exports = function proxyPolyfill() { proxy = []; isArray = true; } else { - proxy = canCreateNullProtoObjects || proto !== null ? objectCreate(proto) : {}; + proxy = (canCreateNullProtoObjects || proto !== null) ? objectCreate(proto) : {}; } // Create default getters/setters. Create different code paths as handler.get/handler.set can't @@ -192,7 +182,7 @@ module.exports = function proxyPolyfill() { } const real = $Object.getOwnPropertyDescriptor(target, prop); const desc = { - enumerable: !!real.enumerable, + enumerable: Boolean(real.enumerable), get: getter.bind(target, prop), set: setter.bind(target, prop), }; @@ -205,9 +195,19 @@ module.exports = function proxyPolyfill() { // An alternative here would be to _just_ clone methods to keep behavior consistent. let prototypeOk = true; if (isMethod || isArray) { - if (setProto && proto !== undefined) { - setProto(proxy, proto); - } else { + // Arrays and methods are special: above, we instantiate boring versions of these then swap + // our their prototype later. So we only need to use setPrototypeOf in these cases. Some old + // engines support `Object.getPrototypeOf` but not `Object.setPrototypeOf`. + const setProto = + $Object.setPrototypeOf || + ([].__proto__ === Array.prototype + ? function setPrototypeOf(O, proto) { + validateProto(proto); + O.__proto__ = proto; + return O; + } + : noop); + if (!(proto && setProto(proxy, proto))) { prototypeOk = false; } } diff --git a/suite.js b/suite.js index 4ddf5a2..2a0eeea 100644 --- a/suite.js +++ b/suite.js @@ -303,6 +303,32 @@ module.exports = () => function(scope) { assert.equal(p.y, 1); } }); + + test('subclass of array', function() { + class Foo extends Array {} + var f = new Foo(2); + f[0] = 'first'; + f[1] = 'second'; + + var p = new impl(f, {get: function(obj, prop) { + return obj[prop]; + }}); + + assert.equal(p[0], 'first'); + assert.equal(p.length, 2); + assert.isTrue(p instanceof Array, 'should be instanceof Array'); + + if (impl === scope.NativeProxy) { + // nb. this fails in NativeProxy + assert.throws(function() { + p instanceof impl; + }, 'Function has non-object prototype', 'cannot instanceof native proxy'); + } else { + assert.isFalse(p instanceof impl, 'should not be instanceof Proxy'); + } + + assert.equal(Object.getPrototypeOf(p), Object.getPrototypeOf(f)); + }); }); } @@ -312,6 +338,16 @@ module.exports = () => function(scope) { } suite('general polyfill', function() { + test('prototypal inheritance', function() { + function Foo() { + } + var f = new Foo(); + var p = new Proxy(f, {}); + + assert.isTrue(p instanceof Foo, 'proxy looks like proxied object'); + assert.isFalse(p instanceof Proxy, 'proxy does not look like Proxy') + }); + test('seals object', function() { var testObj = buildObject(); assert.isNotSealed(testObj);