2019-12-19 16:53:24 -08:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
* Modifications copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
import { CRSession } from './crConnection' ;
import { helper } from '../helper' ;
2019-12-19 17:03:27 -08:00
import { valueFromRemoteObject , getExceptionMessage , releaseObject } from './crProtocolHelper' ;
2019-12-19 16:53:24 -08:00
import { Protocol } from './protocol' ;
import * as js from '../javascript' ;
export class CRExecutionContext implements js . ExecutionContextDelegate {
_client : CRSession ;
_contextId : number ;
constructor ( client : CRSession , contextPayload : Protocol.Runtime.ExecutionContextDescription ) {
this . _client = client ;
this . _contextId = contextPayload . id ;
}
2020-05-20 15:55:33 -07:00
async rawEvaluate ( expression : string ) : Promise < js.RemoteObject > {
const { exceptionDetails , result : remoteObject } = await this . _client . send ( 'Runtime.evaluate' , {
expression : js.ensureSourceUrl ( expression ) ,
contextId : this._contextId ,
} ) . catch ( rewriteError ) ;
if ( exceptionDetails )
throw new Error ( 'Evaluation failed: ' + getExceptionMessage ( exceptionDetails ) ) ;
return remoteObject ;
}
2019-12-19 16:53:24 -08:00
async evaluate ( context : js.ExecutionContext , returnByValue : boolean , pageFunction : Function | string , . . . args : any [ ] ) : Promise < any > {
if ( helper . isString ( pageFunction ) ) {
const contextId = this . _contextId ;
2020-02-07 13:38:50 -08:00
const expression : string = pageFunction ;
2019-12-19 16:53:24 -08:00
const { exceptionDetails , result : remoteObject } = await this . _client . send ( 'Runtime.evaluate' , {
2020-05-19 08:40:45 -07:00
expression : js.ensureSourceUrl ( expression ) ,
2019-12-19 16:53:24 -08:00
contextId ,
returnByValue ,
awaitPromise : true ,
userGesture : true
} ) . catch ( rewriteError ) ;
if ( exceptionDetails )
throw new Error ( 'Evaluation failed: ' + getExceptionMessage ( exceptionDetails ) ) ;
2020-05-15 15:21:49 -07:00
return returnByValue ? valueFromRemoteObject ( remoteObject ) : context . createHandle ( remoteObject ) ;
2019-12-19 16:53:24 -08:00
}
if ( typeof pageFunction !== 'function' )
throw new Error ( ` Expected to get |string| or |function| as the first argument, but got " ${ pageFunction } " instead. ` ) ;
2020-03-19 13:07:33 -07:00
const { functionText , values , handles , dispose } = await js . prepareFunctionCall < Protocol.Runtime.CallArgument > ( pageFunction , context , args , ( value : any ) = > {
2020-03-18 10:41:46 -07:00
if ( typeof value === 'bigint' ) // eslint-disable-line valid-typeof
return { handle : { unserializableValue : ` ${ value . toString ( ) } n ` } } ;
if ( Object . is ( value , - 0 ) )
return { handle : { unserializableValue : '-0' } } ;
if ( Object . is ( value , Infinity ) )
return { handle : { unserializableValue : 'Infinity' } } ;
if ( Object . is ( value , - Infinity ) )
return { handle : { unserializableValue : '-Infinity' } } ;
if ( Object . is ( value , NaN ) )
return { handle : { unserializableValue : 'NaN' } } ;
if ( value && ( value instanceof js . JSHandle ) ) {
const remoteObject = toRemoteObject ( value ) ;
if ( remoteObject . unserializableValue )
return { handle : { unserializableValue : remoteObject.unserializableValue } } ;
if ( ! remoteObject . objectId )
2020-03-19 13:07:33 -07:00
return { handle : { value : remoteObject.value } } ;
2020-03-18 10:41:46 -07:00
return { handle : { objectId : remoteObject.objectId } } ;
2019-12-19 16:53:24 -08:00
}
2020-03-18 10:41:46 -07:00
return { value } ;
} ) ;
2020-03-02 13:51:32 -08:00
2020-03-19 13:07:33 -07:00
try {
2020-05-20 15:55:33 -07:00
const utilityScript = await context . utilityScript ( ) ;
2020-03-19 13:07:33 -07:00
const { exceptionDetails , result : remoteObject } = await this . _client . send ( 'Runtime.callFunctionOn' , {
2020-05-20 15:55:33 -07:00
functionDeclaration : ` function (...args) { return this.evaluate(...args) } ${ js . generateSourceUrl ( ) } ` ,
objectId : utilityScript._remoteObject.objectId ,
2020-03-19 13:07:33 -07:00
arguments : [
2020-05-20 15:55:33 -07:00
{ value : functionText } ,
2020-03-19 13:07:33 -07:00
. . . values . map ( value = > ( { value } ) ) ,
. . . handles ,
] ,
returnByValue ,
awaitPromise : true ,
userGesture : true
} ) . catch ( rewriteError ) ;
if ( exceptionDetails )
throw new Error ( 'Evaluation failed: ' + getExceptionMessage ( exceptionDetails ) ) ;
2020-05-15 15:21:49 -07:00
return returnByValue ? valueFromRemoteObject ( remoteObject ) : context . createHandle ( remoteObject ) ;
2020-03-19 13:07:33 -07:00
} finally {
dispose ( ) ;
}
2019-12-19 16:53:24 -08:00
}
async getProperties ( handle : js.JSHandle ) : Promise < Map < string , js.JSHandle > > {
2020-01-13 13:33:25 -08:00
const objectId = toRemoteObject ( handle ) . objectId ;
if ( ! objectId )
return new Map ( ) ;
2019-12-19 16:53:24 -08:00
const response = await this . _client . send ( 'Runtime.getProperties' , {
2020-01-13 13:33:25 -08:00
objectId ,
2019-12-19 16:53:24 -08:00
ownProperties : true
} ) ;
const result = new Map ( ) ;
for ( const property of response . result ) {
if ( ! property . enumerable )
continue ;
2020-05-15 15:21:49 -07:00
result . set ( property . name , handle . _context . createHandle ( property . value ) ) ;
2019-12-19 16:53:24 -08:00
}
return result ;
}
async releaseHandle ( handle : js.JSHandle ) : Promise < void > {
await releaseObject ( this . _client , toRemoteObject ( handle ) ) ;
}
async handleJSONValue < T > ( handle : js.JSHandle < T > ) : Promise < T > {
const remoteObject = toRemoteObject ( handle ) ;
if ( remoteObject . objectId ) {
const response = await this . _client . send ( 'Runtime.callFunctionOn' , {
functionDeclaration : 'function() { return this; }' ,
objectId : remoteObject.objectId ,
returnByValue : true ,
awaitPromise : true ,
} ) ;
return valueFromRemoteObject ( response . result ) ;
}
return valueFromRemoteObject ( remoteObject ) ;
}
handleToString ( handle : js.JSHandle , includeType : boolean ) : string {
const object = toRemoteObject ( handle ) ;
if ( object . objectId ) {
const type = object . subtype || object . type ;
return 'JSHandle@' + type ;
}
return ( includeType ? 'JSHandle:' : '' ) + valueFromRemoteObject ( object ) ;
}
}
function toRemoteObject ( handle : js.JSHandle ) : Protocol . Runtime . RemoteObject {
return handle . _remoteObject as Protocol . Runtime . RemoteObject ;
}
2020-05-20 15:55:33 -07:00
function rewriteError ( error : Error ) : Protocol . Runtime . evaluateReturnValue {
if ( error . message . includes ( 'Object reference chain is too long' ) )
return { result : { type : 'undefined' } } ;
if ( error . message . includes ( 'Object couldn\'t be returned by value' ) )
return { result : { type : 'undefined' } } ;
if ( error . message . endsWith ( 'Cannot find context with specified id' ) || error . message . endsWith ( 'Inspected target navigated or closed' ) || error . message . endsWith ( 'Execution context was destroyed.' ) )
throw new Error ( 'Execution context was destroyed, most likely because of a navigation.' ) ;
if ( error instanceof TypeError && error . message . startsWith ( 'Converting circular structure to JSON' ) )
error . message += ' Are you passing a nested JSHandle?' ;
throw error ;
}