@@ -22,14 +22,7 @@ import { createTextSelector } from './textSelectorEngine';
22
22
import { XPathEngine } from './xpathSelectorEngine' ;
23
23
24
24
type Falsy = false | 0 | '' | undefined | null ;
25
- type Predicate < T > = ( ) => T | Falsy ;
26
- type InjectedScriptProgress = {
27
- canceled : boolean ;
28
- } ;
29
-
30
- function asCancelablePoll < T > ( result : T ) : types . CancelablePoll < T > {
31
- return { result : Promise . resolve ( result ) , cancel : ( ) => { } } ;
32
- }
25
+ type Predicate < T > = ( progress : types . InjectedScriptProgress ) => T | Falsy ;
33
26
34
27
export default class InjectedScript {
35
28
readonly engines : Map < string , SelectorEngine > ;
@@ -111,14 +104,14 @@ export default class InjectedScript {
111
104
return rect . width > 0 && rect . height > 0 ;
112
105
}
113
106
114
- private _pollRaf < T > ( progress : InjectedScriptProgress , predicate : Predicate < T > ) : Promise < T > {
107
+ private _pollRaf < T > ( progress : types . InjectedScriptProgress , predicate : Predicate < T > ) : Promise < T > {
115
108
let fulfill : ( result : T ) => void ;
116
109
const result = new Promise < T > ( x => fulfill = x ) ;
117
110
118
111
const onRaf = ( ) => {
119
112
if ( progress . canceled )
120
113
return ;
121
- const success = predicate ( ) ;
114
+ const success = predicate ( progress ) ;
122
115
if ( success )
123
116
fulfill ( success ) ;
124
117
else
@@ -129,13 +122,13 @@ export default class InjectedScript {
129
122
return result ;
130
123
}
131
124
132
- private _pollInterval < T > ( progress : InjectedScriptProgress , pollInterval : number , predicate : Predicate < T > ) : Promise < T > {
125
+ private _pollInterval < T > ( progress : types . InjectedScriptProgress , pollInterval : number , predicate : Predicate < T > ) : Promise < T > {
133
126
let fulfill : ( result : T ) => void ;
134
127
const result = new Promise < T > ( x => fulfill = x ) ;
135
128
const onTimeout = ( ) => {
136
129
if ( progress . canceled )
137
130
return ;
138
- const success = predicate ( ) ;
131
+ const success = predicate ( progress ) ;
139
132
if ( success )
140
133
fulfill ( success ) ;
141
134
else
@@ -146,11 +139,39 @@ export default class InjectedScript {
146
139
return result ;
147
140
}
148
141
149
- poll < T > ( polling : 'raf' | number , predicate : Predicate < T > ) : types . CancelablePoll < T > {
150
- const progress = { canceled : false } ;
151
- const cancel = ( ) => { progress . canceled = true ; } ;
152
- const result = polling === 'raf' ? this . _pollRaf ( progress , predicate ) : this . _pollInterval ( progress , polling , predicate ) ;
153
- return { result, cancel } ;
142
+ private _runCancellablePoll < T > ( poll : ( progess : types . InjectedScriptProgress ) => Promise < T > ) : types . InjectedScriptPoll < T > {
143
+ let currentLogs : string [ ] = [ ] ;
144
+ let logReady = ( ) => { } ;
145
+ const createLogsPromise = ( ) => new Promise < types . InjectedScriptLogs > ( fulfill => {
146
+ logReady = ( ) => {
147
+ const current = currentLogs ;
148
+ currentLogs = [ ] ;
149
+ fulfill ( { current, next : createLogsPromise ( ) } ) ;
150
+ } ;
151
+ } ) ;
152
+
153
+ const progress : types . InjectedScriptProgress = {
154
+ canceled : false ,
155
+ log : ( message : string ) => {
156
+ currentLogs . push ( message ) ;
157
+ logReady ( ) ;
158
+ } ,
159
+ } ;
160
+
161
+ // It is important to create logs promise before running the poll to capture logs from the first run.
162
+ const logs = createLogsPromise ( ) ;
163
+
164
+ return {
165
+ logs,
166
+ result : poll ( progress ) ,
167
+ cancel : ( ) => { progress . canceled = true ; } ,
168
+ } ;
169
+ }
170
+
171
+ poll < T > ( polling : 'raf' | number , predicate : Predicate < T > ) : types . InjectedScriptPoll < T > {
172
+ return this . _runCancellablePoll ( progress => {
173
+ return polling === 'raf' ? this . _pollRaf ( progress , predicate ) : this . _pollInterval ( progress , polling , predicate ) ;
174
+ } ) ;
154
175
}
155
176
156
177
getElementBorderWidth ( node : Node ) : { left : number ; top : number ; } {
@@ -327,50 +348,52 @@ export default class InjectedScript {
327
348
input . dispatchEvent ( new Event ( 'change' , { 'bubbles' : true } ) ) ;
328
349
}
329
350
330
- waitForDisplayedAtStablePositionAndEnabled ( node : Node , rafCount : number ) : types . CancelablePoll < types . InjectedScriptResult > {
331
- if ( ! node . isConnected )
332
- return asCancelablePoll ( { status : 'notconnected' } ) ;
333
- const element = node . nodeType === Node . ELEMENT_NODE ? ( node as Element ) : node . parentElement ;
334
- if ( ! element )
335
- return asCancelablePoll ( { status : 'notconnected' } ) ;
336
-
337
- let lastRect : types . Rect | undefined ;
338
- let counter = 0 ;
339
- let samePositionCounter = 0 ;
340
- let lastTime = 0 ;
341
- return this . poll ( 'raf' , ( ) : types . InjectedScriptResult | false => {
342
- // First raf happens in the same animation frame as evaluation, so it does not produce
343
- // any client rect difference compared to synchronous call. We skip the synchronous call
344
- // and only force layout during actual rafs as a small optimisation.
345
- if ( ++ counter === 1 )
346
- return false ;
351
+ waitForDisplayedAtStablePositionAndEnabled ( node : Node , rafCount : number ) : types . InjectedScriptPoll < types . InjectedScriptResult > {
352
+ return this . _runCancellablePoll ( async progress => {
347
353
if ( ! node . isConnected )
348
354
return { status : 'notconnected' } ;
355
+ const element = node . nodeType === Node . ELEMENT_NODE ? ( node as Element ) : node . parentElement ;
356
+ if ( ! element )
357
+ return { status : 'notconnected' } ;
349
358
350
- // Drop frames that are shorter than 16ms - WebKit Win bug.
351
- const time = performance . now ( ) ;
352
- if ( rafCount > 1 && time - lastTime < 15 )
353
- return false ;
354
- lastTime = time ;
355
-
356
- // Note: this logic should be similar to isVisible() to avoid surprises.
357
- const clientRect = element . getBoundingClientRect ( ) ;
358
- const rect = { x : clientRect . top , y : clientRect . left , width : clientRect . width , height : clientRect . height } ;
359
- const samePosition = lastRect && rect . x === lastRect . x && rect . y === lastRect . y && rect . width === lastRect . width && rect . height === lastRect . height && rect . width > 0 && rect . height > 0 ;
360
- lastRect = rect ;
361
- if ( samePosition )
362
- ++ samePositionCounter ;
363
- else
364
- samePositionCounter = 0 ;
365
- const isDisplayedAndStable = samePositionCounter >= rafCount ;
366
-
367
- const style = element . ownerDocument && element . ownerDocument . defaultView ? element . ownerDocument . defaultView . getComputedStyle ( element ) : undefined ;
368
- const isVisible = ! ! style && style . visibility !== 'hidden' ;
369
-
370
- const elementOrButton = element . closest ( 'button, [role=button]' ) || element ;
371
- const isDisabled = [ 'BUTTON' , 'INPUT' , 'SELECT' ] . includes ( elementOrButton . nodeName ) && elementOrButton . hasAttribute ( 'disabled' ) ;
372
-
373
- return isDisplayedAndStable && isVisible && ! isDisabled ? { status : 'success' } : false ;
359
+ let lastRect : types . Rect | undefined ;
360
+ let counter = 0 ;
361
+ let samePositionCounter = 0 ;
362
+ let lastTime = 0 ;
363
+ return this . _pollRaf ( progress , ( ) : types . InjectedScriptResult | false => {
364
+ // First raf happens in the same animation frame as evaluation, so it does not produce
365
+ // any client rect difference compared to synchronous call. We skip the synchronous call
366
+ // and only force layout during actual rafs as a small optimisation.
367
+ if ( ++ counter === 1 )
368
+ return false ;
369
+ if ( ! node . isConnected )
370
+ return { status : 'notconnected' } ;
371
+
372
+ // Drop frames that are shorter than 16ms - WebKit Win bug.
373
+ const time = performance . now ( ) ;
374
+ if ( rafCount > 1 && time - lastTime < 15 )
375
+ return false ;
376
+ lastTime = time ;
377
+
378
+ // Note: this logic should be similar to isVisible() to avoid surprises.
379
+ const clientRect = element . getBoundingClientRect ( ) ;
380
+ const rect = { x : clientRect . top , y : clientRect . left , width : clientRect . width , height : clientRect . height } ;
381
+ const samePosition = lastRect && rect . x === lastRect . x && rect . y === lastRect . y && rect . width === lastRect . width && rect . height === lastRect . height && rect . width > 0 && rect . height > 0 ;
382
+ lastRect = rect ;
383
+ if ( samePosition )
384
+ ++ samePositionCounter ;
385
+ else
386
+ samePositionCounter = 0 ;
387
+ const isDisplayedAndStable = samePositionCounter >= rafCount ;
388
+
389
+ const style = element . ownerDocument && element . ownerDocument . defaultView ? element . ownerDocument . defaultView . getComputedStyle ( element ) : undefined ;
390
+ const isVisible = ! ! style && style . visibility !== 'hidden' ;
391
+
392
+ const elementOrButton = element . closest ( 'button, [role=button]' ) || element ;
393
+ const isDisabled = [ 'BUTTON' , 'INPUT' , 'SELECT' ] . includes ( elementOrButton . nodeName ) && elementOrButton . hasAttribute ( 'disabled' ) ;
394
+
395
+ return isDisplayedAndStable && isVisible && ! isDisabled ? { status : 'success' } : false ;
396
+ } ) ;
374
397
} ) ;
375
398
}
376
399
@@ -421,6 +444,12 @@ export default class InjectedScript {
421
444
}
422
445
return element ;
423
446
}
447
+
448
+ previewElement ( element : Element ) : string {
449
+ const id = element . id ? '#' + element . id : '' ;
450
+ const classes = Array . from ( element . classList ) . map ( c => '.' + c ) . join ( '' ) ;
451
+ return `${ element . nodeName . toLowerCase ( ) } ${ id } ${ classes } ` ;
452
+ }
424
453
}
425
454
426
455
const eventType = new Map < string , 'mouse' | 'keyboard' | 'touch' | 'pointer' | 'focus' | 'drag' > ( [
0 commit comments