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.8.0 to 1.9.0

lib/PipeTransport.js

6

CONTRIBUTING.md

@@ -136,8 +136,2 @@ # How to Contribute

- To filter tests by name:
```bash
npm run unit --filter=waitFor
```
- To run tests in parallel, use `-j` flag:

@@ -144,0 +138,0 @@

12

lib/Browser.js

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

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

@@ -49,5 +50,3 @@ class Browser extends EventEmitter {

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

@@ -192,2 +191,9 @@ this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));

/**
* @return {!Target}
*/
target() {
return this.targets().find(target => target.type() === 'browser');
}
/**
* @return {!Promise<!Array<!Puppeteer.Page>>}

@@ -194,0 +200,0 @@ */

@@ -31,9 +31,38 @@ /**

const DEFAULT_DOWNLOAD_HOST = 'https://storage.googleapis.com';
const supportedPlatforms = ['mac', 'linux', 'win32', 'win64'];
const downloadURLs = {
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip',
mac: '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
win32: '%s/chromium-browser-snapshots/Win/%d/chrome-win32.zip',
win64: '%s/chromium-browser-snapshots/Win_x64/%d/chrome-win32.zip',
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
mac: '%s/chromium-browser-snapshots/Mac/%d/%s.zip',
win32: '%s/chromium-browser-snapshots/Win/%d/%s.zip',
win64: '%s/chromium-browser-snapshots/Win_x64/%d/%s.zip',
};
/**
* @param {string} platform
* @param {string} revision
* @return {string}
*/
function archiveName(platform, revision) {
if (platform === 'linux')
return 'chrome-linux';
if (platform === 'mac')
return 'chrome-mac';
if (platform === 'win32' || platform === 'win64') {
// Windows archive name changed at r591479.
return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
}
return null;
}
/**
* @param {string} platform
* @param {string} host
* @param {string} revision
* @return {string}
*/
function downloadURL(platform, host, revision) {
return util.format(downloadURLs[platform], host, revision, archiveName(platform, revision));
}
const readdirAsync = helper.promisify(fs.readdir.bind(fs));

@@ -70,3 +99,2 @@ const mkdirAsync = helper.promisify(fs.mkdir.bind(fs));

}
const supportedPlatforms = ['mac', 'linux', 'win32', 'win64'];
assert(supportedPlatforms.includes(this._platform), 'Unsupported platform: ' + this._platform);

@@ -87,4 +115,3 @@ }

canDownload(revision) {
const url = util.format(downloadURLs[this._platform], this._downloadHost, revision);
const url = downloadURL(this._platform, this._downloadHost, revision);
let resolve;

@@ -108,4 +135,3 @@ const promise = new Promise(x => resolve = x);

async download(revision, progressCallback) {
let url = downloadURLs[this._platform];
url = util.format(url, this._downloadHost, revision);
const url = downloadURL(this._platform, this._downloadHost, revision);
const zipPath = path.join(this._downloadsFolder, `download-${this._platform}-${revision}.zip`);

@@ -158,11 +184,10 @@ const folderPath = this._getFolderPath(revision);

if (this._platform === 'mac')
executablePath = path.join(folderPath, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
executablePath = path.join(folderPath, archiveName(this._platform, revision), 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
else if (this._platform === 'linux')
executablePath = path.join(folderPath, 'chrome-linux', 'chrome');
executablePath = path.join(folderPath, archiveName(this._platform, revision), 'chrome');
else if (this._platform === 'win32' || this._platform === 'win64')
executablePath = path.join(folderPath, 'chrome-win32', 'chrome.exe');
executablePath = path.join(folderPath, archiveName(this._platform, revision), 'chrome.exe');
else
throw new Error('Unsupported platform: ' + this._platform);
let url = downloadURLs[this._platform];
url = util.format(url, this._downloadHost, revision);
const url = downloadURL(this._platform, this._downloadHost, revision);
const local = fs.existsSync(folderPath);

@@ -193,3 +218,3 @@ return {revision, executablePath, folderPath, local, url};

const [platform, revision] = splits;
if (!downloadURLs[platform])
if (!supportedPlatforms.includes(platform))
return null;

@@ -196,0 +221,0 @@ return {platform, revision};

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

const debugSession = require('debug')('puppeteer:session');
const EventEmitter = require('events');
const WebSocket = require('ws');
const Pipe = require('./Pipe');

@@ -28,25 +25,2 @@ class Connection extends EventEmitter {

* @param {string} url
* @param {number=} delay
* @return {!Promise<!Connection>}
*/
static async createForWebSocket(url, delay = 0) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url, { perMessageDeflate: false });
ws.on('open', () => resolve(new Connection(url, ws, delay)));
ws.on('error', reject);
});
}
/**
* @param {!NodeJS.WritableStream} pipeWrite
* @param {!NodeJS.ReadableStream} pipeRead
* @param {number=} delay
* @return {!Connection}
*/
static createForPipe(pipeWrite, pipeRead, delay = 0) {
return new Connection('', new Pipe(pipeWrite, pipeRead), delay);
}
/**
* @param {string} url
* @param {!Puppeteer.ConnectionTransport} transport

@@ -64,9 +38,22 @@ * @param {number=} delay

this._transport = transport;
this._transport.on('message', this._onMessage.bind(this));
this._transport.on('close', this._onClose.bind(this));
this._transport.onmessage = this._onMessage.bind(this);
this._transport.onclose = this._onClose.bind(this);
/** @type {!Map<string, !CDPSession>}*/
this._sessions = new Map();
this._closed = false;
}
/**
* @param {!CDPSession} session
* @return {!Connection}
*/
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 {string}

