Socket
Socket
Sign inDemoInstall

puppeteer-core

Package Overview
Dependencies
Maintainers
1
Versions
238
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

puppeteer-core - npm Package Compare versions

Comparing version 1.11.0 to 1.12.0

index.d.ts

44

DeviceDescriptors.js

@@ -140,3 +140,3 @@ /**

'name': 'Galaxy S5',
'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -153,3 +153,3 @@ 'width': 360,

'name': 'Galaxy S5 landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -502,3 +502,3 @@ 'width': 640,

'name': 'LG Optimus L70',
'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -515,3 +515,3 @@ 'width': 384,

'name': 'LG Optimus L70 landscape',
'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -564,3 +564,3 @@ 'width': 640,

'name': 'Nexus 10',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
'viewport': {

@@ -577,3 +577,3 @@ 'width': 800,

'name': 'Nexus 10 landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
'viewport': {

@@ -590,3 +590,3 @@ 'width': 1280,

'name': 'Nexus 4',
'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -603,3 +603,3 @@ 'width': 384,

'name': 'Nexus 4 landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -616,3 +616,3 @@ 'width': 640,

'name': 'Nexus 5',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -629,3 +629,3 @@ 'width': 360,

'name': 'Nexus 5 landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -642,3 +642,3 @@ 'width': 640,

'name': 'Nexus 5X',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -655,3 +655,3 @@ 'width': 412,

'name': 'Nexus 5X landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -668,3 +668,3 @@ 'width': 732,

'name': 'Nexus 6',
'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -681,3 +681,3 @@ 'width': 412,

'name': 'Nexus 6 landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -694,3 +694,3 @@ 'width': 732,

'name': 'Nexus 6P',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -707,3 +707,3 @@ 'width': 412,

'name': 'Nexus 6P landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -720,3 +720,3 @@ 'width': 732,

'name': 'Nexus 7',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
'viewport': {

@@ -733,3 +733,3 @@ 'width': 600,

'name': 'Nexus 7 landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
'viewport': {

@@ -794,3 +794,3 @@ 'width': 960,

'name': 'Pixel 2',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -807,3 +807,3 @@ 'width': 411,

'name': 'Pixel 2 landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -820,3 +820,3 @@ 'width': 731,

'name': 'Pixel 2 XL',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -833,3 +833,3 @@ 'width': 411,

'name': 'Pixel 2 XL landscape',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3452.0 Mobile Safari/537.36',
'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
'viewport': {

@@ -836,0 +836,0 @@ 'width': 823,

@@ -24,2 +24,12 @@ /**

if (asyncawait) {
const {helper} = require('./lib/helper');
const api = require('./lib/api');
for (const className in api) {
// Puppeteer-web excludes certain classes from bundle, e.g. BrowserFetcher.
if (typeof api[className] === 'function')
helper.installAsyncStackHooks(api[className]);
}
}
// If node does not support async await, use the compiled version.

@@ -26,0 +36,0 @@ const Puppeteer = asyncawait ? require('./lib/Puppeteer') : require('./node6/lib/Puppeteer');

@@ -16,3 +16,2 @@ /**

*/
const {helper} = require('./helper');

@@ -394,2 +393,1 @@ /**

module.exports = {Accessibility};
helper.tracePublicAPI(Accessibility);

@@ -21,3 +21,3 @@ /**

const {TaskQueue} = require('./TaskQueue');
const {Connection} = require('./Connection');
const {Events} = require('./Events');

@@ -31,2 +31,16 @@ class Browser extends EventEmitter {

* @param {?Puppeteer.ChildProcess} process
* @param {function()=} closeCallback
*/
static async create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
await connection.send('Target.setDiscoverTargets', {discover: true});
return browser;
}
/**
* @param {!Puppeteer.Connection} connection
* @param {!Array<string>} contextIds
* @param {boolean} ignoreHTTPSErrors
* @param {?Puppeteer.Viewport} defaultViewport
* @param {?Puppeteer.ChildProcess} process
* @param {(function():Promise)=} closeCallback

@@ -51,3 +65,3 @@ */

this._targets = new Map();
this._connection.on(Connection.Events.Disconnected, () => this.emit(Browser.Events.Disconnected));
this._connection.on(Events.Connection.Disconnected, () => this.emit(Events.Browser.Disconnected));
this._connection.on('Target.targetCreated', this._targetCreated.bind(this));

@@ -98,16 +112,2 @@ this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));

/**
* @param {!Puppeteer.Connection} connection
* @param {!Array<string>} contextIds
* @param {boolean} ignoreHTTPSErrors
* @param {?Puppeteer.Viewport} defaultViewport
* @param {?Puppeteer.ChildProcess} process
* @param {function()=} closeCallback
*/
static async create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
await connection.send('Target.setDiscoverTargets', {discover: true});
return browser;
}
/**
* @param {!Protocol.Target.targetCreatedPayload} event

@@ -125,4 +125,4 @@ */

if (await target._initializedPromise) {
this.emit(Browser.Events.TargetCreated, target);
context.emit(BrowserContext.Events.TargetCreated, target);
this.emit(Events.Browser.TargetCreated, target);
context.emit(Events.BrowserContext.TargetCreated, target);
}

@@ -140,4 +140,4 @@ }

if (await target._initializedPromise) {
this.emit(Browser.Events.TargetDestroyed, target);
target.browserContext().emit(BrowserContext.Events.TargetDestroyed, target);
this.emit(Events.Browser.TargetDestroyed, target);
target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
}

@@ -156,4 +156,4 @@ }

if (wasInitialized && previousURL !== target.url()) {
this.emit(Browser.Events.TargetChanged, target);
target.browserContext().emit(BrowserContext.Events.TargetChanged, target);
this.emit(Events.Browser.TargetChanged, target);
target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
}

@@ -216,4 +216,4 @@ }

const targetPromise = new Promise(x => resolve = x);
this.on(Browser.Events.TargetCreated, check);
this.on(Browser.Events.TargetChanged, check);
this.on(Events.Browser.TargetCreated, check);
this.on(Events.Browser.TargetChanged, check);
try {

@@ -224,4 +224,4 @@ if (!timeout)

} finally {
this.removeListener(Browser.Events.TargetCreated, check);
this.removeListener(Browser.Events.TargetChanged, check);
this.removeListener(Events.Browser.TargetCreated, check);
this.removeListener(Events.Browser.TargetChanged, check);
}

@@ -280,10 +280,2 @@

/** @enum {string} */
Browser.Events = {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
Disconnected: 'disconnected'
};
class BrowserContext extends EventEmitter {

@@ -394,12 +386,2 @@ /**

/** @enum {string} */
BrowserContext.Events = {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
};
helper.tracePublicAPI(BrowserContext);
helper.tracePublicAPI(Browser);
module.exports = {Browser, BrowserContext};

@@ -128,3 +128,3 @@ /**

* @param {string} revision
* @param {?function(number, number)} progressCallback
* @param {?function(number, number):void} progressCallback
* @return {!Promise<!BrowserFetcher.RevisionInfo>}

@@ -221,3 +221,3 @@ */

* @param {string} destinationPath
* @param {?function(number, number)} progressCallback
* @param {?function(number, number):void} progressCallback
* @return {!Promise}

@@ -224,0 +224,0 @@ */

@@ -16,5 +16,5 @@ /**

*/
const {helper, assert} = require('./helper');
const {assert} = require('./helper');
const {Events} = require('./Events');
const debugProtocol = require('debug')('puppeteer:protocol');
const debugSession = require('debug')('puppeteer:session');
const EventEmitter = require('events');

@@ -49,10 +49,14 @@

static fromSession(session) {
let connection = session._connection;
// TODO(lushnikov): move to flatten protocol to avoid this.
while (connection instanceof CDPSession)
connection = connection._connection;
return connection;
return session._connection;
}
/**
* @param {string} sessionId
* @return {?CDPSession}
*/
session(sessionId) {
return this._sessions.get(sessionId) || null;
}
/**
* @return {string}

@@ -70,6 +74,3 @@ */

send(method, params = {}) {
const id = ++this._lastId;
const message = JSON.stringify({id, method, params});
debugProtocol('SEND ► ' + message);
this._transport.send(message);
const id = this._rawSend({method, params});
return new Promise((resolve, reject) => {

@@ -81,2 +82,14 @@ this._callbacks.set(id, {resolve, reject, error: new Error(), method});

/**
* @param {*} message
* @return {number}
*/
_rawSend(message) {
const id = ++this._lastId;
message = JSON.stringify(Object.assign({}, message, {id}));
debugProtocol('SEND ► ' + message);
this._transport.send(message);
return id;
}
/**
* @param {string} message

@@ -89,3 +102,18 @@ */

const object = JSON.parse(message);
if (object.id) {
if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId;
const session = new CDPSession(this, object.params.targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
} else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.sessionId);
}
}
if (object.sessionId) {
const session = this._sessions.get(object.sessionId);
if (session)
session._onMessage(object);
} else if (object.id) {
const callback = this._callbacks.get(object.id);

@@ -101,14 +129,3 @@ // Callbacks could be all rejected if someone has called `.dispose()`.

} else {
if (object.method === 'Target.receivedMessageFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session)
session._onMessage(object.params.message);
} else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session)
session._onClosed();
this._sessions.delete(object.params.sessionId);
} else {
this.emit(object.method, object.params);
}
this.emit(object.method, object.params);
}

@@ -129,3 +146,3 @@ }

this._sessions.clear();
this.emit(Connection.Events.Disconnected);
this.emit(Events.Connection.Disconnected);
}

@@ -143,16 +160,10 @@

async createSession(targetInfo) {
const {sessionId} = await this.send('Target.attachToTarget', {targetId: targetInfo.targetId});
const session = new CDPSession(this, targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
return session;
const {sessionId} = await this.send('Target.attachToTarget', {targetId: targetInfo.targetId, flatten: true});
return this._sessions.get(sessionId);
}
}
Connection.Events = {
Disconnected: Symbol('Connection.Events.Disconnected'),
};
class CDPSession extends EventEmitter {
/**
* @param {!Connection|!CDPSession} connection
* @param {!Connection} connection
* @param {string} targetType

@@ -163,11 +174,7 @@ * @param {string} sessionId

super();
this._lastId = 0;
/** @type {!Map<number, {resolve: function, reject: function, error: !Error, method: string}>}*/
this._callbacks = new Map();
/** @type {null|Connection|CDPSession} */
this._connection = connection;
this._targetType = targetType;
this._sessionId = sessionId;
/** @type {!Map<string, !CDPSession>}*/
this._sessions = new Map();
}

@@ -183,13 +190,3 @@

return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
const id = ++this._lastId;
const message = JSON.stringify({id, method, params});
debugSession('SEND ► ' + message);
this._connection.send('Target.sendMessageToTarget', {sessionId: this._sessionId, message}).catch(e => {
// The response from target might have been already dispatched.
if (!this._callbacks.has(id))
return;
const callback = this._callbacks.get(id);
this._callbacks.delete(id);
callback.reject(rewriteError(callback.error, e && e.message));
});
const id = this._connection._rawSend({sessionId: this._sessionId, method, params});
return new Promise((resolve, reject) => {

@@ -201,7 +198,5 @@ this._callbacks.set(id, {resolve, reject, error: new Error(), method});

/**
* @param {string} message
* @param {{id?: number, method: string, params: Object, error: {message: string, data: any}, result?: *}} object
*/
_onMessage(message) {
debugSession('◀ RECV ' + message);
const object = JSON.parse(message);
_onMessage(object) {
if (object.id && this._callbacks.has(object.id)) {

@@ -215,13 +210,2 @@ const callback = this._callbacks.get(object.id);

} else {
if (object.method === 'Target.receivedMessageFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session)
session._onMessage(object.params.message);
} else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.sessionId);
}
}
assert(!object.id);

@@ -243,22 +227,6 @@ this.emit(object.method, object.params);

this._connection = null;
this.emit(CDPSession.Events.Disconnected);
this.emit(Events.CDPSession.Disconnected);
}
/**
* @param {string} targetType
* @param {string} sessionId
*/
_createSession(targetType, sessionId) {
const session = new CDPSession(this, targetType, sessionId);
this._sessions.set(sessionId, session);
return session;
}
}
CDPSession.Events = {
Disconnected: Symbol('CDPSession.Events.Disconnected'),
};
helper.tracePublicAPI(CDPSession);
/**

@@ -265,0 +233,0 @@ * @param {!Error} error

@@ -67,3 +67,2 @@ /**

module.exports = {Coverage};
helper.tracePublicAPI(Coverage);

@@ -70,0 +69,0 @@ class JSCoverage {

@@ -17,3 +17,3 @@ /**

const {helper, assert} = require('./helper');
const {assert} = require('./helper');

@@ -85,2 +85,1 @@ class Dialog {

module.exports = {Dialog};
helper.tracePublicAPI(Dialog);

@@ -17,4 +17,4 @@ /**

const {helper, assert, debugError} = require('./helper');
const path = require('path');
const {helper, assert} = require('./helper');
const {createJSHandle, JSHandle} = require('./JSHandle');

@@ -24,11 +24,2 @@ const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';

function createJSHandle(context, remoteObject) {
const frame = context.frame();
if (remoteObject.subtype === 'node' && frame) {
const frameManager = frame._frameManager;
return new ElementHandle(context, context._client, remoteObject, frameManager.page(), frameManager);
}
return new JSHandle(context, context._client, remoteObject);
}
class ExecutionContext {

@@ -38,9 +29,8 @@ /**

* @param {!Protocol.Runtime.ExecutionContextDescription} contextPayload
* @param {?Puppeteer.Frame} frame
* @param {?Puppeteer.DOMWorld} world
*/
constructor(client, contextPayload, frame) {
constructor(client, contextPayload, world) {
this._client = client;
this._frame = frame;
this._world = world;
this._contextId = contextPayload.id;
this._isDefault = contextPayload.auxData ? !!contextPayload.auxData['isDefault'] : false;
}

@@ -52,3 +42,3 @@

frame() {
return this._frame;
return this._world ? this._world.frame() : null;
}

@@ -118,11 +108,18 @@

}
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
executionContextId: this._contextId,
arguments: args.map(convertArgument.bind(this)),
returnByValue: false,
awaitPromise: true,
userGesture: true
}).catch(rewriteError);
let callFunctionOnPromise;
try {
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
executionContextId: this._contextId,
arguments: args.map(convertArgument.bind(this)),
returnByValue: false,
awaitPromise: true,
userGesture: true
});
} catch (err) {
if (err instanceof TypeError && err.message === 'Converting circular structure to JSON')
err.message += ' Are you passing a nested JSHandle?';
throw err;
}
const { exceptionDetails, result: remoteObject } = await callFunctionOnPromise.catch(rewriteError);
if (exceptionDetails)

