11const OriginalObject = Object ;
22const OriginalReflect = Reflect ;
33const OriginalProxy = Proxy ;
4- const { freeze, defineProperty } = OriginalObject ;
4+ const { freeze, defineProperty, hasOwn } = OriginalObject ;
55const { apply, construct, ownKeys } = OriginalReflect ;
66
77const noTrappingSet = new WeakSet ( ) ;
@@ -144,55 +144,115 @@ const addExtras = (base, ...extrasArgs) => {
144144 }
145145} ;
146146
147+ /** In the shim, `ReflectPlus` replaces the global `Reflect`. */
147148const ReflectPlus = { } ;
148149addExtras ( ReflectPlus , OriginalReflect , extraReflectMethods ) ;
149150export { ReflectPlus } ;
150151
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
151158const ObjectPlus = function Object ( ...args ) {
152159 if ( new . target ) {
153160 return construct ( OriginalObject , args , new . target ) ;
154161 } else {
155162 return apply ( OriginalObject , this , args ) ;
156163 }
157164} ;
165+ // @ts -expect-error We actually can assign to its `.prototype`.
158166ObjectPlus . prototype = OriginalObject . prototype ;
159167addExtras ( ObjectPlus , OriginalObject , extraObjectMethods ) ;
160168export { ObjectPlus } ;
161169
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+ } ) ;
173200 }
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+ } ) ;
177217
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+ } ) ;
180239
181240/**
182- * In the shim, `ProxyPlus` should replace the global `Proxy`.
241+ * In the shim, `ProxyPlus` replaces the global `Proxy`.
183242 *
184- * @param {any } target
185- * @param {object } handler
243+ * @type {ProxyConstructor }
186244 */
245+ // @ts -expect-error We reject non-new calls in the body
187246const ProxyPlus = function Proxy ( target , handler ) {
247+ // @ts -expect-error Yes, we mean to compare these.
188248 if ( new . target !== ProxyPlus ) {
189249 if ( new . target === undefined ) {
190250 throw TypeError ( 'Proxy constructor requires "new"' ) ;
191251 }
192252 throw TypeError ( 'Safe Proxy shim does not support subclassing' ) ;
193253 }
194- const safeHandler = makeSafeHandler ( handler ) ;
195- const proxy = new OriginalProxy ( target , safeHandler ) ;
254+ const handlerPlus = makeHandlerPlus ( handler ) ;
255+ const proxy = new OriginalProxy ( target , handlerPlus ) ;
196256 proxyHandlerMap . set ( proxy , [ target , handler ] ) ;
197257 return proxy ;
198258} ;
@@ -201,10 +261,18 @@ const ProxyPlus = function Proxy(target, handler) {
201261// `ProxyPlus.prototype` to `undefined`
202262ProxyPlus . prototype = undefined ;
203263ProxyPlus . 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 ) ;
206266 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+ } ;
208276} ;
209277
210278export { ProxyPlus } ;
0 commit comments