@@ -94,9 +81,2 @@ */

/**
* @param {function()} callback
*/
setClosedCallback(callback) {
this._closeCallback = callback;
}
/**
* @param {string} message

@@ -136,9 +116,7 @@ */

_onClose() {
if (this._closeCallback) {
this._closeCallback();
this._closeCallback = null;
}
this._transport.removeAllListeners();
// If transport throws any error at this point of time, we don't care and should swallow it.
this._transport.on('error', () => {});
if (this._closed)
return;
this._closed = true;
this._transport.onmessage = null;
this._transport.onclose = null;
for (const callback of this._callbacks.values())

@@ -150,2 +128,3 @@ callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));

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

@@ -170,2 +149,6 @@

Connection.Events = {
Disconnected: Symbol('Connection.Events.Disconnected'),
};
class CDPSession extends EventEmitter {

@@ -172,0 +155,0 @@ /**

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

const {TimeoutError} = require('./Errors');
const {NetworkManager} = require('./NetworkManager');
const {Connection} = require('./Connection');

@@ -31,7 +33,10 @@ const readFileAsync = helper.promisify(fs.readFile);

* @param {!Puppeteer.Page} page
* @param {!Puppeteer.NetworkManager} networkManager
*/
constructor(client, frameTree, page) {
constructor(client, frameTree, page, networkManager) {
super();
this._client = client;
this._page = page;
this._networkManager = networkManager;
this._defaultNavigationTimeout = 30000;
/** @type {!Map<string, !Frame>} */

@@ -56,2 +61,73 @@ this._frames = new Map();

/**
* @param {number} timeout
*/
setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
}
/**
* @param {!Puppeteer.Frame} frame
* @param {string} url
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
async navigateFrame(frame, url, options = {}) {
const referrer = typeof options.referer === 'string' ? options.referer : this._networkManager.extraHTTPHeaders()['referer'];
const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
const watcher = new NavigatorWatcher(this._client, this, this._networkManager, frame, timeout, options);
let ensureNewDocumentNavigation = false;
let error = await Promise.race([
navigate(this._client, url, referrer, frame._id),
watcher.timeoutOrTerminationPromise(),
]);
if (!error) {
error = await Promise.race([
watcher.timeoutOrTerminationPromise(),
ensureNewDocumentNavigation ? watcher.newDocumentNavigationPromise() : watcher.sameDocumentNavigationPromise(),
]);
}
watcher.dispose();
if (error)
throw error;
return watcher.navigationResponse();
/**
* @param {!Puppeteer.CDPSession} client
* @param {string} url
* @param {string} referrer
* @param {string} frameId
* @return {!Promise<?Error>}
*/
async function navigate(client, url, referrer, frameId) {
try {
const response = await client.send('Page.navigate', {url, referrer, frameId});
ensureNewDocumentNavigation = !!response.loaderId;
return response.errorText ? new Error(`${response.errorText} at ${url}`) : null;
} catch (error) {
return error;
}
}
}
/**
* @param {!Puppeteer.Frame} frame
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
async waitForFrameNavigation(frame, options) {
const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
const watcher = new NavigatorWatcher(this._client, this, this._networkManager, frame, timeout, options);
const error = await Promise.race([
watcher.timeoutOrTerminationPromise(),
watcher.sameDocumentNavigationPromise(),
watcher.newDocumentNavigationPromise()
]);
watcher.dispose();
if (error)
throw error;
return watcher.navigationResponse();
}
/**
* @param {!Protocol.Page.lifecycleEventPayload} event

@@ -326,2 +402,19 @@ */

/**
* @param {string} url
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
async goto(url, options = {}) {
return await this._frameManager.navigateFrame(this, url, options);
}
/**
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
async waitForNavigation(options = {}) {
return await this._frameManager.waitForFrameNavigation(this, options);
}
/**
* @return {!Promise<!ExecutionContext>}

@@ -703,3 +796,3 @@ */

if (helper.isNumber(selectorOrFunctionOrTimeout))
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
return new Promise(fulfill => setTimeout(fulfill, /** @type {number} */ (selectorOrFunctionOrTimeout)));
if (typeof selectorOrFunctionOrTimeout === 'function')

@@ -1025,2 +1118,174 @@ return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);

