load(libdir + "asserts.js"); // Test ES6 Proxy trap compliance for Object.isExtensible() on exotic proxy // objects. var unsealed = {}; var sealed = Object.seal({}); var handler = {}; assertEq(Object.isExtensible(unsealed), true); assertEq(Object.isExtensible(sealed), false); var targetSealed = new Proxy(sealed, handler); var targetUnsealed = new Proxy(unsealed, handler); var handlerCalled = false; function testExtensible(target, expectedResult, shouldIgnoreHandler = false) { for (let p of [new Proxy(target, handler), Proxy.revocable(target, handler).proxy]) { handlerCalled = false; if (typeof expectedResult === "boolean") assertEq(Object.isExtensible(p), expectedResult, "Must return the correct value."); else assertThrowsInstanceOf(() => Object.isExtensible(p), expectedResult); assertEq(handlerCalled, !shouldIgnoreHandler, "Must call handler appropriately"); } } // without traps, forward to the target // First, make sure we get the obvious answer on a non-exotic target. testExtensible(sealed, false, /* shouldIgnoreHandler = */true); testExtensible(unsealed, true, /* shouldIgnoreHandler = */true); // Now, keep everyone honest. What if the target itself is a proxy? // Note that we cheat a little. |handlerCalled| is true in a sense, just not // for the toplevel handler. // While we're here, test that the argument is passed correctly. var targetsTarget = {}; function ensureCalled(target) { assertEq(target, targetsTarget); handlerCalled = true; return true; } var proxyTarget = new Proxy(targetsTarget, { isExtensible : ensureCalled }); testExtensible(proxyTarget, true); // What if the trap says it's necessarily sealed? function fakeSealed() { handlerCalled = true; return false; } handler.isExtensible = fakeSealed; testExtensible(targetSealed, false); testExtensible(targetUnsealed, TypeError); // What if the trap says it's never sealed? function fakeUnsealed() { handlerCalled = true; return true; } handler.isExtensible = fakeUnsealed; testExtensible(targetSealed, TypeError); testExtensible(targetUnsealed, true); // make sure we are able to prevent further extensions mid-flight and throw if the // hook tries to lie. function makeSealedTruth(target) { handlerCalled = true; Object.preventExtensions(target); return false; } function makeSealedLie(target) { handlerCalled = true; Object.preventExtensions(target); return true; } handler.isExtensible = makeSealedTruth; testExtensible({}, false); handler.isExtensible = makeSealedLie; testExtensible({}, TypeError); // What if the trap doesn't directly return a boolean? function falseyNonBool() { handlerCalled = true; return undefined; } handler.isExtensible = falseyNonBool; testExtensible(sealed, false); testExtensible(unsealed, TypeError); function truthyNonBool() { handlerCalled = true; return {}; } handler.isExtensible = truthyNonBool; testExtensible(sealed, TypeError); testExtensible(unsealed, true); // What if the trap throws? function ExtensibleError() { } ExtensibleError.prototype = new Error(); ExtensibleError.prototype.constructor = ExtensibleError; function throwFromTrap() { throw new ExtensibleError(); } handler.isExtensible = throwFromTrap; // exercise some other code paths and make sure that they invoke the trap and // can handle the ensuing error. for (let p of [new Proxy(sealed, handler), Proxy.revocable(sealed, handler).proxy]) { assertThrowsInstanceOf(function () { Object.isExtensible(p); }, ExtensibleError, "Must throw if the trap does."); assertThrowsInstanceOf(function () { Object.isFrozen(p); }, ExtensibleError, "Must throw if the trap does."); assertThrowsInstanceOf(function () { Object.isSealed(p); }, ExtensibleError, "Must throw if the trap does."); } // What if the trap likes to re-ask old questions? for (let p of [new Proxy(sealed, handler), Proxy.revocable(sealed, handler).proxy]) { handler.isExtensible = (() => Object.isExtensible(p)); assertThrowsInstanceOf(() => Object.isExtensible(p), InternalError, "Should allow and detect infinite recurison."); }