@@ -144,9 +144,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
144
144
return this ;
145
145
}
146
146
147
- _evaluateInUtility : types . EvaluateOn < T > = async ( pageFunction , ...args ) => {
147
+ _evaluateInUtility : types . EvaluateWithInjected < T > = async ( pageFunction , ...args ) => {
148
148
const utility = await this . _context . frame . _utilityContext ( ) ;
149
- return utility . evaluate ( pageFunction as any , this , ...args ) ;
149
+ return utility . evaluate ( pageFunction as any , await utility . _injected ( ) , this , ...args ) ;
150
150
}
151
151
152
152
async ownerFrame ( ) : Promise < frames . Frame | null > {
@@ -163,7 +163,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
163
163
}
164
164
165
165
async contentFrame ( ) : Promise < frames . Frame | null > {
166
- const isFrameElement = await this . _evaluateInUtility ( node => node && ( node . nodeName === 'IFRAME' || node . nodeName === 'FRAME' ) ) ;
166
+ const isFrameElement = await this . _evaluateInUtility ( ( injected , node ) => node && ( node . nodeName === 'IFRAME' || node . nodeName === 'FRAME' ) ) ;
167
167
if ( ! isFrameElement )
168
168
return null ;
169
169
return this . _page . _delegate . getContentFrame ( this ) ;
@@ -219,12 +219,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
219
219
private async _offsetPoint ( offset : types . Point ) : Promise < types . Point > {
220
220
const [ box , border ] = await Promise . all ( [
221
221
this . boundingBox ( ) ,
222
- this . _evaluateInUtility ( ( node : Node ) => {
223
- if ( node . nodeType !== Node . ELEMENT_NODE || ! node . ownerDocument || ! node . ownerDocument . defaultView )
224
- return { x : 0 , y : 0 } ;
225
- const style = node . ownerDocument . defaultView . getComputedStyle ( node as Element ) ;
226
- return { x : parseInt ( style . borderLeftWidth || '' , 10 ) , y : parseInt ( style . borderTopWidth || '' , 10 ) } ;
227
- } ) . catch ( debugError ) ,
222
+ this . _evaluateInUtility ( ( injected , node ) => injected . getElementBorderWidth ( node ) ) . catch ( debugError ) ,
228
223
] ) ;
229
224
const point = { x : offset . x , y : offset . y } ;
230
225
if ( box ) {
@@ -233,8 +228,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
233
228
}
234
229
if ( border ) {
235
230
// Make point relative to the padding box to align with offsetX/offsetY.
236
- point . x += border . x ;
237
- point . y += border . y ;
231
+ point . x += border . left ;
232
+ point . y += border . top ;
238
233
}
239
234
return point ;
240
235
}
@@ -286,92 +281,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
286
281
if ( option . index !== undefined )
287
282
assert ( helper . isNumber ( option . index ) , 'Indices must be numbers. Found index "' + option . index + '" of type "' + ( typeof option . index ) + '"' ) ;
288
283
}
289
- return this . _evaluateInUtility ( ( node : Node , ...optionsToSelect : ( Node | types . SelectOption ) [ ] ) => {
290
- if ( node . nodeName . toLowerCase ( ) !== 'select' )
291
- throw new Error ( 'Element is not a <select> element.' ) ;
292
- const element = node as HTMLSelectElement ;
293
-
294
- const options = Array . from ( element . options ) ;
295
- element . value = undefined as any ;
296
- for ( let index = 0 ; index < options . length ; index ++ ) {
297
- const option = options [ index ] ;
298
- option . selected = optionsToSelect . some ( optionToSelect => {
299
- if ( optionToSelect instanceof Node )
300
- return option === optionToSelect ;
301
- let matches = true ;
302
- if ( optionToSelect . value !== undefined )
303
- matches = matches && optionToSelect . value === option . value ;
304
- if ( optionToSelect . label !== undefined )
305
- matches = matches && optionToSelect . label === option . label ;
306
- if ( optionToSelect . index !== undefined )
307
- matches = matches && optionToSelect . index === index ;
308
- return matches ;
309
- } ) ;
310
- if ( option . selected && ! element . multiple )
311
- break ;
312
- }
313
- element . dispatchEvent ( new Event ( 'input' , { 'bubbles' : true } ) ) ;
314
- element . dispatchEvent ( new Event ( 'change' , { 'bubbles' : true } ) ) ;
315
- return options . filter ( option => option . selected ) . map ( option => option . value ) ;
316
- } , ...options ) ;
284
+ return this . _evaluateInUtility ( ( injected , node , ...optionsToSelect ) => injected . selectOptions ( node , optionsToSelect ) , ...options ) ;
317
285
}
318
286
319
287
async fill ( value : string ) : Promise < void > {
320
288
assert ( helper . isString ( value ) , 'Value must be string. Found value "' + value + '" of type "' + ( typeof value ) + '"' ) ;
321
- const error = await this . _evaluateInUtility ( ( node : Node , value : string ) => {
322
- if ( node . nodeType !== Node . ELEMENT_NODE )
323
- return 'Node is not of type HTMLElement' ;
324
- const element = node as HTMLElement ;
325
- if ( ! element . isConnected )
326
- return 'Element is not attached to the DOM' ;
327
- if ( ! element . ownerDocument || ! element . ownerDocument . defaultView )
328
- return 'Element does not belong to a window' ;
329
-
330
- const style = element . ownerDocument . defaultView . getComputedStyle ( element ) ;
331
- if ( ! style || style . visibility === 'hidden' )
332
- return 'Element is hidden' ;
333
- if ( ! element . offsetParent && element . tagName !== 'BODY' )
334
- return 'Element is not visible' ;
335
- if ( element . nodeName . toLowerCase ( ) === 'input' ) {
336
- const input = element as HTMLInputElement ;
337
- const type = input . getAttribute ( 'type' ) || '' ;
338
- const kTextInputTypes = new Set ( [ '' , 'email' , 'number' , 'password' , 'search' , 'tel' , 'text' , 'url' ] ) ;
339
- if ( ! kTextInputTypes . has ( type . toLowerCase ( ) ) )
340
- return 'Cannot fill input of type "' + type + '".' ;
341
- if ( type . toLowerCase ( ) === 'number' ) {
342
- value = value . trim ( ) ;
343
- if ( ! value || isNaN ( Number ( value ) ) )
344
- return 'Cannot type text into input[type=number].' ;
345
- }
346
- if ( input . disabled )
347
- return 'Cannot fill a disabled input.' ;
348
- if ( input . readOnly )
349
- return 'Cannot fill a readonly input.' ;
350
- input . select ( ) ;
351
- input . focus ( ) ;
352
- } else if ( element . nodeName . toLowerCase ( ) === 'textarea' ) {
353
- const textarea = element as HTMLTextAreaElement ;
354
- if ( textarea . disabled )
355
- return 'Cannot fill a disabled textarea.' ;
356
- if ( textarea . readOnly )
357
- return 'Cannot fill a readonly textarea.' ;
358
- textarea . selectionStart = 0 ;
359
- textarea . selectionEnd = textarea . value . length ;
360
- textarea . focus ( ) ;
361
- } else if ( element . isContentEditable ) {
362
- const range = element . ownerDocument . createRange ( ) ;
363
- range . selectNodeContents ( element ) ;
364
- const selection = element . ownerDocument . defaultView . getSelection ( ) ;
365
- if ( ! selection )
366
- return 'Element belongs to invisible iframe.' ;
367
- selection . removeAllRanges ( ) ;
368
- selection . addRange ( range ) ;
369
- element . focus ( ) ;
370
- } else {
371
- return 'Element is not an <input>, <textarea> or [contenteditable] element.' ;
372
- }
373
- return false ;
374
- } , value ) ;
289
+ const error = await this . _evaluateInUtility ( ( injected , node , value ) => injected . fill ( node , value ) , value ) ;
375
290
if ( error )
376
291
throw new Error ( error ) ;
377
292
if ( value )
@@ -381,7 +296,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
381
296
}
382
297
383
298
async setInputFiles ( ...files : ( string | types . FilePayload ) [ ] ) {
384
- const multiple = await this . _evaluateInUtility ( ( node : Node ) => {
299
+ const multiple = await this . _evaluateInUtility ( ( injected : Injected , node : Node ) => {
385
300
if ( node . nodeType !== Node . ELEMENT_NODE || ( node as Element ) . tagName !== 'INPUT' )
386
301
throw new Error ( 'Node is not an HTMLInputElement' ) ;
387
302
const input = node as HTMLInputElement ;
@@ -403,7 +318,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
403
318
}
404
319
405
320
async focus ( ) {
406
- const errorMessage = await this . _evaluateInUtility ( ( element : Node ) => {
321
+ const errorMessage = await this . _evaluateInUtility ( ( injected : Injected , element : Node ) => {
407
322
if ( ! ( element as any ) [ 'focus' ] )
408
323
return 'Node is not an HTML or SVG element.' ;
409
324
( element as HTMLElement | SVGElement ) . focus ( ) ;
@@ -432,35 +347,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
432
347
}
433
348
434
349
private async _setChecked ( state : boolean , options ?: types . WaitForOptions ) {
435
- const isCheckboxChecked = async ( ) : Promise < boolean > => {
436
- return this . _evaluateInUtility ( ( node : Node ) => {
437
- if ( node . nodeType !== Node . ELEMENT_NODE )
438
- throw new Error ( 'Not a checkbox or radio button' ) ;
439
-
440
- let element : Element | undefined = node as Element ;
441
- if ( element . getAttribute ( 'role' ) === 'checkbox' )
442
- return element . getAttribute ( 'aria-checked' ) === 'true' ;
443
-
444
- if ( element . nodeName === 'LABEL' ) {
445
- const forId = element . getAttribute ( 'for' ) ;
446
- if ( forId && element . ownerDocument )
447
- element = element . ownerDocument . querySelector ( `input[id="${ forId } "]` ) || undefined ;
448
- else
449
- element = element . querySelector ( 'input[type=checkbox],input[type=radio]' ) || undefined ;
450
- }
451
- if ( element && element . nodeName === 'INPUT' ) {
452
- const type = element . getAttribute ( 'type' ) ;
453
- if ( type && ( type . toLowerCase ( ) === 'checkbox' || type . toLowerCase ( ) === 'radio' ) )
454
- return ( element as HTMLInputElement ) . checked ;
455
- }
456
- throw new Error ( 'Not a checkbox' ) ;
457
- } ) ;
458
- } ;
459
-
460
- if ( await isCheckboxChecked ( ) === state )
350
+ if ( await this . _evaluateInUtility ( ( injected , node ) => injected . isCheckboxChecked ( node ) ) === state )
461
351
return ;
462
352
await this . click ( options ) ;
463
- if ( await isCheckboxChecked ( ) !== state )
353
+ if ( await this . _evaluateInUtility ( ( injected , node ) => injected . isCheckboxChecked ( node ) ) !== state )
464
354
throw new Error ( 'Unable to click checkbox' ) ;
465
355
}
466
356
@@ -497,29 +387,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
497
387
}
498
388
499
389
async _waitForStablePosition ( options : types . TimeoutOptions = { } ) : Promise < void > {
500
- const context = await this . _context . frame . _utilityContext ( ) ;
501
- const stablePromise = context . evaluate ( ( injected : Injected , node : Node , timeout : number ) => {
502
- if ( ! node . isConnected )
503
- throw new Error ( 'Element is not attached to the DOM' ) ;
504
- const element = node . nodeType === Node . ELEMENT_NODE ? ( node as Element ) : node . parentElement ;
505
- if ( ! element )
506
- throw new Error ( 'Element is not attached to the DOM' ) ;
507
-
508
- let lastRect : types . Rect | undefined ;
509
- let counter = 0 ;
510
- return injected . poll ( 'raf' , undefined , timeout , ( ) => {
511
- // First raf happens in the same animation frame as evaluation, so it does not produce
512
- // any client rect difference compared to synchronous call. We skip the synchronous call
513
- // and only force layout during actual rafs as a small optimisation.
514
- if ( ++ counter === 1 )
515
- return false ;
516
- const clientRect = element . getBoundingClientRect ( ) ;
517
- const rect = { x : clientRect . top , y : clientRect . left , width : clientRect . width , height : clientRect . height } ;
518
- const isStable = lastRect && rect . x === lastRect . x && rect . y === lastRect . y && rect . width === lastRect . width && rect . height === lastRect . height ;
519
- lastRect = rect ;
520
- return isStable ;
521
- } ) ;
522
- } , await context . _injected ( ) , this , options . timeout || 0 ) ;
390
+ const stablePromise = this . _evaluateInUtility ( ( injected , node , timeout ) => {
391
+ return injected . waitForStablePosition ( node , timeout ) ;
392
+ } , options . timeout || 0 ) ;
523
393
await helper . waitWithTimeout ( stablePromise , 'element to stop moving' , options . timeout || 0 ) ;
524
394
}
525
395
@@ -533,18 +403,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
533
403
// Translate from viewport coordinates to frame coordinates.
534
404
point = { x : point . x - box . x , y : point . y - box . y } ;
535
405
}
536
- const context = await this . _context . frame . _utilityContext ( ) ;
537
- const hitTargetPromise = context . evaluate ( ( injected : Injected , node : Node , timeout : number , point : types . Point ) => {
538
- const element = node . nodeType === Node . ELEMENT_NODE ? ( node as Element ) : node . parentElement ;
539
- if ( ! element )
540
- throw new Error ( 'Element is not attached to the DOM' ) ;
541
- return injected . poll ( 'raf' , undefined , timeout , ( ) => {
542
- let hitElement = injected . utils . deepElementFromPoint ( document , point . x , point . y ) ;
543
- while ( hitElement && hitElement !== element )
544
- hitElement = injected . utils . parentElementOrShadowHost ( hitElement ) ;
545
- return hitElement === element ;
546
- } ) ;
547
- } , await context . _injected ( ) , this , options . timeout || 0 , point ) ;
406
+ const hitTargetPromise = this . _evaluateInUtility ( ( injected , node , timeout , point ) => {
407
+ return injected . waitForHitTargetAt ( node , timeout , point ) ;
408
+ } , options . timeout || 0 , point ) ;
548
409
await helper . waitWithTimeout ( hitTargetPromise , 'element to receive mouse events' , options . timeout || 0 ) ;
549
410
}
550
411
}
0 commit comments