@@ -184,486 +181,21 @@ throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));

}
}
class JSHandle {
/**
* @param {!ExecutionContext} context
* @param {!Puppeteer.CDPSession} client
* @param {!Protocol.Runtime.RemoteObject} remoteObject
* @param {Puppeteer.ElementHandle} elementHandle
* @return {Promise<Puppeteer.ElementHandle>}
*/
constructor(context, client, remoteObject) {
this._context = context;
this._client = client;
this._remoteObject = remoteObject;
this._disposed = false;
}
/**
* @return {!ExecutionContext}
*/
executionContext() {
return this._context;
}
/**
* @param {string} propertyName
* @return {!Promise<?JSHandle>}
*/
async getProperty(propertyName) {
const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
const result = {__proto__: null};
result[propertyName] = object[propertyName];
return result;
}, this, propertyName);
const properties = await objectHandle.getProperties();
const result = properties.get(propertyName) || null;
await objectHandle.dispose();
return result;
}
/**
* @return {!Promise<!Map<string, !JSHandle>>}
*/
async getProperties() {
const response = await this._client.send('Runtime.getProperties', {
objectId: this._remoteObject.objectId,
ownProperties: true
});
const result = new Map();
for (const property of response.result) {
if (!property.enumerable)
continue;
result.set(property.name, createJSHandle(this._context, property.value));
}
return result;
}
/**
* @return {!Promise<?Object>}
*/
async jsonValue() {
if (this._remoteObject.objectId) {
const response = await this._client.send('Runtime.callFunctionOn', {
functionDeclaration: 'function() { return this; }',
objectId: this._remoteObject.objectId,
returnByValue: true,
awaitPromise: true,
});
return helper.valueFromRemoteObject(response.result);
}
return helper.valueFromRemoteObject(this._remoteObject);
}
/**
* @return {?Puppeteer.ElementHandle}
*/
asElement() {
return null;
}
async dispose() {
if (this._disposed)
return;
this._disposed = true;
await helper.releaseObject(this._client, this._remoteObject);
}
/**
* @override
* @return {string}
*/
toString() {
if (this._remoteObject.objectId) {
const type = this._remoteObject.subtype || this._remoteObject.type;
return 'JSHandle@' + type;
}
return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
}
}
class ElementHandle extends JSHandle {
/**
* @param {!Puppeteer.ExecutionContext} context
* @param {!Puppeteer.CDPSession} client
* @param {!Protocol.Runtime.RemoteObject} remoteObject
* @param {!Puppeteer.Page} page
* @param {!Puppeteer.FrameManager} frameManager
*/
constructor(context, client, remoteObject, page, frameManager) {
super(context, client, remoteObject);
this._client = client;
this._remoteObject = remoteObject;
this._page = page;
this._frameManager = frameManager;
this._disposed = false;
}
/**
* @override
* @return {?ElementHandle}
*/
asElement() {
return this;
}
/**
* @return {!Promise<?Puppeteer.Frame>}
*/
async contentFrame() {
async _adoptElementHandle(elementHandle) {
assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
assert(this._world, 'Cannot adopt handle without DOMWorld');
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: this._remoteObject.objectId
objectId: elementHandle._remoteObject.objectId,
});
if (typeof nodeInfo.node.frameId !== 'string')
return null;
return this._frameManager.frame(nodeInfo.node.frameId);
const {object} = await this._client.send('DOM.resolveNode', {
backendNodeId: nodeInfo.node.backendNodeId,
executionContextId: this._contextId,
});
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
}
async _scrollIntoViewIfNeeded() {
const error = await this.executionContext().evaluate(async(element, pageJavascriptEnabled) => {
if (!element.isConnected)
return 'Node is detached from document';
if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
// force-scroll if page's javascript is disabled.
if (!pageJavascriptEnabled) {
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
return false;
}
const visibleRatio = await new Promise(resolve => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
});
if (visibleRatio !== 1.0)
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
return false;
}, this, this._page._javascriptEnabled);
if (error)
throw new Error(error);
}
/**
* @return {!Promise<!{x: number, y: number}>}
*/
async _clickablePoint() {
const result = await this._client.send('DOM.getContentQuads', {
objectId: this._remoteObject.objectId
}).catch(debugError);
if (!result || !result.quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Filter out quads that have too small area to click into.
const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).filter(quad => computeQuadArea(quad) > 1);
if (!quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Return the middle point of the first quad.
const quad = quads[0];
let x = 0;
let y = 0;
for (const point of quad) {
x += point.x;
y += point.y;
}
return {
x: x / 4,
y: y / 4
};
}
/**
* @return {!Promise<void|Protocol.DOM.getBoxModelReturnValue>}
*/
_getBoxModel() {
return this._client.send('DOM.getBoxModel', {
objectId: this._remoteObject.objectId
}).catch(error => debugError(error));
}
/**
* @param {!Array<number>} quad
* @return {!Array<{x: number, y: number}>}
*/
_fromProtocolQuad(quad) {
return [
{x: quad[0], y: quad[1]},
{x: quad[2], y: quad[3]},
{x: quad[4], y: quad[5]},
{x: quad[6], y: quad[7]}
];
}
async hover() {
await this._scrollIntoViewIfNeeded();
const {x, y} = await this._clickablePoint();
await this._page.mouse.move(x, y);
}
/**
* @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
*/
async click(options) {
await this._scrollIntoViewIfNeeded();
const {x, y} = await this._clickablePoint();
await this._page.mouse.click(x, y, options);
}
/**
* @param {!Array<string>} filePaths
*/
async uploadFile(...filePaths) {
const files = filePaths.map(filePath => path.resolve(filePath));
const objectId = this._remoteObject.objectId;
await this._client.send('DOM.setFileInputFiles', { objectId, files });
}
async tap() {
await this._scrollIntoViewIfNeeded();
const {x, y} = await this._clickablePoint();
await this._page.touchscreen.tap(x, y);
}
async focus() {
await this.executionContext().evaluate(element => element.focus(), this);
}
/**
* @param {string} text
* @param {{delay: (number|undefined)}=} options
*/
async type(text, options) {
await this.focus();
await this._page.keyboard.type(text, options);
}
/**
* @param {string} key
* @param {!{delay?: number, text?: string}=} options
*/
async press(key, options) {
await this.focus();
await this._page.keyboard.press(key, options);
}
/**
* @return {!Promise<?{x: number, y: number, width: number, height: number}>}
*/
async boundingBox() {
const result = await this._getBoxModel();
if (!result)
return null;
const quad = result.model.border;
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
return {x, y, width, height};
}
/**
* @return {!Promise<?BoxModel>}
*/
async boxModel() {
const result = await this._getBoxModel();
if (!result)
return null;
const {content, padding, border, margin, width, height} = result.model;
return {
content: this._fromProtocolQuad(content),
padding: this._fromProtocolQuad(padding),
border: this._fromProtocolQuad(border),
margin: this._fromProtocolQuad(margin),
width,
height
};
}
/**
*
* @param {!Object=} options
* @returns {!Promise<string|!Buffer>}
*/
async screenshot(options = {}) {
let needsViewportReset = false;
let boundingBox = await this.boundingBox();
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
const viewport = this._page.viewport();
if (viewport && (boundingBox.width > viewport.width || boundingBox.height > viewport.height)) {
const newViewport = {
width: Math.max(viewport.width, Math.ceil(boundingBox.width)),
height: Math.max(viewport.height, Math.ceil(boundingBox.height)),
};
await this._page.setViewport(Object.assign({}, viewport, newViewport));
needsViewportReset = true;
}
await this._scrollIntoViewIfNeeded();
boundingBox = await this.boundingBox();
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
const { layoutViewport: { pageX, pageY } } = await this._client.send('Page.getLayoutMetrics');
const clip = Object.assign({}, boundingBox);
clip.x += pageX;
clip.y += pageY;
const imageData = await this._page.screenshot(Object.assign({}, {
clip
}, options));
if (needsViewportReset)
await this._page.setViewport(viewport);
return imageData;
}
/**
* @param {string} selector
* @return {!Promise<?ElementHandle>}
*/
async $(selector) {
const handle = await this.executionContext().evaluateHandle(
(element, selector) => element.querySelector(selector),
this, selector
);
const element = handle.asElement();
if (element)
return element;
await handle.dispose();
return null;
}
/**
* @param {string} selector
* @return {!Promise<!Array<!ElementHandle>>}
*/
async $$(selector) {
const arrayHandle = await this.executionContext().evaluateHandle(
(element, selector) => element.querySelectorAll(selector),
this, selector
);
const properties = await arrayHandle.getProperties();
await arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
}
return result;
}
/**
* @param {string} selector
* @param {Function|String} pageFunction
* @param {!Array<*>} args
* @return {!Promise<(!Object|undefined)>}
*/
async $eval(selector, pageFunction, ...args) {
const elementHandle = await this.$(selector);
if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selector}"`);
const result = await this.executionContext().evaluate(pageFunction, elementHandle, ...args);
await elementHandle.dispose();
return result;
}
/**
* @param {string} selector
* @param {Function|String} pageFunction
* @param {!Array<*>} args
* @return {!Promise<(!Object|undefined)>}
*/
async $$eval(selector, pageFunction, ...args) {
const arrayHandle = await this.executionContext().evaluateHandle(
(element, selector) => Array.from(element.querySelectorAll(selector)),
this, selector
);
const result = await this.executionContext().evaluate(pageFunction, arrayHandle, ...args);
await arrayHandle.dispose();
return result;
}
/**
* @param {string} expression
* @return {!Promise<!Array<!ElementHandle>>}
*/
async $x(expression) {
const arrayHandle = await this.executionContext().evaluateHandle(
(element, expression) => {
const document = element.ownerDocument || element;
const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
const array = [];
let item;
while ((item = iterator.iterateNext()))
array.push(item);
return array;
},
this, expression
);
const properties = await arrayHandle.getProperties();
await arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
}
return result;
}
/**
* @returns {!Promise<boolean>}
*/
isIntersectingViewport() {
return this.executionContext().evaluate(async element => {
const visibleRatio = await new Promise(resolve => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
});
return visibleRatio > 0;
}, this);
}
}
function computeQuadArea(quad) {
// Compute sum of all directed areas of adjacent triangles
// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
let area = 0;
for (let i = 0; i < quad.length; ++i) {
const p1 = quad[i];
const p2 = quad[(i + 1) % quad.length];
area += (p1.x * p2.y - p2.x * p1.y) / 2;
}
return Math.abs(area);
}
/**
* @typedef {Object} BoxModel
* @property {!Array<!{x: number, y: number}>} content
* @property {!Array<!{x: number, y: number}>} padding
* @property {!Array<!{x: number, y: number}>} border
* @property {!Array<!{x: number, y: number}>} margin
* @property {number} width
* @property {number} height
*/
helper.tracePublicAPI(ElementHandle);
helper.tracePublicAPI(JSHandle);
helper.tracePublicAPI(ExecutionContext);
module.exports = {ExecutionContext, JSHandle, ElementHandle, createJSHandle, EVALUATION_SCRIPT_URL};
module.exports = {ExecutionContext, EVALUATION_SCRIPT_URL};

@@ -17,11 +17,10 @@ /**

const fs = require('fs');
const EventEmitter = require('events');
const {helper, assert} = require('./helper');
const {ExecutionContext} = require('./ExecutionContext');
const {TimeoutError} = require('./Errors');
const {NetworkManager} = require('./NetworkManager');
const {CDPSession} = require('./Connection');
const {Events} = require('./Events');
const {ExecutionContext, EVALUATION_SCRIPT_URL} = require('./ExecutionContext');
const {LifecycleWatcher} = require('./LifecycleWatcher');
const {DOMWorld} = require('./DOMWorld');
const readFileAsync = helper.promisify(fs.readFile);
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';

@@ -34,4 +33,5 @@ class FrameManager extends EventEmitter {

* @param {!Puppeteer.NetworkManager} networkManager
* @param {!Puppeteer.TimeoutSettings} timeoutSettings
*/
constructor(client, frameTree, page, networkManager) {
constructor(client, frameTree, page, networkManager, timeoutSettings) {
super();

@@ -41,3 +41,3 @@ this._client = client;

this._networkManager = networkManager;
this._defaultNavigationTimeout = 30000;
this._timeoutSettings = timeoutSettings;
/** @type {!Map<string, !Frame>} */

@@ -47,2 +47,4 @@ this._frames = new Map();

this._contextIdToContext = new Map();
/** @type {!Set<string>} */
this._isolatedWorlds = new Set();

@@ -58,3 +60,2 @@ this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));

this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event));
this._handleFrameTree(frameTree);

@@ -64,9 +65,2 @@ }

/**
* @param {number} timeout
*/
setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
}
/**
* @param {!Puppeteer.Frame} frame

@@ -82,3 +76,3 @@ * @param {string} url

waitUntil = ['load'],
timeout = this._defaultNavigationTimeout,
timeout = this._timeoutSettings.navigationTimeout(),
} = options;

@@ -130,3 +124,3 @@

waitUntil = ['load'],
timeout = this._defaultNavigationTimeout,
timeout = this._timeoutSettings.navigationTimeout(),
} = options;

@@ -153,3 +147,3 @@ const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);

frame._onLifecycleEvent(event.loaderId, event.name);
this.emit(FrameManager.Events.LifecycleEvent, frame);
this.emit(Events.FrameManager.LifecycleEvent, frame);
}

@@ -165,3 +159,3 @@

frame._onLoadingStopped();
this.emit(FrameManager.Events.LifecycleEvent, frame);
this.emit(Events.FrameManager.LifecycleEvent, frame);
}

@@ -223,3 +217,3 @@

this._frames.set(frame._id, frame);
this.emit(FrameManager.Events.FrameAttached, frame);
this.emit(Events.FrameManager.FrameAttached, frame);
}

@@ -258,6 +252,28 @@

this.emit(FrameManager.Events.FrameNavigated, frame);
this.emit(Events.FrameManager.FrameNavigated, frame);
}
async ensureSecondaryDOMWorld() {
await this._ensureIsolatedWorld(UTILITY_WORLD_NAME);
}
/**
* @param {string} name
*/
async _ensureIsolatedWorld(name) {
if (this._isolatedWorlds.has(name))
return;
this._isolatedWorlds.add(name);
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
source: `//# sourceURL=${EVALUATION_SCRIPT_URL}`,
worldName: name,
}),
await Promise.all(this.frames().map(frame => this._client.send('Page.createIsolatedWorld', {
frameId: frame._id,
grantUniveralAccess: true,
worldName: name,
})));
}
/**
* @param {string} frameId

@@ -271,4 +287,4 @@ * @param {string} url

frame._navigatedWithinDocument(url);
this.emit(FrameManager.Events.FrameNavigatedWithinDocument, frame);
this.emit(FrameManager.Events.FrameNavigated, frame);
this.emit(Events.FrameManager.FrameNavigatedWithinDocument, frame);
this.emit(Events.FrameManager.FrameNavigated, frame);
}

@@ -288,7 +304,16 @@

const frame = this._frames.get(frameId) || null;
let world = null;
if (frame) {
if (contextPayload.auxData && !!contextPayload.auxData['isDefault'])
world = frame._mainWorld;
else if (contextPayload.name === UTILITY_WORLD_NAME)
world = frame._secondaryWorld;
}
if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated')
this._isolatedWorlds.add(contextPayload.name);
/** @type {!ExecutionContext} */
const context = new ExecutionContext(this._client, contextPayload, frame);
const context = new ExecutionContext(this._client, contextPayload, world);
if (world)
world._setContext(context);
this._contextIdToContext.set(contextPayload.id, context);
if (frame)
frame._addExecutionContext(context);
}

@@ -304,4 +329,4 @@

this._contextIdToContext.delete(executionContextId);
if (context.frame())
context.frame()._removeExecutionContext(context);
if (context._world)
context._world._setContext(null);
}

@@ -311,4 +336,4 @@

for (const context of this._contextIdToContext.values()) {
if (context.frame())
context.frame()._removeExecutionContext(context);
if (context._world)
context._world._setContext(null);
}

@@ -336,17 +361,6 @@ this._contextIdToContext.clear();

this._frames.delete(frame._id);
this.emit(FrameManager.Events.FrameDetached, frame);
this.emit(Events.FrameManager.FrameDetached, frame);
}
}
/** @enum {string} */
FrameManager.Events = {
FrameAttached: 'frameattached',
FrameNavigated: 'framenavigated',
FrameDetached: 'framedetached',
LifecycleEvent: 'lifecycleevent',
FrameNavigatedWithinDocument: 'framenavigatedwithindocument',
ExecutionContextCreated: 'executioncontextcreated',
ExecutionContextDestroyed: 'executioncontextdestroyed',
};
/**

@@ -370,15 +384,7 @@ * @unrestricted

/** @type {?Promise<!Puppeteer.ElementHandle>} */
this._documentPromise = null;
/** @type {!Promise<!ExecutionContext>} */
this._contextPromise;
this._contextResolveCallback = null;
this._setDefaultContext(null);
/** @type {!Set<!WaitTask>} */
this._waitTasks = new Set();
this._loaderId = '';
/** @type {!Set<string>} */
this._lifecycleEvents = new Set();
this._mainWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings);
this._secondaryWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings);

@@ -392,35 +398,2 @@ /** @type {!Set<!Frame>} */

/**
* @param {!ExecutionContext} context
*/
_addExecutionContext(context) {
if (context._isDefault)
this._setDefaultContext(context);
}
/**
* @param {!ExecutionContext} context
*/
_removeExecutionContext(context) {
if (context._isDefault)
this._setDefaultContext(null);
}
/**
* @param {?ExecutionContext} context
*/
_setDefaultContext(context) {
if (context) {
this._contextResolveCallback.call(null, context);
this._contextResolveCallback = null;
for (const waitTask of this._waitTasks)
waitTask.rerun();
} else {
this._documentPromise = null;
this._contextPromise = new Promise(fulfill => {
this._contextResolveCallback = fulfill;
});
}
}
/**
* @param {string} url

@@ -446,7 +419,7 @@ * @param {!{referer?: string, timeout?: number, waitUntil?: string|!Array<string>}=} options

executionContext() {
return this._contextPromise;
return this._mainWorld.executionContext();
}
/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -456,4 +429,3 @@ * @return {!Promise<!Puppeteer.JSHandle>}

async evaluateHandle(pageFunction, ...args) {
const context = await this._contextPromise;
return context.evaluateHandle(pageFunction, ...args);
return this._mainWorld.evaluateHandle(pageFunction, ...args);
}

@@ -467,4 +439,3 @@

async evaluate(pageFunction, ...args) {
const context = await this._contextPromise;
return context.evaluate(pageFunction, ...args);
return this._mainWorld.evaluate(pageFunction, ...args);
}

@@ -477,21 +448,6 @@

async $(selector) {
const document = await this._document();
const value = await document.$(selector);
return value;
return this._mainWorld.$(selector);
}
/**
* @return {!Promise<!Puppeteer.ElementHandle>}
*/
async _document() {
if (this._documentPromise)
return this._documentPromise;
this._documentPromise = this._contextPromise.then(async context => {
const document = await context.evaluateHandle('document');
return document.asElement();
});
return this._documentPromise;
}
/**
* @param {string} expression

@@ -501,5 +457,3 @@ * @return {!Promise<!Array<!Puppeteer.ElementHandle>>}

async $x(expression) {
const document = await this._document();
const value = await document.$x(expression);
return value;
return this._mainWorld.$x(expression);
}

@@ -514,4 +468,3 @@

async $eval(selector, pageFunction, ...args) {
const document = await this._document();
return document.$eval(selector, pageFunction, ...args);
return this._mainWorld.$eval(selector, pageFunction, ...args);
}

@@ -526,5 +479,3 @@

async $$eval(selector, pageFunction, ...args) {
const document = await this._document();
const value = await document.$$eval(selector, pageFunction, ...args);
return value;
return this._mainWorld.$$eval(selector, pageFunction, ...args);
}

@@ -537,5 +488,3 @@

async $$(selector) {
const document = await this._document();
const value = await document.$$(selector);
return value;
return this._mainWorld.$$(selector);
}

@@ -547,10 +496,3 @@

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;
});
return this._secondaryWorld.content();
}

@@ -563,21 +505,3 @@

async setContent(html, options = {}) {
const {
waitUntil = ['load'],
timeout = 30000,
} = options;
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
// lifecycle event. @see https://crrev.com/608658
await this.evaluate(html => {
document.open();
document.write(html);
document.close();
}, html);
const watcher = new LifecycleWatcher(this._frameManager, this, waitUntil, timeout);
const error = await Promise.race([
watcher.timeoutOrTerminationPromise(),
watcher.lifecyclePromise(),
]);
watcher.dispose();
if (error)
throw error;
return this._secondaryWorld.setContent(html, options);
}

@@ -625,66 +549,3 @@

async addScriptTag(options) {
const {
url = null,
path = null,
content = null,
type = ''
} = options;
if (url !== null) {
try {
const context = await this._contextPromise;
return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
} catch (error) {
throw new Error(`Loading script from ${url} failed`);
}
}
if (path !== null) {
let contents = await readFileAsync(path, 'utf8');
contents += '//# sourceURL=' + path.replace(/\n/g, '');
const context = await this._contextPromise;
return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
}
if (content !== null) {
const context = await this._contextPromise;
return (await context.evaluateHandle(addScriptContent, content, type)).asElement();
}
throw new Error('Provide an object with a `url`, `path` or `content` property');
/**
* @param {string} url
* @param {string} type
* @return {!Promise<!HTMLElement>}
*/
async function addScriptUrl(url, type) {
const script = document.createElement('script');
script.src = url;
if (type)
script.type = type;
const promise = new Promise((res, rej) => {
script.onload = res;
script.onerror = rej;
});
document.head.appendChild(script);
await promise;
return script;
}
/**
* @param {string} content
* @param {string} type
* @return {!HTMLElement}
*/
function addScriptContent(content, type = 'text/javascript') {
const script = document.createElement('script');
script.type = type;
script.text = content;
let error = null;
script.onerror = e => error = e;
document.head.appendChild(script);
if (error)
throw error;
return script;
}
return this._mainWorld.addScriptTag(options);
}

