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-08-02 19:06:47 +00:00
const fs = require ( 'fs' ) ;
const EventEmitter = require ( 'events' ) ;
const mime = require ( 'mime' ) ;
const NetworkManager = require ( './NetworkManager' ) ;
const NavigatorWatcher = require ( './NavigatorWatcher' ) ;
const Dialog = require ( './Dialog' ) ;
const EmulationManager = require ( './EmulationManager' ) ;
2017-10-10 05:31:40 +00:00
const { FrameManager } = require ( './FrameManager' ) ;
2017-09-02 02:03:51 +00:00
const { Keyboard , Mouse , Touchscreen } = require ( './Input' ) ;
2017-08-02 19:06:47 +00:00
const Tracing = require ( './Tracing' ) ;
2017-09-11 23:21:51 +00:00
const { helper , debugError } = require ( './helper' ) ;
2017-05-11 07:06:41 +00:00
2017-10-11 07:55:48 +00:00
const writeFileAsync = helper . promisify ( fs . writeFile ) ;
2017-05-11 07:06:41 +00:00
class Page extends EventEmitter {
2017-06-21 20:51:06 +00:00
/ * *
2017-10-10 05:31:40 +00:00
* @ param { ! Puppeteer . Session } client
2017-08-01 22:17:57 +00:00
* @ param { boolean } ignoreHTTPSErrors
2017-09-29 22:16:32 +00:00
* @ param { boolean } appMode
2017-10-10 05:31:40 +00:00
* @ param { ! Puppeteer . TaskQueue } screenshotTaskQueue
2017-06-21 20:58:49 +00:00
* @ return { ! Promise < ! Page > }
* /
2017-09-29 22:16:32 +00:00
static async create ( client , ignoreHTTPSErrors , appMode , screenshotTaskQueue ) {
2017-10-18 02:14:57 +00:00
await client . send ( 'Page.enable' ) ;
const { frameTree } = await client . send ( 'Page.getResourceTree' ) ;
const page = new Page ( client , frameTree , ignoreHTTPSErrors , screenshotTaskQueue ) ;
2017-06-21 20:51:06 +00:00
await Promise . all ( [
2017-11-02 20:26:21 +00:00
client . send ( 'Page.setLifecycleEventsEnabled' , { enabled : true } ) ,
2017-06-21 20:51:06 +00:00
client . send ( 'Network.enable' , { } ) ,
client . send ( 'Runtime.enable' , { } ) ,
client . send ( 'Security.enable' , { } ) ,
2017-11-02 20:26:21 +00:00
client . send ( 'Performance.enable' , { } ) ,
2017-06-21 20:51:06 +00:00
] ) ;
2017-08-01 22:17:57 +00:00
if ( ignoreHTTPSErrors )
await client . send ( 'Security.setOverrideCertificateErrors' , { override : true } ) ;
2017-06-21 20:51:06 +00:00
// Initialize default page size.
2017-09-29 22:16:32 +00:00
if ( ! appMode )
await page . setViewport ( { width : 800 , height : 600 } ) ;
2017-10-18 02:14:57 +00:00
2017-06-21 20:51:06 +00:00
return page ;
}
/ * *
2017-10-10 05:31:40 +00:00
* @ param { ! Puppeteer . Session } client
2017-10-18 02:14:57 +00:00
* @ param { { frame : Object , childFrames : ? Array } } frameTree
2017-08-01 22:17:57 +00:00
* @ param { boolean } ignoreHTTPSErrors
2017-10-10 05:31:40 +00:00
* @ param { ! Puppeteer . TaskQueue } screenshotTaskQueue
2017-06-21 20:58:49 +00:00
* /
2017-10-18 02:14:57 +00:00
constructor ( client , frameTree , ignoreHTTPSErrors , screenshotTaskQueue ) {
2017-06-21 20:51:06 +00:00
super ( ) ;
this . _client = client ;
2017-07-30 02:12:17 +00:00
this . _keyboard = new Keyboard ( client ) ;
this . _mouse = new Mouse ( client , this . _keyboard ) ;
2017-09-02 02:03:51 +00:00
this . _touchscreen = new Touchscreen ( client , this . _keyboard ) ;
2017-10-18 02:14:57 +00:00
this . _frameManager = new FrameManager ( client , frameTree , this ) ;
2017-07-30 02:12:17 +00:00
this . _networkManager = new NetworkManager ( client ) ;
this . _emulationManager = new EmulationManager ( client ) ;
2017-08-02 17:45:11 +00:00
this . _tracing = new Tracing ( client ) ;
2017-10-10 05:31:40 +00:00
/** @type {!Map<string, Function>} */
2017-08-11 04:44:49 +00:00
this . _pageBindings = new Map ( ) ;
2017-08-01 22:17:57 +00:00
this . _ignoreHTTPSErrors = ignoreHTTPSErrors ;
2017-06-21 20:51:06 +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-08-01 22:17:57 +00:00
client . on ( 'Security.certificateError' , event => this . _onCertificateError ( event ) ) ;
2017-08-15 18:13:05 +00:00
client . on ( 'Inspector.targetCrashed' , event => this . _onTargetCrashed ( ) ) ;
2017-10-10 21:50:38 +00:00
client . on ( 'Performance.metrics' , event => this . _emitMetrics ( event ) ) ;
2017-08-15 18:13:05 +00:00
}
_onTargetCrashed ( ) {
this . emit ( 'error' , new Error ( 'Page crashed!' ) ) ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-10-10 05:31:40 +00:00
* @ return { ! Puppeteer . Frame }
2017-06-21 20:58:49 +00:00
* /
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-09-02 02:03:51 +00:00
/ * *
* @ return { ! Touchscreen }
* /
get touchscreen ( ) {
return this . _touchscreen ;
}
/ * *
* @ param { string } selector
* /
async tap ( selector ) {
const handle = await this . $ ( selector ) ;
console . assert ( handle , 'No node found for selector: ' + selector ) ;
await handle . tap ( ) ;
await handle . dispose ( ) ;
}
2017-08-02 17:45:11 +00:00
/ * *
* @ return { ! Tracing }
* /
get tracing ( ) {
return this . _tracing ;
}
2017-06-21 20:51:06 +00:00
/ * *
2017-10-10 05:31:40 +00:00
* @ return { ! Array < Puppeteer . Frame > }
2017-06-21 20:58:49 +00:00
* /
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-08-12 00:24:31 +00:00
* @ param { boolean } value
2017-06-21 20:58:49 +00:00
* /
2017-10-24 21:45:03 +00:00
async setRequestInterception ( value ) {
return this . _networkManager . setRequestInterception ( value ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-15 07:20:37 +00:00
2017-10-13 21:41:39 +00:00
/ * *
* @ param { boolean } enabled
* /
setOfflineMode ( enabled ) {
return this . _networkManager . setOfflineMode ( enabled ) ;
}
2017-08-01 22:17:57 +00:00
/ * *
* @ param { ! Object } event
* /
_onCertificateError ( event ) {
if ( ! this . _ignoreHTTPSErrors )
return ;
this . _client . send ( 'Security.handleCertificateError' , {
eventId : event . eventId ,
action : 'continue'
2017-09-11 23:21:51 +00:00
} ) . catch ( debugError ) ;
2017-08-01 22:17:57 +00:00
}
2017-08-15 21:54:02 +00:00
/ * *
* @ param { string } selector
2017-10-10 05:31:40 +00:00
* @ return { ! Promise < ? Puppeteer . ElementHandle > }
2017-08-15 21:54:02 +00:00
* /
async $ ( selector ) {
return this . mainFrame ( ) . $ ( selector ) ;
}
2017-10-06 22:35:02 +00:00
/ * *
* @ param { function ( ) | string } pageFunction
* @ param { ! Array < * > } args
2017-10-10 05:31:40 +00:00
* @ return { ! Promise < ! Puppeteer . JSHandle > }
2017-10-06 22:35:02 +00:00
* /
async evaluateHandle ( pageFunction , ... args ) {
return this . mainFrame ( ) . executionContext ( ) . evaluateHandle ( pageFunction , ... args ) ;
}
2017-08-15 21:54:02 +00:00
2017-10-11 21:41:20 +00:00
/ * *
* @ param { ! Puppeteer . JSHandle } prototypeHandle
* @ return { ! Promise < ! Puppeteer . JSHandle > }
* /
async queryObjects ( prototypeHandle ) {
return this . mainFrame ( ) . executionContext ( ) . queryObjects ( prototypeHandle ) ;
}
2017-08-31 22:38:01 +00:00
/ * *
* @ param { string } selector
* @ param { function ( ) | string } pageFunction
* @ param { ! Array < * > } args
* @ return { ! Promise < ( ! Object | undefined ) > }
* /
async $eval ( selector , pageFunction , ... args ) {
return this . mainFrame ( ) . $eval ( selector , pageFunction , ... args ) ;
}
2017-10-11 06:23:14 +00:00
/ * *
* @ param { string } selector
* @ param { Function | string } pageFunction
* @ param { ! Array < * > } args
* @ return { ! Promise < ( ! Object | undefined ) > }
* /
async $$eval ( selector , pageFunction , ... args ) {
return this . mainFrame ( ) . $$eval ( selector , pageFunction , ... args ) ;
}
2017-08-23 05:56:55 +00:00
/ * *
* @ param { string } selector
2017-10-10 05:31:40 +00:00
* @ return { ! Promise < ! Array < ! Puppeteer . ElementHandle >> }
2017-08-23 05:56:55 +00:00
* /
async $$ ( selector ) {
return this . mainFrame ( ) . $$ ( selector ) ;
}
2017-08-24 19:21:46 +00:00
/ * *
* @ param { ! Array < string > } urls
* @ return { ! Promise < ! Array < Network . Cookie >> }
* /
async cookies ( ... urls ) {
return ( await this . _client . send ( 'Network.getCookies' , {
urls : urls . length ? urls : [ this . url ( ) ]
} ) ) . cookies ;
}
/ * *
* @ param { Array < Network . CookieParam > } cookies
* /
async deleteCookie ( ... cookies ) {
const pageURL = this . url ( ) ;
for ( const cookie of cookies ) {
const item = Object . assign ( { } , cookie ) ;
if ( ! cookie . url && pageURL . startsWith ( 'http' ) )
item . url = pageURL ;
await this . _client . send ( 'Network.deleteCookies' , item ) ;
}
}
/ * *
* @ param { Array < Network . CookieParam > } cookies
* /
async setCookie ( ... cookies ) {
const items = cookies . map ( cookie => {
const item = Object . assign ( { } , cookie ) ;
const pageURL = this . url ( ) ;
if ( ! item . url && pageURL . startsWith ( 'http' ) )
item . url = this . url ( ) ;
return item ;
} ) ;
await this . deleteCookie ( ... items ) ;
if ( items . length )
await this . _client . send ( 'Network.setCookies' , { cookies : items } ) ;
}
2017-06-21 20:51:06 +00:00
/ * *
2017-10-12 08:26:44 +00:00
* @ param { Object } options
2017-06-21 20:58:49 +00:00
* /
2017-10-12 08:26:44 +00:00
async addScriptTag ( options ) {
return this . mainFrame ( ) . addScriptTag ( options ) ;
2017-06-21 20:51:06 +00:00
}
2017-06-21 20:36:04 +00:00
2017-10-04 20:42:26 +00:00
/ * *
2017-10-12 08:26:44 +00:00
* @ param { Object } options
2017-10-04 20:42:26 +00:00
* /
2017-10-12 08:26:44 +00:00
async addStyleTag ( options ) {
return this . mainFrame ( ) . addStyleTag ( options ) ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } name
2017-08-11 04:44:49 +00:00
* @ param { function ( ? ) } puppeteerFunction
2017-06-21 20:58:49 +00:00
* /
2017-08-16 06:04:16 +00:00
async exposeFunction ( name , puppeteerFunction ) {
2017-08-11 04:44:49 +00:00
if ( this . _pageBindings [ name ] )
throw new Error ( ` Failed to add page binding with name ${ name } : window[' ${ name } '] already exists! ` ) ;
this . _pageBindings [ name ] = puppeteerFunction ;
2017-06-21 20:51:06 +00:00
2017-08-21 23:39:04 +00:00
const expression = helper . evaluationString ( addPageBinding , 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 } ) ;
2017-08-11 04:44:49 +00:00
function addPageBinding ( bindingName ) {
window [ bindingName ] = async ( ... args ) => {
const me = window [ bindingName ] ;
2017-06-21 20:51:06 +00:00
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
2017-08-11 04:44:49 +00:00
console . debug ( 'driver:page-binding' , JSON . stringify ( { name : bindingName , seq , args } ) ) ;
2017-06-21 20:51:06 +00:00
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-09-11 23:32:13 +00:00
/ * *
* @ param { ? { username : string , password : string } } credentials
* /
async authenticate ( credentials ) {
return this . _networkManager . authenticate ( credentials ) ;
}
2017-06-21 20:51:06 +00:00
/ * *
2017-08-28 19:09:24 +00:00
* @ param { ! Object < string , string > } headers
2017-06-21 20:58:49 +00:00
* /
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
* /
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-10-10 21:50:38 +00:00
/ * *
* @ return { ! Promise < ! Object > }
* /
2017-11-01 20:39:31 +00:00
async metrics ( ) {
2017-10-10 21:50:38 +00:00
const response = await this . _client . send ( 'Performance.getMetrics' ) ;
return this . _buildMetricsObject ( response . metrics ) ;
}
/ * *
* @ param { * } event
* /
_emitMetrics ( event ) {
this . emit ( Page . Events . Metrics , {
title : event . title ,
metrics : this . _buildMetricsObject ( event . metrics )
} ) ;
}
/ * *
* @ param { ? Array < ! { name : string , value : number } > } metrics
* @ return { ! Object }
* /
_buildMetricsObject ( metrics ) {
const result = { } ;
for ( const metric of metrics || [ ] ) {
if ( supportedMetrics . has ( metric . name ) )
result [ metric . name ] = metric . value ;
}
return result ;
}
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 ) {
2017-08-21 23:39:04 +00:00
const 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 ) {
2017-08-11 04:44:49 +00:00
if ( event . type === 'debug' && event . args . length && event . args [ 0 ] . value === 'driver:page-binding' ) {
2017-08-21 23:39:04 +00:00
const { name , seq , args } = JSON . parse ( event . args [ 1 ] . value ) ;
const result = await this . _pageBindings [ name ] ( ... args ) ;
const expression = helper . evaluationString ( deliverResult , name , seq , result ) ;
2017-09-11 23:21:51 +00:00
this . _client . send ( 'Runtime.evaluate' , { expression } ) . catch ( debugError ) ;
2017-06-21 20:51:06 +00:00
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-10-10 00:01:01 +00:00
const values = event . args . map ( arg => this . _frameManager . createJSHandle ( event . executionContextId , arg ) ) ;
2017-10-10 17:54:20 +00:00
const textTokens = [ ] ;
for ( let i = 0 ; i < event . args . length ; ++ i ) {
const remoteObject = event . args [ i ] ;
if ( remoteObject . objectId )
textTokens . push ( values [ i ] . toString ( ) ) ;
else
textTokens . push ( helper . valueFromRemoteObject ( remoteObject ) ) ;
}
const message = new ConsoleMessage ( event . type , textTokens . join ( ' ' ) , values ) ;
2017-09-29 18:27:22 +00:00
this . emit ( Page . Events . Console , message ) ;
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-08-21 23:39:04 +00:00
const dialog = new Dialog ( this . _client , dialogType , event . message , event . defaultPrompt ) ;
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-08-21 16:02:30 +00:00
/ * *
* @ return { ! Promise < String > }
* /
async content ( ) {
return await this . evaluate ( ( ) => {
let retVal = '' ;
if ( document . doctype )
retVal = new XMLSerializer ( ) . serializeToString ( document . doctype ) ;
if ( document . documentElement )
retVal += document . documentElement . outerHTML ;
return retVal ;
} ) ;
}
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { string } html
2017-10-24 20:57:39 +00:00
* @ param { ! Object = } options
2017-06-21 20:58:49 +00:00
* /
2017-10-24 20:57:39 +00:00
async setContent ( html , options ) {
await Promise . all ( [
this . evaluate ( html => {
document . open ( ) ;
document . write ( html ) ;
document . close ( ) ;
} , html ) ,
this . waitForNavigation ( options ) ,
] ) ;
2017-06-21 20:51:06 +00:00
}
/ * *
2017-10-10 05:31:40 +00:00
* @ param { string } url
2017-06-21 20:58:49 +00:00
* @ param { ! Object = } options
2017-07-28 23:44:51 +00:00
* @ return { ! Promise < ? Response > }
2017-06-21 20:58:49 +00:00
* /
2017-08-10 07:02:10 +00:00
async goto ( url , options ) {
2017-10-24 01:10:59 +00:00
const mainFrame = this . _frameManager . mainFrame ( ) ;
2017-11-01 20:28:00 +00:00
const watcher = new NavigatorWatcher ( this . _client , mainFrame . _id , options ) ;
const requests = new Map ( ) ;
const listener = helper . addEventListener ( this . _networkManager , NetworkManager . Events . Request , request => requests . set ( request . url , request ) ) ;
2017-09-11 23:43:37 +00:00
const navigationPromise = watcher . waitForNavigation ( ) ;
2017-08-28 19:09:24 +00:00
const referrer = this . _networkManager . extraHTTPHeaders ( ) [ 'referer' ] ;
2017-11-01 21:04:10 +00:00
const error = await Promise . race ( [
this . _client . send ( 'Page.navigate' , { url , referrer } )
. then ( ( ) => navigationPromise )
. catch ( error => error ) ,
navigationPromise
] ) ;
watcher . cancel ( ) ;
2017-07-25 23:06:53 +00:00
helper . removeEventListeners ( [ listener ] ) ;
2017-09-11 23:43:37 +00:00
if ( error )
throw error ;
2017-11-01 20:28:00 +00:00
const unreachableURL = this . _frameManager . unreachableMainFrameURL ( ) ;
if ( unreachableURL ) {
const request = requests . get ( unreachableURL ) || null ;
const failure = request ? request . failure ( ) : null ;
throw new Error ( 'Failed to navigate: ' + url + ( failure ? ' ' + failure . errorText : '' ) ) ;
}
const request = requests . get ( this . mainFrame ( ) . url ( ) ) ;
return request ? request . response ( ) : null ;
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 ) {
2017-09-11 23:21:51 +00:00
const [ response ] = await Promise . all ( [
this . waitForNavigation ( options ) ,
this . _client . send ( 'Page.reload' )
] ) ;
return response ;
2017-07-19 01:54:24 +00:00
}
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-10-24 01:10:59 +00:00
const mainFrame = this . _frameManager . mainFrame ( ) ;
2017-11-01 20:28:00 +00:00
const watcher = new NavigatorWatcher ( this . _client , mainFrame . _id , options ) ;
2017-07-25 23:06:53 +00:00
const responses = new Map ( ) ;
const listener = helper . addEventListener ( this . _networkManager , NetworkManager . Events . Response , response => responses . set ( response . url , response ) ) ;
2017-09-11 23:43:37 +00:00
const error = await watcher . waitForNavigation ( ) ;
2017-07-25 23:06:53 +00:00
helper . removeEventListeners ( [ listener ] ) ;
2017-09-11 23:43:37 +00:00
if ( error )
throw error ;
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 ;
2017-09-11 23:21:51 +00:00
const [ response ] = await Promise . all ( [
this . waitForNavigation ( options ) ,
this . _client . send ( 'Page.navigateToHistoryEntry' , { entryId : entry . id } ) ,
] ) ;
return response ;
2017-07-19 02:11:37 +00:00
}
2017-08-11 01:42:30 +00:00
/ * *
* @ param { ! Object } options
* /
async emulate ( options ) {
return Promise . all ( [
this . setViewport ( options . viewport ) ,
this . setUserAgent ( options . userAgent )
] ) ;
}
2017-08-23 21:08:56 +00:00
/ * *
* @ param { boolean } enabled
* /
async setJavaScriptEnabled ( enabled ) {
await this . _client . send ( 'Emulation.setScriptExecutionDisabled' , { value : ! enabled } ) ;
}
2017-08-18 23:49:02 +00:00
/ * *
* @ param { ? string } mediaType
* /
async emulateMedia ( mediaType ) {
console . assert ( mediaType === 'screen' || mediaType === 'print' || mediaType === null , 'Unsupported media type: ' + mediaType ) ;
await this . _client . send ( 'Emulation.setEmulatedMedia' , { media : mediaType || '' } ) ;
}
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
* /
2017-07-18 01:13:04 +00:00
async setViewport ( viewport ) {
2017-07-30 02:12:17 +00:00
const needsReload = await this . _emulationManager . emulateViewport ( this . _client , viewport ) ;
2017-07-18 01:13:04 +00:00
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
2017-10-10 05:31:40 +00:00
* @ return { ! Promise < * > }
2017-06-21 20:58:49 +00:00
* /
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
* /
2017-07-25 07:00:25 +00:00
async evaluateOnNewDocument ( pageFunction , ... args ) {
2017-08-21 23:39:04 +00:00
const source = helper . evaluationString ( pageFunction , ... args ) ;
2017-07-26 22:19:43 +00:00
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-08-21 23:32:39 +00:00
async screenshot ( options = { } ) {
2017-06-22 20:38:10 +00:00
let screenshotType = null ;
2017-06-21 20:51:06 +00:00
if ( options . path ) {
2017-08-21 23:39:04 +00:00
const 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-08-11 01:42:30 +00:00
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
2017-08-18 04:11:39 +00:00
if ( options . omitBackground )
await this . _client . send ( 'Emulation.setDefaultBackgroundColorOverride' , { color : { r : 0 , g : 0 , b : 0 , a : 0 } } ) ;
2017-08-21 23:39:04 +00:00
const result = await this . _client . send ( 'Page.captureScreenshot' , { format , quality : options . quality , clip } ) ;
2017-08-18 04:11:39 +00:00
if ( options . omitBackground )
await this . _client . send ( 'Emulation.setDefaultBackgroundColorOverride' ) ;
2017-07-17 19:15:06 +00:00
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-08-21 23:39:04 +00:00
const buffer = new Buffer ( result . data , 'base64' ) ;
2017-06-21 20:51:06 +00:00
if ( options . path )
2017-10-11 07:55:48 +00:00
await writeFileAsync ( options . path , buffer ) ;
2017-06-21 20:51:06 +00:00
return buffer ;
}
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Object = } options
2017-07-17 09:06:52 +00:00
* @ return { ! Promise < ! Buffer > }
2017-06-21 20:58:49 +00:00
* /
2017-08-21 23:32:39 +00:00
async pdf ( options = { } ) {
2017-08-21 23:39:04 +00:00
const scale = options . scale || 1 ;
const displayHeaderFooter = ! ! options . displayHeaderFooter ;
const printBackground = ! ! options . printBackground ;
const landscape = ! ! options . landscape ;
const 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-08-21 23:39:04 +00:00
const format = Page . PaperFormats [ options . format . toLowerCase ( ) ] ;
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-08-21 23:39:04 +00:00
const marginOptions = options . margin || { } ;
const marginTop = convertPrintParameterToInches ( marginOptions . top ) || 0 ;
const marginLeft = convertPrintParameterToInches ( marginOptions . left ) || 0 ;
const marginBottom = convertPrintParameterToInches ( marginOptions . bottom ) || 0 ;
const marginRight = convertPrintParameterToInches ( marginOptions . right ) || 0 ;
2017-06-21 20:51:06 +00:00
2017-08-21 23:39:04 +00:00
const 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-08-21 23:39:04 +00:00
const buffer = new Buffer ( result . data , 'base64' ) ;
2017-07-17 09:06:52 +00:00
if ( options . path )
2017-10-11 07:55:48 +00:00
await writeFileAsync ( options . path , buffer ) ;
2017-07-17 09:06:52 +00:00
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 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
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-06-28 01:27:22 +00:00
* /
2017-07-22 03:29:31 +00:00
async click ( selector , options ) {
2017-08-21 23:39:04 +00:00
const handle = await this . $ ( selector ) ;
2017-08-15 21:54:02 +00:00
console . assert ( handle , 'No node found for selector: ' + selector ) ;
await handle . click ( options ) ;
2017-08-16 07:49:20 +00:00
await handle . dispose ( ) ;
2017-07-22 03:29:31 +00:00
}
/ * *
* @ param { string } selector
* /
async hover ( selector ) {
2017-08-21 23:39:04 +00:00
const handle = await this . $ ( selector ) ;
2017-08-15 21:54:02 +00:00
console . assert ( handle , 'No node found for selector: ' + selector ) ;
await handle . hover ( ) ;
2017-08-16 07:49:20 +00:00
await handle . dispose ( ) ;
2017-06-28 01:27:22 +00:00
}
/ * *
* @ param { string } selector
* /
async focus ( selector ) {
2017-08-21 23:39:04 +00:00
const handle = await this . $ ( selector ) ;
2017-08-15 21:54:02 +00:00
console . assert ( handle , 'No node found for selector: ' + selector ) ;
2017-10-07 07:28:24 +00:00
await handle . focus ( ) ;
2017-08-16 07:49:20 +00:00
await handle . dispose ( ) ;
2017-06-28 01:27:22 +00:00
}
2017-09-25 09:23:34 +00:00
/ * *
* @ param { string } selector
* @ param { ! Array < string > } values
2017-11-01 04:47:52 +00:00
* @ return { ! Promise < ! Array < string >> }
2017-09-25 09:23:34 +00:00
* /
async select ( selector , ... values ) {
2017-11-02 05:06:04 +00:00
return this . mainFrame ( ) . select ( selector , ... values ) ;
2017-09-25 09:23:34 +00:00
}
2017-06-28 01:27:22 +00:00
/ * *
2017-10-07 07:28:24 +00:00
* @ param { string } selector
2017-06-28 01:27:22 +00:00
* @ param { string } text
2017-08-01 01:18:15 +00:00
* @ param { { delay : ( number | undefined ) } = } options
2017-06-28 01:27:22 +00:00
* /
2017-10-07 07:28:24 +00:00
async type ( selector , text , options ) {
const handle = await this . $ ( selector ) ;
await handle . type ( text , options ) ;
await handle . dispose ( ) ;
2017-07-19 21:43:07 +00:00
}
2017-07-07 22:39:02 +00:00
/ * *
2017-10-10 05:31:40 +00:00
* @ param { ( string | number | Function ) } selectorOrFunctionOrTimeout
2017-07-21 07:58:38 +00:00
* @ param { ! Object = } options
2017-09-15 21:28:15 +00:00
* @ param { ! Array < * > } args
2017-07-28 00:09:28 +00:00
* @ return { ! Promise }
2017-07-07 22:39:02 +00:00
* /
2017-09-15 21:28:15 +00:00
waitFor ( selectorOrFunctionOrTimeout , options = { } , ... args ) {
return this . mainFrame ( ) . waitFor ( selectorOrFunctionOrTimeout , options , ... args ) ;
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-05-11 07:06:41 +00:00
}
2017-10-10 21:50:38 +00:00
/** @type {!Set<string>} */
const supportedMetrics = new Set ( [
'Timestamp' ,
2017-10-12 08:17:06 +00:00
'Documents' ,
'Frames' ,
'JSEventListeners' ,
'Nodes' ,
2017-10-10 21:50:38 +00:00
'LayoutCount' ,
'RecalcStyleCount' ,
'LayoutDuration' ,
'RecalcStyleDuration' ,
'ScriptDuration' ,
'TaskDuration' ,
'JSHeapUsedSize' ,
'JSHeapTotalSize' ,
] ) ;
2017-05-14 18:29:42 +00:00
/** @enum {string} */
Page . PaperFormats = {
2017-08-04 08:01:10 +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-10-11 20:18:14 +00:00
a6 : { width : 4.13 , height : 5.83 } ,
2017-05-14 18:29:42 +00:00
} ;
2017-08-21 23:39:04 +00:00
const 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-10-10 05:31:40 +00:00
const text = /** @type {string} */ ( parameter ) ;
2017-06-22 20:38:10 +00:00
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-08-21 23:39:04 +00:00
const 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-08-15 18:13:05 +00:00
Error : 'error' ,
2017-10-31 23:10:18 +00:00
// Can't use just 'error' due to node.js special treatment of error events.
2017-07-06 18:22:32 +00:00
// @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-10-10 21:50:38 +00:00
Metrics : 'metrics' ,
2017-05-11 07:06:41 +00:00
} ;
2017-10-10 05:31:40 +00:00
/ * *
* @ typedef { Object } Page . Viewport
* @ property { number } width
* @ property { number } height
* @ property { number = } deviceScaleFactor
* @ property { boolean = } isMobile
* @ property { boolean = } isLandscape
* @ property { boolean = } hasTouch
* /
2017-07-18 01:13:04 +00:00
2017-08-24 19:21:46 +00:00
/ * *
* @ typedef { Object } Network . Cookie
* @ property { string } name
* @ property { string } value
* @ property { string } domain
* @ property { string } path
* @ property { number } expires
* @ property { number } size
* @ property { boolean } httpOnly
* @ property { boolean } secure
* @ property { boolean } session
* @ property { ( "Strict" | "Lax" ) = } sameSite
* /
/ * *
* @ typedef { Object } Network . CookieParam
* @ property { string } name
* @ property { string = } value
* @ property { string = } url
* @ property { string = } domain
* @ property { string = } path
* @ property { number = } expires
* @ property { boolean = } httpOnly
* @ property { boolean = } secure
* @ property { ( "Strict" | "Lax" ) = } sameSite
* /
2017-09-29 18:27:22 +00:00
class ConsoleMessage {
/ * *
* @ param { string } type
* @ param { string } text
* @ param { ! Array < * > } args
* /
constructor ( type , text , args ) {
this . type = type ;
this . text = text ;
this . args = args ;
}
}
2017-08-24 19:21:46 +00:00
2017-05-11 07:06:41 +00:00
module . exports = Page ;
2017-07-19 03:53:00 +00:00
helper . tracePublicAPI ( Page ) ;