lib/page/Page.js
const util = require('util')
const EventEmitter = require('eventemitter3')
const fs = require('fs-extra')
const mime = require('mime')
const ConsoleMessage = require('../ConsoleMessage')
const Dialog = require('../Dialog')
const EmulationManager = require('../EmulationManager')
const Events = require('../Events')
const LogEntry = require('./LogEntry')
const Tracing = require('../Tracing')
const TimeoutSettings = require('../TimeoutSettings')
const Accessibility = require('../accessibility/Accessibility')
const AnimationManager = require('../animations/AnimationManager')
const Coverage = require('../coverage/Coverage')
const DatabaseManager = require('../database/DatabaseManager')
const FrameManager = require('../frames/FrameManager')
const { Keyboard, Mouse, Touchscreen } = require('../input')
const { createJSHandle } = require('../JSHandle')
const NetworkManager = require('../network/NetworkManager')
const TaskQueue = require('../TaskQueue')
const WorkerManager = require('../workers/WorkerManager')
const { helper, debugError, assert } = require('../helper')
const { ensureCookie } = require('../__shared')
/**
* @typedef {Object} EnabledExtras
* @property {?boolean} [animation = false]
* @property {?boolean} [console = false]
* @property {?boolean} [coverage = false]
* @property {?boolean} [database = false]
* @property {?boolean} [log = false]
* @property {?boolean} [performance = false]
* @property {?boolean} [security = false]
* @property {?boolean} [serviceWorkers = false]
* @property {?boolean} [workers = false]
*/
/**
* @typedef {Object} PageInitOptions
* @property {boolean} [ignoreHTTPSErrors]
* @property {?Target} [target]
* @property {?Object} [defaultViewPort]
* @property {?TaskQueue} [screenshotTaskQueue]
* @property {?EnabledExtras} [additionalDomains]
*/
/**
* @type {EnabledExtras}
*/
const DefaultEnabledOptions = {
animation: false,
console: false,
coverage: false,
database: false,
log: false,
performance: false,
security: false,
serviceWorkers: false,
workers: false
}
class Page extends EventEmitter {
/**
* @param {CDPSession|CRIConnection|Chrome|Object} client
* @param {PageInitOptions} [optionals]
* @return {Promise<Page>}
*/
static async create (client, optionals = {}) {
await client.send('Page.enable')
const { frameTree } = await client.send('Page.getFrameTree')
const {
target,
defaultViewport,
screenshotTaskQueue = new TaskQueue(),
additionalDomains,
ignoreHTTPSErrors
} = optionals
/**
* @type {EnabledExtras}
*/
const enableExtraDomains = Object.assign(
{},
DefaultEnabledOptions,
additionalDomains
)
const page = new Page(client, frameTree, {
target,
ignoreHTTPSErrors,
screenshotTaskQueue,
additionalDomains: enableExtraDomains
})
await Promise.all([
client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
client.send('Network.enable', {}),
client
.send('Runtime.enable', {})
.then(() => page.frameManager.ensureSecondaryDOMWorld()),
enableExtraDomains.workers
? page.enableWorkerMonitoring()
: Promise.resolve(),
enableExtraDomains.log
? client.send('Log.enable', {})
: Promise.resolve(),
enableExtraDomains.security || ignoreHTTPSErrors
? client.send('Security.enable', {})
: Promise.resolve(),
enableExtraDomains.performance
? client.send('Performance.enable', {})
: Promise.resolve(),
enableExtraDomains.animation
? page.enableAnimationsDomain()
: Promise.resolve(),
enableExtraDomains.database
? page.enableDatabaseDomain()
: Promise.resolve(),
enableExtraDomains.serviceWorkers
? page.enableServiceWorkersDomain()
: Promise.resolve()
])
if (ignoreHTTPSErrors) {
await client.send('Security.setOverrideCertificateErrors', {
override: true
})
}
// Initialize default page size.
if (defaultViewport) await page.setViewport(defaultViewport)
if (page.target() == null) {
// we dont have a target
await client.send('Target.setDiscoverTargets', { discover: true })
}
return page
}
/**
* @param {Chrome|CRIConnection|CDPSession|Object} client
* @param {!Object} frameTree
* @param {PageInitOptions} initOpts
*/
constructor (client, frameTree, initOpts) {
super()
/** @type {Chrome|CRIConnection|CDPSession|Object} */
this._client = client
/** @type {boolean} */
this._closed = false
/** @type {EnabledExtras} */
this._additionalDomains = initOpts.additionalDomains
/** @type {TimeoutSettings} */
this._timeoutSettings = new TimeoutSettings()
/** @type {?Target} */
this._target = initOpts.target
/** @type {?string} */
this._targetId = null
/** @type {Keyboard} */
this._keyboard = new Keyboard(client)
/** @type {Mouse} */
this._mouse = new Mouse(client, this._keyboard)
/** @type {Touchscreen} */
this._touchscreen = new Touchscreen(client, this._keyboard)
/** @type {Accessibility} */
this._accessibility = new Accessibility(client)
/** @type {!NetworkManager} */
this._networkManager = new NetworkManager(client, this._timeoutSettings)
/** @type {!FrameManager} */
this._frameManager = new FrameManager(
client,
frameTree,
this._timeoutSettings,
this._networkManager,
this
)
this._networkManager.setFrameManager(this._frameManager)
/** @type {AnimationManager} */
this._animationManager = new AnimationManager(client)
/** @type {DatabaseManager} */
this._databaseManager = new DatabaseManager(client)
/** @type {WorkerManager} */
this._workerManager = new WorkerManager(client)
/** @type {EmulationManager} */
this._emulationManager = new EmulationManager(client)
/** @type {Tracing} */
this._tracing = new Tracing(client)
/** @type {!Map<string, Function>} */
this._pageBindings = new Map()
/** @type {boolean} */
this._ignoreHTTPSErrors = initOpts.ignoreHTTPSErrors
/** @type {Coverage} */
this._coverage = new Coverage(client)
/** @type {boolean} */
this._javascriptEnabled = true
/** @type {?Viewport} */
this._viewport = null
/** @type {TaskQueue} */
this._screenshotTaskQueue = initOpts.screenshotTaskQueue || new TaskQueue()
this._frameManager.on(Events.FrameManager.FrameAttached, event =>
this.emit(Events.Page.FrameAttached, event)
)
this._frameManager.on(Events.FrameManager.FrameDetached, event =>
this.emit(Events.Page.FrameDetached, event)
)
this._frameManager.on(Events.FrameManager.FrameNavigated, event =>
this.emit(Events.Page.FrameNavigated, event)
)
this._networkManager.on(Events.NetworkManager.Request, event =>
this.emit(Events.Page.Request, event)
)
this._networkManager.on(Events.NetworkManager.Response, event =>
this.emit(Events.Page.Response, event)
)
this._networkManager.on(Events.NetworkManager.RequestFailed, event =>
this.emit(Events.Page.RequestFailed, event)
)
this._networkManager.on(Events.NetworkManager.RequestFinished, event =>
this.emit(Events.Page.RequestFinished, event)
)
this._animationManager.on(Events.Animations.started, event =>
this.emit(Events.Page.AnimationStarted, event)
)
this._animationManager.on(Events.Animations.canceled, event =>
this.emit(Events.Page.AnimationCanceled, event)
)
this._animationManager.on(Events.Animations.created, event =>
this.emit(Events.Page.AnimationCreated, event)
)
this._databaseManager.on(Events.DataBase.added, database =>
this.emit(Events.Page.DatabaseAdded, database)
)
this._workerManager.on(
Events.WorkerManager.ServiceWorkerAdded,
serviceWorker => this.emit(Events.Page.ServiceWorkerAdded, serviceWorker)
)
this._workerManager.on(
Events.WorkerManager.ServiceWorkerDeleted,
serviceWorker =>
this.emit(Events.Page.ServiceWorkerDeleted, serviceWorker)
)
this._workerManager.on(Events.WorkerManager.Console, consoleMsg =>
this.emit(Events.Page.Console, consoleMsg)
)
this._workerManager.on(Events.WorkerManager.Error, workerError =>
this.emit(Events.Page.Error, workerError)
)
this._workerManager.on(Events.WorkerManager.WorkerCreated, worker =>
this.emit(Events.Page.WorkerCreated, worker)
)
this._workerManager.on(Events.WorkerManager.WorkerDestroyed, worker =>
this.emit(Events.Page.WorkerDestroyed, worker)
)
client.on('Page.domContentEventFired', event =>
this.emit(Events.Page.DOMContentLoaded, event.timestamp)
)
client.on('Page.loadEventFired', event =>
this.emit(Events.Page.Load, event.timestamp)
)
client.on('Runtime.consoleAPICalled', this._onConsoleAPI.bind(this))
client.on('Runtime.bindingCalled', this._onBindingCalled.bind(this))
client.on('Page.javascriptDialogOpening', this._onDialog.bind(this))
client.on('Runtime.exceptionThrown', this._handleException.bind(this))
client.on('Security.certificateError', this._onCertificateError.bind(this))
client.on('Inspector.targetCrashed', this._onTargetCrashed.bind(this))
client.on('Performance.metrics', this._emitMetrics.bind(this))
client.on('Log.entryAdded', this._onLogEntryAdded.bind(this))
if (this._target) {
this._target._isClosedPromise.then(this.__onClose.bind(this))
} else {
this._client.on(this._client.$$disconnectEvent, this.__onClose.bind(this))
}
}
/**
* @return {NetworkManager}
*/
get networkManager () {
return this._networkManager
}
/**
* @return {!FrameManager}
*/
get frameManager () {
return this._frameManager
}
/**
* @type {AnimationManager}
* @since chrome-remote-interface-extra
*/
get animationManager () {
return this._animationManager
}
/**
* @return {DatabaseManager}
* @since chrome-remote-interface-extra
*/
get databaseManager () {
return this._databaseManager
}
/**
* @return {WorkerManager}
* @since chrome-remote-interface-extra
*/
get workerManager () {
return this._workerManager
}
/**
* @return {boolean}
*/
get javascriptEnabled () {
return this._javascriptEnabled
}
/**
* @return {!Keyboard}
*/
get keyboard () {
return this._keyboard
}
/**
* @return {!Touchscreen}
*/
get touchscreen () {
return this._touchscreen
}
/**
* @return {!Coverage}
*/
get coverage () {
return this._coverage
}
/**
* @return {!Tracing}
*/
get tracing () {
return this._tracing
}
/**
* @return {!Accessibility}
*/
get accessibility () {
return this._accessibility
}
/**
* @return {!Mouse}
*/
get mouse () {
return this._mouse
}
/**
* Returns T/F indicating if Worker monitoring is enabled
* @return {boolean}
* @since chrome-remote-interface-extra
*/
workerMonitoringEnabled () {
return this._workerManager.workerMonitoringEnabled()
}
/**
* Returns T/F indicating if the ServiceWorker domain is enabled
* @return {boolean}
* @since chrome-remote-interface-extra
*/
serviceWorkersEnabled () {
return this._workerManager.serviceWorkerDomainEnabled()
}
/**
* Returns T/F indicating if the Log domain is enabled
* @return {boolean}
* @since chrome-remote-interface-extra
*/
logDomainEnabled () {
return this._additionalDomains.log
}
/**
* Returns T/F indicating if the Security domain is enabled
* @return {boolean}
* @since chrome-remote-interface-extra
*/
securityDomainEnabled () {
return this._additionalDomains.security
}
/**
* Returns T/F indicating if the Performance domain is enabled
* @return {boolean}
* @since chrome-remote-interface-extra
*/
performanceDomainEnabled () {
return this._additionalDomains.performance
}
/**
* Returns T/F indicating if the Animation domain is enabled
* @return {boolean}
* @since chrome-remote-interface-extra
*/
animationsDomainEnabled () {
return this._animationManager.enabled()
}
/**
* Returns T/F indicating if the Database domain is enabled
* @return {boolean}
* @since chrome-remote-interface-extra
*/
databaseDomainEnabled () {
return this._databaseManager.enabled()
}
/**
* Returns the {@link Target} class that represents this page if this page was initialized with one, e.g. from the {@link Browser} class.
* @return {?Target}
*/
target () {
return this._target
}
/**
* Returns the browser this page lives in if the page was created from the {@link Browser} class
* @return {?Browser}
*/
browser () {
if (this._target) {
return this._target.browser()
}
return null
}
/**
* Returns the browser context this page lives in if the page was created from the {@link Browser} class
* @return {?BrowserContext}
*/
browserContext () {
if (this._target) {
return this._target.browserContext()
}
return null
}
/**
* Returns the top frame for the page
* @return {!Frame}
*/
mainFrame () {
return this._frameManager.mainFrame()
}
/**
* Returns all frames contained in the page
* @return {Array<Frame>}
*/
frames () {
return this._frameManager.frames()
}
/**
* Returns all workers, if any, that are operating in the page.
* Worker monitoring must be enabled beforehand.
* @return {Array<Worker>}
*/
workers () {
return this._workerManager.workers()
}
/**
* Returns all ServiceWorkers, if any, that are operating in the page
* The ServiceWorker domain must be enabled beforehand.
* @return {Array<ServiceWorker>}
* @since chrome-remote-interface-extra
*/
serviceWorkers () {
return this._workerManager.serviceWorkers()
}
/**
* Returns the URL of the page (top frame)
* @return {!string}
*/
url () {
return this.mainFrame().url()
}
/**
* Returns the string representation of the contents of the page (top frame)
* @return {Promise<string>}
*/
content () {
return this._frameManager.mainFrame().content()
}
/**
* Returns the title of the page (top frame)
* @return {Promise<string>}
*/
title () {
return this.mainFrame().title()
}
/**
* @return {?Viewport}
*/
viewport () {
return this._viewport
}
/**
* Returns T/F indicating if the page is closed
* @return {boolean}
*/
isClosed () {
return this._closed
}
/**
* Evaluates an arbitrary function or string in the pages (top frames) context
* @param {Function|string} pageFunction
* @param {...*} args
* @return {Promise<*>}
*/
evaluate (pageFunction, ...args) {
return this._frameManager.mainFrame().evaluate(pageFunction, ...args)
}
/**
* Clicks the element that the supplied selector matches.
* Evaluation occurs within the context of the top frame
* @param {string} selector
* @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}} [options]
*/
click (selector, options = {}) {
return this.mainFrame().click(selector, options)
}
/**
* Focuses the element that the supplied selector matches.
* Evaluation occurs within the context of the top frame
* @param {string} selector
*/
focus (selector) {
return this.mainFrame().focus(selector)
}
/**
* Hovers the element that the supplied selector matches.
* Evaluation occurs within the context of the top frame
* @param {string} selector
*/
hover (selector) {
return this.mainFrame().hover(selector)
}
/**
* Selects the "select" elements that the supplied selector matches.
* Evaluation occurs within the context of the top frame
* @param {string} selector
* @param {...string} values
* @return {Promise<Array<string>>}
*/
select (selector, ...values) {
return this.mainFrame().select(selector, ...values)
}
/**
* Taps the elements that the supplied selector matches.
* Evaluation occurs within the context of the top frame
* @param {string} selector
*/
tap (selector) {
return this.mainFrame().tap(selector)
}
/**
* Types the supplied text in the elements that the supplied selector matches.
* Evaluation occurs within the context of the top frame
* @param {string} selector
* @param {string} text
* @param {{delay: (number|undefined)}=} options
*/
type (selector, text, options) {
return this.mainFrame().type(selector, text, options)
}
/**
* Waits for a selector or xpath or function or specified amount of time.
* Evaluation occurs within the context of the top frame
* @param {(string|number|Function)} selectorOrFunctionOrTimeout
* @param {!Object=} options
* @param {...*} args
* @return {Promise<JSHandle>}
*/
waitFor (selectorOrFunctionOrTimeout, options = {}, ...args) {
return this.mainFrame().waitFor(
selectorOrFunctionOrTimeout,
options,
...args
)
}
/**
* Waits for a selector.
* Evaluation occurs within the context of the top frame
* @param {string} selector
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}} [options]
* @return {Promise<ElementHandle|undefined>}
*/
waitForSelector (selector, options = {}) {
return this.mainFrame().waitForSelector(selector, options)
}
/**
* Waits for xpath.
* Evaluation occurs within the context of the top frame
* @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {Promise<ElementHandle|undefined>}
*/
waitForXPath (xpath, options = {}) {
return this.mainFrame().waitForXPath(xpath, options)
}
/**
* @param {Function} pageFunction
* @param {!{polling?: string|number, timeout?: number}=} options
* @param {...*} args
* @return {Promise<JSHandle>}
*/
waitForFunction (pageFunction, options = {}, ...args) {
return this.mainFrame().waitForFunction(pageFunction, options, ...args)
}
/**
* Returns metrics relating to the layout of the page, such as viewport bounds/scale
* @return {Promise<{layoutViewport: Object, visualViewport: Object, contentSize: Object}>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page#method-getLayoutMetrics
* @since chrome-remote-interface-extra
*/
getLayoutMetrics () {
return this._client.send('Page.getLayoutMetrics')
}
/**
* @return {Promise<?CDPNavigationEntry>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page#method-getNavigationHistory
* @since chrome-remote-interface-extra
*/
getNavigationHistory () {
return this._client.send('Page.getNavigationHistory')
}
/**
* @param {string} frameId
* @param {string} url
* @return {Promise<Buffer>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page#method-getResourceContent
* @since chrome-remote-interface-extra
*/
getResourceContent (frameId, url) {
return this._frameManager.getFrameResourceContent(frameId, url)
}
/**
* @return {Promise<FrameResourceTree>}
* @since chrome-remote-interface-extra
*/
getResourceTree () {
return this._frameManager.getResourceTree()
}
/**
* @return {Promise<?{url: string, errors: Array<Object>, data: ?string}>}
* @since chrome-remote-interface-extra
*/
getAppManifest () {
return this._client.send('Page.getAppManifest')
}
/**
* Returns all browser cookies.
* Depending on the backend support, will return detailed cookie information in the cookies field.
* @return {Promise<Array<Cookie>>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-getAllCookies
* @since chrome-remote-interface-extra
*/
getAllCookies () {
return this._networkManager.getAllCookies()
}
/**
* Gets the playback rate of animations. See also {@link AnimationManager#getPlaybackRate}
* @return {Promise<number>}
* @since chrome-remote-interface-extra
*/
getAnimationPlaybackRate () {
return this._animationManager.getPlaybackRate()
}
/**
* Sets the playback rate of animations on the page.
* @param {number} playbackRate - Playback rate for animations on page
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
setAnimationPlaybackRate (playbackRate) {
return this._animationManager.setPlaybackRate(playbackRate)
}
/**
* Returns a promise that resolves once this pages network has become idle.
* Detection of network idle considers only the number of in-flight HTTP requests
* for the Page connected to.
* @param {NetIdleOptions} [options]
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
networkIdlePromise (options) {
return this._networkManager.networkIdlePromise(options)
}
/**
* @param {boolean} enabled
*/
setOfflineMode (enabled) {
return this._networkManager.setOfflineMode(enabled)
}
/**
* @param {number} timeout
*/
setDefaultNavigationTimeout (timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout)
}
/**
* @param {number} timeout
*/
setDefaultTimeout (timeout) {
this._timeoutSettings.setDefaultTimeout(timeout)
}
/**
* @param {string} selector
* @return {Promise<ElementHandle|undefined>}
* @since chrome-remote-interface-extra
*/
querySelector (selector) {
return this.$(selector)
}
/**
* @param {string} selector
* @return {Promise<Array<ElementHandle>>}
* @since chrome-remote-interface-extra
*/
querySelectorAll (selector) {
return this.$$(selector)
}
/**
* @param {string} selector
* @param {Function|String} pageFunction
* @param {...*} args
* @return {Promise<Object|undefined>}
* @since chrome-remote-interface-extra
*/
querySelectorEval (selector, pageFunction, ...args) {
return this.$eval(selector, pageFunction, ...args)
}
/**
* @param {string} selector
* @param {Function|String} pageFunction
* @param {...*} args
* @return {Promise<Object|undefined>}
* @since chrome-remote-interface-extra
*/
querySelectorAllEval (selector, pageFunction, ...args) {
return this.$$eval(selector, pageFunction, ...args)
}
/**
* @param {string} elemId
* @return {Promise<ElementHandle|undefined>}
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
* @since chrome-remote-interface-extra
*/
getElementById (elemId) {
return this.mainFrame().getElementById(elemId)
}
/**
* @param {string} expression
* @return {Promise<Array<ElementHandle>>}
* @since chrome-remote-interface-extra
*/
xpathQuery (expression) {
return this.$x(expression)
}
/**
* The method runs document.querySelector within the page.
* If no element matches the selector, the return value resolves to null.
* @param {string} selector
* @return {Promise<ElementHandle|undefined>}
*/
$ (selector) {
return this.mainFrame().$(selector)
}
/**
* @param {string} selector
* @param {Function|string} pageFunction
* @param {...*} args
* @return {Promise<Object|undefined>}
*/
$eval (selector, pageFunction, ...args) {
return this.mainFrame().$eval(selector, pageFunction, ...args)
}
/**
* @param {string} selector
* @param {Function|string} pageFunction
* @param {...*} args
* @return {Promise<Object|undefined>}
*/
$$eval (selector, pageFunction, ...args) {
return this.mainFrame().$$eval(selector, pageFunction, ...args)
}
/**
* @param {string} selector
* @return {Promise<Array<ElementHandle>>}
*/
$$ (selector) {
return this.mainFrame().$$(selector)
}
/**
* @param {string} expression
* @return {Promise<Array<ElementHandle>>}
*/
$x (expression) {
return this.mainFrame().$x(expression)
}
/**
* Returns all browser cookies for the current URL.
* Depending on the backend support, will return detailed cookie information in the cookies field.
* @param {Array<string>} urls - The list of URLs for which applicable cookies will be fetched
* @return {Promise<Array<Cookie>>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-getCookies
* @since chrome-remote-interface-extra && puppeteer
*/
cookies (...urls) {
return this._networkManager.getCookies(urls.length ? urls : [this.url()])
}
/**
* Clears browser cookies
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-clearBrowserCookies
* @since chrome-remote-interface-extra
*/
clearBrowserCookies () {
return this._networkManager.clearBrowserCookies()
}
/**
* Blocks URLs from loading. EXPERIMENTAL
* @param {...string} urls - URL patterns to block. Wildcards ('*') are allowed
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
*/
setBlockedURLs (...urls) {
return this._networkManager.setBlockedURLs(...urls)
}
/**
* Returns the DER-encoded certificate. EXPERIMENTAL
* @param {string} origin - Origin to get certificate for
* @return {Promise<Array<string>>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-getCertificate
*/
getDEREncodedCertificateForOrigin (origin) {
return this._networkManager.getCertificate(origin)
}
/**
* @param {string} url
* @param {!{referer?: string, timeout?: number, waitUntil?: string|Array<string>, transitionType?: string}=} options
* @return {Promise<Response|undefined>}
*/
goto (url, options) {
this._workerManager._clearKnownWorkers()
return this._frameManager.mainFrame().goto(url, options)
}
/**
* @param {!{timeout?: number, waitUntil?: string|Array<string>}=} options
* @return {Promise<?Response>}
*/
waitForNavigation (options = {}) {
this._workerManager._clearKnownWorkers()
return this._frameManager.mainFrame().waitForNavigation(options)
}
/**
* @param {(string|Function)} urlOrPredicate
* @param {{timeout?: number}} [options]
* @return {Promise<Request>}
*/
waitForRequest (urlOrPredicate, options = {}) {
return this._networkManager.waitForRequest(urlOrPredicate, options)
}
/**
* @param {(string|Function)} urlOrPredicate
* @param {{timeout?: number}} [options]
* @return {Promise<Response>}
*/
waitForResponse (urlOrPredicate, options = {}) {
return this._networkManager.waitForResponse(urlOrPredicate, options)
}
/**
* @param {!{timeout?: number, waitUntil?: string|Array<string>}=} options
* @return {Promise<Response|undefined>}
*/
goBack (options) {
this._workerManager._clearKnownWorkers()
return this._go(-1, options)
}
/**
* @param {!{timeout?: number, waitUntil?: string|Array<string>}=} options
* @return {Promise<Response|undefined>}
*/
goForward (options) {
this._workerManager._clearKnownWorkers()
return this._go(+1, options)
}
/**
* @param {?{username: string, password: string}} credentials
*/
authenticate (credentials) {
return this._networkManager.authenticate(credentials)
}
/**
* @param {!Object<string, string>} headers
*/
setExtraHTTPHeaders (headers) {
return this._networkManager.setExtraHTTPHeaders(headers)
}
/**
* @param {string} userAgent
*/
setUserAgent (userAgent) {
return this._networkManager.setUserAgent(userAgent)
}
/**
* @param {{url?: string, path?: string, content?: string, type?: string}} options
* @return {Promise<ElementHandle>}
*/
addScriptTag (options) {
return this.mainFrame().addScriptTag(options)
}
/**
* @param {{url?: string, path?: string, content?: string}} options
* @return {Promise<ElementHandle>}
*/
addStyleTag (options) {
return this.mainFrame().addStyleTag(options)
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enableWorkerMonitoring () {
await this._workerManager.enableWorkerMonitoring()
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disableWorkerMonitoring () {
await this._workerManager.disableWorkerMonitoring()
}
/**
* {@link WorkerManager#enable}
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enableServiceWorkersDomain () {
await this._workerManager.enableServiceWorkerDomain()
}
/**
* {@link WorkerManager#disable}
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disableServiceWorkersDomain () {
await this._workerManager.disableServiceWorkerDomain()
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enableLogDomain () {
if (!this._additionalDomains.log) {
this._additionalDomains.log = true
await this._client.send('Log.enable', {})
}
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disableLogDomain () {
if (this._additionalDomains.log) {
this._additionalDomains.log = false
await this._client.send('Log.disable', {})
}
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enableSecurityDomain () {
if (!this._additionalDomains.security) {
this._additionalDomains.security = true
await this._client.send('Security.enable', {})
}
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disableSecurityDomain () {
if (this._additionalDomains.security) {
this._additionalDomains.security = false
await this._client.send('Security.disable', {})
}
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enablePerformanceDomain () {
if (!this._additionalDomains.performance) {
this._additionalDomains.performance = true
await this._client.send('Performance.enable', {})
}
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disablePerformanceDomain () {
if (this._additionalDomains.performance) {
this._additionalDomains.performance = false
await this._client.send('Performance.disable', {})
}
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enableAnimationsDomain () {
await this._animationManager.enable()
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disableAnimationsDomain () {
await this._animationManager.disable()
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enableDatabaseDomain () {
await this._databaseManager.enable()
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disableDatabaseDomain () {
await this._databaseManager.disable()
}
/**
* Inject object to the target's main frame that provides a communication channel with browser target.
*
* Injected object will be available as window[bindingName].
*
* The object has the following API:
* * binding.send(json) - a method to send messages over the remote debugging protocol
* * binding.onmessage = json => handleMessage(json) - a callback that will be called for the protocol notifications and command responses.
*
* EXPERIMENTAL
* @param {string} [bindingName] - Binding name, 'cdp' if not specified
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Target#method-exposeDevToolsProtocol
* @since chrome-remote-interface-extra
*/
async exposeDevToolsProtocol (bindingName) {
if (this._target) {
await this._target.exposeDevToolsProtocol(bindingName)
return
}
let pageURL
let title
if (this._targetId == null) {
const { targetInfos } = await this._client.send('Target.getTargets', {})
pageURL = this.url()
title = await this.title()
for (let i = 0; i < targetInfos.length; i++) {
const targetInfo = targetInfos[i]
if (pageURL === targetInfo.url) {
this._targetId = targetInfo.targetId
break
}
}
}
if (this._targetId == null) {
throw new Error(
`Failed to expose devtools protocol. This page (url=${pageURL}, title=${title}) was created without passing in a target and we could not find this page's target id`
)
}
await this._client.send('Target.exposeDevToolsProtocol', {
targetId: this._targetId,
bindingName: bindingName || undefined
})
}
/**
* @param {number} entryId
* @return {Promise<void>}
*/
async navigateToHistoryEntry (entryId) {
await this._client.send('Page.navigateToHistoryEntry', { entryId })
}
async resetNavigationHistory () {
await this._client.send('Page.resetNavigationHistory')
}
/**
* Toggles ignoring of service worker for each request. EXPERIMENTAL
* @param {boolean} bypass - Bypass service worker and load from network
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBypassServiceWorker
* @since chrome-remote-interface-extra
*/
async httpRequestsBypassServiceWorker (bypass) {
await this._networkManager.bypassServiceWorker(bypass)
}
/**
* Force the page stop all navigations and pending resource fetches
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page#method-stopLoading
* @since chrome-remote-interface-extra
*/
async stopLoading () {
await this._client.send('Page.stopLoading')
}
/**
* Set the behavior when downloading a file. EXPERIMENTAL
*
* @param {string} behavior - Whether to allow all or deny all download requests, or use default Chrome behavior if available (otherwise deny). Allowed values: deny, allow, default
* @param {string} [downloadPath] - The default path to save downloaded files to. This is requred if behavior is set to 'allow'
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page#method-setDownloadBehavior
* @since chrome-remote-interface-extra
*/
async setDownloadBehavior (behavior, downloadPath) {
await this._client.send('Page.setDownloadBehavior', {
behavior,
downloadPath: downloadPath || undefined
})
}
/**
* Evaluates given script in every frame upon creation (before loading frame's scripts)
* @param {string} source - The string contents of the script
* @param {string} [worldName] - If specified, creates an isolated world with the given name and evaluates given
* script in it. This world name will be used as the ExecutionContextDescription::name when the corresponding
* event is emitted.
* @return {Promise<string>} - Identifier of the added script
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page#method-addScriptToEvaluateOnNewDocument
* @since chrome-remote-interface-extra
*/
async addScriptToEvaluateOnNewDocument (source, worldName) {
const { identifier } = await this._client.send(
'Page.addScriptToEvaluateOnNewDocument',
{ source, woldName: worldName || undefined }
)
return identifier
}
/**
* @param {string} identifier - Identifier of the added script
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page#method-removeScriptToEvaluateOnNewDocument
* @since chrome-remote-interface-extra
*/
async removeScriptToEvaluateOnNewDocument (identifier) {
if (!identifier) return
await this._client.send('Page.removeScriptToEvaluateOnNewDocument', {
identifier
})
}
/**
* @return {Promise<string>}
*/
async userAgent () {
const version = await this._client.send('Browser.getVersion')
return version.userAgent
}
/**
* @param {string} acceptLanguage
* @since chrome-remote-interface-extra
*/
async setAcceptLanguage (acceptLanguage) {
await this._networkManager.setAcceptLanguage(acceptLanguage)
}
/**
* @param {string} platform
* @since chrome-remote-interface-extra
*/
async setNavigatorPlatform (platform) {
await this._networkManager.setNavigatorPlatform(platform)
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async disableNetworkCache () {
await this._networkManager.disableCache()
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async enableNetworkCache () {
await this._networkManager.enableCache()
}
/**
*
* @return {Promise<void>}
* @since chrome-remote-interface-extra
*/
async clearBrowserCache () {
await this._networkManager.clearBrowserCache()
}
/**
* @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options
*/
async setGeolocation (options) {
await this._emulationManager.setGeolocation(options)
}
/**
* @param {boolean} value
*/
async setRequestInterception (value) {
await this._networkManager.setRequestInterception(value)
}
/**
* @param {Function|string} pageFunction
* @param {...*} args
* @return {Promise<JSHandle>}
*/
async evaluateHandle (pageFunction, ...args) {
const context = await this.mainFrame().executionContext()
return context.evaluateHandle(pageFunction, ...args)
}
/**
* @param {!JSHandle} prototypeHandle
* @return {Promise<JSHandle>}
*/
async queryObjects (prototypeHandle) {
const context = await this.mainFrame().executionContext()
return context.queryObjects(prototypeHandle)
}
/**
* Deletes the specified browser cookies with matching name and url or domain/path pair.
* @param {CDPCookie|CookieToBeDeleted|string|Cookie} cookie - The cookie to be deleted
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-deleteCookies
* @since chrome-remote-interface-extra
*/
async deleteCookie (cookie) {
const pageURL = this.url()
const startsWithHTTP = pageURL.startsWith('http')
await this._networkManager.deleteCookie(
ensureCookie(cookie, pageURL, startsWithHTTP)
)
}
/**
* Deletes the specified browser cookies with matching name and url or domain/path pair.
* @param {Array<CDPCookie|CookieToBeDeleted|string|Cookie>} cookies - The cookies to be deleted
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-deleteCookies
* @since chrome-remote-interface-extra
*/
async deleteCookies (...cookies) {
const pageURL = this.url()
const startsWithHTTP = pageURL.startsWith('http')
for (let i = 0; i < cookies.length; i++) {
await this._networkManager.deleteCookie(
ensureCookie(cookies[i], pageURL, startsWithHTTP)
)
}
}
/**
* @param {CDPCookie|Cookie|string} cookie - The new cookie to be set
* @return {Promise<boolean>} - T/F indicating if the cookie was set
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setCookie
* @since chrome-remote-interface-extra
*/
async setCookie (cookie) {
const pageURL = this.url()
const startsWithHTTP = pageURL.startsWith('http')
const cookieToBeSet = ensureCookie(cookie, pageURL, startsWithHTTP)
assert(
cookieToBeSet.url !== 'about:blank',
`Blank page can not have cookie "${cookieToBeSet.name}"`
)
assert(
!String.prototype.startsWith.call(cookieToBeSet.url || '', 'data:'),
`Data URL page can not have cookie "${cookieToBeSet.name}"`
)
await this._networkManager.deleteCookie(cookieToBeSet)
return this._networkManager.setCookie(cookieToBeSet)
}
/**
* Sets given cookies
* @param {Array<CDPCookie|Cookie|string>} cookies
* @return {Promise<void>}
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setCookies
* @since chrome-remote-interface-extra
*/
async setCookies (...cookies) {
if (!cookies.length) return
const pageURL = this.url()
const startsWithHTTP = pageURL.startsWith('http')
const cookiesSet = []
for (let i = 0; i < cookies.length; i++) {
let cookie = ensureCookie(cookies[i], pageURL, startsWithHTTP)
assert(
cookie.url !== 'about:blank',
`Blank page can not have cookie "${cookie.name}"`
)
assert(
!String.prototype.startsWith.call(cookie.url || '', 'data:'),
`Data URL page can not have cookie "${cookie.name}"`
)
cookiesSet.push(cookie)
}
await this._networkManager.deleteCookies(...cookiesSet)
await this._networkManager.setCookies(...cookiesSet)
}
/**
* @param {string} name
* @param {Function} puppeteerFunction
*/
async exposeFunction (name, puppeteerFunction) {
if (this._pageBindings.has(name)) {
throw new Error(
`Failed to add page binding with name ${name}: window['${name}'] already exists!`
)
}
this._pageBindings.set(name, puppeteerFunction)
const expression = helper.evaluationString(addPageBinding, name)
await this._client.send('Runtime.addBinding', { name: name })
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
source: expression
})
await Promise.all(
this.frames().map(frame => frame.evaluate(expression).catch(debugError))
)
function addPageBinding (bindingName) {
const binding = window[bindingName]
window[bindingName] = async (...args) => {
const me = window[bindingName]
let callbacks = me['callbacks']
if (!callbacks) {
callbacks = new Map()
me['callbacks'] = callbacks
}
const seq = (me['lastSeq'] || 0) + 1
me['lastSeq'] = seq
const promise = new Promise((resolve, reject) =>
callbacks.set(seq, { resolve, reject })
)
binding(JSON.stringify({ name: bindingName, seq, args }))
return promise
}
}
}
/**
* @return {Promise<Metrics>}
*/
async metrics () {
const response = await this._client.send('Performance.getMetrics')
return this._buildMetricsObject(response.metrics)
}
/**
* @param {string} html
* @param {!{timeout?: number, waitUntil?: string|Array<string>}=} options
*/
async setContent (html, options) {
await this._frameManager.mainFrame().setContent(html, options)
}
/**
* @param {!{timeout?: number, waitUntil?: string|Array<string>, ignoreCache?: boolean, scriptToEvaluateOnLoad?: string}=} options
* @return {Promise<Response|undefined>}
*/
async reload (options) {
const params = {}
if (options) {
params.ignoreCache = options.ignoreCache
params.scriptToEvaluateOnLoad = options.scriptToEvaluateOnLoad
}
const [response] = await Promise.all([
this.waitForNavigation(options),
this._client.send('Page.reload', params)
])
return response
}
async bringToFront () {
await this._client.send('Page.bringToFront')
}
/**
* @param {!{viewport: !Viewport, userAgent: string}} options
*/
async emulate (options) {
await Promise.all([
this.setViewport(options.viewport),
this.setUserAgent(options.userAgent)
])
}
/**
* @param {boolean} enabled
*/
async setJavaScriptEnabled (enabled) {
if (this._javascriptEnabled === enabled) return
this._javascriptEnabled = enabled
await this._emulationManager.setScriptExecutionDisabled(!enabled)
}
/**
* @param {boolean} enabled
*/
async setBypassCSP (enabled) {
await this._client.send('Page.setBypassCSP', { enabled })
}
/**
* @param {?string} mediaType
*/
async emulateMedia (mediaType) {
await this._emulationManager.setEmulatedMedia(mediaType || '')
}
/**
* @param {!Viewport} viewport
*/
async setViewport (viewport) {
const needsReload = await this._emulationManager.emulateViewport(viewport)
this._viewport = viewport
if (needsReload) await this.reload()
}
/**
* @param {Function|string} pageFunction
* @param {...*} args
*/
async evaluateOnNewDocument (pageFunction, ...args) {
const source = helper.evaluationString(pageFunction, ...args)
await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source })
}
/**
* @param {boolean} enabled
*/
async setCacheEnabled (enabled = true) {
if (enabled) {
await this._networkManager.enableCache()
} else {
await this._networkManager.disableCache()
}
}
/**
* @param {!ScreenshotOptions=} options
* @return {Promise<Buffer|!String>}
*/
async screenshot (options = {}) {
let screenshotType = null
// options.type takes precedence over inferring the type from options.path
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
if (options.type) {
assert(
options.type === 'png' || options.type === 'jpeg',
'Unknown options.type value: ' + options.type
)
screenshotType = options.type
} else if (options.path) {
const mimeType = mime.getType(options.path)
if (mimeType === 'image/png') screenshotType = 'png'
else if (mimeType === 'image/jpeg') screenshotType = 'jpeg'
assert(screenshotType, 'Unsupported screenshot mime type: ' + mimeType)
}
if (!screenshotType) screenshotType = 'png'
if (options.quality) {
assert(
screenshotType === 'jpeg',
'options.quality is unsupported for the ' +
screenshotType +
' screenshots'
)
assert(
typeof options.quality === 'number',
'Expected options.quality to be a number but found ' +
typeof options.quality
)
assert(
Number.isInteger(options.quality),
'Expected options.quality to be an integer'
)
assert(
options.quality >= 0 && options.quality <= 100,
'Expected options.quality to be between 0 and 100 (inclusive), got ' +
options.quality
)
}
assert(
!options.clip || !options.fullPage,
'options.clip and options.fullPage are exclusive'
)
if (options.clip) {
assert(
typeof options.clip.x === 'number',
'Expected options.clip.x to be a number but found ' +
typeof options.clip.x
)
assert(
typeof options.clip.y === 'number',
'Expected options.clip.y to be a number but found ' +
typeof options.clip.y
)
assert(
typeof options.clip.width === 'number',
'Expected options.clip.width to be a number but found ' +
typeof options.clip.width
)
assert(
typeof options.clip.height === 'number',
'Expected options.clip.height to be a number but found ' +
typeof options.clip.height
)
assert(
options.clip.width !== 0,
'Expected options.clip.width not to be 0.'
)
assert(
options.clip.height !== 0,
'Expected options.clip.width not to be 0.'
)
}
return this._screenshotTaskQueue.postTask(
this._screenshotTask.bind(this, screenshotType, options)
)
}
/**
* @param {!PDFOptions=} options
* @return {Promise<Buffer>}
*/
async pdf (options = {}) {
const {
scale = 1,
displayHeaderFooter = false,
headerTemplate = '',
footerTemplate = '',
printBackground = false,
landscape = false,
pageRanges = '',
preferCSSPageSize = false,
margin = {},
path = null
} = options
let paperWidth = 8.5
let paperHeight = 11
if (options.format) {
const format = Page.PaperFormats[options.format.toLowerCase()]
assert(format, 'Unknown paper format: ' + options.format)
paperWidth = format.width
paperHeight = format.height
} else {
paperWidth = convertPrintParameterToInches(options.width) || paperWidth
paperHeight = convertPrintParameterToInches(options.height) || paperHeight
}
const marginTop = convertPrintParameterToInches(margin.top) || 0
const marginLeft = convertPrintParameterToInches(margin.left) || 0
const marginBottom = convertPrintParameterToInches(margin.bottom) || 0
const marginRight = convertPrintParameterToInches(margin.right) || 0
const result = await this._client.send('Page.printToPDF', {
landscape,
displayHeaderFooter,
headerTemplate,
footerTemplate,
printBackground,
scale,
paperWidth,
paperHeight,
marginTop,
marginBottom,
marginLeft,
marginRight,
pageRanges,
preferCSSPageSize
})
const buffer = Buffer.from(result.data, 'base64')
if (path !== null) await fs.writeFile(path, buffer)
return buffer
}
/**
* @param {!{runBeforeUnload: (boolean|undefined)}=} options
*/
async close (options = { runBeforeUnload: undefined }) {
const runBeforeUnload = !!options.runBeforeUnload
if (runBeforeUnload) {
await this._client.send('Page.close')
} else if (this._target) {
await this._target.close()
await this._target._isClosedPromise
}
}
/**
* @param {"png"|"jpeg"} format
* @param {!ScreenshotOptions=} options
* @return {Promise<Buffer|!String>}
*/
async _screenshotTask (format, options) {
if (this._target) {
await this._client.send('Target.activateTarget', {
targetId: this._target.id()
})
}
let clip = options.clip ? processClip(options.clip) : undefined
if (options.fullPage) {
const metrics = await this.getLayoutMetrics()
const width = Math.ceil(metrics.contentSize.width)
const height = Math.ceil(metrics.contentSize.height)
// Overwrite clip for full page at all times.
clip = { x: 0, y: 0, width, height, scale: 1 }
const { isMobile = false, deviceScaleFactor = 1, isLandscape = false } =
this._viewport || {}
/** @type {!Object} */
const screenOrientation = isLandscape
? { angle: 90, type: 'landscapePrimary' }
: { angle: 0, type: 'portraitPrimary' }
await this._client.send('Emulation.setDeviceMetricsOverride', {
mobile: isMobile,
width,
height,
deviceScaleFactor,
screenOrientation
})
}
const shouldSetDefaultBackground =
options.omitBackground && format === 'png'
if (shouldSetDefaultBackground) {
await this._emulationManager.setDefaultBackgroundColorOverride({
r: 0,
g: 0,
b: 0,
a: 0
})
}
const result = await this._client.send('Page.captureScreenshot', {
format,
quality: options.quality,
clip
})
if (shouldSetDefaultBackground) {
await this._emulationManager.setDefaultBackgroundColorOverride()
}
if (options.fullPage && this._viewport) {
await this.setViewport(this._viewport)
}
const buffer =
options.encoding === 'base64'
? result.data
: Buffer.from(result.data, 'base64')
if (options.path) await fs.writeFile(options.path, buffer)
return buffer
function processClip (clip) {
const x = Math.round(clip.x)
const y = Math.round(clip.y)
const width = Math.round(clip.width + clip.x - x)
const height = Math.round(clip.height + clip.y - y)
return { x, y, width, height, scale: 1 }
}
}
/**
* @param delta
* @param {!{timeout?: number, waitUntil?: string|Array<string>}=} options
* @return {Promise<Response|undefined>}
*/
async _go (delta, options) {
const history = await this.getNavigationHistory()
const entry = history.entries[history.currentIndex + delta]
if (!entry) return null
const [response] = await Promise.all([
this.waitForNavigation(options),
this.navigateToHistoryEntry(entry.id)
])
return response
}
/**
* @param {!Object} event
*/
async _onConsoleAPI (event) {
if (event.executionContextId === 0) {
// DevTools protocol stores the last 1000 console messages. These
// messages are always reported even for removed execution contexts. In
// this case, they are marked with executionContextId = 0 and are
// reported upon enabling Runtime agent.
//
// Ignore these messages since:
// - there's no execution context we can use to operate with message
// arguments
// - these messages are reported before Puppeteer clients can subscribe
// to the 'console'
// page event.
//
// @see https://github.com/GoogleChrome/puppeteer/issues/3865
return
}
const context = this._frameManager.executionContextById(
event.executionContextId
)
if (!this.listenerCount(Events.Page.Console)) {
event.args.forEach(arg => createJSHandle(context, arg).dispose())
return
}
const message = new ConsoleMessage(event, { context })
this.emit(Events.Page.Console, message)
}
/**
* @param {!Object} event
*/
async _onBindingCalled (event) {
const { name, seq, args } = JSON.parse(event.payload)
let expression = null
try {
const result = await this._pageBindings.get(name)(...args)
expression = helper.evaluationString(deliverResult, name, seq, result)
} catch (error) {
if (error instanceof Error) {
expression = helper.evaluationString(
deliverError,
name,
seq,
error.message,
error.stack
)
} else {
expression = helper.evaluationString(
deliverErrorValue,
name,
seq,
error
)
}
}
this._client
.send('Runtime.evaluate', {
expression,
contextId: event.executionContextId
})
.catch(debugError)
/**
* @param {string} name
* @param {number} seq
* @param {*} result
*/
function deliverResult (name, seq, result) {
window[name]['callbacks'].get(seq).resolve(result)
window[name]['callbacks'].delete(seq)
}
/**
* @param {string} name
* @param {number} seq
* @param {string} message
* @param {string} stack
*/
function deliverError (name, seq, message, stack) {
const error = new Error(message)
error.stack = stack
window[name]['callbacks'].get(seq).reject(error)
window[name]['callbacks'].delete(seq)
}
/**
* @param {string} name
* @param {number} seq
* @param {*} value
*/
function deliverErrorValue (name, seq, value) {
window[name]['callbacks'].get(seq).reject(value)
window[name]['callbacks'].delete(seq)
}
}
_onTargetCrashed () {
this.emit('error', new Error('Page crashed!'))
this.emit(Events.Page.Crashed)
}
/**
* @param {!Object} event
*/
_onLogEntryAdded (event) {
if (event.entry.source !== 'worker') {
this.emit(Events.Page.LogEntry, new LogEntry(event.entry))
}
if (event.entry.args) {
return Promise.all(
event.entry.args.map(arg => helper.releaseObject(this._client, arg))
)
}
}
/**
* @param {!Object} event
*/
_onCertificateError (event) {
if (!this._ignoreHTTPSErrors) return
this._client
.send('Security.handleCertificateError', {
eventId: event.eventId,
action: 'continue'
})
.catch(debugError)
}
/**
* @param {!Object} event
*/
_emitMetrics (event) {
this.emit(Events.Page.Metrics, {
title: event.title,
metrics: this._buildMetricsObject(event.metrics)
})
}
/**
* @param {?Array<Object>} metrics
* @return {!Metrics}
*/
_buildMetricsObject (metrics) {
const result = {}
const _metrics = metrics || []
for (let i = 0; i < _metrics.length; i++) {
const metric = _metrics[i]
result[metric.name] = metric.value
}
return result
}
/**
* @param {!Object} exception
*/
_handleException (exception) {
const message = helper.getExceptionMessage(exception.exceptionDetails)
const err = new Error(message)
err.stack = '' // Don't report clientside error with a node stack attached
this.emit(Events.Page.PageError, err)
}
_onDialog (event) {
Dialog.assertKnownDialogType(event)
const dialog = new Dialog(this._client, event)
this.emit(Events.Page.Dialog, dialog)
}
__onClose () {
this.emit(Events.Page.Close)
this._closed = true
}
/**
* @return {{frames: !FrameManager, javascriptEnabled: boolean, additionalDomains: EnabledExtras, workers: IterableIterator<Worker>, url: string, target: ?Target, network: !NetworkManager}}
*/
toJSON () {
return {
url: this._frameManager.mainFrame().url(),
target: this._target,
frames: this._frameManager,
network: this._networkManager,
additionalDomains: this._additionalDomains,
workers: this._workers.values(),
javascriptEnabled: this._javascriptEnabled
}
}
/** @ignore */
// eslint-disable-next-line space-before-function-paren
[util.inspect.custom](depth, options) {
if (depth < 0) {
return options.stylize('[Page]', 'special')
}
const newOptions = Object.assign({}, options, {
depth: options.depth == null ? null : options.depth - 1
})
const inner = util.inspect(
{
url: this.url(),
target: this._target,
network: this._networkManager,
frames: this._frameManager,
workers: this._workerManager,
databases: this._databaseManager,
emulation: this._emulationManager,
pageBindings: this._pageBindings,
additionalDomains: this._additionalDomains,
javascriptEnabled: this._javascriptEnabled,
timeoutSettings: this._timeoutSettings
},
newOptions
)
return `${options.stylize('Page', 'special')} ${inner}`
}
}
/** @type {!Set<string>} */
const supportedMetrics = new Set([
'Timestamp',
'Documents',
'Frames',
'JSEventListeners',
'Nodes',
'LayoutCount',
'RecalcStyleCount',
'LayoutDuration',
'RecalcStyleDuration',
'ScriptDuration',
'TaskDuration',
'JSHeapUsedSize',
'JSHeapTotalSize'
])
/** @enum {!{width: number, height: number}} */
Page.PaperFormats = {
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 },
a6: { width: 4.13, height: 5.83 }
}
const unitToPixels = {
px: 1,
in: 96,
cm: 37.8,
mm: 3.78
}
/**
* @param {(string|number|undefined)} parameter
* @return {(number|undefined)}
*/
function convertPrintParameterToInches (parameter) {
if (typeof parameter === 'undefined') return undefined
let pixels
if (helper.isNumber(parameter)) {
// Treat numbers as pixel values to be aligned with phantom's paperSize.
pixels = /** @type {number} */ (parameter)
} else if (helper.isString(parameter)) {
const text = /** @type {string} */ (parameter)
let unit = text.substring(text.length - 2).toLowerCase()
let valueText = ''
if (unitToPixels.hasOwnProperty(unit)) {
valueText = text.substring(0, text.length - 2)
} else {
// 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
}
const value = Number(valueText)
assert(!isNaN(value), 'Failed to parse parameter value: ' + text)
pixels = value * unitToPixels[unit]
} else {
throw new Error(
'page.pdf() Cannot handle parameter type: ' + typeof parameter
)
}
return pixels / 96
}
/**
* @type {Page}
*/
module.exports = Page