1
1
const OriginalObject = Object ;
2
2
const OriginalReflect = Reflect ;
3
3
const OriginalProxy = Proxy ;
4
- const { freeze, defineProperty } = OriginalObject ;
4
+ const { freeze, defineProperty, hasOwn } = OriginalObject ;
5
5
const { apply, construct, ownKeys } = OriginalReflect ;
6
6
7
7
const noTrappingSet = new WeakSet ( ) ;
@@ -144,55 +144,115 @@ const addExtras = (base, ...extrasArgs) => {
144
144
}
145
145
} ;
146
146
147
+ /** In the shim, `ReflectPlus` replaces the global `Reflect`. */
147
148
const ReflectPlus = { } ;
148
149
addExtras ( ReflectPlus , OriginalReflect , extraReflectMethods ) ;
149
150
export { ReflectPlus } ;
150
151
152
+ /**
153
+ * In the shim, `ObjectPlus` replaces the global `Object`.
154
+ *
155
+ * @type {ObjectConstructor }
156
+ */
157
+ // @ts -expect-error TS does not know the rest of the type is added below
151
158
const ObjectPlus = function Object ( ...args ) {
152
159
if ( new . target ) {
153
160
return construct ( OriginalObject , args , new . target ) ;
154
161
} else {
155
162
return apply ( OriginalObject , this , args ) ;
156
163
}
157
164
} ;
165
+ // @ts -expect-error We actually can assign to its `.prototype`.
158
166
ObjectPlus . prototype = OriginalObject . prototype ;
159
167
addExtras ( ObjectPlus , OriginalObject , extraObjectMethods ) ;
160
168
export { ObjectPlus } ;
161
169
162
- const makeMetaHandler = handler =>
163
- freeze ( {
164
- get ( _ , trapName , _receiver ) {
165
- return freeze ( ( target , ...rest ) => {
166
- if (
167
- isNoTrappingInternal ( target , true ) ||
168
- handler [ trapName ] === undefined
169
- ) {
170
- return ReflectPlus [ trapName ] ( target , ...rest ) ;
171
- } else {
172
- return handler [ trapName ] ( target , ...rest ) ;
170
+ const metaHandler = freeze ( {
171
+ get ( _ , trapName , handlerPlus ) {
172
+ /**
173
+ * The `trapPlus` method is an enhanced version of
174
+ * `originalHandler[trapName]`. If the handlerPlus has no own `trapName`
175
+ * property, then the `get` of the metaHandler is called, which returns
176
+ * the `trapPlus`, which is then called as the trap of the returned
177
+ * proxyPlus. When so called, it installs an own `handlerPlus[trapName]`
178
+ * which is either `undefined` or this same `trapPlus`, to avoid further
179
+ * need to meta-handle that `handlerPlus[trapName]`.
180
+ *
181
+ * @param {any } target
182
+ * @param {any[] } rest
183
+ */
184
+ const trapPlus = freeze ( ( target , ...rest ) => {
185
+ if ( isNoTrappingInternal ( target , true ) ) {
186
+ defineProperty ( handlerPlus , trapName , {
187
+ value : undefined ,
188
+ writable : false ,
189
+ enumerable : true ,
190
+ configurable : false ,
191
+ } ) ;
192
+ } else {
193
+ if ( ! hasOwn ( handlerPlus , trapName ) ) {
194
+ defineProperty ( handlerPlus , trapName , {
195
+ value : trapPlus ,
196
+ writable : false ,
197
+ enumerable : true ,
198
+ configurable : true ,
199
+ } ) ;
173
200
}
174
- } ) ;
175
- } ,
176
- } ) ;
201
+ const { originalHandler } = handlerPlus ;
202
+ const trap = originalHandler [ trapName ] ;
203
+ if ( trap !== undefined ) {
204
+ // Note that whether `trap === undefined` can change dynamically,
205
+ // so we do not install an own `handlerPlus[trapName] === undefined`
206
+ // for that case. We still install or preserve an own
207
+ // `handlerPlus[trapName] === trapPlus` until the target is
208
+ // seen to be non-trapping.
209
+ return apply ( trap , originalHandler , [ target , ...rest ] ) ;
210
+ }
211
+ }
212
+ return ReflectPlus [ trapName ] ( target , ...rest ) ;
213
+ } ) ;
214
+ return trapPlus ;
215
+ } ,
216
+ } ) ;
177
217
178
- const makeSafeHandler = handler =>
179
- new OriginalProxy ( { } , makeMetaHandler ( handler ) ) ;
218
+ /**
219
+ * A handlerPlus starts as a fresh empty object that inherits from a proxy
220
+ * whose handler is the shared generic metaHandler.
221
+ * Thus, the metaHandler's `get` method is called only when the
222
+ * `handlerPlus` does not have a property overriding that `trapName`.
223
+ * In that case, the metaHandler's `get` is called with its `receiver`
224
+ * being the `handlerPlus`.
225
+ *
226
+ * @param {ProxyHandler<any> } originalHandler
227
+ * @returns {ProxyHandler<any> & {
228
+ * isNoTrapping: (target: any) => boolean,
229
+ * suppressTrapping: (target: any) => boolean,
230
+ * originalHandler: ProxyHandler<any>
231
+ * }}
232
+ */
233
+ const makeHandlerPlus = originalHandler => ( {
234
+ // @ts -expect-error TS does not know what this __proto__ is doing
235
+ __proto__ : new OriginalProxy ( { } , metaHandler ) ,
236
+ // relies on there never being a trap named `originalHandler`.
237
+ originalHandler,
238
+ } ) ;
180
239
181
240
/**
182
- * In the shim, `ProxyPlus` should replace the global `Proxy`.
241
+ * In the shim, `ProxyPlus` replaces the global `Proxy`.
183
242
*
184
- * @param {any } target
185
- * @param {object } handler
243
+ * @type {ProxyConstructor }
186
244
*/
245
+ // @ts -expect-error We reject non-new calls in the body
187
246
const ProxyPlus = function Proxy ( target , handler ) {
247
+ // @ts -expect-error Yes, we mean to compare these.
188
248
if ( new . target !== ProxyPlus ) {
189
249
if ( new . target === undefined ) {
190
250
throw TypeError ( 'Proxy constructor requires "new"' ) ;
191
251
}
192
252
throw TypeError ( 'Safe Proxy shim does not support subclassing' ) ;
193
253
}
194
- const safeHandler = makeSafeHandler ( handler ) ;
195
- const proxy = new OriginalProxy ( target , safeHandler ) ;
254
+ const handlerPlus = makeHandlerPlus ( handler ) ;
255
+ const proxy = new OriginalProxy ( target , handlerPlus ) ;
196
256
proxyHandlerMap . set ( proxy , [ target , handler ] ) ;
197
257
return proxy ;
198
258
} ;
@@ -201,10 +261,18 @@ const ProxyPlus = function Proxy(target, handler) {
201
261
// `ProxyPlus.prototype` to `undefined`
202
262
ProxyPlus . prototype = undefined ;
203
263
ProxyPlus . revocable = ( target , handler ) => {
204
- const safeHandler = makeSafeHandler ( handler ) ;
205
- const { proxy, revoke } = OriginalProxy . revocable ( target , safeHandler ) ;
264
+ const handlerPlus = makeHandlerPlus ( handler ) ;
265
+ const { proxy, revoke } = OriginalProxy . revocable ( target , handlerPlus ) ;
206
266
proxyHandlerMap . set ( proxy , [ target , handler ] ) ;
207
- return { proxy, revoke } ;
267
+ return {
268
+ proxy,
269
+ revoke ( ) {
270
+ if ( isNoTrappingInternal ( target , true ) ) {
271
+ throw TypeError ( 'Cannot revoke non-trapping proxy' ) ;
272
+ }
273
+ revoke ( ) ;
274
+ } ,
275
+ } ;
208
276
} ;
209
277
210
278
export { ProxyPlus } ;
0 commit comments