|
| 1 | +// Regression test for https://github.com/denoland/deno/issues/34403 |
| 2 | +// Lazy-loaded globals (Response, Request, ReadableStream, WebSocket, ...) used |
| 3 | +// to be writable data properties. After they were converted to accessor |
| 4 | +// properties for lazy loading, assigning through an object whose prototype is |
| 5 | +// globalThis (the cross-fetch / whatwg-fetch polyfill pattern) would clobber |
| 6 | +// the global instead of shadowing on the receiver. |
| 7 | + |
| 8 | +const originalResponse = globalThis.Response; |
| 9 | +const originalRequest = globalThis.Request; |
| 10 | +const originalReadableStream = globalThis.ReadableStream; |
| 11 | +const originalWebSocket = globalThis.WebSocket; |
| 12 | + |
| 13 | +// Mimic the cross-fetch / whatwg-fetch polyfill pattern. |
| 14 | +function Self() {} |
| 15 | +Self.prototype = globalThis; |
| 16 | +const g = new Self(); |
| 17 | +g.Response = class PolyfillResponse {}; |
| 18 | +g.Request = class PolyfillRequest {}; |
| 19 | +g.ReadableStream = class PolyfillReadableStream {}; |
| 20 | +g.WebSocket = class PolyfillWebSocket {}; |
| 21 | + |
| 22 | +console.log( |
| 23 | + "global Response untouched:", |
| 24 | + globalThis.Response === originalResponse, |
| 25 | +); |
| 26 | +console.log( |
| 27 | + "global Request untouched:", |
| 28 | + globalThis.Request === originalRequest, |
| 29 | +); |
| 30 | +console.log( |
| 31 | + "global ReadableStream untouched:", |
| 32 | + globalThis.ReadableStream === originalReadableStream, |
| 33 | +); |
| 34 | +console.log( |
| 35 | + "global WebSocket untouched:", |
| 36 | + globalThis.WebSocket === originalWebSocket, |
| 37 | +); |
| 38 | + |
| 39 | +console.log( |
| 40 | + "own Response on receiver:", |
| 41 | + Object.prototype.hasOwnProperty.call(g, "Response"), |
| 42 | +); |
| 43 | +console.log( |
| 44 | + "own Request on receiver:", |
| 45 | + Object.prototype.hasOwnProperty.call(g, "Request"), |
| 46 | +); |
| 47 | + |
| 48 | +// The shadowing own property on the inheriting receiver should be a normal |
| 49 | +// enumerable, writable, configurable data property - matching standard |
| 50 | +// [[Set]] semantics for an inherited writable data property. |
| 51 | +const receiverDesc = Object.getOwnPropertyDescriptor(g, "Response"); |
| 52 | +console.log( |
| 53 | + "receiver Response descriptor:", |
| 54 | + JSON.stringify({ |
| 55 | + hasValue: Object.hasOwn(receiverDesc, "value"), |
| 56 | + writable: receiverDesc.writable, |
| 57 | + enumerable: receiverDesc.enumerable, |
| 58 | + configurable: receiverDesc.configurable, |
| 59 | + }), |
| 60 | +); |
| 61 | + |
| 62 | +// `new Response()` still produces a real Response (instanceof works). |
| 63 | +const r = new Response("hello"); |
| 64 | +console.log("real Response instanceof Response:", r instanceof Response); |
| 65 | + |
| 66 | +// Direct assignment to globalThis still works, and preserves the original |
| 67 | +// non-enumerable attribute of the global descriptor. |
| 68 | +globalThis.WebSocket = "patched"; |
| 69 | +console.log("direct set on globalThis:", globalThis.WebSocket === "patched"); |
| 70 | +const globalDesc = Object.getOwnPropertyDescriptor(globalThis, "WebSocket"); |
| 71 | +console.log( |
| 72 | + "global WebSocket descriptor:", |
| 73 | + JSON.stringify({ |
| 74 | + hasValue: Object.hasOwn(globalDesc, "value"), |
| 75 | + writable: globalDesc.writable, |
| 76 | + enumerable: globalDesc.enumerable, |
| 77 | + configurable: globalDesc.configurable, |
| 78 | + }), |
| 79 | +); |
| 80 | +globalThis.WebSocket = originalWebSocket; |
0 commit comments