@@ -697,63 +558,3 @@

async addStyleTag(options) {
const {
url = null,
path = null,
content = null
} = options;
if (url !== null) {
try {
const context = await this._contextPromise;
return (await context.evaluateHandle(addStyleUrl, url)).asElement();
} catch (error) {
throw new Error(`Loading style from ${url} failed`);
}
}
if (path !== null) {
let contents = await readFileAsync(path, 'utf8');
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
const context = await this._contextPromise;
return (await context.evaluateHandle(addStyleContent, contents)).asElement();
}
if (content !== null) {
const context = await this._contextPromise;
return (await context.evaluateHandle(addStyleContent, content)).asElement();
}
throw new Error('Provide an object with a `url`, `path` or `content` property');
/**
* @param {string} url
* @return {!Promise<!HTMLElement>}
*/
async function addStyleUrl(url) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
const promise = new Promise((res, rej) => {
link.onload = res;
link.onerror = rej;
});
document.head.appendChild(link);
await promise;
return link;
}
/**
* @param {string} content
* @return {!Promise<!HTMLElement>}
*/
async function addStyleContent(content) {
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(content));
const promise = new Promise((res, rej) => {
style.onload = res;
style.onerror = rej;
});
document.head.appendChild(style);
await promise;
return style;
}
return this._mainWorld.addStyleTag(options);
}

@@ -766,6 +567,3 @@

async click(selector, options) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);
await handle.click(options);
await handle.dispose();
return this._secondaryWorld.click(selector, options);
}

@@ -777,6 +575,3 @@

async focus(selector) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);
await handle.focus();
await handle.dispose();
return this._secondaryWorld.focus(selector);
}

@@ -788,6 +583,3 @@

async hover(selector) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);
await handle.hover();
await handle.dispose();
return this._secondaryWorld.hover(selector);
}

@@ -801,19 +593,3 @@

select(selector, ...values){
for (const value of values)
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
return this.$eval(selector, (element, values) => {
if (element.nodeName.toLowerCase() !== 'select')
throw new Error('Element is not a <select> element.');
const options = Array.from(element.options);
element.value = undefined;
for (const option of options) {
option.selected = values.includes(option.value);
if (option.selected && !element.multiple)
break;
}
element.dispatchEvent(new Event('input', { 'bubbles': true }));
element.dispatchEvent(new Event('change', { 'bubbles': true }));
return options.filter(option => option.selected).map(option => option.value);
}, values);
return this._secondaryWorld.select(selector, ...values);
}

@@ -825,6 +601,3 @@

async tap(selector) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);
await handle.tap();
await handle.dispose();
return this._secondaryWorld.tap(selector);
}

@@ -838,6 +611,3 @@

async type(selector, text, options) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);
await handle.type(text, options);
await handle.dispose();
return this._mainWorld.type(selector, text, options);
}

@@ -849,3 +619,3 @@

* @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>}
* @return {!Promise<?Puppeteer.JSHandle>}
*/

@@ -871,6 +641,12 @@ waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {

* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForSelector(selector, options) {
return this._waitForSelectorOrXPath(selector, false, options);
async waitForSelector(selector, options) {
const handle = await this._secondaryWorld.waitForSelector(selector, options);
if (!handle)
return null;
const mainExecutionContext = await this._mainWorld.executionContext();
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
}

@@ -881,6 +657,12 @@

* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForXPath(xpath, options) {
return this._waitForSelectorOrXPath(xpath, true, options);
async waitForXPath(xpath, options) {
const handle = await this._secondaryWorld.waitForXPath(xpath, options);
if (!handle)
return null;
const mainExecutionContext = await this._mainWorld.executionContext();
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
}

@@ -894,7 +676,3 @@

waitForFunction(pageFunction, options = {}, ...args) {
const {
polling = 'raf',
timeout = 30000
} = options;
return new WaitTask(this, pageFunction, 'function', polling, timeout, ...args).promise;
return this._mainWorld.waitForFunction(pageFunction, options, ...args);
}

@@ -906,54 +684,6 @@

async title() {
return this.evaluate(() => document.title);
return this._secondaryWorld.title();
}
/**
* @param {string} selectorOrXPath
* @param {boolean} isXPath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
*/
_waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
const {
visible: waitForVisible = false,
hidden: waitForHidden = false,
timeout = 30000,
} = options;
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
return new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden).promise;
/**
* @param {string} selectorOrXPath
* @param {boolean} isXPath
* @param {boolean} waitForVisible
* @param {boolean} waitForHidden
* @return {?Node|boolean}
*/
function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
const node = isXPath
? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
: document.querySelector(selectorOrXPath);
if (!node)
return waitForHidden;
if (!waitForVisible && !waitForHidden)
return node;
const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
const style = window.getComputedStyle(element);
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
return success ? node : null;
/**
* @return {boolean}
*/
function hasVisibleBoundingBox() {
const rect = element.getBoundingClientRect();
return !!(rect.top || rect.bottom || rect.width || rect.height);
}
}
}
/**
* @param {!Protocol.Page.Frame} framePayload

@@ -993,5 +723,5 @@ */

_detach() {
for (const waitTask of this._waitTasks)
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
this._detached = true;
this._mainWorld._detach();
this._secondaryWorld._detach();
if (this._parentFrame)

@@ -1002,192 +732,3 @@ this._parentFrame._childFrames.delete(this);

}
helper.tracePublicAPI(Frame);
class WaitTask {
/**
* @param {!Frame} frame
* @param {Function|string} predicateBody
* @param {string|number} polling
* @param {number} timeout
* @param {!Array<*>} args
*/
constructor(frame, predicateBody, title, polling, timeout, ...args) {
if (helper.isString(polling))
assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling);
else if (helper.isNumber(polling))
assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling);
else
throw new Error('Unknown polling options: ' + polling);
this._frame = frame;
this._polling = polling;
this._timeout = timeout;
this._predicateBody = helper.isString(predicateBody) ? 'return ' + predicateBody : 'return (' + predicateBody + ')(...args)';
this._args = args;
this._runCount = 0;
frame._waitTasks.add(this);
this.promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
// Since page navigation requires us to re-install the pageScript, we should track
// timeout on our end.
if (timeout) {
const timeoutError = new TimeoutError(`waiting for ${title} failed: timeout ${timeout}ms exceeded`);
this._timeoutTimer = setTimeout(() => this.terminate(timeoutError), timeout);
}
this.rerun();
}
/**
* @param {!Error} error
*/
terminate(error) {
this._terminated = true;
this._reject(error);
this._cleanup();
}
async rerun() {
const runCount = ++this._runCount;
/** @type {?Puppeteer.JSHandle} */
let success = null;
let error = null;
try {
success = await (await this._frame.executionContext()).evaluateHandle(waitForPredicatePageFunction, this._predicateBody, this._polling, this._timeout, ...this._args);
} catch (e) {
error = e;
}
if (this._terminated || runCount !== this._runCount) {
if (success)
await success.dispose();
return;
}
// Ignore timeouts in pageScript - we track timeouts ourselves.
// If the frame's execution context has already changed, `frame.evaluate` will
// throw an error - ignore this predicate run altogether.
if (!error && await this._frame.evaluate(s => !s, success).catch(e => true)) {
await success.dispose();
return;
}
// When the page is navigated, the promise is rejected.
// We will try again in the new execution context.
if (error && error.message.includes('Execution context was destroyed'))
return;
// We could have tried to evaluate in a context which was already
// destroyed.
if (error && error.message.includes('Cannot find context with specified id'))
return;
if (error)
this._reject(error);
else
this._resolve(success);
this._cleanup();
}
_cleanup() {
clearTimeout(this._timeoutTimer);
this._frame._waitTasks.delete(this);
this._runningTask = null;
}
}
/**
* @param {string} predicateBody
* @param {string} polling
* @param {number} timeout
* @return {!Promise<*>}
*/
async function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {
const predicate = new Function('...args', predicateBody);
let timedOut = false;
if (timeout)
setTimeout(() => timedOut = true, timeout);
if (polling === 'raf')
return await pollRaf();
if (polling === 'mutation')
return await pollMutation();
if (typeof polling === 'number')
return await pollInterval(polling);
/**
* @return {!Promise<*>}
*/
function pollMutation() {
const success = predicate.apply(null, args);
if (success)
return Promise.resolve(success);
let fulfill;
const result = new Promise(x => fulfill = x);
const observer = new MutationObserver(mutations => {
if (timedOut) {
observer.disconnect();
fulfill();
}
const success = predicate.apply(null, args);
if (success) {
observer.disconnect();
fulfill(success);
}
});
observer.observe(document, {
childList: true,
subtree: true,
attributes: true
});
return result;
}
/**
* @return {!Promise<*>}
*/
function pollRaf() {
let fulfill;
const result = new Promise(x => fulfill = x);
onRaf();
return result;
function onRaf() {
if (timedOut) {
fulfill();
return;
}
const success = predicate.apply(null, args);
if (success)
fulfill(success);
else
requestAnimationFrame(onRaf);
}
}
/**
* @param {number} pollInterval
* @return {!Promise<*>}
*/
function pollInterval(pollInterval) {
let fulfill;
const result = new Promise(x => fulfill = x);
onTimeout();
return result;
function onTimeout() {
if (timedOut) {
fulfill();
return;
}
const success = predicate.apply(null, args);
if (success)
fulfill(success);
else
setTimeout(onTimeout, pollInterval);
}
}
}
function assertNoLegacyNavigationOptions(options) {

@@ -1199,179 +740,2 @@ assert(options['networkIdleTimeout'] === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');

class LifecycleWatcher {
/**
* @param {!FrameManager} frameManager
* @param {!Puppeteer.Frame} frame
* @param {string|!Array<string>} waitUntil
* @param {number} timeout
*/
constructor(frameManager, frame, waitUntil, timeout) {
if (Array.isArray(waitUntil))
waitUntil = waitUntil.slice();
else if (typeof waitUntil === 'string')
waitUntil = [waitUntil];
this._expectedLifecycle = waitUntil.map(value => {
const protocolEvent = puppeteerToProtocolLifecycle[value];
assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
return protocolEvent;
});
this._frameManager = frameManager;
this._networkManager = frameManager._networkManager;
this._frame = frame;
this._initialLoaderId = frame._loaderId;
this._timeout = timeout;
/** @type {?Puppeteer.Request} */
this._navigationRequest = null;
this._eventListeners = [
helper.addEventListener(frameManager._client, CDPSession.Events.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))),
helper.addEventListener(this._frameManager, FrameManager.Events.LifecycleEvent, this._checkLifecycleComplete.bind(this)),
helper.addEventListener(this._frameManager, FrameManager.Events.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)),
helper.addEventListener(this._frameManager, FrameManager.Events.FrameDetached, this._onFrameDetached.bind(this)),
helper.addEventListener(this._networkManager, NetworkManager.Events.Request, this._onRequest.bind(this)),
];
this._sameDocumentNavigationPromise = new Promise(fulfill => {
this._sameDocumentNavigationCompleteCallback = fulfill;
});
this._lifecyclePromise = new Promise(fulfill => {
this._lifecycleCallback = fulfill;
});
this._newDocumentNavigationPromise = new Promise(fulfill => {
this._newDocumentNavigationCompleteCallback = fulfill;
});
this._timeoutPromise = this._createTimeoutPromise();
this._terminationPromise = new Promise(fulfill => {
this._terminationCallback = fulfill;
});
}
/**
* @param {!Puppeteer.Request} request
*/
_onRequest(request) {
if (request.frame() !== this._frame || !request.isNavigationRequest())
return;
this._navigationRequest = request;
}
/**
* @param {!Puppeteer.Frame} frame
*/
_onFrameDetached(frame) {
if (this._frame === frame) {
this._terminationCallback.call(null, new Error('Navigating frame was detached'));
return;
}
this._checkLifecycleComplete();
}
/**
* @return {?Puppeteer.Response}
*/
navigationResponse() {
return this._navigationRequest ? this._navigationRequest.response() : null;
}
/**
* @param {!Error} error
*/
_terminate(error) {
this._terminationCallback.call(null, error);
}
/**
* @return {!Promise<?Error>}
*/
sameDocumentNavigationPromise() {
return this._sameDocumentNavigationPromise;
}
/**
* @return {!Promise<?Error>}
*/
newDocumentNavigationPromise() {
return this._newDocumentNavigationPromise;
}
/**
* @return {!Promise}
*/
lifecyclePromise() {
return this._lifecyclePromise;
}
/**
* @return {!Promise<?Error>}
*/
timeoutOrTerminationPromise() {
return Promise.race([this._timeoutPromise, this._terminationPromise]);
}
/**
* @return {!Promise<?Error>}
*/
_createTimeoutPromise() {
if (!this._timeout)
return new Promise(() => {});
const errorMessage = 'Navigation Timeout Exceeded: ' + this._timeout + 'ms exceeded';
return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
.then(() => new TimeoutError(errorMessage));
}
/**
* @param {!Puppeteer.Frame} frame
*/
_navigatedWithinDocument(frame) {
if (frame !== this._frame)
return;
this._hasSameDocumentNavigation = true;
this._checkLifecycleComplete();
}
_checkLifecycleComplete() {
// We expect navigation to commit.
if (!checkLifecycle(this._frame, this._expectedLifecycle))
return;
this._lifecycleCallback();
if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
return;
if (this._hasSameDocumentNavigation)
this._sameDocumentNavigationCompleteCallback();
if (this._frame._loaderId !== this._initialLoaderId)
this._newDocumentNavigationCompleteCallback();
/**
* @param {!Puppeteer.Frame} frame
* @param {!Array<string>} expectedLifecycle
* @return {boolean}
*/
function checkLifecycle(frame, expectedLifecycle) {
for (const event of expectedLifecycle) {
if (!frame._lifecycleEvents.has(event))
return false;
}
for (const child of frame.childFrames()) {
if (!checkLifecycle(child, expectedLifecycle))
return false;
}
return true;
}
}
dispose() {
helper.removeEventListeners(this._eventListeners);
clearTimeout(this._maximumTimer);
}
}
const puppeteerToProtocolLifecycle = {
'load': 'load',
'domcontentloaded': 'DOMContentLoaded',
'networkidle0': 'networkIdle',
'networkidle2': 'networkAlmostIdle',
};
module.exports = {FrameManager, Frame};

