Skip to content

Commit 2c468ee

Browse files
authored
fix(jsdom): reuse abort signals if possible (#9090)
1 parent e1b2e08 commit 2c468ee

File tree

2 files changed

+36
-4
lines changed

2 files changed

+36
-4
lines changed

packages/vitest/src/integrations/env/jsdom.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,22 @@ function createCompatUtils(window: DOMWindow): CompatUtils {
307307
}
308308

309309
function patchAddEventListener(window: DOMWindow) {
310+
const abortControllers = new WeakMap<AbortSignal, AbortController>()
310311
const JSDOMAbortSignal = window.AbortSignal
311312
const JSDOMAbortController = window.AbortController
312313
const originalAddEventListener = window.EventTarget.prototype.addEventListener
313314

315+
function getJsdomAbortController(signal: AbortSignal) {
316+
if (!abortControllers.has(signal)) {
317+
const jsdomAbortController = new JSDOMAbortController()
318+
signal.addEventListener('abort', () => {
319+
jsdomAbortController.abort(signal.reason)
320+
})
321+
abortControllers.set(signal, jsdomAbortController)
322+
}
323+
return abortControllers.get(signal)!
324+
}
325+
314326
window.EventTarget.prototype.addEventListener = function addEventListener(
315327
type: string,
316328
callback: EventListenerOrEventListenerObject | null,
@@ -328,10 +340,7 @@ function patchAddEventListener(window: DOMWindow) {
328340

329341
// use jsdom-native abort controller instead and forward the
330342
// previous one with `addEventListener`
331-
const jsdomAbortController = new JSDOMAbortController()
332-
signal.addEventListener('abort', () => {
333-
jsdomAbortController.abort(signal.reason)
334-
})
343+
const jsdomAbortController = getJsdomAbortController(signal)
335344

336345
jsdomCompatOptions.signal = jsdomAbortController.signal
337346
return originalAddEventListener.call(this, type, callback, jsdomCompatOptions)

test/core/test/environments/jsdom.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @vitest-environment jsdom
22

3+
import { setMaxListeners } from 'node:events'
34
import { stripVTControlCharacters } from 'node:util'
45
import { processError } from '@vitest/utils/error'
56
import { describe, expect, test, vi } from 'vitest'
@@ -203,6 +204,28 @@ test('DOM APIs accept AbortController', () => {
203204
expect(spy).toHaveBeenCalledTimes(1)
204205
})
205206

207+
test('can pass down the same abort signal many times without a warning', ({ onTestFinished }) => {
208+
const controller = new AbortController()
209+
const signal = controller.signal
210+
setMaxListeners(5, signal)
211+
212+
const emitWarning = vi.spyOn(process, 'emitWarning').mockImplementation(() => {})
213+
onTestFinished(() => emitWarning.mockRestore())
214+
215+
const element = document.createElement('div')
216+
document.body.append(element)
217+
218+
for (let i = 0; i < 20; i++) {
219+
element.addEventListener('click', () => {}, {
220+
signal,
221+
})
222+
}
223+
224+
expect(emitWarning).not.toHaveBeenCalledWith(expect.objectContaining({
225+
message: expect.stringContaining('Possible EventTarget memory leak detected.'),
226+
}))
227+
})
228+
206229
test('atob and btoa are available', () => {
207230
expect(atob('aGVsbG8gd29ybGQ=')).toBe('hello world')
208231
expect(btoa('hello world')).toBe('aGVsbG8gd29ybGQ=')

0 commit comments

Comments
 (0)