class NavigatorWatcher {
/**
* @param {!Puppeteer.CDPSession} client
* @param {!FrameManager} frameManager
* @param {!NetworkManager} networkManager
* @param {!Puppeteer.Frame} frame
* @param {number} timeout
* @param {!Object=} options
*/
constructor(client, frameManager, networkManager, frame, timeout, options = {}) {
assert(options.networkIdleTimeout === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
assert(options.networkIdleInflight === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
let waitUntil = ['load'];
if (Array.isArray(options.waitUntil))
waitUntil = options.waitUntil.slice();
else if (typeof options.waitUntil === 'string')
waitUntil = [options.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 = networkManager;
this._frame = frame;
this._initialLoaderId = frame._loaderId;
this._timeout = timeout;
/** @type {?Puppeteer.Request} */
this._navigationRequest = null;
this._hasSameDocumentNavigation = false;
this._eventListeners = [
helper.addEventListener(Connection.fromSession(client), Connection.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._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<?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 (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
return;
if (!checkLifecycle(this._frame, this._expectedLifecycle))
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};

@@ -22,2 +22,35 @@ /**

/**
* @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 {

@@ -107,63 +140,19 @@ /**

static tracePublicAPI(classType, publicName) {
let className = publicName || classType.prototype.constructor.name;
className = className.substring(0, 1).toLowerCase() + className.substring(1);
const debug = require('debug')(`puppeteer:${className}`);
if (!debug.enabled && !apiCoverage)
return;
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')
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction')
continue;
if (apiCoverage)
apiCoverage.set(`${className}.${methodName}`, false);
Reflect.set(classType.prototype, methodName, function(...args) {
const argsText = args.map(stringifyArgument).join(', ');
const callsite = `${className}.${methodName}(${argsText})`;
if (debug.enabled)
debug(callsite);
if (apiCoverage)
apiCoverage.set(`${className}.${methodName}`, true);
return method.call(this, ...args);
const syncStack = new Error();
return method.call(this, ...args).catch(e => {
const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1);
const clientStack = stack.substring(stack.indexOf('\n'));
if (!e.stack.includes(clientStack))
e.stack += '\n -- ASYNC --\n' + stack;
throw e;
});
});
}
if (classType.Events) {
if (apiCoverage) {
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) {
const argsText = [JSON.stringify(event)].concat(args.map(stringifyArgument)).join(', ');
if (debug.enabled && this.listenerCount(event))
debug(`${className}.emit(${argsText})`);
if (apiCoverage && this.listenerCount(event))
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
return method.call(this, event, ...args);
});
}
/**
* @param {!Object} arg
* @return {string}
*/
function stringifyArgument(arg) {
if (Helper.isString(arg) || Helper.isNumber(arg) || !arg)
return JSON.stringify(arg);
if (typeof arg === 'function') {
let text = arg.toString().split('\n').map(line => line.trim()).join('');
if (text.length > 20)
text = text.substring(0, 20) + '…';
return `"${text}"`;
}
const state = {};
const keys = Object.keys(arg);
for (const key of keys) {
const value = arg[key];
if (Helper.isString(value) || Helper.isNumber(value))
state[key] = JSON.stringify(value);
}
const name = arg.constructor.name === 'Object' ? '' : arg.constructor.name;
return name + JSON.stringify(state);
}
traceAPICoverage(classType, publicName);
}

@@ -173,5 +162,5 @@

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

@@ -184,3 +173,3 @@ static addEventListener(emitter, eventName, handler) {

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

@@ -187,0 +176,0 @@ static removeEventListeners(listeners) {

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

const {TimeoutError} = require('./Errors');
const WebSocketTransport = require('./WebSocketTransport');
const PipeTransport = require('./PipeTransport');

@@ -116,2 +118,3 @@ const mkdtempAsync = helper.promisify(fs.mkdtemp);

const usePipe = chromeArguments.includes('--remote-debugging-pipe');
/** @type {!Array<"ignore"|"pipe">} */
const stdio = usePipe ? ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'];

@@ -163,5 +166,7 @@ const chromeProcess = childProcess.spawn(

const browserWSEndpoint = await waitForWSEndpoint(chromeProcess, timeout, this._preferredRevision);
connection = await Connection.createForWebSocket(browserWSEndpoint, slowMo);
const transport = await WebSocketTransport.create(browserWSEndpoint);
connection = new Connection(browserWSEndpoint, transport, slowMo);
} else {
connection = Connection.createForPipe(/** @type {!NodeJS.WritableStream} */(chromeProcess.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (chromeProcess.stdio[4]), slowMo);
const transport = new PipeTransport(/** @type {!NodeJS.WritableStream} */(chromeProcess.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (chromeProcess.stdio[4]));
connection = new Connection('', transport, slowMo);
}

@@ -272,3 +277,3 @@ const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, chromeProcess, gracefullyCloseChrome);

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

@@ -281,5 +286,6 @@ */

defaultViewport = {width: 800, height: 600},
transport = await WebSocketTransport.create(browserWSEndpoint),
slowMo = 0,
} = options;
const connection = await Connection.createForWebSocket(browserWSEndpoint, slowMo);
const connection = new Connection(browserWSEndpoint, transport, slowMo);
const {browserContextIds} = await connection.send('Target.getBrowserContexts');

@@ -286,0 +292,0 @@ return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError));

@@ -23,8 +23,7 @@ /**

* @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.FrameManager} frameManager
*/
constructor(client, frameManager) {
constructor(client) {
super();
this._client = client;
this._frameManager = frameManager;
this._frameManager = null;
/** @type {!Map<string, !Request>} */

@@ -59,2 +58,9 @@ this._requestIdToRequest = new Map();

/**
* @param {!Puppeteer.FrameManager} frameManager
*/
setFrameManager(frameManager) {
this._frameManager = frameManager;
}
/**
* @param {?{username: string, password: string}} credentials

@@ -201,3 +207,3 @@ */

}
const frame = event.frameId ? this._frameManager.frame(event.frameId) : null;
const frame = event.frameId && this._frameManager ? this._frameManager.frame(event.frameId) : null;
const request = new Request(this._client, frame, interceptionId, this._userRequestInterceptionEnabled, event, redirectChain);

@@ -255,3 +261,7 @@ this._requestIdToRequest.set(event.requestId, request);

return;
request.response()._bodyLoadedPromiseFulfill.call(null);
// Under certain conditions we never get the Network.responseReceived
// event from protocol. @see https://crbug.com/883475
if (request.response())
request.response()._bodyLoadedPromiseFulfill.call(null);
this._requestIdToRequest.delete(request._requestId);

@@ -427,6 +437,4 @@ this._attemptedAuthentications.delete(request._interceptionId);

responseHeaders['content-type'] = response.contentType;
if (responseBody && !('content-length' in responseHeaders)) {
// @ts-ignore
if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = Buffer.byteLength(responseBody);
}

@@ -627,2 +635,9 @@ const statusCode = response.status || 200;

}
/**
* @return {?Puppeteer.Frame}
*/
frame() {
return this._request.frame();
}
}

@@ -629,0 +644,0 @@ helper.tracePublicAPI(Response);

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

const {NetworkManager} = require('./NetworkManager');
const {NavigatorWatcher} = require('./NavigatorWatcher');
const {Dialog} = require('./Dialog');

@@ -83,5 +82,6 @@ const {EmulationManager} = require('./EmulationManager');

this._touchscreen = new Touchscreen(client, this._keyboard);
this._networkManager = new NetworkManager(client);
/** @type {!FrameManager} */
this._frameManager = new FrameManager(client, frameTree, this);
this._networkManager = new NetworkManager(client, this._frameManager);
this._frameManager = new FrameManager(client, frameTree, this, this._networkManager);
this._networkManager.setFrameManager(this._frameManager);
this._emulationManager = new EmulationManager(client);

@@ -93,3 +93,2 @@ this._tracing = new Tracing(client);

this._coverage = new Coverage(client);
this._defaultNavigationTimeout = 30000;
this._javascriptEnabled = true;

@@ -260,3 +259,3 @@ /** @type {?Puppeteer.Viewport} */

setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
this._frameManager.setDefaultNavigationTimeout(timeout);
}

@@ -584,49 +583,3 @@

async goto(url, options = {}) {
const referrer = typeof options.referer === 'string' ? options.referer : this._networkManager.extraHTTPHeaders()['referer'];
/** @type {Map<string, !Puppeteer.Request>} */
const requests = new Map();
const eventListeners = [
helper.addEventListener(this._networkManager, NetworkManager.Events.Request, request => {
if (!requests.get(request.url()))
requests.set(request.url(), request);
})
];
const mainFrame = this._frameManager.mainFrame();
const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
const watcher = new NavigatorWatcher(this._frameManager, mainFrame, timeout, options);
let ensureNewDocumentNavigation = false;
let error = await Promise.race([
navigate(this._client, url, referrer),
watcher.timeoutPromise(),
]);
if (!error) {
error = await Promise.race([
watcher.timeoutPromise(),
ensureNewDocumentNavigation ? watcher.newDocumentNavigationPromise() : watcher.sameDocumentNavigationPromise(),
]);
}
watcher.dispose();
helper.removeEventListeners(eventListeners);
if (error)
throw error;
const request = requests.get(mainFrame._navigationURL);
return request ? request.response() : null;
/**
* @param {!Puppeteer.CDPSession} client
* @param {string} url
* @param {string} referrer
* @return {!Promise<?Error>}
*/
async function navigate(client, url, referrer) {
try {
const response = await client.send('Page.navigate', {url, referrer});
ensureNewDocumentNavigation = !!response.loaderId;
return response.errorText ? new Error(`${response.errorText} at ${url}`) : null;
} catch (error) {
return error;
}
}
return await this._frameManager.mainFrame().goto(url, options);
}

@@ -651,18 +604,3 @@

async waitForNavigation(options = {}) {
const mainFrame = this._frameManager.mainFrame();
const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
const watcher = new NavigatorWatcher(this._frameManager, mainFrame, timeout, options);
const responses = new Map();
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url(), response));
const error = await Promise.race([
watcher.timeoutPromise(),
watcher.sameDocumentNavigationPromise(),
watcher.newDocumentNavigationPromise()
]);
watcher.dispose();
helper.removeEventListeners([listener]);
if (error)
throw error;
return responses.get(this.mainFrame().url()) || null;
return await this._frameManager.mainFrame().waitForNavigation(options);
}

@@ -867,3 +805,2 @@

if (options.fullPage) {
assert(this._viewport, 'fullPage screenshots do not work without first setting viewport.');
const metrics = await this._client.send('Page.getLayoutMetrics');

@@ -875,17 +812,19 @@ const width = Math.ceil(metrics.contentSize.width);

clip = { x: 0, y: 0, width, height, scale: 1 };
const mobile = this._viewport.isMobile || false;
const deviceScaleFactor = this._viewport.deviceScaleFactor || 1;
const landscape = this._viewport.isLandscape || false;
const {
isMobile = false,
deviceScaleFactor = 1,
isLandscape = false
} = this._viewport || {};
/** @type {!Protocol.Emulation.ScreenOrientation} */
const screenOrientation = landscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
await this._client.send('Emulation.setDeviceMetricsOverride', { mobile, width, height, deviceScaleFactor, screenOrientation });
const screenOrientation = isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
await this._client.send('Emulation.setDeviceMetricsOverride', { mobile: isMobile, width, height, deviceScaleFactor, screenOrientation });
}
if (options.omitBackground)
const shouldSetDefaultBackground = options.omitBackground && format === 'png';
if (shouldSetDefaultBackground)
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color: { r: 0, g: 0, b: 0, a: 0 } });
const result = await this._client.send('Page.captureScreenshot', { format, quality: options.quality, clip });
if (options.omitBackground)
if (shouldSetDefaultBackground)
await this._client.send('Emulation.setDefaultBackgroundColorOverride');
if (options.fullPage)
if (options.fullPage && this._viewport)
await this.setViewport(this._viewport);