@@ -19,38 +19,3 @@ /**

const debugError = require('debug')(`puppeteer:error`);
/** @type {?Map<string, boolean>} */
let apiCoverage = null;
/**
* @param {!Object} classType
* @param {string=} publicName
*/
function traceAPICoverage(classType, publicName) {
if (!apiCoverage)
return;
let className = publicName || classType.prototype.constructor.name;
className = className.substring(0, 1).toLowerCase() + className.substring(1);
for (const methodName of Reflect.ownKeys(classType.prototype)) {
const method = Reflect.get(classType.prototype, methodName);
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
continue;
apiCoverage.set(`${className}.${methodName}`, false);
Reflect.set(classType.prototype, methodName, function(...args) {
apiCoverage.set(`${className}.${methodName}`, true);
return method.call(this, ...args);
});
}
if (classType.Events) {
for (const event of Object.values(classType.Events))
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false);
const method = Reflect.get(classType.prototype, 'emit');
Reflect.set(classType.prototype, 'emit', function(event, ...args) {
if (this.listenerCount(event))
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
return method.call(this, event, ...args);
});
}
}
class Helper {

@@ -137,5 +102,4 @@ /**

* @param {!Object} classType
* @param {string=} publicName
*/
static tracePublicAPI(classType, publicName) {
static installAsyncStackHooks(classType) {
for (const methodName of Reflect.ownKeys(classType.prototype)) {

@@ -156,4 +120,2 @@ const method = Reflect.get(classType.prototype, methodName);

}
traceAPICoverage(classType, publicName);
}

@@ -164,3 +126,3 @@

* @param {(string|symbol)} eventName
* @param {function(?)} handler
* @param {function(?):void} handler
* @return {{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?)}}

@@ -174,3 +136,3 @@ */

/**
* @param {!Array<{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?)}>} listeners
* @param {!Array<{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?):void}>} listeners
*/

@@ -184,13 +146,2 @@ static removeEventListeners(listeners) {

/**
* @return {?Map<string, boolean>}
*/
static publicAPICoverage() {
return apiCoverage;
}
static recordPublicAPICoverage() {
apiCoverage = new Map();
}
/**
* @param {!Object} obj

@@ -229,3 +180,3 @@ * @return {boolean}

* @param {!NodeJS.EventEmitter} emitter
* @param {string} eventName
* @param {(string|symbol)} eventName
* @param {function} predicate

@@ -232,0 +183,0 @@ * @return {!Promise}

@@ -17,3 +17,3 @@ /**

const {helper, assert} = require('./helper');
const {assert} = require('./helper');
const keyDefinitions = require('./USKeyboardLayout');

@@ -306,4 +306,1 @@

module.exports = { Keyboard, Mouse, Touchscreen};
helper.tracePublicAPI(Keyboard);
helper.tracePublicAPI(Mouse);
helper.tracePublicAPI(Touchscreen);

@@ -18,2 +18,4 @@ /**

const path = require('path');
const http = require('http');
const URL = require('url');
const removeFolder = require('rimraf');

@@ -26,3 +28,3 @@ const childProcess = require('child_process');

const fs = require('fs');
const {helper, debugError} = require('./helper');
const {helper, assert, debugError} = require('./helper');
const {TimeoutError} = require('./Errors');

@@ -39,2 +41,3 @@ const WebSocketTransport = require('./WebSocketTransport');

'--disable-background-networking',
'--enable-features=NetworkService,NetworkServiceInProcess',
'--disable-background-timer-throttling',

@@ -48,3 +51,3 @@ '--disable-backgrounding-occluded-windows',

// TODO: Support OOOPIF. @see https://github.com/GoogleChrome/puppeteer/issues/2548
'--disable-features=site-per-process',
'--disable-features=site-per-process,TranslateUI',
'--disable-hang-monitor',

@@ -56,3 +59,3 @@ '--disable-ipc-flooding-protection',

'--disable-sync',
'--disable-translate',
'--force-color-profile=srgb',
'--metrics-recording-only',

@@ -282,3 +285,3 @@ '--no-first-run',

/**
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint: string, transport?: !Puppeteer.ConnectionTransport})} options
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
* @return {!Promise<!Browser>}

@@ -289,8 +292,23 @@ */

browserWSEndpoint,
browserURL,
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
transport = await WebSocketTransport.create(browserWSEndpoint),
transport,
slowMo = 0,
} = options;
const connection = new Connection(browserWSEndpoint, transport, slowMo);
assert(Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect');
let connection = null;
if (transport) {
connection = new Connection('', transport, slowMo);
} else if (browserWSEndpoint) {
const connectionTransport = await WebSocketTransport.create(browserWSEndpoint);
connection = new Connection(browserWSEndpoint, connectionTransport, slowMo);
} else if (browserURL) {
const connectionURL = await getWSEndpoint(browserURL);
const connectionTransport = await WebSocketTransport.create(connectionURL);
connection = new Connection(connectionURL, connectionTransport, slowMo);
}
const {browserContextIds} = await connection.send('Target.getBrowserContexts');

@@ -384,2 +402,34 @@ return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError));

/**
* @param {string} browserURL
* @return {!Promise<string>}
*/
function getWSEndpoint(browserURL) {
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
const endpointURL = URL.resolve(browserURL, '/json/version');
const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' });
const request = http.request(requestOptions, res => {
let data = '';
if (res.statusCode !== 200) {
// Consume response data to free up memory.
res.resume();
reject(new Error('HTTP ' + res.statusCode));
return;
}
res.setEncoding('utf8');
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(JSON.parse(data).webSocketDebuggerUrl));
});
request.on('error', reject);
request.end();
return promise.catch(e => {
e.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + e.message;
throw e;
});
}
/**
* @typedef {Object} Launcher.ChromeArgOptions

@@ -386,0 +436,0 @@ * @property {boolean=} headless

@@ -18,2 +18,3 @@ /**

const {helper, assert, debugError} = require('./helper');
const {Events} = require('./Events');
const Multimap = require('./Multimap');

@@ -139,3 +140,4 @@

_onRequestWillBeSent(event) {
if (this._protocolRequestInterceptionEnabled) {
// Request interception doesn't happen for data URLs with Network Service.
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
const requestHash = generateRequestHash(event.request);

@@ -210,3 +212,3 @@ const interceptionId = this._requestHashToInterceptionIds.firstValue(requestHash);

this._requestIdToRequest.set(event.requestId, request);
this.emit(NetworkManager.Events.Request, request);
this.emit(Events.NetworkManager.Request, request);
}

@@ -235,4 +237,4 @@

this._attemptedAuthentications.delete(request._interceptionId);
this.emit(NetworkManager.Events.Response, response);
this.emit(NetworkManager.Events.RequestFinished, request);
this.emit(Events.NetworkManager.Response, response);
this.emit(Events.NetworkManager.RequestFinished, request);
}

@@ -250,3 +252,3 @@

request._response = response;
this.emit(NetworkManager.Events.Response, response);
this.emit(Events.NetworkManager.Response, response);
}

@@ -270,3 +272,3 @@

this._attemptedAuthentications.delete(request._interceptionId);
this.emit(NetworkManager.Events.RequestFinished, request);
this.emit(Events.NetworkManager.RequestFinished, request);
}

@@ -289,3 +291,3 @@

this._attemptedAuthentications.delete(request._interceptionId);
this.emit(NetworkManager.Events.RequestFailed, request);
this.emit(Events.NetworkManager.RequestFailed, request);
}

@@ -404,2 +406,5 @@ }

async continue(overrides = {}) {
// Request interception is not supported for data: urls.
if (this._url.startsWith('data:'))
return;
assert(this._allowInterception, 'Request Interception is not enabled!');

@@ -477,2 +482,5 @@ assert(!this._interceptionHandled, 'Request is already handled!');

async abort(errorCode = 'failed') {
// Request interception is not supported for data: urls.
if (this._url.startsWith('data:'))
return;
const errorReason = errorReasons[errorCode];

@@ -511,4 +519,2 @@ assert(errorReason, 'Unknown error code: ' + errorCode);

helper.tracePublicAPI(Request);
class Response {

@@ -654,4 +660,5 @@ /**

}
helper.tracePublicAPI(Response);
const IGNORED_HEADERS = new Set(['accept', 'referer', 'x-devtools-emulate-network-conditions-client-id', 'cookie', 'origin', 'content-type', 'intervention']);
/**

@@ -683,3 +690,3 @@ * @param {!Protocol.Network.Request} request

header = header.toLowerCase();
if (header === 'accept' || header === 'referer' || header === 'x-devtools-emulate-network-conditions-client-id' || header === 'cookie')
if (IGNORED_HEADERS.has(header))
continue;

@@ -740,9 +747,2 @@ hash.headers[header] = headerValue;

NetworkManager.Events = {
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished',
};
const statusTexts = {

@@ -811,2 +811,2 @@ '100': 'Continue',

module.exports = {Request, Response, NetworkManager};
module.exports = {Request, Response, NetworkManager, SecurityDetails};

@@ -20,2 +20,4 @@ /**

const mime = require('mime');
const {Events} = require('./Events');
const {Connection} = require('./Connection');
const {NetworkManager} = require('./NetworkManager');

@@ -30,4 +32,5 @@ const {Dialog} = require('./Dialog');

const {Worker} = require('./Worker');
const {createJSHandle} = require('./ExecutionContext');
const {createJSHandle} = require('./JSHandle');
const {Accessibility} = require('./Accessibility');
const {TimeoutSettings} = require('./TimeoutSettings');
const writeFileAsync = helper.promisify(fs.writeFile);

@@ -49,8 +52,7 @@

const page = new Page(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue);
await Promise.all([
client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false}),
client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}),
client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
client.send('Network.enable', {}),
client.send('Runtime.enable', {}),
client.send('Runtime.enable', {}).then(() => page._frameManager.ensureSecondaryDOMWorld()),
client.send('Security.enable', {}),

@@ -83,2 +85,3 @@ client.send('Performance.enable', {}),

this._mouse = new Mouse(client, this._keyboard);
this._timeoutSettings = new TimeoutSettings();
this._touchscreen = new Touchscreen(client, this._keyboard);

@@ -88,3 +91,3 @@ this._accessibility = new Accessibility(client);

/** @type {!FrameManager} */
this._frameManager = new FrameManager(client, frameTree, this, this._networkManager);
this._frameManager = new FrameManager(client, frameTree, this, this._networkManager, this._timeoutSettings);
this._networkManager.setFrameManager(this._frameManager);

@@ -113,7 +116,6 @@ this._emulationManager = new EmulationManager(client);

}
const session = client._createSession(event.targetInfo.type, event.sessionId);
const session = Connection.fromSession(client).session(event.sessionId);
const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
this._workers.set(event.sessionId, worker);
this.emit(Page.Events.WorkerCreated, worker);
this.emit(Events.Page.WorkerCreated, worker);
});

@@ -124,17 +126,17 @@ client.on('Target.detachedFromTarget', event => {

return;
this.emit(Page.Events.WorkerDestroyed, worker);
this.emit(Events.Page.WorkerDestroyed, worker);
this._workers.delete(event.sessionId);
});
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));
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(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));
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));
client.on('Page.domContentEventFired', event => this.emit(Page.Events.DOMContentLoaded));
client.on('Page.loadEventFired', event => this.emit(Page.Events.Load));
client.on('Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded));
client.on('Page.loadEventFired', event => this.emit(Events.Page.Load));
client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));

@@ -149,3 +151,3 @@ client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));

this._target._isClosedPromise.then(() => {
this.emit(Page.Events.Close);
this.emit(Events.Page.Close);
this._closed = true;

@@ -183,2 +185,9 @@ });

/**
* @return {!Puppeteer.BrowserContext}
*/
browserContext() {
return this._target.browserContext();
}
_onTargetCrashed() {

@@ -192,7 +201,7 @@ this.emit('error', new Error('Page crashed!'));

_onLogEntryAdded(event) {
const {level, text, args, source} = event.entry;
const {level, text, args, source, url, lineNumber} = event.entry;
if (args)
args.map(arg => helper.releaseObject(this._client, arg));
if (source !== 'worker')
this.emit(Page.Events.Console, new ConsoleMessage(level, text));
this.emit(Events.Page.Console, new ConsoleMessage(level, text, [], {url, lineNumber}));
}

@@ -274,6 +283,13 @@

setDefaultNavigationTimeout(timeout) {
this._frameManager.setDefaultNavigationTimeout(timeout);
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
/**
* @param {number} timeout
*/
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
/**
* @param {!Protocol.Security.certificateErrorPayload} event

@@ -299,3 +315,3 @@ */

/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -320,3 +336,3 @@ * @return {!Promise<!Puppeteer.JSHandle>}

* @param {string} selector
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -421,3 +437,3 @@ * @return {!Promise<(!Object|undefined)>}

* @param {string} name
* @param {function(?)} puppeteerFunction
* @param {Function} puppeteerFunction
*/

@@ -485,3 +501,3 @@ async exposeFunction(name, puppeteerFunction) {

_emitMetrics(event) {
this.emit(Page.Events.Metrics, {
this.emit(Events.Page.Metrics, {
title: event.title,

@@ -512,3 +528,3 @@ metrics: this._buildMetricsObject(event.metrics)

err.stack = ''; // Don't report clientside error with a node stack attached
this.emit(Page.Events.PageError, err);
this.emit(Events.Page.PageError, err);
}

@@ -520,5 +536,21 @@

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);
const values = event.args.map(arg => createJSHandle(context, arg));
this._addConsoleMessage(event.type, values);
this._addConsoleMessage(event.type, values, event.stackTrace);
}

@@ -580,5 +612,6 @@

* @param {!Array<!Puppeteer.JSHandle>} args
* @param {Protocol.Runtime.StackTrace=} stackTrace
*/
_addConsoleMessage(type, args) {
if (!this.listenerCount(Page.Events.Console)) {
_addConsoleMessage(type, args, stackTrace) {
if (!this.listenerCount(Events.Page.Console)) {
args.forEach(arg => arg.dispose());

@@ -595,4 +628,9 @@ return;

}
const message = new ConsoleMessage(type, textTokens.join(' '), args);
this.emit(Page.Events.Console, message);
const location = stackTrace && stackTrace.callFrames.length ? {
url: stackTrace.callFrames[0].url,
lineNumber: stackTrace.callFrames[0].lineNumber,
columnNumber: stackTrace.callFrames[0].columnNumber,
} : {};
const message = new ConsoleMessage(type, textTokens.join(' '), args, location);
this.emit(Events.Page.Console, message);
}

@@ -612,3 +650,3 @@

const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt);
this.emit(Page.Events.Dialog, dialog);
this.emit(Events.Page.Dialog, dialog);
}

@@ -674,5 +712,5 @@

const {
timeout = 30000
timeout = this._timeoutSettings.timeout(),
} = options;
return helper.waitForEvent(this._networkManager, NetworkManager.Events.Request, request => {
return helper.waitForEvent(this._networkManager, Events.NetworkManager.Request, request => {
if (helper.isString(urlOrPredicate))

@@ -693,5 +731,5 @@ return (urlOrPredicate === request.url());

const {
timeout = 30000
timeout = this._timeoutSettings.timeout(),
} = options;
return helper.waitForEvent(this._networkManager, NetworkManager.Events.Response, response => {
return helper.waitForEvent(this._networkManager, Events.NetworkManager.Response, response => {
if (helper.isString(urlOrPredicate))

@@ -794,3 +832,3 @@ return (urlOrPredicate === response.url());

/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -804,3 +842,3 @@ * @return {!Promise<*>}

/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -855,2 +893,4 @@ */

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.');
}

@@ -867,3 +907,3 @@ return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, screenshotType, options));

await this._client.send('Target.activateTarget', {targetId: this._target._targetId});
let clip = options.clip ? Object.assign({}, options['clip'], {scale: 1}) : undefined;
let clip = options.clip ? processClip(options.clip) : undefined;

@@ -900,2 +940,10 @@ if (options.fullPage) {

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};
}
}

@@ -1055,3 +1103,3 @@

* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/

@@ -1065,3 +1113,3 @@ waitForSelector(selector, options = {}) {

* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/

@@ -1073,3 +1121,3 @@ waitForXPath(xpath, options = {}) {

/**
* @param {function()} pageFunction
* @param {Function} pageFunction
* @param {!{polling?: string|number, timeout?: number}=} options

@@ -1200,25 +1248,2 @@ * @param {!Array<*>} args

Page.Events = {
Close: 'close',
Console: 'console',
Dialog: 'dialog',
DOMContentLoaded: 'domcontentloaded',
Error: 'error',
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: 'pageerror',
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished',
FrameAttached: 'frameattached',
FrameDetached: 'framedetached',
FrameNavigated: 'framenavigated',
Load: 'load',
Metrics: 'metrics',
WorkerCreated: 'workercreated',
WorkerDestroyed: 'workerdestroyed',
};
/**

@@ -1252,2 +1277,9 @@ * @typedef {Object} Network.Cookie

/**
* @typedef {Object} ConsoleMessage.Location
* @property {string=} url
* @property {number=} lineNumber
* @property {number=} columnNumber
*/
class ConsoleMessage {

@@ -1258,7 +1290,9 @@ /**

* @param {!Array<!Puppeteer.JSHandle>} args
* @param {ConsoleMessage.Location} location
*/
constructor(type, text, args = []) {
constructor(type, text, args, location = {}) {
this._type = type;
this._text = text;
this._args = args;
this._location = location;
}

@@ -1286,6 +1320,12 @@

}
/**
* @return {Object}
*/
location() {
return this._location;
}
}
module.exports = {Page};
helper.tracePublicAPI(Page);
module.exports = {Page, ConsoleMessage};

@@ -16,3 +16,2 @@ /**

*/
const {helper} = require('./helper');
const Launcher = require('./Launcher');

@@ -41,3 +40,3 @@ const BrowserFetcher = require('./BrowserFetcher');

/**
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint: string, transport?: !Puppeteer.ConnectionTransport})} options
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
* @return {!Promise<!Puppeteer.Browser>}

@@ -73,2 +72,1 @@ */

helper.tracePublicAPI(module.exports, 'Puppeteer');

@@ -0,3 +1,19 @@

/**
* Copyright 2019 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.
*/
const {Events} = require('./Events');
const {Page} = require('./Page');
const {helper} = require('./helper');

@@ -23,3 +39,15 @@ class Target {

this._pagePromise = null;
this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill);
this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill).then(async success => {
if (!success)
return false;
const opener = this.opener();
if (!opener || !opener._pagePromise || this.type() !== 'page')
return true;
const openerPage = await opener._pagePromise;
if (!openerPage.listenerCount(Events.Page.Popup))
return true;
const popupPage = await this.page();
openerPage.emit(Events.Page.Popup, popupPage);
return true;
});
this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill);

@@ -104,4 +132,2 @@ this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== '';

helper.tracePublicAPI(Target);
module.exports = {Target};

@@ -7,3 +7,3 @@ class TaskQueue {

/**
* @param {function()} task
* @param {Function} task
* @return {!Promise}

@@ -10,0 +10,0 @@ */

@@ -104,4 +104,3 @@ /**

}
helper.tracePublicAPI(Tracing);
module.exports = Tracing;

@@ -17,4 +17,5 @@ /**

const EventEmitter = require('events');
const {helper, debugError} = require('./helper');
const {ExecutionContext, JSHandle} = require('./ExecutionContext');
const {debugError} = require('./helper');
const {ExecutionContext} = require('./ExecutionContext');
const {JSHandle} = require('./JSHandle');

@@ -25,4 +26,4 @@ class Worker extends EventEmitter {

* @param {string} url
* @param {function(!string, !Array<!JSHandle>)} consoleAPICalled
* @param {function(!Protocol.Runtime.ExceptionDetails)} exceptionThrown
* @param {function(string, !Array<!JSHandle>, Protocol.Runtime.StackTrace=):void} consoleAPICalled
* @param {function(!Protocol.Runtime.ExceptionDetails):void} exceptionThrown
*/

@@ -44,3 +45,3 @@ constructor(client, url, consoleAPICalled, exceptionThrown) {

this._client.on('Runtime.consoleAPICalled', event => consoleAPICalled(event.type, event.args.map(jsHandleFactory)));
this._client.on('Runtime.consoleAPICalled', event => consoleAPICalled(event.type, event.args.map(jsHandleFactory), event.stackTrace));
this._client.on('Runtime.exceptionThrown', exception => exceptionThrown(exception.exceptionDetails));

@@ -64,3 +65,3 @@ }

/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -74,3 +75,3 @@ * @return {!Promise<*>}

/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -85,2 +86,1 @@ * @return {!Promise<!JSHandle>}

module.exports = {Worker};
helper.tracePublicAPI(Worker);

@@ -16,3 +16,2 @@ /**

*/
const {helper} = require('./helper');

@@ -420,2 +419,1 @@ /**

module.exports = {Accessibility};
helper.tracePublicAPI(Accessibility);

@@ -21,3 +21,3 @@ /**

const {TaskQueue} = require('./TaskQueue');
const {Connection} = require('./Connection');
const {Events} = require('./Events');

@@ -31,2 +31,42 @@ class Browser extends EventEmitter {

* @param {?Puppeteer.ChildProcess} process
* @param {function()=} closeCallback
*/
static /* async */ create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
(yield connection.send('Target.setDiscoverTargets', {discover: true}));
return browser;
});}
/**
* @param {!Puppeteer.Connection} connection
* @param {!Array<string>} contextIds
* @param {boolean} ignoreHTTPSErrors
* @param {?Puppeteer.Viewport} defaultViewport
* @param {?Puppeteer.ChildProcess} process
* @param {(function():Promise)=} closeCallback

@@ -51,3 +91,3 @@ */

this._targets = new Map();
this._connection.on(Connection.Events.Disconnected, () => this.emit(Browser.Events.Disconnected));
this._connection.on(Events.Connection.Disconnected, () => this.emit(Events.Browser.Disconnected));
this._connection.on('Target.targetCreated', this._targetCreated.bind(this));

@@ -150,42 +190,2 @@ this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));

/**
* @param {!Puppeteer.Connection} connection
* @param {!Array<string>} contextIds
* @param {boolean} ignoreHTTPSErrors
* @param {?Puppeteer.Viewport} defaultViewport
* @param {?Puppeteer.ChildProcess} process
* @param {function()=} closeCallback
*/
static /* async */ create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
(yield connection.send('Target.setDiscoverTargets', {discover: true}));
return browser;
});}
/**
* @param {!Protocol.Target.targetCreatedPayload} event

@@ -229,4 +229,4 @@ */

if ((yield target._initializedPromise)) {
this.emit(Browser.Events.TargetCreated, target);
context.emit(BrowserContext.Events.TargetCreated, target);
this.emit(Events.Browser.TargetCreated, target);
context.emit(Events.BrowserContext.TargetCreated, target);
}

@@ -270,4 +270,4 @@ });}

