2017-05-11 07:06:41 +00:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
*
* 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 .
* /
2017-06-22 20:38:10 +00:00
let fs = require ( 'fs' ) ;
let EventEmitter = require ( 'events' ) ;
let mime = require ( 'mime' ) ;
2017-06-29 06:09:28 +00:00
let NetworkManager = require ( './NetworkManager' ) ;
2017-07-19 01:54:24 +00:00
let NavigatorWatcher = require ( './NavigatorWatcher' ) ;
2017-06-22 20:38:10 +00:00
let Dialog = require ( './Dialog' ) ;
2017-07-18 01:13:04 +00:00
let EmulationManager = require ( './EmulationManager' ) ;
2017-06-22 20:38:10 +00:00
let FrameManager = require ( './FrameManager' ) ;
2017-07-18 01:49:52 +00:00
let Keyboard = require ( './Keyboard' ) ;
2017-06-27 19:40:46 +00:00
let helper = require ( './helper' ) ;
2017-05-11 07:06:41 +00:00
class Page extends EventEmitter {
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Connection } client
2017-07-19 05:10:38 +00:00
* @ param { ! TaskQueue } screenshotTaskQueue
2017-06-21 20:58:49 +00:00
* @ return { ! Promise < ! Page > }
* /
2017-07-19 05:10:38 +00:00
static async create ( client , screenshotTaskQueue ) {
2017-06-21 20:51:06 +00:00
await Promise . all ( [
client . send ( 'Network.enable' , { } ) ,
client . send ( 'Page.enable' , { } ) ,
client . send ( 'Runtime.enable' , { } ) ,
client . send ( 'Security.enable' , { } ) ,
] ) ;
2017-06-29 06:09:28 +00:00
let userAgentExpression = helper . evaluationString ( ( ) => window . navigator . userAgent ) ;
let { result : { value : userAgent } } = await client . send ( 'Runtime.evaluate' , { expression : userAgentExpression , returnByValue : true } ) ;
2017-06-22 20:38:10 +00:00
let frameManager = await FrameManager . create ( client ) ;
2017-06-29 06:09:28 +00:00
let networkManager = new NetworkManager ( client , userAgent ) ;
2017-07-19 05:10:38 +00:00
let page = new Page ( client , frameManager , networkManager , screenshotTaskQueue ) ;
2017-06-21 20:51:06 +00:00
// Initialize default page size.
2017-07-18 01:13:04 +00:00
await page . setViewport ( { width : 400 , height : 300 } ) ;
2017-06-21 20:51:06 +00:00
return page ;
}
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Connection } client
* @ param { ! FrameManager } frameManager
2017-06-29 06:09:28 +00:00
* @ param { ! NetworkManager } networkManager
2017-07-19 05:10:38 +00:00
* @ param { ! TaskQueue } screenshotTaskQueue
2017-06-21 20:58:49 +00:00
* /
2017-07-19 05:10:38 +00:00
constructor ( client , frameManager , networkManager , screenshotTaskQueue ) {
2017-06-21 20:51:06 +00:00
super ( ) ;
this . _client = client ;
this . _frameManager = frameManager ;
2017-06-29 06:09:28 +00:00
this . _networkManager = networkManager ;
2017-06-21 20:51:06 +00:00
/** @type {!Map<string, function>} */
this . _inPageCallbacks = new Map ( ) ;
2017-07-18 01:49:52 +00:00
this . _keyboard = new Keyboard ( this . _client ) ;
2017-07-19 05:10:38 +00:00
this . _screenshotTaskQueue = screenshotTaskQueue ;
2017-06-21 20:51:06 +00:00
this . _frameManager . on ( FrameManager . Events . FrameAttached , event => this . emit ( Page . Events . FrameAttached , event ) ) ;
this . _frameManager . on ( FrameManager . Events . FrameDetached , event => this . emit ( Page . Events . FrameDetached , event ) ) ;
this . _frameManager . on ( FrameManager . Events . FrameNavigated , event => this . emit ( Page . Events . FrameNavigated , event ) ) ;
2017-06-29 06:09:28 +00:00
this . _networkManager . on ( NetworkManager . Events . Request , event => this . emit ( Page . Events . Request , event ) ) ;
this . _networkManager . on ( NetworkManager . Events . Response , event => this . emit ( Page . Events . Response , event ) ) ;
this . _networkManager . on ( NetworkManager . Events . RequestFailed , event => this . emit ( Page . Events . RequestFailed , event ) ) ;
this . _networkManager . on ( NetworkManager . Events . RequestFinished , event => this . emit ( Page . Events . RequestFinished , event ) ) ;
2017-06-28 05:02:46 +00:00
client . on ( 'Page.loadEventFired' , event => this . emit ( Page . Events . Load ) ) ;
2017-06-21 20:51:06 +00:00
client . on ( 'Runtime.consoleAPICalled' , event => this . _onConsoleAPI ( event ) ) ;
client . on ( 'Page.javascriptDialogOpening' , event => this . _onDialog ( event ) ) ;
client . on ( 'Runtime.exceptionThrown' , exception => this . _handleException ( exception . exceptionDetails ) ) ;
}
/ * *
2017-06-21 20:58:49 +00:00
* @ return { ! Frame }
* /
2017-06-21 20:51:06 +00:00
mainFrame ( ) {
return this . _frameManager . mainFrame ( ) ;
}
2017-06-21 20:36:04 +00:00
2017-07-18 01:49:52 +00:00
/ * *
* @ return { ! Keyboard }
* /
get keyboard ( ) {
return this . _keyboard ;
}
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ return { ! Array < ! Frame > }
* /
2017-06-21 20:51:06 +00:00
frames ( ) {
return this . _frameManager . frames ( ) ;
}
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-28 06:31:38 +00:00
* @ param { ? function ( ! InterceptedRequest ) } interceptor
2017-06-21 20:58:49 +00:00
* /
2017-06-21 20:51:06 +00:00
async setRequestInterceptor ( interceptor ) {
2017-06-29 06:09:28 +00:00
return this . _networkManager . setRequestInterceptor ( interceptor ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-15 07:20:37 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } url
* @ return { ! Promise }
* /
2017-06-21 20:51:06 +00:00
async addScriptTag ( url ) {
return this . evaluate ( addScriptTag , url ) ;
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } url
* /
2017-06-21 20:51:06 +00:00
function addScriptTag ( url ) {
2017-06-22 20:38:10 +00:00
let script = document . createElement ( 'script' ) ;
2017-06-21 20:51:06 +00:00
script . src = url ;
2017-06-22 20:38:10 +00:00
let promise = new Promise ( x => script . onload = x ) ;
2017-06-21 20:51:06 +00:00
document . head . appendChild ( script ) ;
return promise ;
2017-05-12 17:01:22 +00:00
}
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } filePath
* @ return { ! Promise }
* /
2017-06-21 20:51:06 +00:00
async injectFile ( filePath ) {
2017-07-06 23:09:23 +00:00
const contents = await new Promise ( ( resolve , reject ) => {
fs . readFile ( filePath , 'utf8' , ( err , data ) => {
if ( err ) return reject ( err ) ;
resolve ( data ) ;
} ) ;
} ) ;
return this . _client . send ( 'Runtime.evaluate' , { expression : contents , returnByValue : true } ) ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } name
* @ param { function ( ? ) } callback
* /
2017-06-21 20:51:06 +00:00
async setInPageCallback ( name , callback ) {
if ( this . _inPageCallbacks [ name ] )
throw new Error ( ` Failed to set in-page callback with name ${ name } : window[' ${ name } '] already exists! ` ) ;
this . _inPageCallbacks [ name ] = callback ;
2017-06-27 19:40:46 +00:00
let expression = helper . evaluationString ( inPageCallback , name ) ;
2017-06-21 20:51:06 +00:00
await this . _client . send ( 'Page.addScriptToEvaluateOnLoad' , { scriptSource : expression } ) ;
await this . _client . send ( 'Runtime.evaluate' , { expression , returnByValue : true } ) ;
function inPageCallback ( callbackName ) {
window [ callbackName ] = async ( ... args ) => {
const me = window [ callbackName ] ;
let callbacks = me [ 'callbacks' ] ;
if ( ! callbacks ) {
callbacks = new Map ( ) ;
me [ 'callbacks' ] = callbacks ;
2017-06-20 21:54:53 +00:00
}
2017-06-21 20:51:06 +00:00
const seq = ( me [ 'lastSeq' ] || 0 ) + 1 ;
me [ 'lastSeq' ] = seq ;
const promise = new Promise ( fulfill => callbacks . set ( seq , fulfill ) ) ;
// eslint-disable-next-line no-console
console . debug ( 'driver:InPageCallback' , JSON . stringify ( { name : callbackName , seq , args } ) ) ;
return promise ;
} ;
2017-05-11 07:06:41 +00:00
}
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Object } headers
* @ return { ! Promise }
* /
2017-06-29 06:09:28 +00:00
async setHTTPHeaders ( headers ) {
return this . _networkManager . setHTTPHeaders ( headers ) ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-06-21 20:58:49 +00:00
* @ return { ! Object }
* /
2017-06-29 06:09:28 +00:00
httpHeaders ( ) {
return this . _networkManager . httpHeaders ( ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } userAgent
* @ return { ! Promise }
* /
2017-06-29 06:09:28 +00:00
async setUserAgent ( userAgent ) {
return this . _networkManager . setUserAgent ( userAgent ) ;
2017-06-21 20:51:06 +00:00
}
2017-05-11 07:06:41 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ return { string }
* /
2017-06-29 06:09:28 +00:00
userAgent ( ) {
return this . _networkManager . userAgent ( ) ;
2017-06-21 20:51:06 +00:00
}
2017-05-11 07:06:41 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Object } exceptionDetails
* /
2017-06-21 20:51:06 +00:00
async _handleException ( exceptionDetails ) {
2017-06-27 19:40:46 +00:00
let message = await helper . getExceptionMessage ( this . _client , exceptionDetails ) ;
2017-07-06 18:22:32 +00:00
this . emit ( Page . Events . PageError , new Error ( message ) ) ;
2017-06-21 20:51:06 +00:00
}
async _onConsoleAPI ( event ) {
if ( event . type === 'debug' && event . args . length && event . args [ 0 ] . value === 'driver:InPageCallback' ) {
2017-06-22 20:38:10 +00:00
let { name , seq , args } = JSON . parse ( event . args [ 1 ] . value ) ;
let result = await this . _inPageCallbacks [ name ] ( ... args ) ;
2017-06-27 19:40:46 +00:00
let expression = helper . evaluationString ( deliverResult , name , seq , result ) ;
2017-06-21 20:51:06 +00:00
this . _client . send ( 'Runtime.evaluate' , { expression } ) ;
function deliverResult ( name , seq , result ) {
window [ name ] [ 'callbacks' ] . get ( seq ) ( result ) ;
window [ name ] [ 'callbacks' ] . delete ( seq ) ;
}
return ;
}
2017-07-17 08:57:37 +00:00
let values = await Promise . all ( event . args . map ( arg => helper . serializeRemoteObject ( this . _client , arg ) ) ) ;
2017-07-18 03:38:11 +00:00
this . emit ( Page . Events . Console , ... values ) ;
2017-06-21 20:51:06 +00:00
}
_onDialog ( event ) {
2017-06-22 20:38:10 +00:00
let dialogType = null ;
2017-06-21 20:51:06 +00:00
if ( event . type === 'alert' )
dialogType = Dialog . Type . Alert ;
else if ( event . type === 'confirm' )
dialogType = Dialog . Type . Confirm ;
else if ( event . type === 'prompt' )
dialogType = Dialog . Type . Prompt ;
else if ( event . type === 'beforeunload' )
dialogType = Dialog . Type . BeforeUnload ;
console . assert ( dialogType , 'Unknown javascript dialog type: ' + event . type ) ;
2017-06-22 20:38:10 +00:00
let dialog = new Dialog ( this . _client , dialogType , event . message ) ;
2017-06-21 20:51:06 +00:00
this . emit ( Page . Events . Dialog , dialog ) ;
}
/ * *
2017-07-14 21:05:27 +00:00
* @ return { ! string }
2017-06-21 20:58:49 +00:00
* /
2017-07-14 21:05:27 +00:00
url ( ) {
return this . mainFrame ( ) . url ( ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } html
* @ return { ! Promise }
* /
2017-06-21 20:51:06 +00:00
async setContent ( html ) {
2017-07-06 02:07:33 +00:00
this . evaluate ( html => {
2017-06-21 20:51:06 +00:00
document . open ( ) ;
document . write ( html ) ;
document . close ( ) ;
} , html ) ;
}
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } html
* @ param { ! Object = } options
2017-07-10 22:09:52 +00:00
* @ return { ! Promise < ! Response > }
2017-06-21 20:58:49 +00:00
* /
2017-07-10 22:09:52 +00:00
async navigate ( url , options ) {
2017-07-19 01:54:24 +00:00
const watcher = new NavigatorWatcher ( this . _client , this . _networkManager , options ) ;
const result = watcher . waitForNavigation ( ) ;
2017-07-17 19:15:06 +00:00
const referrer = this . _networkManager . httpHeaders ( ) [ 'referer' ] ;
2017-07-19 01:54:24 +00:00
try {
await this . _client . send ( 'Page.navigate' , { url , referrer } ) ;
} catch ( e ) {
watcher . cancel ( ) ;
throw e ;
}
const responses = await result ;
return responses . get ( this . mainFrame ( ) . url ( ) ) ;
2017-07-18 01:13:04 +00:00
}
/ * *
2017-07-19 01:54:24 +00:00
* @ param { ! Object = } options
* @ return { ! Promise < ? Response > }
2017-07-18 01:13:04 +00:00
* /
2017-07-19 01:54:24 +00:00
async reload ( options ) {
this . _client . send ( 'Page.reload' ) ;
return this . waitForNavigation ( options ) ;
}
2017-07-18 01:13:04 +00:00
2017-07-19 01:54:24 +00:00
/ * *
* @ param { ! Object = } options
2017-07-19 02:11:37 +00:00
* @ return { ! Promise < ! Response > }
2017-07-19 01:54:24 +00:00
* /
async waitForNavigation ( options ) {
const watcher = new NavigatorWatcher ( this . _client , this . _networkManager , options ) ;
const responses = await watcher . waitForNavigation ( ) ;
return responses . get ( this . mainFrame ( ) . url ( ) ) || null ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-07-19 02:11:37 +00:00
/ * *
* @ param { ! Object = } options
* @ return { ! Promise < ? Response > }
* /
async goBack ( options ) {
return this . _go ( - 1 , options ) ;
}
/ * *
* @ param { ! Object = } options
* @ return { ! Promise < ? Response > }
* /
async goForward ( options ) {
return this . _go ( + 1 , options ) ;
}
/ * *
* @ param { ! Object = } options
* @ return { ! Promise < ? Response > }
* /
async _go ( delta , options ) {
const history = await this . _client . send ( 'Page.getNavigationHistory' ) ;
const entry = history . entries [ history . currentIndex + delta ] ;
if ( ! entry )
return null ;
const result = this . waitForNavigation ( options ) ;
this . _client . send ( 'Page.navigateToHistoryEntry' , { entryId : entry . id } ) ;
return result ;
}
2017-06-21 20:51:06 +00:00
/ * *
2017-07-18 01:13:04 +00:00
* @ param { ! Page . Viewport } viewport
2017-06-21 20:58:49 +00:00
* @ return { ! Promise }
* /
2017-07-18 01:13:04 +00:00
async setViewport ( viewport ) {
const needsReload = await EmulationManager . emulateViewport ( this . _client , viewport ) ;
this . _viewport = viewport ;
if ( needsReload )
await this . reload ( ) ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-07-18 01:13:04 +00:00
* @ return { ! Page . Viewport }
2017-06-21 20:58:49 +00:00
* /
2017-07-18 01:13:04 +00:00
viewport ( ) {
return this . _viewport ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-07-18 01:13:04 +00:00
/ * *
* @ return { ! Promise < ! Array < string >> }
* /
static emulatedDevices ( ) {
return EmulationManager . deviceNames ( name ) ;
}
/ * *
* @ param { string } name
* @ param { ! Object = } options
* @ return { ! Promise }
* /
emulate ( name , options ) {
2017-07-17 19:15:06 +00:00
return Promise . all ( [
2017-07-18 01:13:04 +00:00
this . setUserAgent ( EmulationManager . deviceUserAgent ( name ) ) ,
this . setViewport ( EmulationManager . deviceViewport ( name , options ) )
2017-07-17 19:15:06 +00:00
] ) ;
}
2017-06-21 20:51:06 +00:00
/ * *
2017-07-14 20:51:22 +00:00
* @ param { function ( ) } pageFunction
2017-06-21 20:58:49 +00:00
* @ param { ! Array < * > } args
* @ return { ! Promise < ( ! Object | undefined ) > }
* /
2017-07-14 20:51:22 +00:00
async evaluate ( pageFunction , ... args ) {
return this . _frameManager . mainFrame ( ) . evaluate ( pageFunction , ... args ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-07-14 20:51:22 +00:00
* @ param { function ( ) } pageFunction
2017-06-21 20:58:49 +00:00
* @ param { ! Array < * > } args
* @ return { ! Promise }
* /
2017-07-14 20:51:22 +00:00
async evaluateOnInitialized ( pageFunction , ... args ) {
let scriptSource = helper . evaluationString ( pageFunction , ... args ) ;
2017-06-21 20:51:06 +00:00
await this . _client . send ( 'Page.addScriptToEvaluateOnLoad' , { scriptSource } ) ;
}
2017-06-20 01:03:01 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Object = } options
* @ return { ! Promise < ! Buffer > }
* /
2017-06-21 20:51:06 +00:00
async screenshot ( options ) {
options = options || { } ;
2017-06-22 20:38:10 +00:00
let screenshotType = null ;
2017-06-21 20:51:06 +00:00
if ( options . path ) {
2017-06-22 20:38:10 +00:00
let mimeType = mime . lookup ( options . path ) ;
2017-06-21 20:51:06 +00:00
if ( mimeType === 'image/png' )
screenshotType = 'png' ;
else if ( mimeType === 'image/jpeg' )
screenshotType = 'jpeg' ;
console . assert ( screenshotType , 'Unsupported screenshot mime type: ' + mimeType ) ;
}
if ( options . type ) {
console . assert ( ! screenshotType || options . type === screenshotType , ` Passed screenshot type ' ${ options . type } ' does not match the type inferred from the file path: ' ${ screenshotType } ' ` ) ;
console . assert ( options . type === 'png' || options . type === 'jpeg' , 'Unknown options.type value: ' + options . type ) ;
screenshotType = options . type ;
}
if ( ! screenshotType )
screenshotType = 'png' ;
if ( options . quality ) {
console . assert ( screenshotType === 'jpeg' , 'options.quality is unsupported for the ' + screenshotType + ' screenshots' ) ;
console . assert ( typeof options . quality === 'number' , 'Expected options.quality to be a number but found ' + ( typeof options . quality ) ) ;
console . assert ( Number . isInteger ( options . quality ) , 'Expected options.quality to be an integer' ) ;
console . assert ( options . quality >= 0 && options . quality <= 100 , 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options . quality ) ;
}
console . assert ( ! options . clip || ! options . fullPage , 'options.clip and options.fullPage are exclusive' ) ;
if ( options . clip ) {
console . assert ( typeof options . clip . x === 'number' , 'Expected options.clip.x to be a number but found ' + ( typeof options . clip . x ) ) ;
console . assert ( typeof options . clip . y === 'number' , 'Expected options.clip.y to be a number but found ' + ( typeof options . clip . y ) ) ;
console . assert ( typeof options . clip . width === 'number' , 'Expected options.clip.width to be a number but found ' + ( typeof options . clip . width ) ) ;
console . assert ( typeof options . clip . height === 'number' , 'Expected options.clip.height to be a number but found ' + ( typeof options . clip . height ) ) ;
}
2017-07-19 05:10:38 +00:00
return this . _screenshotTaskQueue . postTask ( this . _screenshotTask . bind ( this , screenshotType , options ) ) ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-07-17 19:15:06 +00:00
* @ param { string } format
2017-06-21 20:58:49 +00:00
* @ param { ! Object = } options
* @ return { ! Promise < ! Buffer > }
* /
2017-07-17 19:15:06 +00:00
async _screenshotTask ( format , options ) {
2017-07-19 05:10:38 +00:00
await this . _client . send ( 'Target.activateTarget' , { targetId : this . _client . targetId ( ) } ) ;
2017-07-17 19:15:06 +00:00
if ( options . fullPage ) {
2017-07-18 01:13:04 +00:00
const metrics = await this . _client . send ( 'Page.getLayoutMetrics' ) ;
2017-07-17 19:15:06 +00:00
const width = Math . ceil ( metrics . contentSize . width ) ;
const height = Math . ceil ( metrics . contentSize . height ) ;
await this . _client . send ( 'Emulation.resetPageScaleFactor' ) ;
2017-07-18 01:13:04 +00:00
const mobile = this . _viewport . isMobile || false ;
const deviceScaleFactor = this . _viewport . deviceScaleFactor || 1 ;
const landscape = this . _viewport . isLandscape || false ;
2017-07-17 19:15:06 +00:00
const screenOrientation = landscape ? { angle : 90 , type : 'landscapePrimary' } : { angle : 0 , type : 'portraitPrimary' } ;
await this . _client . send ( 'Emulation.setDeviceMetricsOverride' , { mobile , width , height , deviceScaleFactor , screenOrientation } ) ;
2017-06-21 20:51:06 +00:00
}
2017-07-17 19:15:06 +00:00
let clip = options . clip ? Object . assign ( { } , options [ 'clip' ] ) : undefined ;
if ( clip )
clip . scale = 1 ;
let result = await this . _client . send ( 'Page.captureScreenshot' , { format , quality : options . quality , clip } ) ;
if ( options . fullPage )
2017-07-18 01:13:04 +00:00
await this . setViewport ( this . _viewport ) ;
2017-07-17 19:15:06 +00:00
2017-06-22 20:38:10 +00:00
let buffer = new Buffer ( result . data , 'base64' ) ;
2017-06-21 20:51:06 +00:00
if ( options . path )
fs . writeFileSync ( options . path , buffer ) ;
return buffer ;
}
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } filePath
* @ param { ! Object = } options
2017-07-17 09:06:52 +00:00
* @ return { ! Promise < ! Buffer > }
2017-06-21 20:58:49 +00:00
* /
2017-07-17 09:06:52 +00:00
async pdf ( options ) {
2017-06-21 20:51:06 +00:00
options = options || { } ;
2017-06-22 20:38:10 +00:00
let scale = options . scale || 1 ;
let displayHeaderFooter = options . displayHeaderFooter || false ;
let printBackground = options . printBackground || true ;
let landscape = options . landscape || false ;
let pageRanges = options . pageRanges || '' ;
2017-06-21 20:51:06 +00:00
2017-06-22 20:38:10 +00:00
let paperWidth = 8.5 ;
let paperHeight = 11 ;
2017-06-21 20:51:06 +00:00
if ( options . format ) {
2017-06-22 20:38:10 +00:00
let format = Page . PaperFormats [ options . format ] ;
2017-06-21 20:51:06 +00:00
console . assert ( format , 'Unknown paper format: ' + options . format ) ;
paperWidth = format . width ;
paperHeight = format . height ;
} else {
paperWidth = convertPrintParameterToInches ( options . width ) || paperWidth ;
paperHeight = convertPrintParameterToInches ( options . height ) || paperHeight ;
}
2017-06-22 20:38:10 +00:00
let marginOptions = options . margin || { } ;
let marginTop = convertPrintParameterToInches ( marginOptions . top ) || 0 ;
let marginLeft = convertPrintParameterToInches ( marginOptions . left ) || 0 ;
let marginBottom = convertPrintParameterToInches ( marginOptions . bottom ) || 0 ;
let marginRight = convertPrintParameterToInches ( marginOptions . right ) || 0 ;
2017-06-21 20:51:06 +00:00
2017-06-22 20:38:10 +00:00
let result = await this . _client . send ( 'Page.printToPDF' , {
2017-06-21 20:51:06 +00:00
landscape : landscape ,
displayHeaderFooter : displayHeaderFooter ,
printBackground : printBackground ,
scale : scale ,
paperWidth : paperWidth ,
paperHeight : paperHeight ,
marginTop : marginTop ,
marginBottom : marginBottom ,
marginLeft : marginLeft ,
marginRight : marginRight ,
pageRanges : pageRanges
} ) ;
2017-06-22 20:38:10 +00:00
let buffer = new Buffer ( result . data , 'base64' ) ;
2017-07-17 09:06:52 +00:00
if ( options . path )
fs . writeFileSync ( options . path , buffer ) ;
return buffer ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-06-21 20:58:49 +00:00
* @ return { ! Promise < string > }
* /
2017-06-21 20:51:06 +00:00
async plainText ( ) {
return this . evaluate ( ( ) => document . body . innerText ) ;
}
2017-06-21 20:36:04 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ return { ! Promise < string > }
* /
2017-06-21 20:51:06 +00:00
async title ( ) {
return this . evaluate ( ( ) => document . title ) ;
}
2017-05-11 07:06:41 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ return { ! Promise }
* /
2017-06-21 20:51:06 +00:00
async close ( ) {
await this . _client . dispose ( ) ;
}
2017-06-28 01:27:22 +00:00
/ * *
* @ param { string } selector
* @ param { ! Promise < number > }
* /
async _querySelector ( selector ) {
2017-07-11 23:25:25 +00:00
let { root } = await this . _client . send ( 'DOM.getDocument' , { depth : 1 } ) ;
2017-07-10 18:21:46 +00:00
let { nodeId } = await this . _client . send ( 'DOM.querySelector' , {
2017-07-11 23:25:25 +00:00
nodeId : root . nodeId ,
2017-06-28 01:27:22 +00:00
selector
2017-07-10 18:21:46 +00:00
} ) ;
if ( ! nodeId )
throw new Error ( 'No node found for selector: ' + selector ) ;
return nodeId ;
2017-06-28 01:27:22 +00:00
}
/ * *
* @ param { string } selector
* @ param { ! Promise }
* /
async click ( selector ) {
2017-07-14 01:32:34 +00:00
let center = await this . evaluate ( selector => {
let node = document . querySelector ( selector ) ;
if ( ! node )
return null ;
let rect = node . getBoundingClientRect ( ) ;
return {
x : ( rect . left + rect . right ) / 2 ,
y : ( rect . top + rect . bottom ) / 2
} ;
} , selector ) ;
if ( ! center )
throw new Error ( 'No node found for selector: ' + selector ) ;
2017-07-17 19:15:06 +00:00
let x = Math . round ( center . x ) ;
let y = Math . round ( center . y ) ;
2017-07-14 01:32:34 +00:00
this . _client . send ( 'Input.dispatchMouseEvent' , {
type : 'mouseMoved' ,
x , y
} ) ;
this . _client . send ( 'Input.dispatchMouseEvent' , {
type : 'mousePressed' ,
button : 'left' ,
x , y ,
clickCount : 1
} ) ;
await this . _client . send ( 'Input.dispatchMouseEvent' , {
type : 'mouseReleased' ,
button : 'left' ,
x , y ,
clickCount : 1
} ) ;
2017-06-28 01:27:22 +00:00
}
/ * *
* @ param { string } selector
* @ param { ! Promise }
* /
async focus ( selector ) {
2017-07-14 01:32:34 +00:00
let success = await this . evaluate ( selector => {
let node = document . querySelector ( selector ) ;
if ( ! node )
return false ;
node . focus ( ) ;
return true ;
} , selector ) ;
if ( ! success )
throw new Error ( 'No node found for selector: ' + selector ) ;
2017-06-28 01:27:22 +00:00
}
/ * *
* @ param { string } text
* @ param { ! Promise }
* /
async type ( text ) {
2017-07-18 01:49:52 +00:00
return this . _keyboard . type ( text ) ;
2017-06-28 01:27:22 +00:00
}
2017-07-07 22:39:02 +00:00
2017-07-19 21:43:07 +00:00
/ * *
* @ param { string } text
* @ param { ! Object = } options
* /
async press ( key , options ) {
return this . _keyboard . press ( key , options ) ;
}
2017-07-07 22:39:02 +00:00
/ * *
* @ param { string } selector
* @ return { ! Promise < undefined > }
* /
waitFor ( selector ) {
return this . mainFrame ( ) . waitFor ( selector ) ;
}
2017-07-10 18:21:46 +00:00
/ * *
* @ param { string } selector
2017-07-11 15:57:26 +00:00
* @ param { ! Array < string > } filePaths
2017-07-10 18:21:46 +00:00
* @ return { ! Promise }
* /
2017-07-11 15:57:26 +00:00
async uploadFile ( selector , ... filePaths ) {
2017-07-11 23:25:25 +00:00
try {
const nodeId = await this . _querySelector ( selector ) ;
2017-07-11 15:57:26 +00:00
await this . _client . send ( 'DOM.setFileInputFiles' , { nodeId , files : filePaths } ) ;
2017-07-11 23:25:25 +00:00
} finally {
await this . _client . send ( 'DOM.disable' ) ;
}
2017-07-10 18:21:46 +00:00
}
2017-07-18 01:56:56 +00:00
/ * *
* @ param { string } selector
* @ param { function ( ! Element ) : T } pageFunction
* @ param { ! Array < * > } args
* @ return { ! Promise < ? T > }
* /
async $ ( selector , pageFunction , ... args ) {
return this . mainFrame ( ) . $ ( selector , pageFunction , ... args ) ;
}
/ * *
* @ param { string } selector
* @ param { function ( ! Element ) : T } pageFunction
* @ param { ! Array < * > } args
* @ return { ! Promise < ! Array < T >> }
* /
async $$ ( selector , pageFunction , ... args ) {
return this . mainFrame ( ) . $$ ( selector , pageFunction , ... args ) ;
}
2017-05-11 07:06:41 +00:00
}
2017-05-14 18:29:42 +00:00
/** @enum {string} */
Page . PaperFormats = {
2017-06-21 20:51:06 +00:00
Letter : { width : 8.5 , height : 11 } ,
Legal : { width : 8.5 , height : 14 } ,
Tabloid : { width : 11 , height : 17 } ,
Ledger : { width : 17 , height : 11 } ,
A0 : { width : 33.1 , height : 46.8 } ,
A1 : { width : 23.4 , height : 33.1 } ,
A2 : { width : 16.5 , height : 23.4 } ,
A3 : { width : 11.7 , height : 16.5 } ,
A4 : { width : 8.27 , height : 11.7 } ,
A5 : { width : 5.83 , height : 8.27 } ,
2017-05-14 18:29:42 +00:00
} ;
2017-06-22 20:38:10 +00:00
let unitToPixels = {
2017-06-21 20:51:06 +00:00
'px' : 1 ,
'in' : 96 ,
'cm' : 37.8 ,
'mm' : 3.78
2017-05-14 18:29:42 +00:00
} ;
/ * *
* @ param { ( string | number | undefined ) } parameter
* @ return { ( number | undefined ) }
* /
function convertPrintParameterToInches ( parameter ) {
2017-06-21 20:51:06 +00:00
if ( typeof parameter === 'undefined' )
return undefined ;
2017-06-22 20:38:10 +00:00
let pixels ;
2017-06-21 20:51:06 +00:00
if ( typeof parameter === 'number' ) {
// Treat numbers as pixel values to be aligned with phantom's paperSize.
pixels = /** @type {number} */ ( parameter ) ;
} else if ( typeof parameter === 'string' ) {
2017-06-22 20:38:10 +00:00
let text = parameter ;
let unit = text . substring ( text . length - 2 ) . toLowerCase ( ) ;
let valueText = '' ;
2017-06-21 20:51:06 +00:00
if ( unitToPixels . hasOwnProperty ( unit ) ) {
valueText = text . substring ( 0 , text . length - 2 ) ;
2017-05-14 18:29:42 +00:00
} else {
2017-06-21 20:51:06 +00:00
// In case of unknown unit try to parse the whole parameter as number of pixels.
// This is consistent with phantom's paperSize behavior.
unit = 'px' ;
valueText = text ;
}
2017-06-22 20:38:10 +00:00
let value = Number ( valueText ) ;
2017-06-21 20:51:06 +00:00
console . assert ( ! isNaN ( value ) , 'Failed to parse parameter value: ' + text ) ;
pixels = value * unitToPixels [ unit ] ;
} else {
2017-07-17 09:06:52 +00:00
throw new Error ( 'page.pdf() Cannot handle parameter type: ' + ( typeof parameter ) ) ;
2017-06-21 20:51:06 +00:00
}
return pixels / 96 ;
2017-06-11 08:32:59 +00:00
}
2017-05-14 18:29:42 +00:00
2017-05-11 07:06:41 +00:00
Page . Events = {
2017-07-18 03:38:11 +00:00
Console : 'console' ,
2017-06-21 20:51:06 +00:00
Dialog : 'dialog' ,
2017-07-06 18:22:32 +00:00
// Can'e use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError : 'pageerror' ,
2017-06-28 08:10:23 +00:00
Request : 'request' ,
2017-06-29 06:09:28 +00:00
Response : 'response' ,
RequestFailed : 'requestfailed' ,
RequestFinished : 'requestfinished' ,
2017-06-21 20:51:06 +00:00
FrameAttached : 'frameattached' ,
FrameDetached : 'framedetached' ,
FrameNavigated : 'framenavigated' ,
2017-06-28 05:02:46 +00:00
Load : 'load' ,
2017-05-11 07:06:41 +00:00
} ;
2017-07-18 01:13:04 +00:00
/** @typedef {{width: number, height: number, deviceScaleFactor: number|undefined, isMobile: boolean|undefined, isLandscape: boolean, hasTouch: boolean|undefined}} */
Page . Viewport ;
2017-05-11 07:06:41 +00:00
module . exports = Page ;
2017-07-19 03:53:00 +00:00
helper . tracePublicAPI ( Page ) ;