feat(Launcher): introduce pipe option (#2288)

This patch introduces a new `pipe` option to the launcher to connect over a pipe.

In certain environments, exposing web socket for remote debugging is a security risk.
Pipe connection eliminates this risk.
This commit is contained in:
JoelEinbinder
2018-04-03 15:05:27 -07:00
committed by Andrey Lushnikov
parent f51a315911
commit 94c32e4bc8
6 changed files with 76 additions and 15 deletions

View File

@@ -22,7 +22,7 @@ const TaskQueue = require('./TaskQueue');
class Browser extends EventEmitter {
/**
* @param {!Puppeteer.Connection} connection
* @param {!Object=} options
* @param {!BrowserOptions=} options
* @param {?Puppeteer.ChildProcess} process
* @param {(function():Promise)=} closeCallback
*/
@@ -53,7 +53,7 @@ class Browser extends EventEmitter {
/**
* @param {!Puppeteer.Connection} connection
* @param {!Object=} options
* @param {!BrowserOptions=} options
* @param {?Puppeteer.ChildProcess} process
* @param {function()=} closeCallback
*/
@@ -68,7 +68,7 @@ class Browser extends EventEmitter {
*/
async _targetCreated(event) {
const targetInfo = event.targetInfo;
const target = new Target(targetInfo, () => this._connection.createSession(targetInfo.targetId), this._ignoreHTTPSErrors, this._appMode, this._screenshotTaskQueue);
const target = new Target(targetInfo, () => this._connection.createSession(targetInfo.targetId), this._ignoreHTTPSErrors, !this._appMode, this._screenshotTaskQueue);
console.assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
this._targets.set(event.targetInfo.targetId, target);
@@ -178,3 +178,9 @@ Browser.Events = {
helper.tracePublicAPI(Browser);
module.exports = Browser;
/**
* @typedef {Object} BrowserOptions
* @property {boolean=} appMode
* @property {boolean=} ignoreHTTPSErrors
*/

View File

@@ -50,12 +50,11 @@ const AUTOMATION_ARGS = [
'--enable-automation',
'--password-store=basic',
'--use-mock-keychain',
'--remote-debugging-port=0',
];
class Launcher {
/**
* @param {!Object=} options
* @param {!LaunchOptions=} options
* @return {!Promise<!Browser>}
*/
static async launch(options) {
@@ -67,11 +66,14 @@ class Launcher {
chromeArguments.push(...DEFAULT_ARGS);
if (options.appMode) {
options.headless = false;
chromeArguments.push('--remote-debugging-pipe');
options.pipe = true;
} else if (!options.ignoreDefaultArgs) {
chromeArguments.push(...AUTOMATION_ARGS);
}
if (!options.ignoreDefaultArgs || !chromeArguments.some(argument => argument.startsWith('--remote-debugging-')))
chromeArguments.push(options.pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0');
if (!options.args || !options.args.some(arg => arg.startsWith('--user-data-dir'))) {
if (!options.userDataDir)
temporaryUserDataDir = await mkdtempAsync(CHROME_PROFILE_PATH);
@@ -100,8 +102,10 @@ class Launcher {
if (Array.isArray(options.args))
chromeArguments.push(...options.args);
const usePipe = chromeArguments.includes('--remote-debugging-pipe');
const stdio = ['pipe', 'pipe', 'pipe'];
if (options.appMode)
if (usePipe)
stdio.push('pipe', 'pipe');
const chromeProcess = childProcess.spawn(
chromeExecutable,
@@ -147,7 +151,7 @@ class Launcher {
let connection = null;
try {
const connectionDelay = options.slowMo || 0;
if (!options.appMode) {
if (!usePipe) {
const timeout = helper.isNumber(options.timeout) ? options.timeout : 30000;
const browserWSEndpoint = await waitForWSEndpoint(chromeProcess, timeout);
connection = await Connection.createForWebSocket(browserWSEndpoint, connectionDelay);
@@ -277,4 +281,25 @@ function waitForWSEndpoint(chromeProcess, timeout) {
});
}
/**
* @typedef {Object} LaunchOptions
* @property {boolean=} ignoreHTTPSErrors
* @property {boolean=} headless
* @property {string=} executablePath
* @property {number=} slowMo
* @property {!Array<string>=} args
* @property {boolean=} ignoreDefaultArgs
* @property {boolean=} handleSIGINT
* @property {boolean=} handleSIGTERM
* @property {boolean=} handleSIGHUP
* @property {number=} timeout
* @property {boolean=} dumpio
* @property {string=} userDataDir
* @property {!Object<string, string | undefined>=} env
* @property {boolean=} devtools
* @property {boolean=} pipe
* @property {boolean=} appMode
*/
module.exports = Launcher;

View File

@@ -34,11 +34,11 @@ class Page extends EventEmitter {
* @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.Target} target
* @param {boolean} ignoreHTTPSErrors
* @param {boolean} appMode
* @param {boolean} setDefaultViewport
* @param {!Puppeteer.TaskQueue} screenshotTaskQueue
* @return {!Promise<!Page>}
*/
static async create(client, target, ignoreHTTPSErrors, appMode, screenshotTaskQueue) {
static async create(client, target, ignoreHTTPSErrors, setDefaultViewport, screenshotTaskQueue) {
await client.send('Page.enable');
const {frameTree} = await client.send('Page.getFrameTree');
@@ -54,7 +54,7 @@ class Page extends EventEmitter {
if (ignoreHTTPSErrors)
await client.send('Security.setOverrideCertificateErrors', {override: true});
// Initialize default page size.
if (!appMode)
if (setDefaultViewport)
await page.setViewport({width: 800, height: 600});
return page;

View File

@@ -6,15 +6,15 @@ class Target {
* @param {!Puppeteer.TargetInfo} targetInfo
* @param {!function():!Promise<!Puppeteer.CDPSession>} sessionFactory
* @param {boolean} ignoreHTTPSErrors
* @param {boolean} appMode
* @param {boolean} setDefaultViewport
* @param {!Puppeteer.TaskQueue} screenshotTaskQueue
*/
constructor(targetInfo, sessionFactory, ignoreHTTPSErrors, appMode, screenshotTaskQueue) {
constructor(targetInfo, sessionFactory, ignoreHTTPSErrors, setDefaultViewport, screenshotTaskQueue) {
this._targetInfo = targetInfo;
this._targetId = targetInfo.targetId;
this._sessionFactory = sessionFactory;
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._appMode = appMode;
this._setDefaultViewport = setDefaultViewport;
this._screenshotTaskQueue = screenshotTaskQueue;
/** @type {?Promise<!Puppeteer.Page>} */
this._pagePromise = null;
@@ -38,7 +38,7 @@ class Target {
async page() {
if (this._targetInfo.type === 'page' && !this._pagePromise) {
this._pagePromise = this._sessionFactory()
.then(client => Page.create(client, this, this._ignoreHTTPSErrors, this._appMode, this._screenshotTaskQueue));
.then(client => Page.create(client, this, this._ignoreHTTPSErrors, this._setDefaultViewport, this._screenshotTaskQueue));
}
return this._pagePromise;
}