1+ import type { DOMWindow } from 'jsdom'
12import type { Environment } from '../../types/environment'
23import type { JSDOMOptions } from '../../types/jsdom-options'
34import { populateGlobal } from './utils'
45
5- function catchWindowErrors ( window : Window ) {
6+ function catchWindowErrors ( window : DOMWindow ) {
67 let userErrorListenerCount = 0
78 function throwUnhandlerError ( e : ErrorEvent ) {
89 if ( userErrorListenerCount === 0 && e . error != null ) {
@@ -70,7 +71,10 @@ export default <Environment>{
7071 userAgent,
7172 ...restOptions ,
7273 } )
73- const clearWindowErrors = catchWindowErrors ( dom . window as any )
74+
75+ const clearAddEventListenerPatch = patchAddEventListener ( dom . window )
76+
77+ const clearWindowErrors = catchWindowErrors ( dom . window )
7478
7579 // TODO: browser doesn't expose Buffer, but a lot of dependencies use it
7680 dom . window . Buffer = Buffer
@@ -120,6 +124,7 @@ export default <Environment>{
120124 return dom . getInternalVMContext ( )
121125 } ,
122126 teardown ( ) {
127+ clearAddEventListenerPatch ( )
123128 clearWindowErrors ( )
124129 dom . window . close ( )
125130 dom = undefined as any
@@ -161,6 +166,8 @@ export default <Environment>{
161166 ...restOptions ,
162167 } )
163168
169+ const clearAddEventListenerPatch = patchAddEventListener ( dom . window )
170+
164171 const { keys, originals } = populateGlobal ( global , dom . window , {
165172 bindFunctions : true ,
166173 } )
@@ -171,6 +178,7 @@ export default <Environment>{
171178
172179 return {
173180 teardown ( global ) {
181+ clearAddEventListenerPatch ( )
174182 clearWindowErrors ( )
175183 dom . window . close ( )
176184 delete global . jsdom
@@ -180,3 +188,43 @@ export default <Environment>{
180188 }
181189 } ,
182190}
191+
192+ function patchAddEventListener ( window : DOMWindow ) {
193+ const JSDOMAbortSignal = window . AbortSignal
194+ const JSDOMAbortController = window . AbortController
195+ const originalAddEventListener = window . EventTarget . prototype . addEventListener
196+
197+ window . EventTarget . prototype . addEventListener = function addEventListener (
198+ type : string ,
199+ callback : EventListenerOrEventListenerObject | null ,
200+ options ?: AddEventListenerOptions | boolean ,
201+ ) {
202+ if ( typeof options === 'object' && options . signal != null ) {
203+ const { signal, ...otherOptions } = options
204+ // - this happens because AbortSignal is provided by Node.js,
205+ // but jsdom APIs require jsdom's AbortSignal, while Node APIs
206+ // (like fetch and Request) require a Node.js AbortSignal
207+ // - disable narrow typing with "as any" because we need it later
208+ if ( ! ( ( signal as any ) instanceof JSDOMAbortSignal ) ) {
209+ const jsdomCompatOptions = Object . create ( null )
210+ Object . assign ( jsdomCompatOptions , otherOptions )
211+
212+ // use jsdom-native abort controller instead and forward the
213+ // previous one with `addEventListener`
214+ const jsdomAbortController = new JSDOMAbortController ( )
215+ signal . addEventListener ( 'abort' , ( ) => {
216+ jsdomAbortController . abort ( signal . reason )
217+ } )
218+
219+ jsdomCompatOptions . signal = jsdomAbortController . signal
220+ return originalAddEventListener . call ( this , type , callback , jsdomCompatOptions )
221+ }
222+ }
223+
224+ return originalAddEventListener . call ( this , type , callback , options )
225+ }
226+
227+ return ( ) => {
228+ window . EventTarget . prototype . addEventListener = originalAddEventListener
229+ }
230+ }
0 commit comments