// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. function sloppyDefaultSet(o, p, v) { return o[p] = v } function sloppyReflectSet(o, p, v) { return Reflect.set(o, p, v) } function strictDefaultSet(o, p, v) { "use strict"; return o[p] = v } function strictReflectSet(o, p, v) { "use strict"; return Reflect.set(o, p, v) } sloppyDefaultSet.shouldThrow = false; sloppyReflectSet.shouldThrow = false; strictDefaultSet.shouldThrow = true; strictReflectSet.shouldThrow = false; sloppyDefaultSet.returnsBool = false; sloppyReflectSet.returnsBool = true; strictDefaultSet.returnsBool = false; strictReflectSet.returnsBool = true; function assertTrueIf(flag, x) { if (flag) assertTrue(x) } function assertFalseIf(flag, x) { if (flag) assertFalse(x) } function assertSetFails(mySet, o, p, v) { if (mySet.shouldThrow) { assertThrows(() => mySet(o, p, v), TypeError); } else { assertFalseIf(mySet.returnsBool, mySet(o, p, v)); } } function dataDescriptor(x) { return {value: x, writable: true, enumerable: true, configurable: true}; } function toKey(x) { if (typeof x === "symbol") return x; return String(x); } var properties = ["bla", "0", 1, Symbol(), {[Symbol.toPrimitive]() {return "a"}}]; function TestForwarding(handler, mySet) { assertTrue(undefined == handler.set); assertTrue(undefined == handler.getOwnPropertyDescriptor); assertTrue(undefined == handler.defineProperty); var target = {}; var proxy = new Proxy(target, handler); // Property does not exist on target. for (var p of properties) { assertTrueIf(mySet.returnsBool, mySet(proxy, p, 42)); assertSame(42, target[p]); } // Property exists as writable data on target. for (var p of properties) { assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0)); assertSame(0, target[p]); } // Property exists as non-writable data on target. for (var p of properties) { Object.defineProperty(target, p, {value: 42, configurable: true, writable: false}); assertSetFails(mySet, proxy, p, 42); assertSetFails(mySet, proxy, p, 0); assertEquals(42, target[p]); } }; (function () { // No trap. var handler = {}; TestForwarding(handler, sloppyDefaultSet); TestForwarding(handler, sloppyReflectSet); TestForwarding(handler, strictDefaultSet); TestForwarding(handler, strictReflectSet); })(); (function () { // "Undefined" trap. var handler = { set: null }; TestForwarding(handler, sloppyDefaultSet); TestForwarding(handler, sloppyReflectSet); TestForwarding(handler, strictDefaultSet); TestForwarding(handler, strictReflectSet); })(); function TestForwarding2(mySet) { // Check that setting on a proxy without "set" trap correctly triggers its // "getOwnProperty" trap and its "defineProperty" trap. var target = {}; var handler = {}; var observations = []; var proxy = new Proxy(target, handler); handler.getOwnPropertyDescriptor = function() { observations.push(arguments); return Reflect.getOwnPropertyDescriptor(...arguments); } handler.defineProperty = function() { observations.push(arguments); return Reflect.defineProperty(...arguments); } for (var p of properties) { mySet(proxy, p, 42); assertEquals(2, observations.length) assertArrayEquals([target, toKey(p)], observations[0]); assertSame(target, observations[0][0]); assertArrayEquals([target, toKey(p), dataDescriptor(42)], observations[1]); assertSame(target, observations[1][0]); observations = []; mySet(proxy, p, 42); assertEquals(2, observations.length) assertArrayEquals([target, toKey(p)], observations[0]); assertSame(target, observations[0][0]); assertArrayEquals([target, toKey(p), {value: 42}], observations[1]); assertSame(target, observations[1][0]); observations = []; } } TestForwarding2(sloppyDefaultSet); TestForwarding2(sloppyReflectSet); TestForwarding2(strictDefaultSet); TestForwarding2(strictReflectSet); function TestInvalidTrap(proxy, mySet) { for (var p of properties) { assertThrows(() => mySet(proxy, p, 42), TypeError); } } (function () { var target = {}; var handler = { set: true }; var proxy = new Proxy(target, handler); TestInvalidTrap(proxy, sloppyDefaultSet); TestInvalidTrap(proxy, sloppyReflectSet); TestInvalidTrap(proxy, strictDefaultSet); TestInvalidTrap(proxy, strictReflectSet); })(); function TestTrappingFalsish(mySet) { var target = {}; var handler = { set() {return ""} }; var proxy = new Proxy(target, handler); for (var p of properties) { assertSetFails(mySet, proxy, p, 42); } } TestTrappingFalsish(sloppyDefaultSet); TestTrappingFalsish(sloppyReflectSet); TestTrappingFalsish(strictDefaultSet); TestTrappingFalsish(strictReflectSet); function TestTrappingTrueish(mySet) { var target = {}; var handler = { set() {return 42} }; var proxy = new Proxy(target, handler); // Trap returns trueish and property does not exist in target. for (var p of properties) { assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0)); } // Trap returns trueish and target property is configurable or writable data. for (var p of properties) { Object.defineProperty(target, p, {configurable: true, writable: true}); assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0)); Object.defineProperty(target, p, {configurable: true, writable: false}); assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0)); Object.defineProperty(target, p, {configurable: false, writable: true}); assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0)); } } TestTrappingTrueish(sloppyDefaultSet); TestTrappingTrueish(sloppyReflectSet); TestTrappingTrueish(strictDefaultSet); TestTrappingTrueish(strictReflectSet); function TestTrappingTrueish2(mySet) { var target = {}; var handler = { set() {return 42} }; var proxy = new Proxy(target, handler); // Trap returns trueish but target property is frozen data. for (var p of properties) { Object.defineProperty(target, p, { configurable: false, writable: false, value: 0 }); assertThrows(() => mySet(proxy, p, 666), TypeError); // New value. assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0)); // Old value. } }; TestTrappingTrueish2(sloppyDefaultSet); TestTrappingTrueish2(sloppyReflectSet); TestTrappingTrueish2(strictDefaultSet); TestTrappingTrueish2(strictReflectSet); function TestTrappingTrueish3(mySet) { var target = {}; var handler = { set() {return 42} }; var proxy = new Proxy(target, handler); // Trap returns trueish and target property is configurable accessor. for (var p of properties) { Object.defineProperty(target, p, { configurable: true, set: undefined }); assertTrueIf(mySet.returnsBool, mySet(proxy, p, 0)); } // Trap returns trueish and target property is non-configurable accessor. for (var p of properties) { Object.defineProperty(target, p, { configurable: false, set: undefined }); assertThrows(() => mySet(proxy, p, 0)); } }; TestTrappingTrueish3(sloppyDefaultSet); TestTrappingTrueish3(sloppyReflectSet); TestTrappingTrueish3(strictDefaultSet); TestTrappingTrueish3(strictReflectSet); function TestTrapReceiverArgument(mySet) { var target = {}; var handler = {}; var observations = []; var proxy = new Proxy(target, handler); var object = Object.create(proxy); handler.set = function() { observations.push(arguments); return Reflect.set(...arguments); } for (var p of properties) { mySet(object, p, 42); assertEquals(1, observations.length) assertArrayEquals([target, toKey(p), 42, object], observations[0]); assertSame(target, observations[0][0]); assertSame(object, observations[0][3]); observations = []; } }; TestTrapReceiverArgument(sloppyDefaultSet); TestTrapReceiverArgument(sloppyReflectSet); TestTrapReceiverArgument(strictDefaultSet); TestTrapReceiverArgument(strictReflectSet); (function TestTrapReceiverArgument2() { // Check that non-object receiver is passed through as well. var target = {}; var handler = {}; var observations = []; var proxy = new Proxy(target, handler); handler.set = function() { observations.push(arguments); return Reflect.set(...arguments); } for (var p of properties) { for (var receiver of [null, undefined, 1]) { Reflect.set(proxy, p, 42, receiver); assertEquals(1, observations.length) assertArrayEquals([target, toKey(p), 42, receiver], observations[0]); assertSame(target, observations[0][0]); assertSame(receiver, observations[0][3]); observations = []; } } var object = Object.create(proxy); for (var p of properties) { for (var receiver of [null, undefined, 1]) { Reflect.set(object, p, 42, receiver); assertEquals(1, observations.length); assertArrayEquals([target, toKey(p), 42, receiver], observations[0]); assertSame(target, observations[0][0]); assertSame(receiver, observations[0][3]); observations = []; } } })(); function TestTargetProxy(mySet) { var q = new Proxy({}, {}); var proxy = new Proxy(q, { set: function(t, k, v) { return Reflect.set(t, k, v); } }); for (var p of properties) { assertTrueIf(mySet.returnsBool, mySet(proxy, p, 42)); assertSame(42, q[p]); } }; TestTargetProxy(sloppyDefaultSet); TestTargetProxy(sloppyReflectSet); TestTargetProxy(strictDefaultSet); TestTargetProxy(strictReflectSet); (function TestAccessorNoSet() { var target = { }; Object.defineProperty(target, 'prop', { get: function() { return 42; }, configurable: false }) var handler = { set: function() { return true; } } var proxy = new Proxy(target, handler); assertThrows(function() { proxy.prop = 0; }, TypeError); })(); (function TestProxyInPrototype() { var handler = { set: function(t, k, v) { Reflect.set(t, k, v); } }; var obj = {}; var proxy = new Proxy(obj, handler); var o = Object.create(proxy); for (var i = 0; i < 3; ++i) { o.prop = 42 + i; assertEquals(42 + i, obj.prop); } })(); (function TestProxyInPrototypeNoTrap() { var handler = { }; var obj = {}; var proxy = new Proxy(obj, handler); var o = Object.create(proxy); for (var i = 0; i < 3; ++i) { o.prop = 42 + i; assertEquals(42 + i, o.prop); assertEquals(undefined, obj.prop); } })(); // Note: this case is currently handled by runtime. (function TestDifferentHolder() { var obj = { '1337': 100 }; var handler = { set(target, name, value, receiver) { if (name != '1337') return Reflect.set(target, name, value, receiver); assertSame(target, obj); assertSame(receiver, p); return target[name] = value; } }; var p = new Proxy(obj, handler); for (var i = 0; i < 3; ++i) { assertEquals(42, p[1337] = 42); } })(); (function test32BitIndex() { var index = (1 << 31) + 1; var obj = {}; obj[index] = 42; var p = new Proxy(obj, {}); for (var i = 0; i < 3; ++i) { p[index] = 100; assertEquals(100, obj[index]); } })();