@@ -1091,3 +1030,3 @@

/** @enum {string} */
/** @enum {!{width: number, height: number}} */
Page.PaperFormats = {

@@ -1094,0 +1033,0 @@ letter: {width: 8.5, height: 11},

@@ -40,3 +40,3 @@ /**

/**
* @param {{browserWSEndpoint: string, ignoreHTTPSErrors: boolean}} options
* @param {{browserWSEndpoint: string, ignoreHTTPSErrors: boolean, transport?: !Puppeteer.ConnectionTransport}} options
* @return {!Promise<!Puppeteer.Browser>}

@@ -43,0 +43,0 @@ */

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

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

@@ -49,5 +50,3 @@ class Browser extends EventEmitter {

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

@@ -374,2 +373,9 @@ this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));

/**
* @return {!Target}
*/
target() {
return this.targets().find(target => target.type() === 'browser');
}
/**
* @return {!Promise<!Array<!Puppeteer.Page>>}

@@ -376,0 +382,0 @@ */

@@ -31,9 +31,38 @@ /**

const DEFAULT_DOWNLOAD_HOST = 'https://storage.googleapis.com';
const supportedPlatforms = ['mac', 'linux', 'win32', 'win64'];
const downloadURLs = {
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip',
mac: '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
win32: '%s/chromium-browser-snapshots/Win/%d/chrome-win32.zip',
win64: '%s/chromium-browser-snapshots/Win_x64/%d/chrome-win32.zip',
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
mac: '%s/chromium-browser-snapshots/Mac/%d/%s.zip',
win32: '%s/chromium-browser-snapshots/Win/%d/%s.zip',
win64: '%s/chromium-browser-snapshots/Win_x64/%d/%s.zip',
};
/**
* @param {string} platform
* @param {string} revision
* @return {string}
*/
function archiveName(platform, revision) {
if (platform === 'linux')
return 'chrome-linux';
if (platform === 'mac')
return 'chrome-mac';
if (platform === 'win32' || platform === 'win64') {
// Windows archive name changed at r591479.
return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
}
return null;
}
/**
* @param {string} platform
* @param {string} host
* @param {string} revision
* @return {string}
*/
function downloadURL(platform, host, revision) {
return util.format(downloadURLs[platform], host, revision, archiveName(platform, revision));
}
const readdirAsync = helper.promisify(fs.readdir.bind(fs));