if ((yield target._initializedPromise)) {
this.emit(Browser.Events.TargetDestroyed, target);
target.browserContext().emit(BrowserContext.Events.TargetDestroyed, target);
this.emit(Events.Browser.TargetDestroyed, target);
target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
}

@@ -286,4 +286,4 @@ });}

if (wasInitialized && previousURL !== target.url()) {
this.emit(Browser.Events.TargetChanged, target);
target.browserContext().emit(BrowserContext.Events.TargetChanged, target);
this.emit(Events.Browser.TargetChanged, target);
target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
}

@@ -424,4 +424,4 @@ }

const targetPromise = new Promise(x => resolve = x);
this.on(Browser.Events.TargetCreated, check);
this.on(Browser.Events.TargetChanged, check);
this.on(Events.Browser.TargetCreated, check);
this.on(Events.Browser.TargetChanged, check);
try {

@@ -432,4 +432,4 @@ if (!timeout)

} finally {
this.removeListener(Browser.Events.TargetCreated, check);
this.removeListener(Browser.Events.TargetChanged, check);
this.removeListener(Events.Browser.TargetCreated, check);
this.removeListener(Events.Browser.TargetChanged, check);
}

@@ -592,10 +592,2 @@

/** @enum {string} */
Browser.Events = {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
Disconnected: 'disconnected'
};
class BrowserContext extends EventEmitter {

@@ -810,12 +802,2 @@ /**

/** @enum {string} */
BrowserContext.Events = {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
};
helper.tracePublicAPI(BrowserContext);
helper.tracePublicAPI(Browser);
module.exports = {Browser, BrowserContext};

@@ -128,3 +128,3 @@ /**

* @param {string} revision
* @param {?function(number, number)} progressCallback
* @param {?function(number, number):void} progressCallback
* @return {!Promise<!BrowserFetcher.RevisionInfo>}

@@ -299,3 +299,3 @@ */

* @param {string} destinationPath
* @param {?function(number, number)} progressCallback
* @param {?function(number, number):void} progressCallback
* @return {!Promise}

@@ -302,0 +302,0 @@ */

@@ -16,5 +16,5 @@ /**

*/
const {helper, assert} = require('./helper');
const {assert} = require('./helper');
const {Events} = require('./Events');
const debugProtocol = require('debug')('puppeteer:protocol');
const debugSession = require('debug')('puppeteer:session');
const EventEmitter = require('events');

@@ -49,10 +49,14 @@

static fromSession(session) {
let connection = session._connection;
// TODO(lushnikov): move to flatten protocol to avoid this.
while (connection instanceof CDPSession)
connection = connection._connection;
return connection;
return session._connection;
}
/**
* @param {string} sessionId
* @return {?CDPSession}
*/
session(sessionId) {
return this._sessions.get(sessionId) || null;
}
/**
* @return {string}

@@ -70,6 +74,3 @@ */

send(method, params = {}) {
const id = ++this._lastId;
const message = JSON.stringify({id, method, params});
debugProtocol('SEND ► ' + message);
this._transport.send(message);
const id = this._rawSend({method, params});
return new Promise((resolve, reject) => {

@@ -81,2 +82,14 @@ this._callbacks.set(id, {resolve, reject, error: new Error(), method});

/**
* @param {*} message
* @return {number}
*/
_rawSend(message) {
const id = ++this._lastId;
message = JSON.stringify(Object.assign({}, message, {id}));
debugProtocol('SEND ► ' + message);
this._transport.send(message);
return id;
}
/**
* @param {string} message

@@ -115,3 +128,18 @@ */

const object = JSON.parse(message);
if (object.id) {
if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId;
const session = new CDPSession(this, object.params.targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
} else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.sessionId);
}
}
if (object.sessionId) {
const session = this._sessions.get(object.sessionId);
if (session)
session._onMessage(object);
} else if (object.id) {
const callback = this._callbacks.get(object.id);

@@ -127,14 +155,3 @@ // Callbacks could be all rejected if someone has called `.dispose()`.

} else {
if (object.method === 'Target.receivedMessageFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session)
session._onMessage(object.params.message);
} else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session)
session._onClosed();
this._sessions.delete(object.params.sessionId);
} else {
this.emit(object.method, object.params);
}
this.emit(object.method, object.params);
}

@@ -155,3 +172,3 @@ });}

this._sessions.clear();
this.emit(Connection.Events.Disconnected);
this.emit(Events.Connection.Disconnected);
}

@@ -195,16 +212,10 @@

})(function*(){
const {sessionId} = (yield this.send('Target.attachToTarget', {targetId: targetInfo.targetId}));
const session = new CDPSession(this, targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
return session;
const {sessionId} = (yield this.send('Target.attachToTarget', {targetId: targetInfo.targetId, flatten: true}));
return this._sessions.get(sessionId);
});}
}
Connection.Events = {
Disconnected: Symbol('Connection.Events.Disconnected'),
};
class CDPSession extends EventEmitter {
/**
* @param {!Connection|!CDPSession} connection
* @param {!Connection} connection
* @param {string} targetType

@@ -215,11 +226,7 @@ * @param {string} sessionId

super();
this._lastId = 0;
/** @type {!Map<number, {resolve: function, reject: function, error: !Error, method: string}>}*/
this._callbacks = new Map();
/** @type {null|Connection|CDPSession} */
this._connection = connection;
this._targetType = targetType;
this._sessionId = sessionId;
/** @type {!Map<string, !CDPSession>}*/
this._sessions = new Map();
}

@@ -235,13 +242,3 @@

return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
const id = ++this._lastId;
const message = JSON.stringify({id, method, params});
debugSession('SEND ► ' + message);
this._connection.send('Target.sendMessageToTarget', {sessionId: this._sessionId, message}).catch(e => {
// The response from target might have been already dispatched.
if (!this._callbacks.has(id))
return;
const callback = this._callbacks.get(id);
this._callbacks.delete(id);
callback.reject(rewriteError(callback.error, e && e.message));
});
const id = this._connection._rawSend({sessionId: this._sessionId, method, params});
return new Promise((resolve, reject) => {

@@ -253,7 +250,5 @@ this._callbacks.set(id, {resolve, reject, error: new Error(), method});

/**
* @param {string} message
* @param {{id?: number, method: string, params: Object, error: {message: string, data: any}, result?: *}} object
*/
_onMessage(message) {
debugSession('◀ RECV ' + message);
const object = JSON.parse(message);
_onMessage(object) {
if (object.id && this._callbacks.has(object.id)) {

@@ -267,13 +262,2 @@ const callback = this._callbacks.get(object.id);

} else {
if (object.method === 'Target.receivedMessageFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session)
session._onMessage(object.params.message);
} else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.sessionId);
}
}
assert(!object.id);

@@ -321,22 +305,6 @@ this.emit(object.method, object.params);

this._connection = null;
this.emit(CDPSession.Events.Disconnected);
this.emit(Events.CDPSession.Disconnected);
}
/**
* @param {string} targetType
* @param {string} sessionId
*/
_createSession(targetType, sessionId) {
const session = new CDPSession(this, targetType, sessionId);
this._sessions.set(sessionId, session);
return session;
}
}
CDPSession.Events = {
Disconnected: Symbol('CDPSession.Events.Disconnected'),
};
helper.tracePublicAPI(CDPSession);
/**

@@ -343,0 +311,0 @@ * @param {!Error} error

@@ -171,3 +171,2 @@ /**

module.exports = {Coverage};
helper.tracePublicAPI(Coverage);

@@ -174,0 +173,0 @@ class JSCoverage {

@@ -17,3 +17,3 @@ /**

const {helper, assert} = require('./helper');
const {assert} = require('./helper');

@@ -137,2 +137,1 @@ class Dialog {

module.exports = {Dialog};
helper.tracePublicAPI(Dialog);

@@ -17,4 +17,4 @@ /**

const {helper, assert, debugError} = require('./helper');
const path = require('path');
const {helper, assert} = require('./helper');
const {createJSHandle, JSHandle} = require('./JSHandle');

@@ -24,11 +24,2 @@ const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';

function createJSHandle(context, remoteObject) {
const frame = context.frame();
if (remoteObject.subtype === 'node' && frame) {
const frameManager = frame._frameManager;
return new ElementHandle(context, context._client, remoteObject, frameManager.page(), frameManager);
}
return new JSHandle(context, context._client, remoteObject);
}
class ExecutionContext {

@@ -38,9 +29,8 @@ /**

* @param {!Protocol.Runtime.ExecutionContextDescription} contextPayload
* @param {?Puppeteer.Frame} frame
* @param {?Puppeteer.DOMWorld} world
*/
constructor(client, contextPayload, frame) {
constructor(client, contextPayload, world) {
this._client = client;
this._frame = frame;
this._world = world;
this._contextId = contextPayload.id;
this._isDefault = contextPayload.auxData ? !!contextPayload.auxData['isDefault'] : false;
}

@@ -52,3 +42,3 @@

frame() {
return this._frame;
return this._world ? this._world.frame() : null;
}

@@ -170,11 +160,18 @@

}
const { exceptionDetails, result: remoteObject } = (yield this._client.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
executionContextId: this._contextId,
arguments: args.map(convertArgument.bind(this)),
returnByValue: false,
awaitPromise: true,
userGesture: true
}).catch(rewriteError));
let callFunctionOnPromise;
try {
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
executionContextId: this._contextId,
arguments: args.map(convertArgument.bind(this)),
returnByValue: false,
awaitPromise: true,
userGesture: true
});
} catch (err) {
if (err instanceof TypeError && err.message === 'Converting circular structure to JSON')
err.message += ' Are you passing a nested JSHandle?';
throw err;
}
const { exceptionDetails, result: remoteObject } = (yield callFunctionOnPromise.catch(rewriteError));
if (exceptionDetails)

