2017-08-15 01:08:06 +00:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2017-08-22 03:12:16 +00:00
const os = require ( 'os' ) ;
2017-08-15 01:08:06 +00:00
const path = require ( 'path' ) ;
2017-08-21 22:43:36 +00:00
const removeSync = require ( 'rimraf' ) . sync ;
2017-08-15 01:08:06 +00:00
const childProcess = require ( 'child_process' ) ;
const Downloader = require ( '../utils/ChromiumDownloader' ) ;
const Connection = require ( './Connection' ) ;
const Browser = require ( './Browser' ) ;
const readline = require ( 'readline' ) ;
2017-08-22 03:12:16 +00:00
const fs = require ( 'fs' ) ;
2017-08-21 22:43:36 +00:00
const helper = require ( './helper' ) ;
const ChromiumRevision = require ( '../package.json' ) . puppeteer . chromium _revision ;
2017-08-15 01:08:06 +00:00
2017-08-22 03:12:16 +00:00
const CHROME _PROFILE _PATH = path . join ( os . tmpdir ( ) , 'puppeteer_dev_profile-' ) ;
2017-08-15 01:08:06 +00:00
const DEFAULT _ARGS = [
'--disable-background-networking' ,
'--disable-background-timer-throttling' ,
'--disable-client-side-phishing-detection' ,
'--disable-default-apps' ,
'--disable-hang-monitor' ,
'--disable-popup-blocking' ,
'--disable-prompt-on-repost' ,
'--disable-sync' ,
'--enable-automation' ,
2017-08-22 21:15:02 +00:00
'--enable-devtools-experiments' ,
2017-08-15 01:08:06 +00:00
'--metrics-recording-only' ,
'--no-first-run' ,
'--password-store=basic' ,
'--remote-debugging-port=0' ,
'--safebrowsing-disable-auto-update' ,
'--use-mock-keychain' ,
] ;
class Launcher {
/ * *
2017-08-21 23:32:39 +00:00
* @ param { ! Object = } options
2017-08-15 01:08:06 +00:00
* @ return { ! Promise < ! Browser > }
* /
2017-08-22 03:12:16 +00:00
static async launch ( options ) {
options = options || { } ;
2017-08-28 19:14:21 +00:00
let temporaryUserDataDir = null ;
const chromeArguments = [ ] . concat ( DEFAULT _ARGS ) ;
2017-08-28 21:59:41 +00:00
if ( ! options . args || ! options . args . some ( arg => arg . startsWith ( '--user-data-dir' ) ) ) {
2017-08-28 19:14:21 +00:00
if ( ! options . userDataDir )
temporaryUserDataDir = fs . mkdtempSync ( CHROME _PROFILE _PATH ) ;
2017-08-18 06:18:08 +00:00
2017-08-28 19:14:21 +00:00
chromeArguments . push ( ` --user-data-dir= ${ options . userDataDir || temporaryUserDataDir } ` ) ;
}
2017-08-15 01:08:06 +00:00
if ( typeof options . headless !== 'boolean' || options . headless ) {
chromeArguments . push (
2017-08-21 23:32:39 +00:00
'--headless' ,
'--disable-gpu' ,
'--hide-scrollbars' ,
2017-08-18 03:54:16 +00:00
'--mute-audio'
2017-08-15 01:08:06 +00:00
) ;
}
let chromeExecutable = options . executablePath ;
if ( typeof chromeExecutable !== 'string' ) {
2017-08-21 23:32:39 +00:00
const revisionInfo = Downloader . revisionInfo ( Downloader . currentPlatform ( ) , ChromiumRevision ) ;
2017-08-21 20:34:10 +00:00
console . assert ( revisionInfo . downloaded , ` Chromium revision is not downloaded. Run "npm install" ` ) ;
2017-08-15 01:08:06 +00:00
chromeExecutable = revisionInfo . executablePath ;
}
if ( Array . isArray ( options . args ) )
chromeArguments . push ( ... options . args ) ;
2017-08-21 22:43:36 +00:00
2017-08-30 21:16:03 +00:00
const chromeProcess = childProcess . spawn ( chromeExecutable , chromeArguments , { detached : true } ) ;
2017-08-15 01:08:06 +00:00
if ( options . dumpio ) {
chromeProcess . stdout . pipe ( process . stdout ) ;
chromeProcess . stderr . pipe ( process . stderr ) ;
}
2017-08-21 22:43:36 +00:00
// Cleanup as processes exit.
2017-08-28 19:14:21 +00:00
if ( temporaryUserDataDir )
chromeProcess . once ( 'close' , ( ) => removeSync ( temporaryUserDataDir ) ) ;
2017-08-23 18:55:33 +00:00
2017-08-30 05:49:50 +00:00
const listeners = [ helper . addEventListener ( process , 'exit' , killChrome ) ] ;
2017-08-21 22:43:36 +00:00
if ( options . handleSIGINT !== false )
2017-08-30 05:49:50 +00:00
listeners . push ( helper . addEventListener ( process , 'SIGINT' , killChrome ) ) ;
2017-08-23 18:55:33 +00:00
2017-08-21 22:43:36 +00:00
try {
2017-08-21 23:32:39 +00:00
const connectionDelay = options . slowMo || 0 ;
const browserWSEndpoint = await waitForWSEndpoint ( chromeProcess , options . timeout || 30 * 1000 ) ;
const connection = await Connection . create ( browserWSEndpoint , connectionDelay ) ;
2017-08-23 18:55:33 +00:00
return new Browser ( connection , ! ! options . ignoreHTTPSErrors , killChrome ) ;
2017-08-21 22:43:36 +00:00
} catch ( e ) {
2017-08-23 18:55:33 +00:00
killChrome ( ) ;
2017-08-21 22:43:36 +00:00
throw e ;
2017-08-15 01:08:06 +00:00
}
2017-08-23 18:55:33 +00:00
function killChrome ( ) {
2017-08-30 05:49:50 +00:00
helper . removeEventListeners ( listeners ) ;
2017-08-23 18:55:33 +00:00
if ( process . platform === 'win32' )
childProcess . execSync ( ` taskkill /pid ${ chromeProcess . pid } /T /F ` ) ;
else
2017-08-30 21:16:03 +00:00
process . kill ( - chromeProcess . pid ) ;
2017-08-21 22:43:36 +00:00
}
2017-08-15 01:08:06 +00:00
}
2017-08-15 21:29:42 +00:00
/ * *
* @ param { string } options
* @ return { ! Promise < ! Browser > }
* /
2017-08-16 08:10:55 +00:00
static async connect ( { browserWSEndpoint , ignoreHTTPSErrors = false } ) {
2017-08-21 23:39:04 +00:00
const connection = await Connection . create ( browserWSEndpoint ) ;
2017-08-15 21:29:42 +00:00
return new Browser ( connection , ! ! ignoreHTTPSErrors ) ;
}
2017-08-15 01:08:06 +00:00
}
/ * *
* @ param { ! ChildProcess } chromeProcess
2017-08-21 22:43:36 +00:00
* @ param { number } timeout
2017-08-15 21:29:42 +00:00
* @ return { ! Promise < string > }
2017-08-15 01:08:06 +00:00
* /
2017-08-21 22:43:36 +00:00
function waitForWSEndpoint ( chromeProcess , timeout ) {
return new Promise ( ( resolve , reject ) => {
2017-08-15 01:08:06 +00:00
const rl = readline . createInterface ( { input : chromeProcess . stderr } ) ;
2017-08-21 22:43:36 +00:00
let stderr = '' ;
2017-08-21 23:39:04 +00:00
const listeners = [
2017-08-21 22:43:36 +00:00
helper . addEventListener ( rl , 'line' , onLine ) ,
helper . addEventListener ( rl , 'close' , onClose ) ,
helper . addEventListener ( chromeProcess , 'exit' , onClose )
] ;
2017-08-21 23:39:04 +00:00
const timeoutId = timeout ? setTimeout ( onTimeout , timeout ) : 0 ;
2017-08-21 22:43:36 +00:00
function onClose ( ) {
cleanup ( ) ;
reject ( new Error ( [
'Failed to launch chrome!' ,
stderr ,
'' ,
'TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md' ,
'' ,
] . join ( '\n' ) ) ) ;
}
function onTimeout ( ) {
cleanup ( ) ;
reject ( new Error ( ` Timed out after ${ timeout } ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is r ${ ChromiumRevision } ` ) ) ;
}
2017-08-15 01:08:06 +00:00
/ * *
* @ param { string } line
* /
function onLine ( line ) {
2017-08-21 22:43:36 +00:00
stderr += line + '\n' ;
2017-08-15 21:29:42 +00:00
const match = line . match ( /^DevTools listening on (ws:\/\/.*)$/ ) ;
2017-08-15 01:08:06 +00:00
if ( ! match )
return ;
2017-08-21 22:43:36 +00:00
cleanup ( ) ;
resolve ( match [ 1 ] ) ;
}
function cleanup ( ) {
if ( timeoutId )
clearTimeout ( timeoutId ) ;
helper . removeEventListeners ( listeners ) ;
2017-08-15 01:08:06 +00:00
}
} ) ;
}
module . exports = Launcher ;