@@ -70,3 +99,2 @@ const mkdirAsync = helper.promisify(fs.mkdir.bind(fs));

}
const supportedPlatforms = ['mac', 'linux', 'win32', 'win64'];
assert(supportedPlatforms.includes(this._platform), 'Unsupported platform: ' + this._platform);

@@ -87,4 +115,3 @@ }

canDownload(revision) {
const url = util.format(downloadURLs[this._platform], this._downloadHost, revision);
const url = downloadURL(this._platform, this._downloadHost, revision);
let resolve;

@@ -134,4 +161,3 @@ const promise = new Promise(x => resolve = x);

})(function*(){
let url = downloadURLs[this._platform];
url = util.format(url, this._downloadHost, revision);
const url = downloadURL(this._platform, this._downloadHost, revision);
const zipPath = path.join(this._downloadsFolder, `download-${this._platform}-${revision}.zip`);

@@ -236,11 +262,10 @@ const folderPath = this._getFolderPath(revision);

if (this._platform === 'mac')
executablePath = path.join(folderPath, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
executablePath = path.join(folderPath, archiveName(this._platform, revision), 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
else if (this._platform === 'linux')
executablePath = path.join(folderPath, 'chrome-linux', 'chrome');
executablePath = path.join(folderPath, archiveName(this._platform, revision), 'chrome');
else if (this._platform === 'win32' || this._platform === 'win64')
executablePath = path.join(folderPath, 'chrome-win32', 'chrome.exe');
executablePath = path.join(folderPath, archiveName(this._platform, revision), 'chrome.exe');
else
throw new Error('Unsupported platform: ' + this._platform);
let url = downloadURLs[this._platform];
url = util.format(url, this._downloadHost, revision);
const url = downloadURL(this._platform, this._downloadHost, revision);
const local = fs.existsSync(folderPath);

@@ -271,3 +296,3 @@ return {revision, executablePath, folderPath, local, url};

const [platform, revision] = splits;
if (!downloadURLs[platform])
if (!supportedPlatforms.includes(platform))
return null;

@@ -274,0 +299,0 @@ return {platform, revision};

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

const debugSession = require('debug')('puppeteer:session');
const EventEmitter = require('events');
const WebSocket = require('ws');
const Pipe = require('./Pipe');

@@ -28,51 +25,2 @@ class Connection extends EventEmitter {

* @param {string} url
* @param {number=} delay
* @return {!Promise<!Connection>}
*/
static /* async */ createForWebSocket(url, delay = 0) {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*(){
return new Promise((resolve, reject) => {
const ws = new WebSocket(url, { perMessageDeflate: false });
ws.on('open', () => resolve(new Connection(url, ws, delay)));
ws.on('error', reject);
});
});}
/**
* @param {!NodeJS.WritableStream} pipeWrite
* @param {!NodeJS.ReadableStream} pipeRead
* @param {number=} delay
* @return {!Connection}
*/
static createForPipe(pipeWrite, pipeRead, delay = 0) {
return new Connection('', new Pipe(pipeWrite, pipeRead), delay);
}
/**
* @param {string} url
* @param {!Puppeteer.ConnectionTransport} transport

@@ -90,9 +38,22 @@ * @param {number=} delay

this._transport = transport;
this._transport.on('message', this._onMessage.bind(this));
this._transport.on('close', this._onClose.bind(this));
this._transport.onmessage = this._onMessage.bind(this);
this._transport.onclose = this._onClose.bind(this);
/** @type {!Map<string, !CDPSession>}*/
this._sessions = new Map();
this._closed = false;
}
/**
* @param {!CDPSession} session
* @return {!Connection}
*/
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 {string}

@@ -120,9 +81,2 @@ */

/**
* @param {function()} callback
*/
setClosedCallback(callback) {
this._closeCallback = callback;
}
/**
* @param {string} message

@@ -188,9 +142,7 @@ */

_onClose() {
if (this._closeCallback) {
this._closeCallback();
this._closeCallback = null;
}
this._transport.removeAllListeners();
// If transport throws any error at this point of time, we don't care and should swallow it.
this._transport.on('error', () => {});
if (this._closed)
return;
this._closed = true;
this._transport.onmessage = null;
this._transport.onclose = null;
for (const callback of this._callbacks.values())

@@ -202,2 +154,3 @@ callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));

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

@@ -248,2 +201,6 @@

Connection.Events = {
Disconnected: Symbol('Connection.Events.Disconnected'),
};
class CDPSession extends EventEmitter {

@@ -250,0 +207,0 @@ /**

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

const {TimeoutError} = require('./Errors');
const {NetworkManager} = require('./NetworkManager');
const {Connection} = require('./Connection');

@@ -31,7 +33,10 @@ const readFileAsync = helper.promisify(fs.readFile);

* @param {!Puppeteer.Page} page
* @param {!Puppeteer.NetworkManager} networkManager
*/
constructor(client, frameTree, page) {
constructor(client, frameTree, page, networkManager) {
super();
this._client = client;
this._page = page;
this._networkManager = networkManager;
this._defaultNavigationTimeout = 30000;
/** @type {!Map<string, !Frame>} */

@@ -56,2 +61,151 @@ this._frames = new Map();

/**
* @param {number} timeout
*/
setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
}
/**
* @param {!Puppeteer.Frame} frame
* @param {string} url
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
/* async */ navigateFrame(frame, url, 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*(){
const referrer = typeof options.referer === 'string' ? options.referer : this._networkManager.extraHTTPHeaders()['referer'];
const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
const watcher = new NavigatorWatcher(this._client, this, this._networkManager, frame, timeout, options);
let ensureNewDocumentNavigation = false;
let error = (yield Promise.race([
navigate(this._client, url, referrer, frame._id),
watcher.timeoutOrTerminationPromise(),
]));
if (!error) {
error = (yield Promise.race([
watcher.timeoutOrTerminationPromise(),
ensureNewDocumentNavigation ? watcher.newDocumentNavigationPromise() : watcher.sameDocumentNavigationPromise(),
]));
}
watcher.dispose();
if (error)
throw error;
return watcher.navigationResponse();
/**
* @param {!Puppeteer.CDPSession} client
* @param {string} url
* @param {string} referrer
* @param {string} frameId
* @return {!Promise<?Error>}
*/
/* async */ function navigate(client, url, referrer, frameId) {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*(){
try {
const response = (yield client.send('Page.navigate', {url, referrer, frameId}));
ensureNewDocumentNavigation = !!response.loaderId;
return response.errorText ? new Error(`${response.errorText} at ${url}`) : null;
} catch (error) {
return error;
}
});}
});}
/**
* @param {!Puppeteer.Frame} frame
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
/* async */ waitForFrameNavigation(frame, 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*(){
const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
const watcher = new NavigatorWatcher(this._client, this, this._networkManager, frame, timeout, options);
const error = (yield Promise.race([
watcher.timeoutOrTerminationPromise(),
watcher.sameDocumentNavigationPromise(),
watcher.newDocumentNavigationPromise()
]));
watcher.dispose();
if (error)
throw error;
return watcher.navigationResponse();
});}
/**
* @param {!Protocol.Page.lifecycleEventPayload} event

@@ -326,2 +480,71 @@ */

/**
* @param {string} url
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
/* async */ goto(url, 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*(){
return (yield this._frameManager.navigateFrame(this, url, options));
});}
/**
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}
*/
/* async */ waitForNavigation(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*(){
return (yield this._frameManager.waitForFrameNavigation(this, options));
});}
/**
* @return {!Promise<!ExecutionContext>}

@@ -1249,3 +1472,3 @@ */

