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-25 21:35:03 +00:00
let { Keyboard , Mouse } = require ( './Input' ) ;
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-07-25 21:35:03 +00:00
const keyboard = new Keyboard ( client ) ;
const mouse = new Mouse ( client , keyboard ) ;
const frameManager = await FrameManager . create ( client , mouse ) ;
const networkManager = new NetworkManager ( client ) ;
const page = new Page ( client , frameManager , networkManager , screenshotTaskQueue , mouse , keyboard ) ;
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-07-22 03:29:31 +00:00
* @ param { ! Mouse } mouse
* @ param { ! Keyboard } keyboard
2017-06-21 20:58:49 +00:00
* /
2017-07-22 03:29:31 +00:00
constructor ( client , frameManager , networkManager , screenshotTaskQueue , mouse , keyboard ) {
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-22 03:29:31 +00:00
this . _keyboard = keyboard ;
this . _mouse = mouse ;
2017-07-18 01:49:52 +00:00
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 ) {
2017-07-25 18:37:46 +00:00
return this . mainFrame ( ) . addScriptTag ( 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 } filePath
* @ return { ! Promise }
* /
2017-06-21 20:51:06 +00:00
async injectFile ( filePath ) {
2017-07-25 18:37:46 +00:00
return this . mainFrame ( ) . injectFile ( filePath ) ;
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-07-26 22:19:43 +00:00
await this . _client . send ( 'Page.addScriptToEvaluateOnNewDocument' , { source : expression } ) ;
2017-06-21 20:51:06 +00:00
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-07-27 19:25:52 +00:00
* @ param { ! Map < string , string > } headers
2017-06-21 20:58:49 +00:00
* @ return { ! Promise }
* /
2017-07-27 19:25:52 +00:00
async setExtraHTTPHeaders ( headers ) {
return this . _networkManager . setExtraHTTPHeaders ( headers ) ;
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
* @ param { ! Object } exceptionDetails
* /
2017-07-21 18:57:25 +00:00
_handleException ( exceptionDetails ) {
let message = helper . getExceptionMessage ( 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-25 04:43:54 +00:00
if ( ! this . listenerCount ( Page . Events . Console ) ) {
event . args . map ( arg => helper . releaseObject ( this . _client , arg ) ) ;
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-25 23:06:53 +00:00
const watcher = new NavigatorWatcher ( this . _client , options ) ;
const responses = new Map ( ) ;
const listener = helper . addEventListener ( this . _networkManager , NetworkManager . Events . Response , response => responses . set ( response . url , response ) ) ;
2017-07-19 01:54:24 +00:00
const result = watcher . waitForNavigation ( ) ;
2017-07-25 23:06:53 +00:00
2017-07-27 19:25:52 +00:00
const referrer = this . _networkManager . extraHTTPHeaders ( ) . get ( 'referer' ) ;
2017-07-19 01:54:24 +00:00
try {
2017-07-22 23:25:00 +00:00
// Await for the command to throw exception in case of illegal arguments.
2017-07-19 01:54:24 +00:00
await this . _client . send ( 'Page.navigate' , { url , referrer } ) ;
} catch ( e ) {
watcher . cancel ( ) ;
throw e ;
}
2017-07-25 23:06:53 +00:00
await result ;
helper . removeEventListeners ( [ listener ] ) ;
2017-07-28 00:54:39 +00:00
let response = responses . get ( this . mainFrame ( ) . url ( ) ) ;
if ( ! response )
throw new Error ( 'Failed to navigate: ' + url ) ;
return response ;
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 ) {
2017-07-25 23:06:53 +00:00
const watcher = new NavigatorWatcher ( this . _client , options ) ;
const responses = new Map ( ) ;
const listener = helper . addEventListener ( this . _networkManager , NetworkManager . Events . Response , response => responses . set ( response . url , response ) ) ;
await watcher . waitForNavigation ( ) ;
helper . removeEventListeners ( [ listener ] ) ;
2017-07-19 01:54:24 +00:00
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-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-27 19:23:41 +00:00
* @ param { function ( ) | string } pageFunction
2017-06-21 20:58:49 +00:00
* @ param { ! Array < * > } args
* @ return { ! Promise }
* /
2017-07-25 07:00:25 +00:00
async evaluateOnNewDocument ( pageFunction , ... args ) {
2017-07-26 22:19:43 +00:00
let source = helper . evaluationString ( pageFunction , ... args ) ;
await this . _client . send ( 'Page.addScriptToEvaluateOnNewDocument' , { source } ) ;
2017-06-21 20:51:06 +00:00
}
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-26 22:28:44 +00:00
let clip = options . clip ? Object . assign ( { } , options [ 'clip' ] ) : undefined ;
if ( clip )
clip . scale = 1 ;
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 ) ;
2017-07-26 22:28:44 +00:00
// Overwrite clip for full page at all times.
clip = { x : 0 , y : 0 , width , height , scale : 1 } ;
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 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 ( ) {
2017-07-25 18:37:46 +00:00
return this . mainFrame ( ) . title ( ) ;
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 { ! Promise }
* /
2017-06-21 20:51:06 +00:00
async close ( ) {
await this . _client . dispose ( ) ;
}
2017-06-28 01:27:22 +00:00
2017-07-22 03:29:31 +00:00
/ * *
* @ return { ! Mouse }
* /
get mouse ( ) {
return this . _mouse ;
}
2017-06-28 01:27:22 +00:00
/ * *
* @ param { string } selector
2017-07-22 03:29:31 +00:00
* @ param { ! Object } options
2017-07-22 03:00:09 +00:00
* @ return { ! Promise }
2017-06-28 01:27:22 +00:00
* /
2017-07-22 03:29:31 +00:00
async click ( selector , options ) {
await this . mainFrame ( ) . click ( selector , options ) ;
}
/ * *
* @ param { string } selector
* @ param { ! Promise }
* /
async hover ( selector ) {
await this . mainFrame ( ) . hover ( selector ) ;
2017-06-28 01:27:22 +00:00
}
/ * *
* @ param { string } selector
2017-07-22 03:00:09 +00:00
* @ return { ! Promise }
2017-06-28 01:27:22 +00:00
* /
async focus ( selector ) {
2017-07-25 18:37:46 +00:00
return this . mainFrame ( ) . focus ( selector ) ;
2017-06-28 01:27:22 +00:00
}
/ * *
* @ param { string } text
2017-07-22 03:00:09 +00:00
* @ return { ! Promise }
2017-06-28 01:27:22 +00:00
* /
async type ( text ) {
2017-07-22 03:00:09 +00:00
let last ;
for ( let char of text )
last = this . press ( char , { text : char } ) ;
await last ;
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
2017-07-22 03:00:09 +00:00
* @ return { ! Promise }
2017-07-19 21:43:07 +00:00
* /
async press ( key , options ) {
2017-07-22 03:00:09 +00:00
this . _keyboard . down ( key , options ) ;
await this . _keyboard . up ( key ) ;
2017-07-19 21:43:07 +00:00
}
2017-07-07 22:39:02 +00:00
/ * *
2017-07-28 00:09:28 +00:00
* @ param { ( string | number | function ( ) ) } selectorOrTimeout
2017-07-21 07:58:38 +00:00
* @ param { ! Object = } options
2017-07-28 00:09:28 +00:00
* @ return { ! Promise }
2017-07-07 22:39:02 +00:00
* /
2017-07-28 00:09:28 +00:00
waitFor ( selectorOrFunctionOrTimeout , options = { } ) {
return this . mainFrame ( ) . waitFor ( selectorOrFunctionOrTimeout , options ) ;
2017-07-21 19:41:49 +00:00
}
/ * *
* @ param { string } selector
* @ param { ! Object = } options
* @ return { ! Promise }
* /
waitForSelector ( selector , options = { } ) {
return this . mainFrame ( ) . waitForSelector ( selector , options ) ;
2017-07-07 22:39:02 +00:00
}
2017-07-10 18:21:46 +00:00
2017-07-27 22:17:43 +00:00
/ * *
* @ param { function ( ) } pageFunction
* @ param { ! Object = } options
* @ param { ! Array < * > } args
* @ return { ! Promise }
* /
waitForFunction ( pageFunction , options = { } , ... args ) {
return this . mainFrame ( ) . waitForFunction ( pageFunction , options , ... args ) ;
}
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-26 22:19:43 +00:00
let expression = helper . evaluationString ( selector => document . querySelector ( selector ) , selector ) ;
const { result } = await this . _client . send ( 'Runtime.evaluate' , { expression } ) ;
if ( ! result )
return ;
const objectId = result . objectId ;
return this . _client . send ( 'DOM.setFileInputFiles' , { objectId , files : filePaths } ) ;
2017-07-10 18:21:46 +00:00
}
2017-07-18 01:56:56 +00:00
/ * *
2017-07-26 16:58:03 +00:00
* @ template T
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 ) ;
}
/ * *
2017-07-26 16:58:03 +00:00
* @ template T
2017-07-18 01:56:56 +00:00
* @ 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-07-27 22:17:43 +00:00
if ( helper . isNumber ( parameter ) ) {
2017-06-21 20:51:06 +00:00
// Treat numbers as pixel values to be aligned with phantom's paperSize.
pixels = /** @type {number} */ ( parameter ) ;
2017-07-27 22:17:43 +00:00
} else if ( helper . isString ( parameter ) ) {
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 ) ;