22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5+ type TrackedAsyncOperation = 'Promise' | 'requestAnimationFrame' | 'setTimeout' | 'setInterval' | 'requestIdleCallback' |
6+ 'cancelIdleCallback' | 'cancelAnimationFrame' | 'clearTimeout' | 'clearInterval' ;
7+
8+ type OriginalTrackedAsyncOperations = {
9+ [ K in TrackedAsyncOperation ] : typeof window [ K ] ;
10+ } ;
11+ /**
12+ * Capture the original at point in creation of the module
13+ * Unless something before this is loaded
14+ * This should always be the original
15+ */
16+ const originals : Readonly < OriginalTrackedAsyncOperations > = {
17+ Promise,
18+ requestAnimationFrame : requestAnimationFrame . bind ( window ) ,
19+ requestIdleCallback : requestIdleCallback . bind ( window ) ,
20+ setInterval : setInterval . bind ( window ) ,
21+ setTimeout : setTimeout . bind ( window ) ,
22+ cancelAnimationFrame : cancelAnimationFrame . bind ( window ) ,
23+ clearInterval : clearInterval . bind ( window ) ,
24+ clearTimeout : clearTimeout . bind ( window ) ,
25+ cancelIdleCallback : cancelIdleCallback . bind ( window )
26+ } ;
27+
28+ // We can't use Sinon for stubbing as 1) we need to double wrap sometimes
29+ interface Stub < TKey extends TrackedAsyncOperation > {
30+ name : TKey ;
31+ stubWith : ( typeof window ) [ TKey ] ;
32+ }
33+ const stubs : Array < Stub < TrackedAsyncOperation > > = [ ] ;
34+ function stub < T extends TrackedAsyncOperation > ( name : T , stubWith : ( typeof window ) [ T ] ) {
35+ window [ name ] = stubWith ;
36+ stubs . push ( { name, stubWith} ) ;
37+ }
38+
39+ function restoreAll ( ) {
40+ for ( const { name} of stubs ) {
41+ ( window [ name ] as unknown ) = originals [ name ] ;
42+ }
43+ stubs . length = 0 ;
44+ }
545interface AsyncActivity {
6- type : 'promise' | 'requestAnimationFrame' | 'setTimeout' | 'setInterval' | 'requestIdleCallback' ;
46+ type : TrackedAsyncOperation ;
747 pending : boolean ;
848 cancelDelayed ?: ( ) => void ;
949 id ?: string ;
@@ -41,13 +81,15 @@ export async function checkForPendingActivity(testName = '') {
4181 const pendingCount = asyncActivity . filter ( a => a . pending ) . length ;
4282 const totalCount = asyncActivity . length ;
4383 try {
84+ const PromiseConstructor = originals . Promise ;
4485 // First we wait for the pending async activity to finish normally
45- await original ( Promise ) . all ( asyncActivity . filter ( a => a . pending ) . map ( a => original ( Promise ) . race ( [
86+ await PromiseConstructor . all ( asyncActivity . filter ( a => a . pending ) . map ( a => PromiseConstructor . race ( [
4687 a . promise ,
47- new ( original ( Promise ) ) (
48- ( _ , reject ) => original ( setTimeout ) (
88+ new PromiseConstructor < void > (
89+ ( resolve , reject ) => originals . setTimeout (
4990 ( ) => {
5091 if ( ! a . pending ) {
92+ resolve ( ) ;
5193 return ;
5294 }
5395 // If something is still pending after some time, we try to
@@ -96,16 +138,16 @@ export function stopTrackingAsyncActivity() {
96138function trackingRequestAnimationFrame ( fn : FrameRequestCallback ) {
97139 const activity : AsyncActivity = { type : 'requestAnimationFrame' , pending : true , stack : getStack ( new Error ( ) ) } ;
98140 let id = 0 ;
99- activity . promise = new ( original ( Promise < void > ) ) ( resolve => {
141+ activity . promise = new originals . Promise < void > ( resolve => {
100142 activity . runImmediate = ( ) => {
101143 fn ( performance . now ( ) ) ;
102144 activity . pending = false ;
103145 resolve ( ) ;
104146 } ;
105- id = original ( requestAnimationFrame ) ( activity . runImmediate ) ;
147+ id = originals . requestAnimationFrame ( activity . runImmediate ) ;
106148 activity . id = 'a' + id ;
107149 activity . cancelDelayed = ( ) => {
108- original ( cancelAnimationFrame ) ( id ) ;
150+ originals . cancelAnimationFrame ( id ) ;
109151 activity . pending = false ;
110152 resolve ( ) ;
111153 } ;
@@ -117,16 +159,16 @@ function trackingRequestAnimationFrame(fn: FrameRequestCallback) {
117159function trackingRequestIdleCallback ( fn : IdleRequestCallback , opts ?: IdleRequestOptions ) : number {
118160 const activity : AsyncActivity = { type : 'requestIdleCallback' , pending : true , stack : getStack ( new Error ( ) ) } ;
119161 let id = 0 ;
120- activity . promise = new ( original ( Promise < void > ) ) ( resolve => {
162+ activity . promise = new originals . Promise < void > ( resolve => {
121163 activity . runImmediate = ( idleDeadline ?: IdleDeadline ) => {
122164 fn ( idleDeadline ?? { didTimeout : true , timeRemaining : ( ) => 0 } as IdleDeadline ) ;
123165 activity . pending = false ;
124166 resolve ( ) ;
125167 } ;
126- id = original ( requestIdleCallback ) ( activity . runImmediate , opts ) ;
168+ id = originals . requestIdleCallback ( activity . runImmediate , opts ) ;
127169 activity . id = 'd' + id ;
128170 activity . cancelDelayed = ( ) => {
129- original ( cancelIdleCallback ) ( id ) ;
171+ originals . cancelIdleCallback ( id ) ;
130172 activity . pending = false ;
131173 resolve ( ) ;
132174 } ;
@@ -137,9 +179,10 @@ function trackingRequestIdleCallback(fn: IdleRequestCallback, opts?: IdleRequest
137179
138180function trackingSetTimeout ( arg : TimerHandler , time ?: number , ...params : unknown [ ] ) {
139181 const activity : AsyncActivity = { type : 'setTimeout' , pending : true , stack : getStack ( new Error ( ) ) } ;
140- let id : ReturnType < typeof setTimeout > | undefined ;
141- activity . promise = new ( original ( Promise < void > ) ) ( resolve => {
182+ let id : number | undefined ;
183+ activity . promise = new originals . Promise < void > ( resolve => {
142184 activity . runImmediate = ( ) => {
185+ originals . clearTimeout ( id ) ;
143186 if ( typeof ( arg ) === 'function' ) {
144187 arg ( ...params ) ;
145188 } else {
@@ -148,10 +191,10 @@ function trackingSetTimeout(arg: TimerHandler, time?: number, ...params: unknown
148191 activity . pending = false ;
149192 resolve ( ) ;
150193 } ;
151- id = original ( setTimeout ) ( activity . runImmediate , time ) ;
194+ id = originals . setTimeout ( activity . runImmediate , time ) ;
152195 activity . id = 't' + id ;
153196 activity . cancelDelayed = ( ) => {
154- original ( clearTimeout ) ( id ) ;
197+ originals . clearTimeout ( id ) ;
155198 activity . pending = false ;
156199 resolve ( ) ;
157200 } ;
@@ -167,11 +210,11 @@ function trackingSetInterval(arg: TimerHandler, time?: number, ...params: unknow
167210 stack : getStack ( new Error ( ) ) ,
168211 } ;
169212 let id = 0 ;
170- activity . promise = new ( original ( Promise < void > ) ) ( resolve => {
171- id = original ( setInterval ) ( arg , time , ...params ) ;
213+ activity . promise = new originals . Promise < void > ( resolve => {
214+ id = originals . setInterval ( arg , time , ...params ) ;
172215 activity . id = 'i' + id ;
173216 activity . cancelDelayed = ( ) => {
174- original ( clearInterval ) ( id ) ;
217+ originals . clearInterval ( id ) ;
175218 activity . pending = false ;
176219 resolve ( ) ;
177220 } ;
@@ -210,10 +253,9 @@ const BasePromise: Omit<PromiseConstructor, UntrackedPromiseMethod> = {
210253// which never settles.
211254const TrackingPromise : PromiseConstructor = Object . assign (
212255 function < T > ( arg : ( resolve : ( value : T | PromiseLike < T > ) => void , reject : ( reason ?: unknown ) => void ) => void ) {
213- const originalPromiseType = original ( Promise ) ;
214- const promise = new ( originalPromiseType ) ( arg ) ;
256+ const promise = new originals . Promise ( arg ) ;
215257 const activity : AsyncActivity = {
216- type : 'promise ' ,
258+ type : 'Promise ' ,
217259 promise,
218260 stack : getStack ( new Error ( ) ) ,
219261 pending : false ,
@@ -223,7 +265,7 @@ const TrackingPromise: PromiseConstructor = Object.assign(
223265 onRejected ?: ( ( reason : unknown ) => TResult2 | PromiseLike < TResult2 > ) | undefined |
224266 null ) : Promise < TResult1 | TResult2 > {
225267 activity . pending = true ;
226- return originalPromiseType . prototype . then . apply ( this , [
268+ return originals . Promise . prototype . then . apply ( this , [
227269 result => {
228270 if ( ! onFulfilled ) {
229271 return this ;
@@ -250,30 +292,3 @@ const TrackingPromise: PromiseConstructor = Object.assign(
250292function getStack ( error : Error ) : string {
251293 return ( error . stack ?? 'No stack' ) . split ( '\n' ) . slice ( 2 ) . join ( '\n' ) ;
252294}
253-
254- // We can't use Sinon for stubbing as 1) we need to double wrap sometimes and 2)
255- // we need to access original values.
256- interface Stub < TKey extends keyof typeof window > {
257- name : TKey ;
258- original : ( typeof window ) [ TKey ] ;
259- stubWith : ( typeof window ) [ TKey ] ;
260- }
261-
262- const stubs : Array < Stub < keyof typeof window > > = [ ] ;
263-
264- function stub < T extends keyof typeof window > ( name : T , stubWith : ( typeof window ) [ T ] ) {
265- const original = window [ name ] ;
266- window [ name ] = stubWith ;
267- stubs . push ( { name, original, stubWith} ) ;
268- }
269-
270- function original < T > ( stubWith : T ) : T {
271- return stubs . find ( s => s . stubWith === stubWith ) ?. original ;
272- }
273-
274- function restoreAll ( ) {
275- for ( const { name, original} of stubs ) {
276- ( window [ name ] as typeof original ) = original ;
277- }
278- stubs . length = 0 ;
279- }
0 commit comments