@@ -262,29 +259,8 @@ throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));

});}
}
class JSHandle {
/**
* @param {!ExecutionContext} context
* @param {!Puppeteer.CDPSession} client
* @param {!Protocol.Runtime.RemoteObject} remoteObject
* @param {Puppeteer.ElementHandle} elementHandle
* @return {Promise<Puppeteer.ElementHandle>}
*/
constructor(context, client, remoteObject) {
this._context = context;
this._client = client;
this._remoteObject = remoteObject;
this._disposed = false;
}
/**
* @return {!ExecutionContext}
*/
executionContext() {
return this._context;
}
/**
* @param {string} propertyName
* @return {!Promise<?JSHandle>}
*/
/* async */ getProperty(propertyName) {return (fn => {
/* async */ _adoptElementHandle(elementHandle) {return (fn => {
const gen = fn.call(this);

@@ -316,1057 +292,15 @@ return new Promise((resolve, reject) => {

})(function*(){
const objectHandle = (yield this._context.evaluateHandle((object, propertyName) => {
const result = {__proto__: null};
result[propertyName] = object[propertyName];
return result;
}, this, propertyName));
const properties = (yield objectHandle.getProperties());
const result = properties.get(propertyName) || null;
(yield objectHandle.dispose());
return result;
});}
/**
* @return {!Promise<!Map<string, !JSHandle>>}
*/
/* async */ getProperties() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const response = (yield this._client.send('Runtime.getProperties', {
objectId: this._remoteObject.objectId,
ownProperties: true
}));
const result = new Map();
for (const property of response.result) {
if (!property.enumerable)
continue;
result.set(property.name, createJSHandle(this._context, property.value));
}
return result;
});}
/**
* @return {!Promise<?Object>}
*/
/* async */ jsonValue() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
if (this._remoteObject.objectId) {
const response = (yield this._client.send('Runtime.callFunctionOn', {
functionDeclaration: 'function() { return this; }',
objectId: this._remoteObject.objectId,
returnByValue: true,
awaitPromise: true,
}));
return helper.valueFromRemoteObject(response.result);
}
return helper.valueFromRemoteObject(this._remoteObject);
});}
/**
* @return {?Puppeteer.ElementHandle}
*/
asElement() {
return null;
}
/* async */ dispose() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
if (this._disposed)
return;
this._disposed = true;
(yield helper.releaseObject(this._client, this._remoteObject));
});}
/**
* @override
* @return {string}
*/
toString() {
if (this._remoteObject.objectId) {
const type = this._remoteObject.subtype || this._remoteObject.type;
return 'JSHandle@' + type;
}
return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
}
}
class ElementHandle extends JSHandle {
/**
* @param {!Puppeteer.ExecutionContext} context
* @param {!Puppeteer.CDPSession} client
* @param {!Protocol.Runtime.RemoteObject} remoteObject
* @param {!Puppeteer.Page} page
* @param {!Puppeteer.FrameManager} frameManager
*/
constructor(context, client, remoteObject, page, frameManager) {
super(context, client, remoteObject);
this._client = client;
this._remoteObject = remoteObject;
this._page = page;
this._frameManager = frameManager;
this._disposed = false;
}
/**
* @override
* @return {?ElementHandle}
*/
asElement() {
return this;
}
/**
* @return {!Promise<?Puppeteer.Frame>}
*/
/* async */ contentFrame() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
assert(this._world, 'Cannot adopt handle without DOMWorld');
const nodeInfo = (yield this._client.send('DOM.describeNode', {
objectId: this._remoteObject.objectId
objectId: elementHandle._remoteObject.objectId,
}));
if (typeof nodeInfo.node.frameId !== 'string')
return null;
return this._frameManager.frame(nodeInfo.node.frameId);
const {object} = (yield this._client.send('DOM.resolveNode', {
backendNodeId: nodeInfo.node.backendNodeId,
executionContextId: this._contextId,
}));
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
});}
/* async */ _scrollIntoViewIfNeeded() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const error = (yield this.executionContext().evaluate(/* async */(element, pageJavascriptEnabled) => {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
if (!element.isConnected)
return 'Node is detached from document';
if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
// force-scroll if page's javascript is disabled.
if (!pageJavascriptEnabled) {
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
return false;
}
const visibleRatio = (yield new Promise(resolve => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
}));
if (visibleRatio !== 1.0)
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
return false;
});}, this, this._page._javascriptEnabled));
if (error)
throw new Error(error);
});}
/**
* @return {!Promise<!{x: number, y: number}>}
*/
/* async */ _clickablePoint() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const result = (yield this._client.send('DOM.getContentQuads', {
objectId: this._remoteObject.objectId
}).catch(debugError));
if (!result || !result.quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Filter out quads that have too small area to click into.
const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).filter(quad => computeQuadArea(quad) > 1);
if (!quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Return the middle point of the first quad.
const quad = quads[0];
let x = 0;
let y = 0;
for (const point of quad) {
x += point.x;
y += point.y;
}
return {
x: x / 4,
y: y / 4
};
});}
/**
* @return {!Promise<void|Protocol.DOM.getBoxModelReturnValue>}
*/
_getBoxModel() {
return this._client.send('DOM.getBoxModel', {
objectId: this._remoteObject.objectId
}).catch(error => debugError(error));
}
/**
* @param {!Array<number>} quad
* @return {!Array<{x: number, y: number}>}
*/
_fromProtocolQuad(quad) {
return [
{x: quad[0], y: quad[1]},
{x: quad[2], y: quad[3]},
{x: quad[4], y: quad[5]},
{x: quad[6], y: quad[7]}
];
}
/* async */ hover() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
(yield this._scrollIntoViewIfNeeded());
const {x, y} = (yield this._clickablePoint());
(yield this._page.mouse.move(x, y));
});}
/**
* @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
*/
/* async */ click(options) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
(yield this._scrollIntoViewIfNeeded());
const {x, y} = (yield this._clickablePoint());
(yield this._page.mouse.click(x, y, options));
});}
/**
* @param {!Array<string>} filePaths
*/
/* async */ uploadFile(...filePaths) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const files = filePaths.map(filePath => path.resolve(filePath));
const objectId = this._remoteObject.objectId;
(yield this._client.send('DOM.setFileInputFiles', { objectId, files }));
});}
/* async */ tap() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
(yield this._scrollIntoViewIfNeeded());
const {x, y} = (yield this._clickablePoint());
(yield this._page.touchscreen.tap(x, y));
});}
/* async */ focus() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
(yield this.executionContext().evaluate(element => element.focus(), this));
});}
/**
* @param {string} text
* @param {{delay: (number|undefined)}=} options
*/
/* async */ type(text, options) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
(yield this.focus());
(yield this._page.keyboard.type(text, options));
});}
/**
* @param {string} key
* @param {!{delay?: number, text?: string}=} options
*/
/* async */ press(key, options) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
(yield this.focus());
(yield this._page.keyboard.press(key, options));
});}
/**
* @return {!Promise<?{x: number, y: number, width: number, height: number}>}
*/
/* async */ boundingBox() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const result = (yield this._getBoxModel());
if (!result)
return null;
const quad = result.model.border;
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
return {x, y, width, height};
});}
/**
* @return {!Promise<?BoxModel>}
*/
/* async */ boxModel() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const result = (yield this._getBoxModel());
if (!result)
return null;
const {content, padding, border, margin, width, height} = result.model;
return {
content: this._fromProtocolQuad(content),
padding: this._fromProtocolQuad(padding),
border: this._fromProtocolQuad(border),
margin: this._fromProtocolQuad(margin),
width,
height
};
});}
/**
*
* @param {!Object=} options
* @returns {!Promise<string|!Buffer>}
*/
/* async */ screenshot(options = {}) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
let needsViewportReset = false;
let boundingBox = (yield this.boundingBox());
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
const viewport = this._page.viewport();
if (viewport && (boundingBox.width > viewport.width || boundingBox.height > viewport.height)) {
const newViewport = {
width: Math.max(viewport.width, Math.ceil(boundingBox.width)),
height: Math.max(viewport.height, Math.ceil(boundingBox.height)),
};
(yield this._page.setViewport(Object.assign({}, viewport, newViewport)));
needsViewportReset = true;
}
(yield this._scrollIntoViewIfNeeded());
boundingBox = (yield this.boundingBox());
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
const { layoutViewport: { pageX, pageY } } = (yield this._client.send('Page.getLayoutMetrics'));
const clip = Object.assign({}, boundingBox);
clip.x += pageX;
clip.y += pageY;
const imageData = (yield this._page.screenshot(Object.assign({}, {
clip
}, options)));
if (needsViewportReset)
(yield this._page.setViewport(viewport));
return imageData;
});}
/**
* @param {string} selector
* @return {!Promise<?ElementHandle>}
*/
/* async */ $(selector) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const handle = (yield this.executionContext().evaluateHandle(
(element, selector) => element.querySelector(selector),
this, selector
));
const element = handle.asElement();
if (element)
return element;
(yield handle.dispose());
return null;
});}
/**
* @param {string} selector
* @return {!Promise<!Array<!ElementHandle>>}
*/
/* async */ $$(selector) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const arrayHandle = (yield this.executionContext().evaluateHandle(
(element, selector) => element.querySelectorAll(selector),
this, selector
));
const properties = (yield arrayHandle.getProperties());
(yield arrayHandle.dispose());
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
}
return result;
});}
/**
* @param {string} selector
* @param {Function|String} pageFunction
* @param {!Array<*>} args
* @return {!Promise<(!Object|undefined)>}
*/
/* async */ $eval(selector, pageFunction, ...args) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const elementHandle = (yield this.$(selector));
if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selector}"`);
const result = (yield this.executionContext().evaluate(pageFunction, elementHandle, ...args));
(yield elementHandle.dispose());
return result;
});}
/**
* @param {string} selector
* @param {Function|String} pageFunction
* @param {!Array<*>} args
* @return {!Promise<(!Object|undefined)>}
*/
/* async */ $$eval(selector, pageFunction, ...args) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const arrayHandle = (yield this.executionContext().evaluateHandle(
(element, selector) => Array.from(element.querySelectorAll(selector)),
this, selector
));
const result = (yield this.executionContext().evaluate(pageFunction, arrayHandle, ...args));
(yield arrayHandle.dispose());
return result;
});}
/**
* @param {string} expression
* @return {!Promise<!Array<!ElementHandle>>}
*/
/* async */ $x(expression) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const arrayHandle = (yield this.executionContext().evaluateHandle(
(element, expression) => {
const document = element.ownerDocument || element;
const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
const array = [];
let item;
while ((item = iterator.iterateNext()))
array.push(item);
return array;
},
this, expression
));
const properties = (yield arrayHandle.getProperties());
(yield arrayHandle.dispose());
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
}
return result;
});}
/**
* @returns {!Promise<boolean>}
*/
isIntersectingViewport() {
return this.executionContext().evaluate(/* async */ element => {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const visibleRatio = (yield new Promise(resolve => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
}));
return visibleRatio > 0;
});}, this);
}
}
function computeQuadArea(quad) {
// Compute sum of all directed areas of adjacent triangles
// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
let area = 0;
for (let i = 0; i < quad.length; ++i) {
const p1 = quad[i];
const p2 = quad[(i + 1) % quad.length];
area += (p1.x * p2.y - p2.x * p1.y) / 2;
}
return Math.abs(area);
}
/**
* @typedef {Object} BoxModel
* @property {!Array<!{x: number, y: number}>} content
* @property {!Array<!{x: number, y: number}>} padding
* @property {!Array<!{x: number, y: number}>} border
* @property {!Array<!{x: number, y: number}>} margin
* @property {number} width
* @property {number} height
*/
helper.tracePublicAPI(ElementHandle);
helper.tracePublicAPI(JSHandle);
helper.tracePublicAPI(ExecutionContext);
module.exports = {ExecutionContext, JSHandle, ElementHandle, createJSHandle, EVALUATION_SCRIPT_URL};
module.exports = {ExecutionContext, EVALUATION_SCRIPT_URL};

@@ -17,11 +17,10 @@ /**

const fs = require('fs');
const EventEmitter = require('events');
const {helper, assert} = require('./helper');
const {ExecutionContext} = require('./ExecutionContext');
const {TimeoutError} = require('./Errors');
const {NetworkManager} = require('./NetworkManager');
const {CDPSession} = require('./Connection');
const {Events} = require('./Events');
const {ExecutionContext, EVALUATION_SCRIPT_URL} = require('./ExecutionContext');
const {LifecycleWatcher} = require('./LifecycleWatcher');
const {DOMWorld} = require('./DOMWorld');
const readFileAsync = helper.promisify(fs.readFile);
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';

@@ -34,4 +33,5 @@ class FrameManager extends EventEmitter {

* @param {!Puppeteer.NetworkManager} networkManager
* @param {!Puppeteer.TimeoutSettings} timeoutSettings
*/
constructor(client, frameTree, page, networkManager) {
constructor(client, frameTree, page, networkManager, timeoutSettings) {
super();

@@ -41,3 +41,3 @@ this._client = client;

this._networkManager = networkManager;
this._defaultNavigationTimeout = 30000;
this._timeoutSettings = timeoutSettings;
/** @type {!Map<string, !Frame>} */

@@ -47,2 +47,4 @@ this._frames = new Map();

this._contextIdToContext = new Map();
/** @type {!Set<string>} */
this._isolatedWorlds = new Set();

@@ -58,3 +60,2 @@ this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));

this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event));
this._handleFrameTree(frameTree);

@@ -64,9 +65,2 @@ }

/**
* @param {number} timeout
*/
setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
}
/**
* @param {!Puppeteer.Frame} frame

@@ -108,3 +102,3 @@ * @param {string} url

waitUntil = ['load'],
timeout = this._defaultNavigationTimeout,
timeout = this._timeoutSettings.navigationTimeout(),
} = options;

@@ -208,3 +202,3 @@

waitUntil = ['load'],
timeout = this._defaultNavigationTimeout,
timeout = this._timeoutSettings.navigationTimeout(),
} = options;

@@ -231,3 +225,3 @@ const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);

frame._onLifecycleEvent(event.loaderId, event.name);
this.emit(FrameManager.Events.LifecycleEvent, frame);
this.emit(Events.FrameManager.LifecycleEvent, frame);
}

@@ -243,3 +237,3 @@

frame._onLoadingStopped();
this.emit(FrameManager.Events.LifecycleEvent, frame);
this.emit(Events.FrameManager.LifecycleEvent, frame);
}

@@ -301,3 +295,3 @@

this._frames.set(frame._id, frame);
this.emit(FrameManager.Events.FrameAttached, frame);
this.emit(Events.FrameManager.FrameAttached, frame);
}

@@ -336,6 +330,80 @@

this.emit(FrameManager.Events.FrameNavigated, frame);
this.emit(Events.FrameManager.FrameNavigated, frame);
}
/* async */ ensureSecondaryDOMWorld() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
(yield this._ensureIsolatedWorld(UTILITY_WORLD_NAME));
});}
/**
* @param {string} name
*/
/* async */ _ensureIsolatedWorld(name) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
if (this._isolatedWorlds.has(name))
return;
this._isolatedWorlds.add(name);
(yield this._client.send('Page.addScriptToEvaluateOnNewDocument', {
source: `//# sourceURL=${EVALUATION_SCRIPT_URL}`,
worldName: name,
})),
(yield Promise.all(this.frames().map(frame => this._client.send('Page.createIsolatedWorld', {
frameId: frame._id,
grantUniveralAccess: true,
worldName: name,
}))));
});}
/**
* @param {string} frameId

@@ -349,4 +417,4 @@ * @param {string} url

frame._navigatedWithinDocument(url);
this.emit(FrameManager.Events.FrameNavigatedWithinDocument, frame);
this.emit(FrameManager.Events.FrameNavigated, frame);
this.emit(Events.FrameManager.FrameNavigatedWithinDocument, frame);
this.emit(Events.FrameManager.FrameNavigated, frame);
}

@@ -366,7 +434,16 @@

const frame = this._frames.get(frameId) || null;
let world = null;
if (frame) {
if (contextPayload.auxData && !!contextPayload.auxData['isDefault'])
world = frame._mainWorld;
else if (contextPayload.name === UTILITY_WORLD_NAME)
world = frame._secondaryWorld;
}
if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated')
this._isolatedWorlds.add(contextPayload.name);
/** @type {!ExecutionContext} */
const context = new ExecutionContext(this._client, contextPayload, frame);
const context = new ExecutionContext(this._client, contextPayload, world);
if (world)
world._setContext(context);
this._contextIdToContext.set(contextPayload.id, context);
if (frame)
frame._addExecutionContext(context);
}

@@ -382,4 +459,4 @@

this._contextIdToContext.delete(executionContextId);
if (context.frame())
context.frame()._removeExecutionContext(context);
if (context._world)
context._world._setContext(null);
}

@@ -389,4 +466,4 @@

for (const context of this._contextIdToContext.values()) {
if (context.frame())
context.frame()._removeExecutionContext(context);
if (context._world)
context._world._setContext(null);
}

@@ -414,17 +491,6 @@ this._contextIdToContext.clear();

this._frames.delete(frame._id);
this.emit(FrameManager.Events.FrameDetached, frame);
this.emit(Events.FrameManager.FrameDetached, frame);
}
}
/** @enum {string} */
FrameManager.Events = {
FrameAttached: 'frameattached',
FrameNavigated: 'framenavigated',
FrameDetached: 'framedetached',
LifecycleEvent: 'lifecycleevent',
FrameNavigatedWithinDocument: 'framenavigatedwithindocument',
ExecutionContextCreated: 'executioncontextcreated',
ExecutionContextDestroyed: 'executioncontextdestroyed',
};
/**

@@ -448,15 +514,7 @@ * @unrestricted

/** @type {?Promise<!Puppeteer.ElementHandle>} */
this._documentPromise = null;
/** @type {!Promise<!ExecutionContext>} */
this._contextPromise;
this._contextResolveCallback = null;
this._setDefaultContext(null);
/** @type {!Set<!WaitTask>} */
this._waitTasks = new Set();
this._loaderId = '';
/** @type {!Set<string>} */
this._lifecycleEvents = new Set();
this._mainWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings);
this._secondaryWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings);

@@ -470,35 +528,2 @@ /** @type {!Set<!Frame>} */

/**
* @param {!ExecutionContext} context
*/
_addExecutionContext(context) {
if (context._isDefault)
this._setDefaultContext(context);
}
/**
* @param {!ExecutionContext} context
*/
_removeExecutionContext(context) {
if (context._isDefault)
this._setDefaultContext(null);
}
/**
* @param {?ExecutionContext} context
*/
_setDefaultContext(context) {
if (context) {
this._contextResolveCallback.call(null, context);
this._contextResolveCallback = null;
for (const waitTask of this._waitTasks)
waitTask.rerun();
} else {
this._documentPromise = null;
this._contextPromise = new Promise(fulfill => {
this._contextResolveCallback = fulfill;
});
}
}
/**
* @param {string} url

@@ -576,7 +601,7 @@ * @param {!{referer?: string, timeout?: number, waitUntil?: string|!Array<string>}=} options

executionContext() {
return this._contextPromise;
return this._mainWorld.executionContext();
}
/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -612,4 +637,3 @@ * @return {!Promise<!Puppeteer.JSHandle>}

})(function*(){
const context = (yield this._contextPromise);
return context.evaluateHandle(pageFunction, ...args);
return this._mainWorld.evaluateHandle(pageFunction, ...args);
});}

@@ -649,4 +673,3 @@

})(function*(){
const context = (yield this._contextPromise);
return context.evaluate(pageFunction, ...args);
return this._mainWorld.evaluate(pageFunction, ...args);
});}

@@ -685,73 +708,6 @@

})(function*(){
const document = (yield this._document());
const value = (yield document.$(selector));
return value;
return this._mainWorld.$(selector);
});}
/**
* @return {!Promise<!Puppeteer.ElementHandle>}
*/
/* async */ _document() {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
if (this._documentPromise)
return this._documentPromise;
this._documentPromise = this._contextPromise.then(/* async */ context => {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const document = (yield context.evaluateHandle('document'));
return document.asElement();
});});
return this._documentPromise;
});}
/**
* @param {string} expression

@@ -787,5 +743,3 @@ * @return {!Promise<!Array<!Puppeteer.ElementHandle>>}

})(function*(){
const document = (yield this._document());
const value = (yield document.$x(expression));
return value;
return this._mainWorld.$x(expression);
});}

@@ -826,4 +780,3 @@

})(function*(){
const document = (yield this._document());
return document.$eval(selector, pageFunction, ...args);
return this._mainWorld.$eval(selector, pageFunction, ...args);
});}

@@ -864,5 +817,3 @@

})(function*(){
const document = (yield this._document());
const value = (yield document.$$eval(selector, pageFunction, ...args));
return value;
return this._mainWorld.$$eval(selector, pageFunction, ...args);
});}

@@ -901,5 +852,3 @@

})(function*(){
const document = (yield this._document());
const value = (yield document.$$(selector));
return value;
return this._mainWorld.$$(selector);
});}

@@ -937,10 +886,3 @@

})(function*(){
return (yield this.evaluate(() => {
let retVal = '';
if (document.doctype)
retVal = new XMLSerializer().serializeToString(document.doctype);
if (document.documentElement)
retVal += document.documentElement.outerHTML;
return retVal;
}));
return this._secondaryWorld.content();
});}

@@ -979,21 +921,3 @@

})(function*(){
const {
waitUntil = ['load'],
timeout = 30000,
} = options;
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
// lifecycle event. @see https://crrev.com/608658
(yield this.evaluate(html => {
document.open();
document.write(html);
document.close();
}, html));
const watcher = new LifecycleWatcher(this._frameManager, this, waitUntil, timeout);
const error = (yield Promise.race([
watcher.timeoutOrTerminationPromise(),
watcher.lifecyclePromise(),
]));
watcher.dispose();
if (error)
throw error;
return this._secondaryWorld.setContent(html, options);
});}

@@ -1067,92 +991,3 @@

})(function*(){
const {
url = null,
path = null,
content = null,
type = ''
} = options;
if (url !== null) {
try {
const context = (yield this._contextPromise);
return ((yield context.evaluateHandle(addScriptUrl, url, type))).asElement();
} catch (error) {
throw new Error(`Loading script from ${url} failed`);
}
}
if (path !== null) {
let contents = (yield readFileAsync(path, 'utf8'));
contents += '//# sourceURL=' + path.replace(/\n/g, '');
const context = (yield this._contextPromise);
return ((yield context.evaluateHandle(addScriptContent, contents, type))).asElement();
}
if (content !== null) {
const context = (yield this._contextPromise);
return ((yield context.evaluateHandle(addScriptContent, content, type))).asElement();
}
throw new Error('Provide an object with a `url`, `path` or `content` property');
/**
* @param {string} url
* @param {string} type
* @return {!Promise<!HTMLElement>}
*/
/* async */ function addScriptUrl(url, type) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const script = document.createElement('script');
script.src = url;
if (type)
script.type = type;
const promise = new Promise((res, rej) => {
script.onload = res;
script.onerror = rej;
});
document.head.appendChild(script);
(yield promise);
return script;
});}
/**
* @param {string} content
* @param {string} type
* @return {!HTMLElement}
*/
function addScriptContent(content, type = 'text/javascript') {
const script = document.createElement('script');
script.type = type;
script.text = content;
let error = null;
script.onerror = e => error = e;
document.head.appendChild(script);
if (error)
throw error;
return script;
}
return this._mainWorld.addScriptTag(options);
});}