if (helper.isNumber(selectorOrFunctionOrTimeout))
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
return new Promise(fulfill => setTimeout(fulfill, /** @type {number} */ (selectorOrFunctionOrTimeout)));
if (typeof selectorOrFunctionOrTimeout === 'function')

@@ -1649,2 +1872,174 @@ return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);

class NavigatorWatcher {
/**
* @param {!Puppeteer.CDPSession} client
* @param {!FrameManager} frameManager
* @param {!NetworkManager} networkManager
* @param {!Puppeteer.Frame} frame
* @param {number} timeout
* @param {!Object=} options
*/
constructor(client, frameManager, networkManager, frame, timeout, options = {}) {
assert(options.networkIdleTimeout === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
assert(options.networkIdleInflight === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
let waitUntil = ['load'];
if (Array.isArray(options.waitUntil))
waitUntil = options.waitUntil.slice();
else if (typeof options.waitUntil === 'string')
waitUntil = [options.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 = networkManager;
this._frame = frame;
this._initialLoaderId = frame._loaderId;
this._timeout = timeout;
/** @type {?Puppeteer.Request} */
this._navigationRequest = null;
this._hasSameDocumentNavigation = false;
this._eventListeners = [
helper.addEventListener(Connection.fromSession(client), Connection.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._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<?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 (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
return;
if (!checkLifecycle(this._frame, this._expectedLifecycle))
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};

@@ -22,2 +22,35 @@ /**

/**
* @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 {

@@ -133,63 +166,19 @@ /**

static tracePublicAPI(classType, publicName) {
let className = publicName || classType.prototype.constructor.name;
className = className.substring(0, 1).toLowerCase() + className.substring(1);
const debug = require('debug')(`puppeteer:${className}`);
if (!debug.enabled && !apiCoverage)
return;
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')
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction')
continue;
if (apiCoverage)
apiCoverage.set(`${className}.${methodName}`, false);
Reflect.set(classType.prototype, methodName, function(...args) {
const argsText = args.map(stringifyArgument).join(', ');
const callsite = `${className}.${methodName}(${argsText})`;
if (debug.enabled)
debug(callsite);
if (apiCoverage)
apiCoverage.set(`${className}.${methodName}`, true);
return method.call(this, ...args);
const syncStack = new Error();
return method.call(this, ...args).catch(e => {
const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1);
const clientStack = stack.substring(stack.indexOf('\n'));
if (!e.stack.includes(clientStack))
e.stack += '\n -- ASYNC --\n' + stack;
throw e;
});
});
}
if (classType.Events) {
if (apiCoverage) {
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) {
const argsText = [JSON.stringify(event)].concat(args.map(stringifyArgument)).join(', ');
if (debug.enabled && this.listenerCount(event))
debug(`${className}.emit(${argsText})`);
if (apiCoverage && this.listenerCount(event))
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
return method.call(this, event, ...args);
});
}
/**
* @param {!Object} arg
* @return {string}
*/
function stringifyArgument(arg) {
if (Helper.isString(arg) || Helper.isNumber(arg) || !arg)
return JSON.stringify(arg);
if (typeof arg === 'function') {
let text = arg.toString().split('\n').map(line => line.trim()).join('');
if (text.length > 20)
text = text.substring(0, 20) + '…';
return `"${text}"`;
}
const state = {};
const keys = Object.keys(arg);
for (const key of keys) {
const value = arg[key];
if (Helper.isString(value) || Helper.isNumber(value))
state[key] = JSON.stringify(value);
}
const name = arg.constructor.name === 'Object' ? '' : arg.constructor.name;
return name + JSON.stringify(state);
}
traceAPICoverage(classType, publicName);
}

@@ -199,5 +188,5 @@

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

@@ -210,3 +199,3 @@ static addEventListener(emitter, eventName, handler) {

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

@@ -213,0 +202,0 @@ static removeEventListeners(listeners) {

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

const {TimeoutError} = require('./Errors');
const WebSocketTransport = require('./WebSocketTransport');
const PipeTransport = require('./PipeTransport');

@@ -142,2 +144,3 @@ const mkdtempAsync = helper.promisify(fs.mkdtemp);

const usePipe = chromeArguments.includes('--remote-debugging-pipe');
/** @type {!Array<"ignore"|"pipe">} */
const stdio = usePipe ? ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'];

@@ -189,5 +192,7 @@ const chromeProcess = childProcess.spawn(

const browserWSEndpoint = (yield waitForWSEndpoint(chromeProcess, timeout, this._preferredRevision));
connection = (yield Connection.createForWebSocket(browserWSEndpoint, slowMo));
const transport = (yield WebSocketTransport.create(browserWSEndpoint));
connection = new Connection(browserWSEndpoint, transport, slowMo);
} else {
connection = Connection.createForPipe(/** @type {!NodeJS.WritableStream} */(chromeProcess.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (chromeProcess.stdio[4]), slowMo);
const transport = new PipeTransport(/** @type {!NodeJS.WritableStream} */(chromeProcess.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (chromeProcess.stdio[4]));
connection = new Connection('', transport, slowMo);
}

@@ -324,3 +329,3 @@ const browser = (yield Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, chromeProcess, gracefullyCloseChrome));

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

@@ -359,5 +364,6 @@ */

defaultViewport = {width: 800, height: 600},
transport = (yield WebSocketTransport.create(browserWSEndpoint)),
slowMo = 0,
} = options;
const connection = (yield Connection.createForWebSocket(browserWSEndpoint, slowMo));
const connection = new Connection(browserWSEndpoint, transport, slowMo);
const {browserContextIds} = (yield connection.send('Target.getBrowserContexts'));

@@ -364,0 +370,0 @@ return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError));

@@ -23,8 +23,7 @@ /**

* @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.FrameManager} frameManager
*/
constructor(client, frameManager) {
constructor(client) {
super();
this._client = client;
this._frameManager = frameManager;
this._frameManager = null;
/** @type {!Map<string, !Request>} */

@@ -59,2 +58,9 @@ this._requestIdToRequest = new Map();

/**
* @param {!Puppeteer.FrameManager} frameManager
*/
setFrameManager(frameManager) {
this._frameManager = frameManager;
}
/**
* @param {?{username: string, password: string}} credentials

@@ -357,3 +363,3 @@ */

}
const frame = event.frameId ? this._frameManager.frame(event.frameId) : null;
const frame = event.frameId && this._frameManager ? this._frameManager.frame(event.frameId) : null;
const request = new Request(this._client, frame, interceptionId, this._userRequestInterceptionEnabled, event, redirectChain);

@@ -411,3 +417,7 @@ this._requestIdToRequest.set(event.requestId, request);

return;
request.response()._bodyLoadedPromiseFulfill.call(null);
// Under certain conditions we never get the Network.responseReceived
// event from protocol. @see https://crbug.com/883475
if (request.response())
request.response()._bodyLoadedPromiseFulfill.call(null);
this._requestIdToRequest.delete(request._requestId);

@@ -635,6 +645,4 @@ this._attemptedAuthentications.delete(request._interceptionId);

responseHeaders['content-type'] = response.contentType;
if (responseBody && !('content-length' in responseHeaders)) {
// @ts-ignore
if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = Buffer.byteLength(responseBody);
}

@@ -939,2 +947,9 @@ const statusCode = response.status || 200;

}
/**
* @return {?Puppeteer.Frame}
*/
frame() {
return this._request.frame();
}
}

@@ -941,0 +956,0 @@ helper.tracePublicAPI(Response);

@@ -40,3 +40,3 @@ /**

/**
* @param {{browserWSEndpoint: string, ignoreHTTPSErrors: boolean}} options
* @param {{browserWSEndpoint: string, ignoreHTTPSErrors: boolean, transport?: !Puppeteer.ConnectionTransport}} options
* @return {!Promise<!Puppeteer.Browser>}

@@ -43,0 +43,0 @@ */

{
"name": "puppeteer-core",
"version": "1.8.0",
"version": "1.9.0",
"description": "A high-level API to control headless Chrome over the DevTools Protocol",

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

"puppeteer": {
"chromium_revision": "588429"
"chromium_revision": "594312"
},

@@ -28,3 +28,5 @@ "scripts": {

"prepublishOnly": "npm run build",
"apply-next-version": "node utils/apply_next_version.js"
"apply-next-version": "node utils/apply_next_version.js",
"bundle": "npx browserify -r ./index.js:puppeteer -o utils/browser/puppeteer-web.js",
"unit-bundle": "node utils/browser/test.js"
},

@@ -47,3 +49,3 @@ "author": "The Chromium Authors",

"@types/mime": "^2.0.0",
"@types/node": "^8.0.26",
"@types/node": "^8.10.34",
"@types/rimraf": "^2.0.2",

@@ -55,2 +57,3 @@ "@types/ws": "^3.0.2",

"esprima": "^4.0.0",
"jpeg-js": "^0.3.4",
"minimist": "^1.2.0",

@@ -61,4 +64,13 @@ "ncp": "^2.0.0",

"text-diff": "^1.0.1",
"typescript": "^3.0.1"
"typescript": "^3.1.1"
},
"browser": {
"./lib/BrowserFetcher.js": false,
"./node6/lib/Puppeteer": false,
"ws": "./utils/browser/WebSocket",
"fs": false,
"child_process": false,
"rimraf": false,
"readline": false
}
}

@@ -9,3 +9,3 @@ # Puppeteer

###### [API](https://github.com/GoogleChrome/puppeteer/blob/v1.8.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md)
###### [API](https://github.com/GoogleChrome/puppeteer/blob/v1.9.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.8.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.9.0/docs/api.md#environment-variables).

@@ -52,2 +52,3 @@

npm i puppeteer-core
# or "yarn add puppeteer-core"
```

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

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.8.0/docs/api.md#).
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/GoogleChrome/puppeteer/blob/v1.9.0/docs/api.md#).

@@ -90,3 +91,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.8.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.9.0/docs/api.md#pagesetviewportviewport).

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

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

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

See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v1.8.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.9.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`.

@@ -162,3 +163,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.8.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.9.0/docs/api.md#puppeteerlaunchoptions) when launching a browser:

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

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

@@ -192,6 +193,7 @@ 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/+/lkcr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.

- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v1.8.0/docs/api.md)
- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v1.9.0/docs/api.md)
- [Examples](https://github.com/GoogleChrome/puppeteer/tree/master/examples/)
- [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer)
<!-- [START debugging] -->

@@ -242,3 +244,3 @@

5. Enable verbose logging - All public API calls and internal protocol traffic
5. Enable verbose logging - internal DevTools protocol traffic
will be logged via the [`debug`](https://github.com/visionmedia/debug) module under the `puppeteer` namespace.

@@ -250,9 +252,21 @@

# Debug output can be enabled/disabled by namespace
env DEBUG="puppeteer:*,-puppeteer:protocol" node script.js # everything BUT protocol messages
env DEBUG="puppeteer:protocol" node script.js # protocol connection messages
env DEBUG="puppeteer:session" node script.js # protocol session messages (protocol messages to targets)
env DEBUG="puppeteer:mouse,puppeteer:keyboard" node script.js # only Mouse and Keyboard API calls
# Protocol traffic can be rather noisy. This example filters out all Network domain messages
env DEBUG="puppeteer:*" env DEBUG_COLORS=true node script.js 2>&1 | grep -v '"Network'
env DEBUG="puppeteer:session" env DEBUG_COLORS=true node script.js 2>&1 | grep -v '"Network'
6. Debug your Puppeteer (node) code easily, using [ndb](https://github.com/GoogleChromeLabs/ndb)
- `npm install -g ndb` (or even better, use [npx](https://github.com/zkat/npx)!)
- add a `debugger` to your Puppeteer (node) code
- add `ndb` (or `npx ndb`) before your test command. For example:
`ndb jest` or `ndb mocha` (or `npx ndb jest` / `npx ndb mocha`)
- debug your test inside chromium like a boss!
<!-- [END debugging] -->

@@ -347,3 +361,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.8.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.9.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).

@@ -350,0 +364,0 @@

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