mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
11 years ago
42 changed files with 1859 additions and 137 deletions
@ -0,0 +1,155 @@ |
|||
/*
|
|||
* ES6 Proxy handlers can also be native Duktape/C functions. |
|||
* |
|||
* Just a very basic test to ensure proxy handlers work as expected. |
|||
*/ |
|||
|
|||
/*===
|
|||
*** test_1 (duk_safe_call) |
|||
top: 0 |
|||
top: 2 |
|||
handle_get: key=getTest |
|||
get result: rc=1, value=123 |
|||
top: 2 |
|||
handle_get: key=_getTest |
|||
get result: rc=1, value=fake_value |
|||
top: 2 |
|||
handle_set: key=setTest, val=testValue |
|||
set result: rc=1 |
|||
top: 2 |
|||
handle_set: key=_setTest, val=testValue |
|||
set result: rc=0 |
|||
top: 2 |
|||
handle_delete: key=deleteTest |
|||
delete result: rc=1 |
|||
top: 2 |
|||
handle_delete: key=_deleteTest |
|||
delete result: rc=0 |
|||
top: 2 |
|||
final top: 0 |
|||
==> rc=0, result='undefined' |
|||
===*/ |
|||
|
|||
static int handle_get(duk_context *ctx) { |
|||
/* 'this' binding: handler
|
|||
* [0]: target |
|||
* [1]: key |
|||
* [2]: receiver (proxy) |
|||
*/ |
|||
|
|||
const char *key = duk_to_string(ctx, 1); |
|||
|
|||
printf("handle_get: key=%s\n", key); |
|||
|
|||
if (key != NULL && key[0] == '_') { |
|||
/* Provide a fake value for properties beginning with an underscore. */ |
|||
duk_push_string(ctx, "fake_value"); |
|||
} else { |
|||
/* For others, read from target. */ |
|||
duk_dup(ctx, 1); |
|||
duk_get_prop(ctx, 0); |
|||
} |
|||
return 1; |
|||
} |
|||
|
|||
static int handle_set(duk_context *ctx) { |
|||
/* 'this' binding: handler
|
|||
* [0]: target |
|||
* [1]: key |
|||
* [2]: val |
|||
* [3]: receiver (proxy) |
|||
*/ |
|||
|
|||
const char *key = duk_to_string(ctx, 1); |
|||
const char *val = duk_to_string(ctx, 2); |
|||
|
|||
printf("handle_set: key=%s, val=%s\n", key, val); |
|||
|
|||
if (key != NULL && key[0] == '_') { |
|||
/* Indicate set failure for properties beginning with underscore. */ |
|||
duk_push_false(ctx); |
|||
} else { |
|||
duk_push_true(ctx); |
|||
} |
|||
return 1; |
|||
} |
|||
|
|||
static int handle_delete(duk_context *ctx) { |
|||
/* 'this' binding: handler
|
|||
* [0]: target |
|||
* [1]: key |
|||
*/ |
|||
|
|||
const char *key = duk_to_string(ctx, 1); |
|||
|
|||
printf("handle_delete: key=%s\n", key); |
|||
|
|||
if (key != NULL && key[0] == '_') { |
|||
/* Indicate delete failure for properties beginning with underscore. */ |
|||
duk_push_false(ctx); |
|||
} else { |
|||
duk_push_true(ctx); |
|||
} |
|||
return 1; |
|||
} |
|||
|
|||
static const duk_function_list_entry handler_funcs[] = { |
|||
{ "get", handle_get, 3 }, |
|||
{ "set", handle_set, 4 }, |
|||
{ "deleteProperty", handle_delete, 2 }, |
|||
{ NULL, NULL, 0 } |
|||
}; |
|||
|
|||
static int test_1(duk_context *ctx) { |
|||
int rc; |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
|
|||
/* new Proxy(target, handler) */ |
|||
duk_push_global_object(ctx); |
|||
duk_get_prop_string(ctx, -1, "Proxy"); |
|||
duk_push_object(ctx); /* target */ |
|||
duk_push_number(ctx, 123); |
|||
duk_put_prop_string(ctx, -2, "getTest"); |
|||
duk_push_object(ctx); /* handler */ |
|||
duk_put_function_list(ctx, -1, handler_funcs); |
|||
duk_new(ctx, 2); /* [ global Proxy target handler ] -> [ global proxy_object ] */ |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
rc = duk_get_prop_string(ctx, -1, "getTest"); |
|||
printf("get result: rc=%d, value=%s\n", rc, duk_to_string(ctx, -1)); |
|||
duk_pop(ctx); |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
rc = duk_get_prop_string(ctx, -1, "_getTest"); |
|||
printf("get result: rc=%d, value=%s\n", rc, duk_to_string(ctx, -1)); |
|||
duk_pop(ctx); |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
duk_push_string(ctx, "testValue"); |
|||
rc = duk_put_prop_string(ctx, -2, "setTest"); |
|||
printf("set result: rc=%d\n", rc); |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
duk_push_string(ctx, "testValue"); |
|||
rc = duk_put_prop_string(ctx, -2, "_setTest"); |
|||
printf("set result: rc=%d\n", rc); |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
rc = duk_del_prop_string(ctx, -1, "deleteTest"); |
|||
printf("delete result: rc=%d\n", rc); |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
rc = duk_del_prop_string(ctx, -1, "_deleteTest"); |
|||
printf("delete result: rc=%d\n", rc); |
|||
|
|||
printf("top: %d\n", duk_get_top(ctx)); |
|||
duk_pop_2(ctx); |
|||
printf("final top: %d\n", duk_get_top(ctx)); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
void test(duk_context *ctx) { |
|||
TEST_SAFE_CALL(test_1); |
|||
} |
@ -0,0 +1,85 @@ |
|||
/* |
|||
* The current ES6 Proxy subset behavior is skipped entirely for Duktape |
|||
* internal keys. Any property read/write/delete operations on internal |
|||
* keys behave as if the proxy handler did not exist, so that the operations |
|||
* are applied to the target object instead. |
|||
* |
|||
* Duktape accesses internal properties like _finalizer with ordinary |
|||
* property reads and writes, which causes some side effects when combining |
|||
* proxies and finalizers. In essence, you can only set a finalizer to the |
|||
* target object. |
|||
*/ |
|||
|
|||
/*=== |
|||
get for key: "foo" |
|||
target finalized (2) |
|||
===*/ |
|||
|
|||
function test1() { |
|||
var target = {}; |
|||
var proxy = new Proxy(target, { |
|||
get: function(targ, key, receiver) { |
|||
print('get for key:', Duktape.enc('jsonx', key)); |
|||
return targ[key]; |
|||
} |
|||
}); |
|||
|
|||
Duktape.fin(target, function () { print('target finalized (1)'); }); |
|||
|
|||
// Because there is no 'set' handler, the _finalizer write will go into
|
|||
// the target object and overwrites the previous finalizer.
|
|||
Duktape.fin(proxy, function () { print('target finalized (2)'); }); |
|||
|
|||
// When proxy is made unreachable, there is no _finalizer read because
|
|||
// the *proxy* does not have a finalizer property from Duktape's perspective
|
|||
// (there is no proxy handler for property existence check now, and the
|
|||
// current finalizer code uses duk_hobject_hasprop_raw() which ignores proxies).
|
|||
target = null; // reachable through proxy
|
|||
void proxy.foo; |
|||
proxy = null; |
|||
} |
|||
|
|||
try { |
|||
test1(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
/*=== |
|||
target finalized (2) |
|||
===*/ |
|||
|
|||
function test2() { |
|||
var target = {}; |
|||
var proxy = new Proxy(target, { |
|||
set: function(targ, key, val, receiver) { |
|||
print('set for key:', Duktape.enc('jsonx', key)); |
|||
targ[key] = val; |
|||
return true; |
|||
} |
|||
}); |
|||
|
|||
Duktape.fin(target, function () { print('target finalized (1)'); }); |
|||
|
|||
// The 'set' handler is skipped for internal keys, so the finalizer is set
|
|||
// into the target again, overwriting the previous finalizer. Nothing is
|
|||
// logged by the 'set' handler.
|
|||
Duktape.fin(proxy, function () { print('target finalized (2)'); }); |
|||
|
|||
// Like in test1(), no finalizer read for proxy.
|
|||
target = null; // reachable through proxy
|
|||
void proxy.foo; |
|||
proxy = null; |
|||
} |
|||
|
|||
try { |
|||
test2(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
/*=== |
|||
finished |
|||
===*/ |
|||
|
|||
print('finished'); |
@ -0,0 +1,816 @@ |
|||
/* |
|||
* Proxy (ES6 draft) subset |
|||
*/ |
|||
|
|||
/*=== |
|||
proxy existence |
|||
Proxy exists: true function |
|||
Proxy.length: 2 |
|||
Proxy.name: Proxy |
|||
Proxy desc: writable=true enumerable=false configurable=true |
|||
Proxy.revocable exists: false undefined |
|||
===*/ |
|||
|
|||
function proxyExistenceTest() { |
|||
var pd; |
|||
|
|||
print('Proxy exists:', 'Proxy' in this, typeof this.Proxy); |
|||
print('Proxy.length:', this.Proxy.length); |
|||
print('Proxy.name:', this.Proxy.name); |
|||
pd = Object.getOwnPropertyDescriptor(this, 'Proxy'); |
|||
if (pd) { |
|||
print('Proxy desc:', 'writable=' + pd.writable, 'enumerable=' + pd.enumerable, |
|||
'configurable=' + pd.configurable); |
|||
} |
|||
|
|||
print('Proxy.revocable exists:', 'revocable' in this.Proxy, typeof this.Proxy.revocable); |
|||
/* |
|||
print('Proxy.revocable.length:', this.Proxy.revocable.length); |
|||
print('Proxy.revocable.name:', this.Proxy.revocable.name); |
|||
pd = Object.getOwnPropertyDescriptor(this.Proxy, 'revocable'); |
|||
if (pd) { |
|||
print('Proxy.revocable desc:', 'writable=' + pd.writable, 'enumerable=' + pd.enumerable, |
|||
'configurable=' + pd.configurable); |
|||
} |
|||
*/ |
|||
} |
|||
|
|||
print('proxy existence'); |
|||
|
|||
try { |
|||
proxyExistenceTest(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
/*=== |
|||
subset proxy |
|||
object |
|||
[object Object] |
|||
get foo: 123 |
|||
get 1000: thousand |
|||
get special: specialValue |
|||
counts: get=0 set=0 del=0 |
|||
handler.get: true true string foo true |
|||
get foo: fake-for-key-foo |
|||
handler.get: true true string bar true |
|||
get bar: fake-for-key-bar |
|||
handler.get: true true string 1000 true |
|||
get 1000 (string): fake-for-key-1000 |
|||
handler.get: true true number 1000 true |
|||
get 1000 (number): 2000 |
|||
counts: get=4 set=0 del=0 |
|||
handler.get: true true string special true |
|||
get special: specialValue |
|||
handler.get: true true string special true |
|||
get special: uncaughtValue |
|||
target.special: uncaughtValue |
|||
counts: get=6 set=0 del=0 |
|||
handler.set: true true string foo number 1001 true |
|||
handler.set: true true string bar number 1002 true |
|||
handler.set: true true string quux number 1003 true |
|||
handler.set: true true number 123 string foo true |
|||
handler.set: true true string rejectSet1 string reject true |
|||
handler.set: true true string rejectSet2 string reject true |
|||
handler.set: true true string rejectSet1 string throw true |
|||
TypeError |
|||
handler.set: true true string rejectSet2 string throw true |
|||
TypeError |
|||
counts: get=6 set=8 del=0 |
|||
target.foo: 123 |
|||
target.bar: 1002 |
|||
target.quux: 1003 |
|||
target[123]: foo |
|||
target.rejectSet1: undefined |
|||
target.rejectSet2: undefined |
|||
counts: get=6 set=8 del=0 |
|||
true |
|||
target.foo: undefined |
|||
target.bar: 1002 |
|||
counts: get=6 set=8 del=0 |
|||
handler.deleteProperty: true true string rejectDel1 |
|||
false |
|||
handler.deleteProperty: true true string rejectDel2 |
|||
false |
|||
handler.deleteProperty: true true string rejectDel1 |
|||
TypeError |
|||
handler.deleteProperty: true true string rejectDel2 |
|||
TypeError |
|||
handler.deleteProperty: true true string foo |
|||
true |
|||
handler.deleteProperty: true true number 1234 |
|||
true |
|||
counts: get=6 set=8 del=6 |
|||
target.rejectDel1: reject1 |
|||
target.rejectDel2: reject2 |
|||
target.foo 123 |
|||
target[1234] undefined |
|||
===*/ |
|||
|
|||
/* Test simple usage of the current Proxy subset. Does not exercise the |
|||
* hook behaviors related to checking for conflicting properties in the |
|||
* target object. |
|||
*/ |
|||
function subsetProxyTest() { |
|||
var getCount = 0; |
|||
var setCount = 0; |
|||
var deleteCount = 0; |
|||
var target = { foo: 123, '1000': 'thousand', special: 'specialValue' }; |
|||
var handler = {}; |
|||
var proxy = new Proxy(target, handler); |
|||
|
|||
function printCounts() { |
|||
print('counts:', 'get=' + getCount, 'set=' + setCount, 'del=' + deleteCount); |
|||
} |
|||
|
|||
print(typeof proxy); |
|||
print(Object.prototype.toString.call(proxy)); // XXX: now class is 'Object'
|
|||
|
|||
// without a 'get' hook, reads go through
|
|||
print('get foo:', proxy.foo); |
|||
print('get 1000:', proxy[1000]); |
|||
print('get special:', proxy.special); |
|||
|
|||
// handler 'get' hook
|
|||
handler.get = function(targ, key, receiver) { |
|||
print('handler.get:', this === handler, targ === target, typeof key, key, receiver === proxy); |
|||
getCount++; |
|||
if (typeof key === 'number') { |
|||
return 2 * (+key); |
|||
} else if (key === 'special') { |
|||
return targ.special; |
|||
} else { |
|||
return 'fake-for-key-' + key; |
|||
} |
|||
}; |
|||
|
|||
// Get tests
|
|||
printCounts(); |
|||
print('get foo:', proxy.foo); |
|||
print('get bar:', proxy.bar); |
|||
print('get 1000 (string):', proxy['1000']); |
|||
print('get 1000 (number):', proxy[1000]); |
|||
printCounts(); |
|||
|
|||
// without a 'set' hook, writes go through
|
|||
print('get special:', proxy.special); |
|||
proxy.special = 'uncaughtValue'; // goes into 'target'
|
|||
print('get special:', proxy.special); |
|||
print('target.special:', target.special); |
|||
|
|||
// handler 'set' hook
|
|||
handler.set = function(targ, key, val, receiver) { |
|||
print('handler.set:', this === handler, targ === target, typeof key, key, typeof val, val, receiver === proxy); |
|||
setCount++; |
|||
if (key === 'rejectSet1') { |
|||
// False: indicate that property set is rejected (TypeError in strict mode).
|
|||
// No change happens to the target object.
|
|||
return false; |
|||
} |
|||
if (key === 'rejectSet2') { |
|||
// Same for any 'falsy' value.
|
|||
return 0; |
|||
} |
|||
if (key === 'foo') { |
|||
// True: indicate that property set is allowed, but no change happens
|
|||
// to the target object if we don't do it explicitly here.
|
|||
return true; |
|||
} |
|||
|
|||
// Setting to target must be done explicitly.
|
|||
targ[key] = val; |
|||
return true; |
|||
}; |
|||
|
|||
// Set tests
|
|||
printCounts(); |
|||
proxy.foo = 1001; |
|||
proxy.bar = 1002; |
|||
proxy.quux = 1003; |
|||
proxy[123] = 'foo'; |
|||
proxy.rejectSet1 = 'reject'; // reject silently in non-strict mode
|
|||
proxy.rejectSet2 = 'reject'; |
|||
try { |
|||
(function () { 'use strict'; proxy.rejectSet1 = 'throw'; })(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
|||
try { |
|||
(function () { 'use strict'; proxy.rejectSet2 = 'throw'; })(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
|||
printCounts(); |
|||
print('target.foo:', target.foo); |
|||
print('target.bar:', target.bar); |
|||
print('target.quux:', target.quux); |
|||
print('target[123]:', target[123]); |
|||
print('target.rejectSet1:', target.rejectSet1); |
|||
print('target.rejectSet2:', target.rejectSet2); |
|||
|
|||
// without a 'deleteProperty' hook, deletes go through
|
|||
printCounts(); |
|||
print(delete proxy.foo); |
|||
print('target.foo:', target.foo); |
|||
print('target.bar:', target.bar); |
|||
printCounts(); |
|||
|
|||
// handler 'deleteProperty' hook
|
|||
handler.deleteProperty = function(targ, key) { |
|||
print('handler.deleteProperty:', this === handler, targ === target, typeof key, key); |
|||
deleteCount++; |
|||
if (key === 'rejectDel1') { |
|||
// False return code indicates delete is rejected.
|
|||
return false; |
|||
} |
|||
if (key === 'rejectDel2') { |
|||
// Same for any 'falsy' value.
|
|||
return 0; |
|||
} |
|||
if (key === 'foo') { |
|||
// True return code indicates delete is accepted (but it has no
|
|||
// effect on the target unless we delete the property from the
|
|||
// target here).
|
|||
return true; |
|||
} |
|||
|
|||
// Deletion to target must be done explicitly.
|
|||
delete targ[key]; |
|||
return true; |
|||
}; |
|||
|
|||
target.rejectDel1 = 'reject1'; |
|||
target.rejectDel2 = 'reject2'; |
|||
target.foo = 123; |
|||
target[1234] = 4321; |
|||
print(delete proxy.rejectDel1); |
|||
print(delete proxy.rejectDel2); |
|||
try { |
|||
(function () { 'use strict'; print(delete proxy.rejectDel1); })(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
|||
try { |
|||
(function () { 'use strict'; print(delete proxy.rejectDel2); })(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
|||
print(delete proxy.foo); // allowed, but no effect on target
|
|||
print(delete proxy[1234]); // allowed, deletes value on target
|
|||
printCounts(); |
|||
print('target.rejectDel1:', target.rejectDel1); |
|||
print('target.rejectDel2:', target.rejectDel2); |
|||
print('target.foo', target.foo); |
|||
print('target[1234]', target[1234]); |
|||
} |
|||
|
|||
print('subset proxy'); |
|||
|
|||
try { |
|||
subsetProxyTest(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
/*=== |
|||
hook post-checks |
|||
get hook |
|||
property1: success, value: whatever |
|||
property2: success, value: whatever |
|||
property3: success, value: whatever |
|||
property4: success, value: whatever |
|||
property5: success, value: 42 |
|||
property6: success, value: NaN |
|||
property7: success, value: 0 |
|||
property1: success, value: whatever |
|||
property2: success, value: whatever |
|||
property3: success, value: whatever |
|||
property4: success, value: whatever |
|||
property5: TypeError |
|||
property6: TypeError |
|||
property7: TypeError |
|||
accessor1: success, value: undefined |
|||
accessor2: success, value: undefined |
|||
accessor3: success, value: undefined |
|||
accessor4: success, value: undefined |
|||
accessor5: success, value: undefined |
|||
accessor6: success, value: undefined |
|||
accessor7: success, value: undefined |
|||
accessor1: success, value: 123 |
|||
accessor2: success, value: 123 |
|||
accessor3: success, value: 123 |
|||
accessor4: success, value: 123 |
|||
accessor5: TypeError |
|||
accessor6: TypeError |
|||
accessor7: success, value: 123 |
|||
set hook |
|||
property1: success, property1 set to 42 |
|||
property2: success, property2 set to 42 |
|||
property3: success, property3 set to 42 |
|||
property4: success, property4 set to 42 |
|||
property5: success, property5 set to 42 |
|||
property6: success, property6 set to NaN |
|||
property7: success, property7 set to 0 |
|||
property1: success, property1 set to 142 |
|||
property2: success, property2 set to 142 |
|||
property3: success, property3 set to 142 |
|||
property4: success, property4 set to 142 |
|||
property5: TypeError trying to set value to 142 |
|||
property6: TypeError trying to set value to Infinity |
|||
property7: TypeError trying to set value to 0 |
|||
accessor1: success, accessor1 set to whatever |
|||
accessor2: success, accessor2 set to whatever |
|||
accessor3: success, accessor3 set to whatever |
|||
accessor4: success, accessor4 set to whatever |
|||
accessor5: TypeError trying to set value to whatever |
|||
accessor6: success, accessor6 set to whatever |
|||
accessor7: TypeError trying to set value to whatever |
|||
deleteProperty hook |
|||
property1: success, result: true |
|||
property2: success, result: true |
|||
property3: TypeError |
|||
property4: TypeError |
|||
property5: TypeError |
|||
property6: TypeError |
|||
property7: TypeError |
|||
accessor1: success, result: true |
|||
accessor2: success, result: true |
|||
accessor3: TypeError |
|||
accessor4: success, result: true |
|||
accessor5: TypeError |
|||
accessor6: TypeError |
|||
accessor7: TypeError |
|||
===*/ |
|||
|
|||
/* If a hook exists and is successfully called, ES6 specifies interesting |
|||
* post-hook behavior where a TypeError may be raised if the hook return |
|||
* value conflicts in some way with a property of the same name in the |
|||
* target object. |
|||
*/ |
|||
|
|||
function makeDataTestObject() { |
|||
var obj = {}; |
|||
|
|||
Object.defineProperties(obj, { |
|||
// Various properties whose behavior to compare against the hooks
|
|||
|
|||
property1: { |
|||
writable: true, enumerable: true, configurable: true, value: 42 |
|||
}, |
|||
property2: { |
|||
writable: false, enumerable: true, configurable: true, value: 42 |
|||
}, |
|||
property3: { |
|||
writable: true, enumerable: true, configurable: false, value: 42 |
|||
}, |
|||
property4: { |
|||
writable: true, enumerable: false, configurable: false, value: 42 |
|||
}, |
|||
property5: { |
|||
writable: false, enumerable: true, configurable: false, value: 42 |
|||
}, |
|||
property6: { |
|||
writable: false, enumerable: true, configurable: false, value: NaN |
|||
}, |
|||
property7: { |
|||
writable: false, enumerable: true, configurable: false, value: +0 |
|||
} |
|||
}); |
|||
|
|||
return obj; |
|||
} |
|||
|
|||
function makeAccessorTestObject() { |
|||
var obj = {}; |
|||
|
|||
function getter() { |
|||
print('getter called'); |
|||
return 'getter-value'; |
|||
} |
|||
|
|||
function setter(v) { |
|||
print('setter called:', v); |
|||
} |
|||
|
|||
Object.defineProperties(obj, { |
|||
// Various properties whose behavior to compare against the hooks
|
|||
accessor1: { |
|||
enumerable: true, configurable: true, set: setter, get: getter |
|||
}, |
|||
accessor2: { |
|||
enumerable: false, configurable: true, set: setter, get: getter |
|||
}, |
|||
accessor3: { |
|||
enumerable: true, configurable: false, set: setter, get: getter |
|||
}, |
|||
accessor4: { |
|||
enumerable: true, configurable: true, set: undefined, get: undefined |
|||
}, |
|||
accessor5: { |
|||
enumerable: true, configurable: false, set: undefined, get: undefined |
|||
}, |
|||
accessor6: { |
|||
enumerable: true, configurable: false, set: setter, get: undefined |
|||
}, |
|||
accessor7: { |
|||
enumerable: true, configurable: false, set: undefined, get: getter |
|||
} |
|||
}); |
|||
|
|||
return obj; |
|||
} |
|||
|
|||
function getHookPostChecksTest() { |
|||
var target, handler, proxy; |
|||
|
|||
function getTest(proxy, propname) { |
|||
try { |
|||
var val = proxy[propname]; |
|||
print(propname + ': success, value:', val); |
|||
} catch (e) { |
|||
print(propname + ':', e.name); |
|||
} |
|||
} |
|||
|
|||
/* 'get' return value is rejected if the target has a property of the |
|||
* same name and the property: |
|||
* |
|||
* - Is a data property, is not configurable, is not writable, and |
|||
* hook provided value does not match with the current value |
|||
* (as compared with SameValue). |
|||
* |
|||
* - Is an accessor property, is not configurable, getter is not |
|||
* defined, and hook provided value is not undefined. |
|||
*/ |
|||
|
|||
/* |
|||
* Data properties |
|||
*/ |
|||
|
|||
target = makeDataTestObject(); |
|||
|
|||
handler = {}; |
|||
handler.get = function (targ, key, receiver) { |
|||
if (key === 'property5') { return 42; } // same value, no error
|
|||
if (key === 'property6') { return NaN; } // same value, no error
|
|||
if (key === 'property7') { return +0; } // same value, no error
|
|||
return 'whatever'; // differs from all values
|
|||
}; |
|||
proxy = new Proxy(target, handler); |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
getTest(proxy, propname); |
|||
}); |
|||
|
|||
handler = {}; |
|||
handler.get = function (targ, key, receiver) { |
|||
if (key === 'property5') { return 41; } // different value, error
|
|||
if (key === 'property6') { return undefined; } // different value, error
|
|||
if (key === 'property7') { return -0; } // different value, error
|
|||
return 'whatever'; // differs from all values
|
|||
}; |
|||
proxy = new Proxy(target, handler); |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
getTest(proxy, propname); |
|||
}); |
|||
|
|||
/* |
|||
* Accessor properties |
|||
*/ |
|||
|
|||
target = makeAccessorTestObject(); |
|||
|
|||
handler = {}; |
|||
proxy = new Proxy(target, handler); |
|||
handler.get = function (targ, key, receiver) { |
|||
// If trapResult is undefined, post-hook checks always pass
|
|||
return undefined; |
|||
} |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
getTest(proxy, propname); |
|||
}); |
|||
|
|||
handler = {}; |
|||
proxy = new Proxy(target, handler); |
|||
handler.get = function (targ, key, receiver) { |
|||
// If trapResult is not undefined, post-hook checks cause a TypeError
|
|||
// if property is non-configurable and getter is undefined.
|
|||
return 123; |
|||
} |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
getTest(proxy, propname); |
|||
}); |
|||
} |
|||
|
|||
function setHookPostChecksTest() { |
|||
var target, handler, proxy; |
|||
|
|||
function setTest(proxy, propname, propvalue) { |
|||
try { |
|||
proxy[propname] = propvalue; |
|||
print(propname + ': success,', propname, 'set to', propvalue); |
|||
} catch (e) { |
|||
print(propname + ':', e.name, 'trying to set value to', propvalue); |
|||
} |
|||
} |
|||
|
|||
/* 'set' is rejected if the target has a property of the same name |
|||
* and the property: |
|||
* |
|||
* - Is a data property, is not configurable, is not writable, and |
|||
* the assignment value does not match with the current value |
|||
* (as compared with SameValue). |
|||
* |
|||
* - Is an accessor property, is not configurable, and setter is not |
|||
* defined. Unlike for 'get' the value does not matter. |
|||
*/ |
|||
|
|||
/* |
|||
* Data properties |
|||
*/ |
|||
|
|||
target = makeDataTestObject(); |
|||
|
|||
handler = {}; |
|||
proxy = new Proxy(target, handler); |
|||
handler.set = function (targ, key, val, receiver) { |
|||
// If 'false' is returned, property write is rejected and the post-hook
|
|||
// behavior doesn't activate at all, so always return true here.
|
|||
return true; |
|||
} |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
// Choose test value to match current value (with SameValue).
|
|||
// No TypeError is triggered even if other conditions are met.
|
|||
|
|||
var propval = { |
|||
property1: 42, |
|||
property2: 42, |
|||
property3: 42, |
|||
property4: 42, |
|||
property5: 42, |
|||
property6: NaN, |
|||
property7: +0 |
|||
}[propname]; |
|||
|
|||
setTest(proxy, propname, propval); |
|||
}); |
|||
|
|||
handler.set = function (targ, key, val, receiver) { |
|||
return true; |
|||
}; |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
// Choose test value to match current value (with SameValue).
|
|||
// No TypeError is triggered even if other conditions are met.
|
|||
|
|||
var propval = { |
|||
property1: 142, |
|||
property2: 142, |
|||
property3: 142, |
|||
property4: 142, |
|||
property5: 142, |
|||
property6: 1/0, |
|||
property7: -0 // SameValue, even sign matters
|
|||
}[propname]; |
|||
|
|||
setTest(proxy, propname, propval); |
|||
}); |
|||
|
|||
/* |
|||
* Accessor properties |
|||
*/ |
|||
|
|||
target = makeAccessorTestObject(); |
|||
|
|||
handler = {}; |
|||
proxy = new Proxy(target, handler); |
|||
handler.set = function (targ, key, val, receiver) { |
|||
// If 'false' is returned, property write is rejected and the post-hook
|
|||
// behavior doesn't activate at all, so always return true here.
|
|||
return true; |
|||
} |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
// For accessor + 'set' hook, property value does not matter.
|
|||
setTest(proxy, propname, 'whatever'); |
|||
}); |
|||
} |
|||
|
|||
function deleteHookPostChecksTest() { |
|||
var target, handler, proxy; |
|||
|
|||
function deleteTest(proxy, propname) { |
|||
try { |
|||
print(propname + ': success, result:', delete proxy[propname]); |
|||
} catch (e) { |
|||
print(propname + ':', e.name); |
|||
} |
|||
} |
|||
|
|||
/* 'deleteProperty' is rejected if the target has a property of the |
|||
* same name and the property: |
|||
* |
|||
* - Is not configurable |
|||
*/ |
|||
|
|||
/* |
|||
* Data properties |
|||
*/ |
|||
|
|||
target = makeDataTestObject(); |
|||
|
|||
handler = {}; |
|||
proxy = new Proxy(target, handler); |
|||
handler.deleteProperty = function (targ, key, val, receiver) { |
|||
// If 'false' is returned, property delete is rejected and the post-hook
|
|||
// behavior doesn't activate at all, so always return true here.
|
|||
return true; |
|||
} |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
deleteTest(proxy, propname); |
|||
}); |
|||
|
|||
/* |
|||
* Accessor properties |
|||
*/ |
|||
|
|||
target = makeAccessorTestObject(); |
|||
|
|||
handler = {}; |
|||
proxy = new Proxy(target, handler); |
|||
handler.deleteProperty = function (targ, key, val, receiver) { |
|||
// If 'false' is returned, property delete is rejected and the post-hook
|
|||
// behavior doesn't activate at all, so always return true here.
|
|||
return true; |
|||
} |
|||
Object.getOwnPropertyNames(target).forEach(function (propname) { |
|||
deleteTest(proxy, propname); |
|||
}); |
|||
} |
|||
|
|||
print('hook post-checks'); |
|||
|
|||
try { |
|||
print('get hook'); |
|||
getHookPostChecksTest(); |
|||
print('set hook'); |
|||
setHookPostChecksTest(); |
|||
print('deleteProperty hook'); |
|||
deleteHookPostChecksTest(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
/*=== |
|||
recursive proxies |
|||
TypeError |
|||
TypeError |
|||
===*/ |
|||
|
|||
/* Currently Duktape doesn't allow a proxy as either a handler or a target. |
|||
* This makes it easier to implement because there is no arbitrary depth C |
|||
* recursion when doing proxy lookups. |
|||
*/ |
|||
|
|||
function proxyHandlerTest() { |
|||
var target = { foo: 123 }; |
|||
var handler = new Proxy({}, {}); |
|||
var proxy = new Proxy(target, handler); |
|||
} |
|||
|
|||
function proxyTargetTest() { |
|||
var target = new Proxy({}, {}); |
|||
var handler = {}; |
|||
var proxy = new Proxy(target, handler); |
|||
} |
|||
|
|||
print('recursive proxies'); |
|||
|
|||
try { |
|||
proxyHandlerTest(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
|||
|
|||
try { |
|||
proxyTargetTest(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
|||
|
|||
/*=== |
|||
getter handler |
|||
handler.get: true true foo true |
|||
proxy.foo: dummy-value |
|||
===*/ |
|||
|
|||
/* A getter as a handler property. No reason why this wouldn't work but |
|||
* test just in case. |
|||
*/ |
|||
|
|||
function getterHandlerTest() { |
|||
var target = { foo: 123 }; |
|||
var handler = {}; |
|||
var proxy = new Proxy(target, handler); |
|||
|
|||
Object.defineProperty(handler, 'get', { |
|||
enumerable: true, |
|||
configurable: true, |
|||
get: function () { |
|||
return function (targ, key, receiver) { |
|||
print('handler.get:', this === handler, targ === target, key, receiver === proxy); |
|||
return 'dummy-value'; |
|||
}; |
|||
} |
|||
}); |
|||
|
|||
print('proxy.foo:', proxy.foo); |
|||
} |
|||
|
|||
print('getter handler'); |
|||
|
|||
try { |
|||
getterHandlerTest(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
/*=== |
|||
non-callable handler |
|||
TypeError |
|||
===*/ |
|||
|
|||
/* A non-callable handler property. This is not checked during proxy creation |
|||
* and should cause a TypeError. |
|||
*/ |
|||
|
|||
function nonCallableHandlerTest() { |
|||
var target = { foo: 123 }; |
|||
var handler = { get: 'dummy' }; |
|||
var proxy = new Proxy(target, handler); |
|||
print('proxy.foo:', proxy.foo); |
|||
} |
|||
|
|||
print('non-callable handler'); |
|||
|
|||
try { |
|||
nonCallableHandlerTest(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
|||
|
|||
/*=== |
|||
throwing handler |
|||
handler.get: about to throw |
|||
URIError: fake error |
|||
===*/ |
|||
|
|||
/* Handler function throws. Nothing special here. */ |
|||
|
|||
function throwingHandlerTest() { |
|||
var target = { foo: 123 }; |
|||
var handler = { |
|||
get: function() { |
|||
print('handler.get: about to throw'); |
|||
throw new URIError('fake error'); |
|||
} |
|||
}; |
|||
var proxy = new Proxy(target, handler); |
|||
print('proxy.foo:', proxy.foo); |
|||
|
|||
} |
|||
|
|||
print('throwing handler'); |
|||
|
|||
try { |
|||
throwingHandlerTest(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
|||
|
|||
/*=== |
|||
proxy revocation |
|||
handler.get: true true foo true |
|||
proxy.foo: dummy-value |
|||
===*/ |
|||
|
|||
/* Revoked proxy. */ |
|||
|
|||
function proxyRevocationTest() { |
|||
var target = { foo: 123 }; |
|||
var handler = { |
|||
get: function(targ, key, receiver) { |
|||
print('handler.get:', this === handler, targ === target, key, receiver === proxy); |
|||
return 'dummy-value'; |
|||
} |
|||
}; |
|||
var proxy = new Proxy(target, handler); |
|||
print('proxy.foo:', proxy.foo); |
|||
|
|||
// FIXME: unimplemented
|
|||
} |
|||
|
|||
print('proxy revocation'); |
|||
|
|||
try { |
|||
proxyRevocationTest(); |
|||
} catch (e) { |
|||
print(e.name); |
|||
} |
@ -0,0 +1,28 @@ |
|||
/* |
|||
* Rejected delete property had a bug which caused the value stack to be out |
|||
* of balance when returning to the bytecode executor. This test used to fail |
|||
* with assertions enabled, when the base object for deletion was a string |
|||
* (e.g. attempt to delete str.length or a virtual index). |
|||
*/ |
|||
|
|||
/*=== |
|||
true |
|||
false |
|||
false |
|||
===*/ |
|||
|
|||
function test() { |
|||
// must be non-strict so that delete failure is silent
|
|||
|
|||
var str = 'foo'; |
|||
|
|||
print(delete str.nonexistent); |
|||
print(delete str.length); |
|||
print(delete str[1]); |
|||
} |
|||
|
|||
try { |
|||
test(); |
|||
} catch (e) { |
|||
print(e); |
|||
} |
@ -0,0 +1,60 @@ |
|||
/*
|
|||
* Proxy built-in (ES6 draft) |
|||
*/ |
|||
|
|||
#include "duk_internal.h" |
|||
|
|||
#if defined(DUK_USE_ES6_PROXY) |
|||
duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) { |
|||
duk_hobject *h_target; |
|||
duk_hobject *h_handler; |
|||
|
|||
if (!duk_is_constructor_call(ctx)) { |
|||
return DUK_RET_TYPE_ERROR; |
|||
} |
|||
|
|||
/* Reject a proxy object as the target because it would need
|
|||
* special handler in property lookups. (ES6 has no such restriction) |
|||
*/ |
|||
h_target = duk_require_hobject(ctx, 0); |
|||
DUK_ASSERT(h_target != NULL); |
|||
if (DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(h_target)) { |
|||
return DUK_RET_TYPE_ERROR; |
|||
} |
|||
|
|||
/* Reject a proxy object as the handler because it would cause
|
|||
* potentially unbounded recursion. (ES6 has no such restriction) |
|||
*/ |
|||
h_handler = duk_require_hobject(ctx, 1); |
|||
DUK_ASSERT(h_handler != NULL); |
|||
if (DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(h_handler)) { |
|||
return DUK_RET_TYPE_ERROR; |
|||
} |
|||
|
|||
/* XXX: the returned value is exotic in ES6 (draft), but we use a
|
|||
* simple object here with no prototype. Without a prototype, |
|||
* [[DefaultValue]] coercion fails which is abit confusing. |
|||
* No callable check/handling in the current Proxy subset. |
|||
*/ |
|||
(void) duk_push_object_helper_proto(ctx, |
|||
DUK_HOBJECT_FLAG_SPECIAL_PROXYOBJ | |
|||
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), |
|||
NULL); |
|||
DUK_ASSERT_TOP(ctx, 3); |
|||
|
|||
/* Proxy target */ |
|||
duk_dup(ctx, 0); |
|||
duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_WC); |
|||
|
|||
/* Proxy handler */ |
|||
duk_dup(ctx, 1); |
|||
duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_HANDLER, DUK_PROPDESC_FLAGS_WC); |
|||
|
|||
return 1; /* replacement handler */ |
|||
} |
|||
#else /* DUK_USE_ES6_PROXY */ |
|||
duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) { |
|||
DUK_UNREF(ctx); |
|||
return DUK_RET_UNSUPPORTED_ERROR; |
|||
} |
|||
#endif /* DUK_USE_ES6_PROXY */ |
@ -1,23 +1,47 @@ |
|||
<h1 id="es6features">Ecmascript E6 features</h1> |
|||
|
|||
<p>This section describes the small set of features Duktape borrows from the |
|||
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html">current ES6 draft</a>. |
|||
These features are not fully compliant; the intent is to minimize custom features |
|||
and to align with the coming ES6 specification.</p> |
|||
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html">current ES6 draft</a> |
|||
("Version: Rev 23, April 5, 2014 Draft"). These features are not fully compliant; |
|||
the intent is to minimize custom features and to align with the coming ES6 specification.</p> |
|||
|
|||
<h2 id="es6-proto">Object.setPrototypeOf and Object.prototype.__proto__</h2> |
|||
|
|||
<p><a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.setprototypeof">Object.setPrototypeOf</a> |
|||
allows user to set the internal prototype of an object which is not supported in |
|||
Ecmascript E5. The Ecmascript E6 draft also provides |
|||
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.__proto__">Object.prototype.__proto__</a> |
|||
which is an accessor property (setter/getter) which provides the same functionality |
|||
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.__proto__">Object.prototype.__proto__</a>, |
|||
an accessor property (setter/getter) which provides the same functionality |
|||
but is compatible with existing code base which has relied on a non-standard |
|||
<code>__proto__</code> property for a while. Duktape does not support the |
|||
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-__proto___-property-names-in-object-initializers">__proto__ property name in an object initializer</a>.</p> |
|||
|
|||
<p>These custom features can be disabled with feature options.</p> |
|||
<p>These custom features can be disabled with the feature options |
|||
<code>DUK_OPT_NO_OBJECT_ES6_SETPROTOTYPEOF</code> and |
|||
<code>DUK_OPT_NO_OBJECT_ES6_PROTO_PROPERTY</code>.</p> |
|||
|
|||
<h2 id="es6-proxy">Proxy object (subset)</h2> |
|||
|
|||
<p>FIXME.</p> |
|||
<p>The Ecmascript E6 <code>Proxy</code> object allows property virtualization |
|||
and fine-grained access control for accessing an underlying plain object. |
|||
Duktape implements a strict subset of the <code>Proxy</code> object from the |
|||
ES6 draft (Rev 23).</p> |
|||
|
|||
<p>Limitations include:</p> |
|||
<ul> |
|||
<li>Limited to <code>get</code>, <code>set</code>, <code>deleteProperty</code> |
|||
handler methods; ES6 draft allows a lot more, e.g. to interact with |
|||
properties being called or constructed, enumeration, etc. This causes |
|||
odd behavior if you enumerate a proxy object, call |
|||
<code>Object.defineProperty()</code> with the proxy object as an argument, |
|||
etc.</li> |
|||
<li>Proxy revocation feature of ES6 draft is not supported.</li> |
|||
<li>The target and handler objects given to <code>new Proxy()</code> cannot |
|||
be proxy objects themselves. ES6 poses no such limitation, but Duktape |
|||
enforces it to simplify the internal implementation.</li> |
|||
<li>As ES6 is still in a draft phase so the subset implemented by Duktape |
|||
may not be forwards compatible.</li> |
|||
</ul> |
|||
|
|||
<p>This custom feature can be disabled with the feature option |
|||
<code>DUK_OPT_NO_ES6_PROXY</code>.</p> |
|||
|
@ -0,0 +1,218 @@ |
|||
<h2 id="propertyvirtualization">Property virtualization</h2> |
|||
|
|||
<p>This section describes the two different mechanisms Duktape provides |
|||
for interacting with property accesses programmatically.</p> |
|||
|
|||
<h3 id="virtualization-accessors">Ecmascript E5 accessor properties (getters and setters)</h3> |
|||
|
|||
<p>Ecmascript Edition 5 provides <b>accessor properties</b> (also called |
|||
"setters and getters") which allow property read/write operations to be |
|||
captured by a user function.</p> |
|||
|
|||
<p>For example, to capture writes to <code>obj.color</code> so that you |
|||
can validate the color value and trigger a redraw as a side effect:</p> |
|||
<pre class="ecmascript-code"> |
|||
var obj = {}; |
|||
|
|||
Object.defineProperty(obj, 'color', { |
|||
enumerable: false, |
|||
configurable: false, |
|||
get: function () { |
|||
// current color is stored in the raw _color property here |
|||
return this._color; |
|||
}, |
|||
set: function (v) { |
|||
if (!validateColor(v)) { |
|||
// only allow valid color formats to be assigned |
|||
throw new TypeError('invalid color: ' + v); |
|||
} |
|||
this._color = v; |
|||
redraw(); |
|||
} |
|||
}); |
|||
|
|||
// Change to red and trigger a redraw. |
|||
obj.color = '#ff0000'; |
|||
</pre> |
|||
|
|||
<p>Setters and getters have the advantage of being part of the E5 standard |
|||
and of being widely implemented. However, they have significant limitations:</p> |
|||
<ul> |
|||
<li>They are limited to interacting with property read and write operations. |
|||
You cannot capture property deletions, interact with object enumeration, etc.</li> |
|||
<li>They only apply to individual properties which you set up as accessors |
|||
beforehand. You cannot capture all property accesses of a certain object, |
|||
which limits their usefulness in some scenarios.</li> |
|||
</ul> |
|||
|
|||
<h3 id="virtualization-proxy-object">Ecmascript E6 (draft) Proxy subset</h3> |
|||
|
|||
<p>In addition to accessors, Duktape provides a subset implementation of the |
|||
Ecmascript E6 (draft) <code>Proxy</code> concept (see |
|||
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-proxy-objects">Proxy Objects (E6 draft)</a> |
|||
and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy (Mozilla)</a>). |
|||
The current subset is limited to <code>get</code>, <code>set</code>, and |
|||
<code>deleteProperty</code> handler methods.</p> |
|||
|
|||
<p>For example, to simply print a line whenever any property is accessed:</p> |
|||
<pre class="ecmascript-code"> |
|||
// Underlying plain object. |
|||
var target = { foo: 'bar' }; |
|||
|
|||
// Handler table, provides methods for interaction (can be modified on-the-fly). |
|||
var handler = { |
|||
get: function (targ, key, recv) { |
|||
print('get called for key=' + key); |
|||
return targ[key]; // return unmodified value |
|||
}, |
|||
|
|||
set: function (targ, key, val, recv) { |
|||
print('set called for key=' + key + ', val=' + val); |
|||
targ[key] = val; // must perform write to target manually if 'set' defined |
|||
return true; // true: indicate that property write was allowed |
|||
}, |
|||
|
|||
deleteProperty: function (targ, key) { |
|||
print('deleteProperty called for key=' + key); |
|||
delete targ[key]; // must perform delete to target manually if 'deleteProperty' defined |
|||
return true; // true: indicate that property delete was allowed |
|||
} |
|||
}; |
|||
|
|||
// Create proxy object. |
|||
var proxy = new Proxy(target, handler); |
|||
|
|||
// Proxy object is then accessed normally. |
|||
proxy.foo = 321; |
|||
print(proxy.foo); |
|||
delete proxy.foo; |
|||
</pre> |
|||
|
|||
<p>A Proxy object can also be used to create a read-only version of an |
|||
underlying object (which is quite tedious otherwise):</p> |
|||
<pre class="ecmascript-code"> |
|||
var proxy = new Proxy(target, { |
|||
// get is omitted: reads go through to the target object automatically |
|||
|
|||
// set returns false: rejects write |
|||
set: function () { return false; }, |
|||
|
|||
// deleteProperty returns false: rejects delete |
|||
deleteProperty: function () { return false; } |
|||
}); |
|||
</pre> |
|||
|
|||
<p>You can also create a write-only version of an object (which is not |
|||
possible otherwise):</p> |
|||
<pre class="ecmascript-code"> |
|||
var proxy = new Proxy(target, { |
|||
get: function() { throw new TypeError('read not allowed'); } |
|||
|
|||
// set and deleteProperty are omitted: write/delete operations |
|||
// are allowed and go through to the target automatically |
|||
}); |
|||
</pre> |
|||
|
|||
<p>The following is a more convoluted example combining multiple (somewhat |
|||
artificial) behaviors:</p> |
|||
<pre class="ecmascript-code"> |
|||
var target = { foo: 'bar' }; |
|||
|
|||
/* |
|||
* - 'color' behaves like in the getter/setter example, cannot be deleted |
|||
* (attempt to do so causes a TypeError) |
|||
* |
|||
* - all string values are uppercased when read |
|||
* |
|||
* - property names beginning with an underscore are read/write/delete |
|||
* protected in a few different ways |
|||
*/ |
|||
|
|||
var handler = { |
|||
get: function (targ, key, recv) { |
|||
// this binding: handler table |
|||
// targ: underlying plain object (= target, above) |
|||
// key: key (can be any value, not just a string) |
|||
// recv: object being read from (= the proxy object) |
|||
|
|||
if (typeof key === 'string' && key[0] === '_') { |
|||
throw new TypeError('attempt to access a read-protected property'); |
|||
} |
|||
|
|||
// Return value: value provided as property lookup result. |
|||
var val = targ[key]; |
|||
return (typeof val === 'string' ? val.toUpperCase() : val); |
|||
}, |
|||
|
|||
set: function (targ, key, val, recv) { |
|||
// this binding: handler table |
|||
// targ: underlying plain object (= target, above) |
|||
// key: key (can be any value, not just a string) |
|||
// val: value |
|||
// recv: object being read from (= the proxy object) |
|||
|
|||
if (typeof key === 'string') { |
|||
if (key === 'color') { |
|||
if (!validateColor(val)) { |
|||
throw new TypeError('invalid color: ' + val); |
|||
} |
|||
targ.color = val; |
|||
redraw(); |
|||
|
|||
// True: indicates to caller that property write allowed. |
|||
return true; |
|||
} else if (key[0] === '_') { |
|||
// False: indicates to caller that property write rejected. |
|||
// In non-strict mode this is ignored silently, but in strict |
|||
// mode a TypeError is thrown. |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// Write to target. We could also return true without writing to the |
|||
// target to simulate a successful write without changing the target. |
|||
targ[key] = val; |
|||
return true; |
|||
}, |
|||
|
|||
deleteProperty: function (targ, key) { |
|||
// this binding: handler table |
|||
// targ: underlying plain object (= target, above) |
|||
// key: key (can be any value, not just a string) |
|||
|
|||
if (typeof key === 'string') { |
|||
if (key === 'color') { |
|||
// For 'color' a delete attempt causes an explicit error. |
|||
throw new TypeError('attempt to delete the color property'); |
|||
} else if (key[0] === '_') { |
|||
// False: indicates to caller that property delete rejected. |
|||
// In non-strict mode this is ignored silently, but in strict |
|||
// mode a TypeError is thrown. |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// Delete from target. We could also return true without deleting |
|||
// from the target to simulate a successful delete without changing |
|||
// the target. |
|||
delete targ[key]; |
|||
return true; |
|||
} |
|||
}; |
|||
</pre> |
|||
|
|||
<p>The ES6 draft semantics reject some property accesses even if the handler |
|||
method would allow it. This happens if the proxy's target object has a non-configurable |
|||
conflicting property; see E6 draft Sections 9.5.8, 9.5.9, and 9.5.10 for details. |
|||
You can easily avoid any such behaviors by keeping the target object empty and, |
|||
if necessary, backing the virtual properties in an unrelated plain object.</p> |
|||
|
|||
<p>Also see:</p> |
|||
<ul> |
|||
<li><a href="#es6-proxy">Proxy object (subset)</a>: |
|||
discussion of current limitations in Duktape's <code>Proxy</code> implementation.</li> |
|||
<li><a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-proxy-objects">Proxy Objects (E6 draft)</a>: |
|||
ES6 draft specification for the <code>Proxy</code> object.</li> |
|||
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy (Mozilla)</a>: |
|||
Mozilla's description of the <code>Proxy</code> implemented in Firefox, contains a lot of examples.</li> |
|||
</ul> |
Loading…
Reference in new issue