@@ -1191,115 +1026,3 @@

})(function*(){
const {
url = null,
path = null,
content = null
} = options;
if (url !== null) {
try {
const context = (yield this._contextPromise);
return ((yield context.evaluateHandle(addStyleUrl, url))).asElement();
} catch (error) {
throw new Error(`Loading style from ${url} failed`);
}
}
if (path !== null) {
let contents = (yield readFileAsync(path, 'utf8'));
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
const context = (yield this._contextPromise);
return ((yield context.evaluateHandle(addStyleContent, contents))).asElement();
}
if (content !== null) {
const context = (yield this._contextPromise);
return ((yield context.evaluateHandle(addStyleContent, content))).asElement();
}
throw new Error('Provide an object with a `url`, `path` or `content` property');
/**
* @param {string} url
* @return {!Promise<!HTMLElement>}
*/
/* async */ function addStyleUrl(url) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
const promise = new Promise((res, rej) => {
link.onload = res;
link.onerror = rej;
});
document.head.appendChild(link);
(yield promise);
return link;
});}
/**
* @param {string} content
* @return {!Promise<!HTMLElement>}
*/
/* async */ function addStyleContent(content) {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(content));
const promise = new Promise((res, rej) => {
style.onload = res;
style.onerror = rej;
});
document.head.appendChild(style);
(yield promise);
return style;
});}
return this._mainWorld.addStyleTag(options);
});}

@@ -1338,6 +1061,3 @@

})(function*(){
const handle = (yield this.$(selector));
assert(handle, 'No node found for selector: ' + selector);
(yield handle.click(options));
(yield handle.dispose());
return this._secondaryWorld.click(selector, options);
});}

@@ -1375,6 +1095,3 @@

})(function*(){
const handle = (yield this.$(selector));
assert(handle, 'No node found for selector: ' + selector);
(yield handle.focus());
(yield handle.dispose());
return this._secondaryWorld.focus(selector);
});}

@@ -1412,6 +1129,3 @@

})(function*(){
const handle = (yield this.$(selector));
assert(handle, 'No node found for selector: ' + selector);
(yield handle.hover());
(yield handle.dispose());
return this._secondaryWorld.hover(selector);
});}

@@ -1425,19 +1139,3 @@

select(selector, ...values){
for (const value of values)
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
return this.$eval(selector, (element, values) => {
if (element.nodeName.toLowerCase() !== 'select')
throw new Error('Element is not a <select> element.');
const options = Array.from(element.options);
element.value = undefined;
for (const option of options) {
option.selected = values.includes(option.value);
if (option.selected && !element.multiple)
break;
}
element.dispatchEvent(new Event('input', { 'bubbles': true }));
element.dispatchEvent(new Event('change', { 'bubbles': true }));
return options.filter(option => option.selected).map(option => option.value);
}, values);
return this._secondaryWorld.select(selector, ...values);
}

@@ -1475,6 +1173,3 @@

})(function*(){
const handle = (yield this.$(selector));
assert(handle, 'No node found for selector: ' + selector);
(yield handle.tap());
(yield handle.dispose());
return this._secondaryWorld.tap(selector);
});}

@@ -1514,6 +1209,3 @@

})(function*(){
const handle = (yield this.$(selector));
assert(handle, 'No node found for selector: ' + selector);
(yield handle.type(text, options));
(yield handle.dispose());
return this._mainWorld.type(selector, text, options);
});}

@@ -1525,3 +1217,3 @@

* @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>}
* @return {!Promise<?Puppeteer.JSHandle>}
*/

