File size: 4,956 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
'use strict';
const { target: tv, unwrap, bound, unbound } = require('proxy-target/array');
const { create: createGCHook } = require('gc-hook');
const {
ARRAY,
OBJECT,
FUNCTION,
NUMBER,
STRING,
SYMBOL
} = require('proxy-target/types');
const {
TypedArray,
augment,
asEntry,
symbol,
transform
} = require('./utils.js');
const {
APPLY,
CONSTRUCT,
DEFINE_PROPERTY,
DELETE_PROPERTY,
GET,
GET_OWN_PROPERTY_DESCRIPTOR,
GET_PROTOTYPE_OF,
HAS,
IS_EXTENSIBLE,
OWN_KEYS,
PREVENT_EXTENSION,
SET,
SET_PROTOTYPE_OF,
DELETE
} = require('./traps.js');
module.exports = name => {
let id = 0;
const ids = new Map;
const values = new Map;
const __proxy__ = Symbol();
return function (main, MAIN, THREAD) {
const $ = this?.transform || transform;
const { [MAIN]: __main__ } = main;
const proxies = new Map;
const onGarbageCollected = value => {
proxies.delete(value);
__main__(DELETE, argument(value));
};
const argument = asEntry(
(type, value) => {
if (__proxy__ in value)
return unbound(value[__proxy__]);
if (type === FUNCTION) {
value = $(value);
if (!values.has(value)) {
let sid;
// a bit apocalyptic scenario but if this thread runs forever
// and the id does a whole int32 roundtrip we might have still
// some reference dangling around
while (values.has(sid = String(id++)));
ids.set(value, sid);
values.set(sid, value);
}
return tv(type, ids.get(value));
}
if (!(value instanceof TypedArray)) {
value = $(value);
for(const key in value)
value[key] = argument(value[key]);
}
return tv(type, value);
}
);
const register = (entry, type, value) => {
const retained = proxies.get(value)?.deref();
if (retained) return retained;
const target = type === FUNCTION ? bound(entry) : entry;
const proxy = new Proxy(target, proxyHandler);
proxies.set(value, new WeakRef(proxy));
return createGCHook(value, onGarbageCollected, {
return: proxy,
token: false,
});
};
const fromEntry = entry => unwrap(entry, (type, value) => {
switch (type) {
case OBJECT:
if (value === null) return globalThis;
case ARRAY:
return typeof value === NUMBER ? register(entry, type, value) : value;
case FUNCTION:
return typeof value === STRING ? values.get(value) : register(entry, type, value);
case SYMBOL:
return symbol(value);
}
return value;
});
const result = (TRAP, target, ...args) => fromEntry(__main__(TRAP, unbound(target), ...args));
const proxyHandler = {
[APPLY]: (target, thisArg, args) => result(APPLY, target, argument(thisArg), args.map(argument)),
[CONSTRUCT]: (target, args) => result(CONSTRUCT, target, args.map(argument)),
[DEFINE_PROPERTY]: (target, name, descriptor) => {
const { get, set, value } = descriptor;
if (typeof get === FUNCTION) descriptor.get = argument(get);
if (typeof set === FUNCTION) descriptor.set = argument(set);
if (typeof value === FUNCTION) descriptor.value = argument(value);
return result(DEFINE_PROPERTY, target, argument(name), descriptor);
},
[DELETE_PROPERTY]: (target, name) => result(DELETE_PROPERTY, target, argument(name)),
[GET_PROTOTYPE_OF]: target => result(GET_PROTOTYPE_OF, target),
[GET]: (target, name) => name === __proxy__ ? target : result(GET, target, argument(name)),
[GET_OWN_PROPERTY_DESCRIPTOR]: (target, name) => {
const descriptor = result(GET_OWN_PROPERTY_DESCRIPTOR, target, argument(name));
return descriptor && augment(descriptor, fromEntry);
},
[HAS]: (target, name) => name === __proxy__ || result(HAS, target, argument(name)),
[IS_EXTENSIBLE]: target => result(IS_EXTENSIBLE, target),
[OWN_KEYS]: target => result(OWN_KEYS, target).map(fromEntry),
[PREVENT_EXTENSION]: target => result(PREVENT_EXTENSION, target),
[SET]: (target, name, value) => result(SET, target, argument(name), argument(value)),
[SET_PROTOTYPE_OF]: (target, proto) => result(SET_PROTOTYPE_OF, target, argument(proto)),
};
main[THREAD] = (trap, entry, ctx, args) => {
switch (trap) {
case APPLY:
return fromEntry(entry).apply(fromEntry(ctx), args.map(fromEntry));
case DELETE: {
const id = fromEntry(entry);
ids.delete(values.get(id));
values.delete(id);
}
}
};
const global = new Proxy(tv(OBJECT, null), proxyHandler);
return {
[name.toLowerCase()]: global,
[`is${name}Proxy`]: value => typeof value === OBJECT && !!value && __proxy__ in value,
proxy: main
};
};
};
|