| /* |
| * Any copyright is dedicated to the Public Domain. |
| * http://creativecommons.org/publicdomain/zero/1.0/ |
| */ |
| |
| var gTestfile = "setImmutablePrototype.js"; |
| //----------------------------------------------------------------------------- |
| var BUGNUMBER = 1052139; |
| var summary = |
| "Implement JSAPI and a shell function to prevent modifying an extensible " + |
| "object's [[Prototype]]"; |
| |
| print(BUGNUMBER + ": " + summary); |
| |
| /************** |
| * BEGIN TEST * |
| **************/ |
| |
| if (typeof evaluate !== "function") |
| { |
| // Not totally faithful semantics, but approximately close enough for this |
| // test's purposes. |
| evaluate = eval; |
| } |
| |
| var usingRealSetImmutablePrototype = true; |
| |
| if (typeof setImmutablePrototype !== "function") |
| { |
| usingRealSetImmutablePrototype = false; |
| |
| if (typeof SpecialPowers !== "undefined") |
| { |
| setImmutablePrototype = |
| SpecialPowers.Cu.getJSTestingFunctions().setImmutablePrototype; |
| } |
| } |
| |
| if (typeof immutablePrototypesEnabled !== "function" && |
| typeof SpecialPowers !== "undefined") |
| { |
| immutablePrototypesEnabled = |
| SpecialPowers.Cu.getJSTestingFunctions().immutablePrototypesEnabled; |
| } |
| |
| if (typeof wrap !== "function") |
| { |
| // good enough |
| wrap = function(x) { return x; }; |
| } |
| |
| function setViaProtoSetter(obj, newProto) |
| { |
| var setter = |
| Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set; |
| setter.call(obj, newProto); |
| } |
| |
| function checkPrototypeMutationFailure(obj, desc) |
| { |
| var initialProto = Object.getPrototypeOf(obj); |
| |
| // disconnected from any [[Prototype]] chains for use on any object at all |
| var newProto = Object.create(null); |
| |
| function tryMutate(func, method) |
| { |
| try |
| { |
| func(obj, newProto); |
| throw new Error(desc + ": no error thrown, prototype " + |
| (Object.getPrototypeOf(obj) === initialProto |
| ? "wasn't" |
| : "was") + |
| " changed"); |
| } |
| catch (e) |
| { |
| // Note: This is always a TypeError from *this* global object, because |
| // Object.setPrototypeOf and the __proto__ setter come from *this* |
| // global object. |
| assertEq(e instanceof TypeError, true, |
| desc + ": should have thrown TypeError setting [[Prototype]] " + |
| "via " + method + ", got " + e); |
| assertEq(Object.getPrototypeOf(obj), initialProto, |
| desc + ": shouldn't observe [[Prototype]] change"); |
| } |
| } |
| |
| tryMutate(Object.setPrototypeOf, "Object.setPrototypeOf"); |
| tryMutate(setViaProtoSetter, "__proto__ setter"); |
| } |
| |
| function runNormalTests(global) |
| { |
| if (typeof setImmutablePrototype !== "function" || |
| typeof immutablePrototypesEnabled !== "function" || |
| !immutablePrototypesEnabled()) |
| { |
| print("no testable setImmutablePrototype function available, skipping tests"); |
| return; |
| } |
| |
| // regular old object, non-null [[Prototype]] |
| |
| var emptyLiteral = global.evaluate("({})"); |
| assertEq(setImmutablePrototype(emptyLiteral), true); |
| checkPrototypeMutationFailure(emptyLiteral, "empty literal"); |
| |
| // regular old object, null [[Prototype]] |
| |
| var nullProto = global.Object.create(null); |
| assertEq(setImmutablePrototype(nullProto), true); |
| checkPrototypeMutationFailure(nullProto, "nullProto"); |
| |
| // Shocker: SpecialPowers's little mind doesn't understand proxies. Abort. |
| if (!usingRealSetImmutablePrototype) |
| return; |
| |
| // direct proxies |
| |
| var emptyTarget = global.evaluate("({})"); |
| var directProxy = new global.Proxy(emptyTarget, {}); |
| assertEq(setImmutablePrototype(directProxy), true); |
| checkPrototypeMutationFailure(directProxy, "direct proxy to empty target"); |
| checkPrototypeMutationFailure(emptyTarget, "empty target"); |
| |
| var anotherTarget = global.evaluate("({})"); |
| var anotherDirectProxy = new global.Proxy(anotherTarget, {}); |
| assertEq(setImmutablePrototype(anotherTarget), true); |
| checkPrototypeMutationFailure(anotherDirectProxy, "another direct proxy to empty target"); |
| checkPrototypeMutationFailure(anotherTarget, "another empty target"); |
| |
| var nestedTarget = global.evaluate("({})"); |
| var nestedProxy = new global.Proxy(new Proxy(nestedTarget, {}), {}); |
| assertEq(setImmutablePrototype(nestedProxy), true); |
| checkPrototypeMutationFailure(nestedProxy, "nested proxy to empty target"); |
| checkPrototypeMutationFailure(nestedTarget, "nested target"); |
| |
| // revocable proxies |
| |
| var revocableTarget = global.evaluate("({})"); |
| var revocable = global.Proxy.revocable(revocableTarget, {}); |
| assertEq(setImmutablePrototype(revocable.proxy), true); |
| checkPrototypeMutationFailure(revocableTarget, "revocable target"); |
| checkPrototypeMutationFailure(revocable.proxy, "revocable proxy"); |
| |
| assertEq(revocable.revoke(), undefined); |
| try |
| { |
| setImmutablePrototype(revocable.proxy); |
| throw new Error("expected to throw on revoked proxy"); |
| } |
| catch (e) |
| { |
| // Note: This is a TypeError from |global|, because the proxy's |
| // |setImmutablePrototype| method is what actually throws here. |
| // (Usually the method simply sets |*succeeded| to false and the |
| // caller handles or throws as needed after that. But not here.) |
| assertEq(e instanceof global.TypeError, true, |
| "expected TypeError, instead threw " + e); |
| } |
| |
| var anotherRevocableTarget = global.evaluate("({})"); |
| assertEq(setImmutablePrototype(anotherRevocableTarget), true); |
| checkPrototypeMutationFailure(anotherRevocableTarget, "another revocable target"); |
| |
| var anotherRevocable = global.Proxy.revocable(anotherRevocableTarget, {}); |
| checkPrototypeMutationFailure(anotherRevocable.proxy, "another revocable target"); |
| |
| assertEq(anotherRevocable.revoke(), undefined); |
| try |
| { |
| var rv = setImmutablePrototype(anotherRevocable.proxy); |
| throw new Error("expected to throw on another revoked proxy, returned " + rv); |
| } |
| catch (e) |
| { |
| // NOTE: Again from |global|, as above. |
| assertEq(e instanceof global.TypeError, true, |
| "expected TypeError, instead threw " + e); |
| } |
| |
| // hated indirect proxies |
| var oldProto = {}; |
| var indirectProxy = global.Proxy.create({}, oldProto); |
| assertEq(setImmutablePrototype(indirectProxy), true); |
| assertEq(Object.getPrototypeOf(indirectProxy), oldProto); |
| checkPrototypeMutationFailure(indirectProxy, "indirectProxy"); |
| |
| var indirectFunctionProxy = global.Proxy.createFunction({}, function call() {}); |
| assertEq(setImmutablePrototype(indirectFunctionProxy), true); |
| assertEq(Object.getPrototypeOf(indirectFunctionProxy), global.Function.prototype); |
| checkPrototypeMutationFailure(indirectFunctionProxy, "indirectFunctionProxy"); |
| } |
| |
| var global = this; |
| runNormalTests(global); // same-global |
| |
| if (typeof newGlobal === "function") |
| { |
| var otherGlobal = newGlobal(); |
| var subsumingNothing = newGlobal({ principal: 0 }); |
| var subsumingEverything = newGlobal({ principal: ~0 }); |
| |
| runNormalTests(otherGlobal); // cross-global |
| runNormalTests(subsumingNothing); |
| runNormalTests(subsumingEverything); |
| } |
| |
| /******************************************************************************/ |
| |
| if (typeof reportCompare === "function") |
| reportCompare(true, true); |
| |
| print("Tests complete"); |