@@ -1547,34 +1239,5 @@ waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {

* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForSelector(selector, options) {
return this._waitForSelectorOrXPath(selector, false, options);
}
/**
* @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
*/
waitForXPath(xpath, options) {
return this._waitForSelectorOrXPath(xpath, true, options);
}
/**
* @param {Function|string} pageFunction
* @param {!{polling?: string|number, timeout?: number}=} options
* @return {!Promise<!Puppeteer.JSHandle>}
*/
waitForFunction(pageFunction, options = {}, ...args) {
const {
polling = 'raf',
timeout = 30000
} = options;
return new WaitTask(this, pageFunction, 'function', polling, timeout, ...args).promise;
}
/**
* @return {!Promise<string>}
*/
/* async */ title() {return (fn => {
/* async */ waitForSelector(selector, options) {return (fn => {
const gen = fn.call(this);

@@ -1606,144 +1269,17 @@ return new Promise((resolve, reject) => {

})(function*(){
return this.evaluate(() => document.title);
const handle = (yield this._secondaryWorld.waitForSelector(selector, options));
if (!handle)
return null;
const mainExecutionContext = (yield this._mainWorld.executionContext());
const result = (yield mainExecutionContext._adoptElementHandle(handle));
(yield handle.dispose());
return result;
});}
/**
* @param {string} selectorOrXPath
* @param {boolean} isXPath
* @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
_waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
const {
visible: waitForVisible = false,
hidden: waitForHidden = false,
timeout = 30000,
} = options;
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
return new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden).promise;
/**
* @param {string} selectorOrXPath
* @param {boolean} isXPath
* @param {boolean} waitForVisible
* @param {boolean} waitForHidden
* @return {?Node|boolean}
*/
function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
const node = isXPath
? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
: document.querySelector(selectorOrXPath);
if (!node)
return waitForHidden;
if (!waitForVisible && !waitForHidden)
return node;
const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
const style = window.getComputedStyle(element);
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
return success ? node : null;
/**
* @return {boolean}
*/
function hasVisibleBoundingBox() {
const rect = element.getBoundingClientRect();
return !!(rect.top || rect.bottom || rect.width || rect.height);
}
}
}
/**
* @param {!Protocol.Page.Frame} framePayload
*/
_navigated(framePayload) {
this._name = framePayload.name;
// TODO(lushnikov): remove this once requestInterception has loaderId exposed.
this._navigationURL = framePayload.url;
this._url = framePayload.url;
}
/**
* @param {string} url
*/
_navigatedWithinDocument(url) {
this._url = url;
}
/**
* @param {string} loaderId
* @param {string} name
*/
_onLifecycleEvent(loaderId, name) {
if (name === 'init') {
this._loaderId = loaderId;
this._lifecycleEvents.clear();
}
this._lifecycleEvents.add(name);
}
_onLoadingStopped() {
this._lifecycleEvents.add('DOMContentLoaded');
this._lifecycleEvents.add('load');
}
_detach() {
for (const waitTask of this._waitTasks)
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
this._detached = true;
if (this._parentFrame)
this._parentFrame._childFrames.delete(this);
this._parentFrame = null;
}
}
helper.tracePublicAPI(Frame);
class WaitTask {
/**
* @param {!Frame} frame
* @param {Function|string} predicateBody
* @param {string|number} polling
* @param {number} timeout
* @param {!Array<*>} args
*/
constructor(frame, predicateBody, title, polling, timeout, ...args) {
if (helper.isString(polling))
assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling);
else if (helper.isNumber(polling))
assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling);
else
throw new Error('Unknown polling options: ' + polling);
this._frame = frame;
this._polling = polling;
this._timeout = timeout;
this._predicateBody = helper.isString(predicateBody) ? 'return ' + predicateBody : 'return (' + predicateBody + ')(...args)';
this._args = args;
this._runCount = 0;
frame._waitTasks.add(this);
this.promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
// Since page navigation requires us to re-install the pageScript, we should track
// timeout on our end.
if (timeout) {
const timeoutError = new TimeoutError(`waiting for ${title} failed: timeout ${timeout}ms exceeded`);
this._timeoutTimer = setTimeout(() => this.terminate(timeoutError), timeout);
}
this.rerun();
}
/**
* @param {!Error} error
*/
terminate(error) {
this._terminated = true;
this._reject(error);
this._cleanup();
}
/* async */ rerun() {return (fn => {
/* async */ waitForXPath(xpath, options) {return (fn => {
const gen = fn.call(this);

@@ -1775,58 +1311,24 @@ return new Promise((resolve, reject) => {

})(function*(){
const runCount = ++this._runCount;
/** @type {?Puppeteer.JSHandle} */
let success = null;
let error = null;
try {
success = (yield ((yield this._frame.executionContext())).evaluateHandle(waitForPredicatePageFunction, this._predicateBody, this._polling, this._timeout, ...this._args));
} catch (e) {
error = e;
}
if (this._terminated || runCount !== this._runCount) {
if (success)
(yield success.dispose());
return;
}
// Ignore timeouts in pageScript - we track timeouts ourselves.
// If the frame's execution context has already changed, `frame.evaluate` will
// throw an error - ignore this predicate run altogether.
if (!error && (yield this._frame.evaluate(s => !s, success).catch(e => true))) {
(yield success.dispose());
return;
}
// When the page is navigated, the promise is rejected.
// We will try again in the new execution context.
if (error && error.message.includes('Execution context was destroyed'))
return;
// We could have tried to evaluate in a context which was already
// destroyed.
if (error && error.message.includes('Cannot find context with specified id'))
return;
if (error)
this._reject(error);
else
this._resolve(success);
this._cleanup();
const handle = (yield this._secondaryWorld.waitForXPath(xpath, options));
if (!handle)
return null;
const mainExecutionContext = (yield this._mainWorld.executionContext());
const result = (yield mainExecutionContext._adoptElementHandle(handle));
(yield handle.dispose());
return result;
});}
_cleanup() {
clearTimeout(this._timeoutTimer);
this._frame._waitTasks.delete(this);
this._runningTask = null;
/**
* @param {Function|string} pageFunction
* @param {!{polling?: string|number, timeout?: number}=} options
* @return {!Promise<!Puppeteer.JSHandle>}
*/
waitForFunction(pageFunction, options = {}, ...args) {
return this._mainWorld.waitForFunction(pageFunction, options, ...args);
}
}
/**
* @param {string} predicateBody
* @param {string} polling
* @param {number} timeout
* @return {!Promise<*>}
*/
/* async */ function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {return (fn => {
/**
* @return {!Promise<string>}
*/
/* async */ title() {return (fn => {
const gen = fn.call(this);

@@ -1858,88 +1360,49 @@ return new Promise((resolve, reject) => {

})(function*(){
const predicate = new Function('...args', predicateBody);
let timedOut = false;
if (timeout)
setTimeout(() => timedOut = true, timeout);
if (polling === 'raf')
return (yield pollRaf());
if (polling === 'mutation')
return (yield pollMutation());
if (typeof polling === 'number')
return (yield pollInterval(polling));
return this._secondaryWorld.title();
});}
/**
* @return {!Promise<*>}
* @param {!Protocol.Page.Frame} framePayload
*/
function pollMutation() {
const success = predicate.apply(null, args);
if (success)
return Promise.resolve(success);
let fulfill;
const result = new Promise(x => fulfill = x);
const observer = new MutationObserver(mutations => {
if (timedOut) {
observer.disconnect();
fulfill();
}
const success = predicate.apply(null, args);
if (success) {
observer.disconnect();
fulfill(success);
}
});
observer.observe(document, {
childList: true,
subtree: true,
attributes: true
});
return result;
_navigated(framePayload) {
this._name = framePayload.name;
// TODO(lushnikov): remove this once requestInterception has loaderId exposed.
this._navigationURL = framePayload.url;
this._url = framePayload.url;
}
/**
* @return {!Promise<*>}
* @param {string} url
*/
function pollRaf() {
let fulfill;
const result = new Promise(x => fulfill = x);
onRaf();
return result;
function onRaf() {
if (timedOut) {
fulfill();
return;
}
const success = predicate.apply(null, args);
if (success)
fulfill(success);
else
requestAnimationFrame(onRaf);
}
_navigatedWithinDocument(url) {
this._url = url;
}
/**
* @param {number} pollInterval
* @return {!Promise<*>}
* @param {string} loaderId
* @param {string} name
*/
function pollInterval(pollInterval) {
let fulfill;
const result = new Promise(x => fulfill = x);
onTimeout();
return result;
function onTimeout() {
if (timedOut) {
fulfill();
return;
}
const success = predicate.apply(null, args);
if (success)
fulfill(success);
else
setTimeout(onTimeout, pollInterval);
_onLifecycleEvent(loaderId, name) {
if (name === 'init') {
this._loaderId = loaderId;
this._lifecycleEvents.clear();
}
this._lifecycleEvents.add(name);
}
});}
_onLoadingStopped() {
this._lifecycleEvents.add('DOMContentLoaded');
this._lifecycleEvents.add('load');
}
_detach() {
this._detached = true;
this._mainWorld._detach();
this._secondaryWorld._detach();
if (this._parentFrame)
this._parentFrame._childFrames.delete(this);
this._parentFrame = null;
}
}
function assertNoLegacyNavigationOptions(options) {

@@ -1951,179 +1414,2 @@ assert(options['networkIdleTimeout'] === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');

class LifecycleWatcher {
/**
* @param {!FrameManager} frameManager
* @param {!Puppeteer.Frame} frame
* @param {string|!Array<string>} waitUntil
* @param {number} timeout
*/
constructor(frameManager, frame, waitUntil, timeout) {
if (Array.isArray(waitUntil))
waitUntil = waitUntil.slice();
else if (typeof waitUntil === 'string')
waitUntil = [waitUntil];
this._expectedLifecycle = waitUntil.map(value => {
const protocolEvent = puppeteerToProtocolLifecycle[value];
assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
return protocolEvent;
});
this._frameManager = frameManager;
this._networkManager = frameManager._networkManager;
this._frame = frame;
this._initialLoaderId = frame._loaderId;
this._timeout = timeout;
/** @type {?Puppeteer.Request} */
this._navigationRequest = null;
this._eventListeners = [
helper.addEventListener(frameManager._client, CDPSession.Events.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))),
helper.addEventListener(this._frameManager, FrameManager.Events.LifecycleEvent, this._checkLifecycleComplete.bind(this)),
helper.addEventListener(this._frameManager, FrameManager.Events.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)),
helper.addEventListener(this._frameManager, FrameManager.Events.FrameDetached, this._onFrameDetached.bind(this)),
helper.addEventListener(this._networkManager, NetworkManager.Events.Request, this._onRequest.bind(this)),
];
this._sameDocumentNavigationPromise = new Promise(fulfill => {
this._sameDocumentNavigationCompleteCallback = fulfill;
});
this._lifecyclePromise = new Promise(fulfill => {
this._lifecycleCallback = fulfill;
});
this._newDocumentNavigationPromise = new Promise(fulfill => {
this._newDocumentNavigationCompleteCallback = fulfill;
});
this._timeoutPromise = this._createTimeoutPromise();
this._terminationPromise = new Promise(fulfill => {
this._terminationCallback = fulfill;
});
}
/**
* @param {!Puppeteer.Request} request
*/
_onRequest(request) {
if (request.frame() !== this._frame || !request.isNavigationRequest())
return;
this._navigationRequest = request;
}
/**
* @param {!Puppeteer.Frame} frame
*/
_onFrameDetached(frame) {
if (this._frame === frame) {
this._terminationCallback.call(null, new Error('Navigating frame was detached'));
return;
}
this._checkLifecycleComplete();
}
/**
* @return {?Puppeteer.Response}
*/
navigationResponse() {
return this._navigationRequest ? this._navigationRequest.response() : null;
}
/**
* @param {!Error} error
*/
_terminate(error) {
this._terminationCallback.call(null, error);
}
/**
* @return {!Promise<?Error>}
*/
sameDocumentNavigationPromise() {
return this._sameDocumentNavigationPromise;
}
/**
* @return {!Promise<?Error>}
*/
newDocumentNavigationPromise() {
return this._newDocumentNavigationPromise;
}
/**
* @return {!Promise}
*/
lifecyclePromise() {
return this._lifecyclePromise;
}
/**
* @return {!Promise<?Error>}
*/
timeoutOrTerminationPromise() {
return Promise.race([this._timeoutPromise, this._terminationPromise]);
}
/**
* @return {!Promise<?Error>}
*/
_createTimeoutPromise() {
if (!this._timeout)
return new Promise(() => {});
const errorMessage = 'Navigation Timeout Exceeded: ' + this._timeout + 'ms exceeded';
return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
.then(() => new TimeoutError(errorMessage));
}
/**
* @param {!Puppeteer.Frame} frame
*/
_navigatedWithinDocument(frame) {
if (frame !== this._frame)
return;
this._hasSameDocumentNavigation = true;
this._checkLifecycleComplete();
}
_checkLifecycleComplete() {
// We expect navigation to commit.
if (!checkLifecycle(this._frame, this._expectedLifecycle))
return;
this._lifecycleCallback();
if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
return;
if (this._hasSameDocumentNavigation)
this._sameDocumentNavigationCompleteCallback();
if (this._frame._loaderId !== this._initialLoaderId)
this._newDocumentNavigationCompleteCallback();
/**
* @param {!Puppeteer.Frame} frame
* @param {!Array<string>} expectedLifecycle
* @return {boolean}
*/
function checkLifecycle(frame, expectedLifecycle) {
for (const event of expectedLifecycle) {
if (!frame._lifecycleEvents.has(event))
return false;
}
for (const child of frame.childFrames()) {
if (!checkLifecycle(child, expectedLifecycle))
return false;
}
return true;
}
}
dispose() {
helper.removeEventListeners(this._eventListeners);
clearTimeout(this._maximumTimer);
}
}
const puppeteerToProtocolLifecycle = {
'load': 'load',
'domcontentloaded': 'DOMContentLoaded',
'networkidle0': 'networkIdle',
'networkidle2': 'networkAlmostIdle',
};
module.exports = {FrameManager, Frame};

@@ -19,38 +19,3 @@ /**

const debugError = require('debug')(`puppeteer:error`);
/** @type {?Map<string, boolean>} */
let apiCoverage = null;
/**
* @param {!Object} classType
* @param {string=} publicName
*/
function traceAPICoverage(classType, publicName) {
if (!apiCoverage)
return;
let className = publicName || classType.prototype.constructor.name;
className = className.substring(0, 1).toLowerCase() + className.substring(1);
for (const methodName of Reflect.ownKeys(classType.prototype)) {
const method = Reflect.get(classType.prototype, methodName);
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
continue;
apiCoverage.set(`${className}.${methodName}`, false);
Reflect.set(classType.prototype, methodName, function(...args) {
apiCoverage.set(`${className}.${methodName}`, true);
return method.call(this, ...args);
});
}
if (classType.Events) {
for (const event of Object.values(classType.Events))
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false);
const method = Reflect.get(classType.prototype, 'emit');
Reflect.set(classType.prototype, 'emit', function(event, ...args) {
if (this.listenerCount(event))
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
return method.call(this, event, ...args);
});
}
}
class Helper {

@@ -163,5 +128,4 @@ /**

* @param {!Object} classType
* @param {string=} publicName
*/
static tracePublicAPI(classType, publicName) {
static installAsyncStackHooks(classType) {
for (const methodName of Reflect.ownKeys(classType.prototype)) {

@@ -182,4 +146,2 @@ const method = Reflect.get(classType.prototype, methodName);

}
traceAPICoverage(classType, publicName);
}

@@ -190,3 +152,3 @@

* @param {(string|symbol)} eventName
* @param {function(?)} handler
* @param {function(?):void} handler
* @return {{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?)}}

@@ -200,3 +162,3 @@ */

/**
* @param {!Array<{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?)}>} listeners
* @param {!Array<{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?):void}>} listeners
*/

@@ -210,13 +172,2 @@ static removeEventListeners(listeners) {

/**
* @return {?Map<string, boolean>}
*/
static publicAPICoverage() {
return apiCoverage;
}
static recordPublicAPICoverage() {
apiCoverage = new Map();
}
/**
* @param {!Object} obj

@@ -255,3 +206,3 @@ * @return {boolean}

* @param {!NodeJS.EventEmitter} emitter
* @param {string} eventName
* @param {(string|symbol)} eventName
* @param {function} predicate

@@ -258,0 +209,0 @@ * @return {!Promise}

@@ -17,3 +17,3 @@ /**

const {helper, assert} = require('./helper');
const {assert} = require('./helper');
const keyDefinitions = require('./USKeyboardLayout');

@@ -566,4 +566,1 @@

module.exports = { Keyboard, Mouse, Touchscreen};
helper.tracePublicAPI(Keyboard);
helper.tracePublicAPI(Mouse);
helper.tracePublicAPI(Touchscreen);

@@ -18,2 +18,4 @@ /**

const path = require('path');
const http = require('http');
const URL = require('url');
const removeFolder = require('rimraf');

@@ -26,3 +28,3 @@ const childProcess = require('child_process');

const fs = require('fs');
const {helper, debugError} = require('./helper');
const {helper, assert, debugError} = require('./helper');
const {TimeoutError} = require('./Errors');

@@ -39,2 +41,3 @@ const WebSocketTransport = require('./WebSocketTransport');

'--disable-background-networking',
'--enable-features=NetworkService,NetworkServiceInProcess',
'--disable-background-timer-throttling',

@@ -48,3 +51,3 @@ '--disable-backgrounding-occluded-windows',

// TODO: Support OOOPIF. @see https://github.com/GoogleChrome/puppeteer/issues/2548
'--disable-features=site-per-process',
'--disable-features=site-per-process,TranslateUI',
'--disable-hang-monitor',

@@ -56,3 +59,3 @@ '--disable-ipc-flooding-protection',

'--disable-sync',
'--disable-translate',
'--force-color-profile=srgb',
'--metrics-recording-only',

@@ -334,3 +337,3 @@ '--no-first-run',

/**
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint: string, transport?: !Puppeteer.ConnectionTransport})} options
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
* @return {!Promise<!Browser>}

@@ -367,8 +370,23 @@ */

browserWSEndpoint,
browserURL,
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
transport = (yield WebSocketTransport.create(browserWSEndpoint)),
transport,
slowMo = 0,
} = options;
const connection = new Connection(browserWSEndpoint, transport, slowMo);
assert(Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect');
let connection = null;
if (transport) {
connection = new Connection('', transport, slowMo);
} else if (browserWSEndpoint) {
const connectionTransport = (yield WebSocketTransport.create(browserWSEndpoint));
connection = new Connection(browserWSEndpoint, connectionTransport, slowMo);
} else if (browserURL) {
const connectionURL = (yield getWSEndpoint(browserURL));
const connectionTransport = (yield WebSocketTransport.create(connectionURL));
connection = new Connection(connectionURL, connectionTransport, slowMo);
}
const {browserContextIds} = (yield connection.send('Target.getBrowserContexts'));

@@ -462,2 +480,34 @@ return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError));

/**
* @param {string} browserURL
* @return {!Promise<string>}
*/
function getWSEndpoint(browserURL) {
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
const endpointURL = URL.resolve(browserURL, '/json/version');
const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' });
const request = http.request(requestOptions, res => {
let data = '';
if (res.statusCode !== 200) {
// Consume response data to free up memory.
res.resume();
reject(new Error('HTTP ' + res.statusCode));
return;
}
res.setEncoding('utf8');
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(JSON.parse(data).webSocketDebuggerUrl));
});
request.on('error', reject);
request.end();
return promise.catch(e => {
e.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + e.message;
throw e;
});
}
/**
* @typedef {Object} Launcher.ChromeArgOptions

@@ -464,0 +514,0 @@ * @property {boolean=} headless

@@ -18,2 +18,3 @@ /**

const {helper, assert, debugError} = require('./helper');
const {Events} = require('./Events');
const Multimap = require('./Multimap');

@@ -295,3 +296,4 @@

_onRequestWillBeSent(event) {
if (this._protocolRequestInterceptionEnabled) {
// Request interception doesn't happen for data URLs with Network Service.
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
const requestHash = generateRequestHash(event.request);

@@ -366,3 +368,3 @@ const interceptionId = this._requestHashToInterceptionIds.firstValue(requestHash);

this._requestIdToRequest.set(event.requestId, request);
this.emit(NetworkManager.Events.Request, request);
this.emit(Events.NetworkManager.Request, request);
}

@@ -391,4 +393,4 @@

this._attemptedAuthentications.delete(request._interceptionId);
this.emit(NetworkManager.Events.Response, response);
this.emit(NetworkManager.Events.RequestFinished, request);
this.emit(Events.NetworkManager.Response, response);
this.emit(Events.NetworkManager.RequestFinished, request);
}

@@ -406,3 +408,3 @@

request._response = response;
this.emit(NetworkManager.Events.Response, response);
this.emit(Events.NetworkManager.Response, response);
}

@@ -426,3 +428,3 @@

this._attemptedAuthentications.delete(request._interceptionId);
this.emit(NetworkManager.Events.RequestFinished, request);
this.emit(Events.NetworkManager.RequestFinished, request);
}

@@ -445,3 +447,3 @@

this._attemptedAuthentications.delete(request._interceptionId);
this.emit(NetworkManager.Events.RequestFailed, request);
this.emit(Events.NetworkManager.RequestFailed, request);
}

@@ -586,2 +588,5 @@ }

})(function*(){
// Request interception is not supported for data: urls.
if (this._url.startsWith('data:'))
return;
assert(this._allowInterception, 'Request Interception is not enabled!');

@@ -711,2 +716,5 @@ assert(!this._interceptionHandled, 'Request is already handled!');

})(function*(){
// Request interception is not supported for data: urls.
if (this._url.startsWith('data:'))
return;
const errorReason = errorReasons[errorCode];

@@ -745,4 +753,2 @@ assert(errorReason, 'Unknown error code: ' + errorCode);

helper.tracePublicAPI(Request);
class Response {

@@ -966,4 +972,5 @@ /**

}
helper.tracePublicAPI(Response);
const IGNORED_HEADERS = new Set(['accept', 'referer', 'x-devtools-emulate-network-conditions-client-id', 'cookie', 'origin', 'content-type', 'intervention']);
/**

@@ -995,3 +1002,3 @@ * @param {!Protocol.Network.Request} request

header = header.toLowerCase();
if (header === 'accept' || header === 'referer' || header === 'x-devtools-emulate-network-conditions-client-id' || header === 'cookie')
if (IGNORED_HEADERS.has(header))
continue;

@@ -1052,9 +1059,2 @@ hash.headers[header] = headerValue;

NetworkManager.Events = {
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished',
};
const statusTexts = {

@@ -1123,2 +1123,2 @@ '100': 'Continue',

module.exports = {Request, Response, NetworkManager};
module.exports = {Request, Response, NetworkManager, SecurityDetails};

@@ -16,3 +16,2 @@ /**

*/
const {helper} = require('./helper');
const Launcher = require('./Launcher');

@@ -41,3 +40,3 @@ const BrowserFetcher = require('./BrowserFetcher');

/**
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint: string, transport?: !Puppeteer.ConnectionTransport})} options
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
* @return {!Promise<!Puppeteer.Browser>}

@@ -73,2 +72,1 @@ */

helper.tracePublicAPI(module.exports, 'Puppeteer');

@@ -0,3 +1,19 @@

/**
* Copyright 2019 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.
*/
const {Events} = require('./Events');
const {Page} = require('./Page');
const {helper} = require('./helper');

@@ -23,3 +39,41 @@ class Target {

this._pagePromise = null;
this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill);
this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill).then(/* async */ success => {return (fn => {
const gen = fn.call(this);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
value => {
step('next', value);
},
err => {
step('throw', err);
});
}
}
return step('next');
});
})(function*(){
if (!success)
return false;
const opener = this.opener();
if (!opener || !opener._pagePromise || this.type() !== 'page')
return true;
const openerPage = (yield opener._pagePromise);
if (!openerPage.listenerCount(Events.Page.Popup))
return true;
const popupPage = (yield this.page());
openerPage.emit(Events.Page.Popup, popupPage);
return true;
});});
this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill);

@@ -130,4 +184,2 @@ this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== '';

helper.tracePublicAPI(Target);
module.exports = {Target};

@@ -7,3 +7,3 @@ class TaskQueue {

/**
* @param {function()} task
* @param {Function} task
* @return {!Promise}

@@ -10,0 +10,0 @@ */

@@ -182,4 +182,3 @@ /**

}
helper.tracePublicAPI(Tracing);
module.exports = Tracing;

@@ -17,4 +17,5 @@ /**

const EventEmitter = require('events');
const {helper, debugError} = require('./helper');
const {ExecutionContext, JSHandle} = require('./ExecutionContext');
const {debugError} = require('./helper');
const {ExecutionContext} = require('./ExecutionContext');
const {JSHandle} = require('./JSHandle');

@@ -25,4 +26,4 @@ class Worker extends EventEmitter {

* @param {string} url
* @param {function(!string, !Array<!JSHandle>)} consoleAPICalled
* @param {function(!Protocol.Runtime.ExceptionDetails)} exceptionThrown
* @param {function(string, !Array<!JSHandle>, Protocol.Runtime.StackTrace=):void} consoleAPICalled
* @param {function(!Protocol.Runtime.ExceptionDetails):void} exceptionThrown
*/

@@ -70,3 +71,3 @@ constructor(client, url, consoleAPICalled, exceptionThrown) {

this._client.on('Runtime.consoleAPICalled', event => consoleAPICalled(event.type, event.args.map(jsHandleFactory)));
this._client.on('Runtime.consoleAPICalled', event => consoleAPICalled(event.type, event.args.map(jsHandleFactory), event.stackTrace));
this._client.on('Runtime.exceptionThrown', exception => exceptionThrown(exception.exceptionDetails));

@@ -116,3 +117,3 @@ }

/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -152,3 +153,3 @@ * @return {!Promise<*>}

/**
* @param {function()|string} pageFunction
* @param {Function|string} pageFunction
* @param {!Array<*>} args

@@ -189,2 +190,1 @@ * @return {!Promise<!JSHandle>}

module.exports = {Worker};
helper.tracePublicAPI(Worker);
{
"name": "puppeteer-core",
"version": "1.11.0",
"version": "1.12.0",
"description": "A high-level API to control headless Chrome over the DevTools Protocol",

@@ -11,3 +11,3 @@ "main": "index.js",

"puppeteer": {
"chromium_revision": "609904"
"chromium_revision": "624487"
},

@@ -18,3 +18,3 @@ "scripts": {

"test-doclint": "node utils/doclint/check_public_api/test/test.js && node utils/doclint/preprocessor/test.js",
"test": "npm run lint --silent && npm run coverage && npm run test-doclint && npm run test-node6-transformer",
"test": "npm run lint --silent && npm run coverage && npm run test-doclint && npm run test-node6-transformer && npm run test-types",
"install": "node install.js",

@@ -25,3 +25,3 @@ "lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .) && npm run tsc && npm run doc",

"test-node6-transformer": "node utils/node6-transform/test/test.js",
"build": "node utils/node6-transform/index.js",
"build": "node utils/node6-transform/index.js && node utils/doclint/generate_types",
"unit-node6": "node node6/test/test.js",

@@ -32,2 +32,3 @@ "tsc": "tsc -p .",

"bundle": "npx browserify -r ./index.js:puppeteer -o utils/browser/puppeteer-web.js",
"test-types": "node utils/doclint/generate_types && npx -p typescript@2.1 tsc -p utils/doclint/generate_types/test/",
"unit-bundle": "node utils/browser/test.js"

@@ -64,3 +65,3 @@ },

"text-diff": "^1.0.1",
"typescript": "3.1.6"
"typescript": "3.2.2"
},

@@ -67,0 +68,0 @@ "browser": {

# Puppeteer
<!-- [START badges] -->
[![Linux Build Status](https://img.shields.io/travis/GoogleChrome/puppeteer/master.svg)](https://travis-ci.org/GoogleChrome/puppeteer) [![Windows Build Status](https://img.shields.io/appveyor/ci/aslushnikov/puppeteer/master.svg?logo=appveyor)](https://ci.appveyor.com/project/aslushnikov/puppeteer/branch/master) [![Build Status](https://api.cirrus-ci.com/github/GoogleChrome/puppeteer.svg)](https://cirrus-ci.com/github/GoogleChrome/puppeteer) [![NPM puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer)
[![Linux Build Status](https://img.shields.io/travis/com/GoogleChrome/puppeteer/master.svg)](https://travis-ci.com/GoogleChrome/puppeteer) [![Windows Build Status](https://img.shields.io/appveyor/ci/aslushnikov/puppeteer/master.svg?logo=appveyor)](https://ci.appveyor.com/project/aslushnikov/puppeteer/branch/master) [![Build Status](https://api.cirrus-ci.com/github/GoogleChrome/puppeteer.svg)](https://cirrus-ci.com/github/GoogleChrome/puppeteer) [![NPM puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer)
<!-- [END badges] -->

@@ -9,3 +9,3 @@

###### [API](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md)
###### [API](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md)

@@ -41,3 +41,3 @@ > Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium.

Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, see [Environment variables](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#environment-variables).
Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, see [Environment variables](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#environment-variables).

@@ -55,3 +55,4 @@

`puppeteer-core` is intended to be a lightweight version of Puppeteer for launching an existing browser installation or for connecting to a remote one.
`puppeteer-core` is intended to be a lightweight version of Puppeteer for launching an existing browser installation or for connecting to a remote one. Be sure that the version of puppeteer-core you install is compatible with the
browser you intend to connect to.

@@ -65,3 +66,3 @@ See [puppeteer vs puppeteer-core](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core).

Puppeteer will be familiar to people using other browser testing frameworks. You create an instance
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#).
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#).

@@ -91,3 +92,3 @@ **Example** - navigating to https://example.com and saving a screenshot as *example.png*:

Puppeteer sets an initial page size to 800px x 600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pagesetviewportviewport).
Puppeteer sets an initial page size to 800px x 600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#pagesetviewportviewport).

@@ -117,3 +118,3 @@ **Example** - create a PDF.

See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pagepdfoptions) for more information about creating pdfs.
See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#pagepdfoptions) for more information about creating pdfs.

@@ -153,3 +154,3 @@ **Example** - evaluate script in the context of the page

See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`.
See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`.

@@ -163,3 +164,3 @@ <!-- [END getstarted] -->

Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the ['headless' option](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#puppeteerlaunchoptions) when launching a browser:
Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the ['headless' option](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#puppeteerlaunchoptions) when launching a browser:

@@ -180,3 +181,3 @@ ```js

See [`Puppeteer.launch()`](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#puppeteerlaunchoptions) for more information.
See [`Puppeteer.launch()`](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#puppeteerlaunchoptions) for more information.

@@ -193,3 +194,3 @@ See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/master/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.

- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md)
- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md)
- [Examples](https://github.com/GoogleChrome/puppeteer/tree/master/examples/)

@@ -323,4 +324,9 @@ - [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer)

However, oftentimes it is desirable to use Puppeteer with the official Google Chrome rather than Chromium. For this to work, you should pick the version of Puppeteer that uses the Chromium version close enough to Chrome.
However, oftentimes it is desirable to use Puppeteer with the official Google Chrome rather than Chromium. For this to work, you should install a `puppeteer-core` version that corresponds to the Chrome version.
For example, in order to drive Chrome 71 with puppeteer-core, use `chrome-71` npm tag:
```bash
npm install puppeteer-core@chrome-71
```
#### Q: Which Chromium version does Puppeteer use?

@@ -360,3 +366,3 @@

* Puppeteer is bundled with Chromium--not Chrome--and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.)
* Puppeteer is bundled with Chromium--not Chrome--and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/GoogleChrome/puppeteer/blob/v1.12.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.)
* Since Puppeteer (in all configurations) controls a desktop version of Chromium/Chrome, features that are only supported by the mobile version of Chrome are not supported. This means that Puppeteer [does not support HTTP Live Streaming (HLS)](https://caniuse.com/#feat=http-live-streaming).

@@ -363,0 +369,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc