@@ -13,9 +13,8 @@ import {getRandomPort} from './random-port';
13
13
import { DEFAULT_FLAGS } from './flags' ;
14
14
import { makeTmpDir , defaults , delay , getPlatform , toWin32Path , InvalidUserDataDirectoryError , UnsupportedPlatformError , ChromeNotInstalledError } from './utils' ;
15
15
import { ChildProcess } from 'child_process' ;
16
+ import { spawn , spawnSync } from 'child_process' ;
16
17
const log = require ( 'lighthouse-logger' ) ;
17
- const spawn = childProcess . spawn ;
18
- const execSync = childProcess . execSync ;
19
18
const isWsl = getPlatform ( ) === 'wsl' ;
20
19
const isWindows = getPlatform ( ) === 'win32' ;
21
20
const _SIGINT = 'SIGINT' ;
@@ -81,7 +80,7 @@ async function launch(opts: Options = {}): Promise<LaunchedChrome> {
81
80
return instance . kill ( ) ;
82
81
} ;
83
82
84
- return { pid : instance . pid ! , port : instance . port ! , kill, process : instance . chrome ! } ;
83
+ return { pid : instance . pid ! , port : instance . port ! , kill, process : instance . chromeProcess ! } ;
85
84
}
86
85
87
86
/** Returns Chrome installation path that chrome-launcher will launch by default. */
@@ -126,7 +125,7 @@ class Launcher {
126
125
private useDefaultProfile : boolean ;
127
126
private envVars : { [ key : string ] : string | undefined } ;
128
127
129
- chrome ?: childProcess . ChildProcess ;
128
+ chromeProcess ?: childProcess . ChildProcess ;
130
129
userDataDir ?: string ;
131
130
port ?: number ;
132
131
pid ?: number ;
@@ -175,6 +174,8 @@ class Launcher {
175
174
flags . push ( `--user-data-dir=${ isWsl ? toWin32Path ( this . userDataDir ) : this . userDataDir } ` ) ;
176
175
}
177
176
177
+ if ( process . env . HEADLESS ) flags . push ( '--headless' ) ;
178
+
178
179
flags . push ( ...this . chromeFlags ) ;
179
180
flags . push ( this . startingUrl ) ;
180
181
@@ -276,9 +277,9 @@ class Launcher {
276
277
277
278
private async spawnProcess ( execPath : string ) {
278
279
const spawnPromise = ( async ( ) => {
279
- if ( this . chrome ) {
280
- log . log ( 'ChromeLauncher' , `Chrome already running with pid ${ this . chrome . pid } .` ) ;
281
- return this . chrome . pid ;
280
+ if ( this . chromeProcess ) {
281
+ log . log ( 'ChromeLauncher' , `Chrome already running with pid ${ this . chromeProcess . pid } .` ) ;
282
+ return this . chromeProcess . pid ;
282
283
}
283
284
284
285
@@ -292,17 +293,23 @@ class Launcher {
292
293
293
294
log . verbose (
294
295
'ChromeLauncher' , `Launching with command:\n"${ execPath } " ${ this . flags . join ( ' ' ) } ` ) ;
295
- const chrome = this . spawn (
296
- execPath , this . flags ,
297
- { detached : true , stdio : [ 'ignore' , this . outFile , this . errFile ] , env : this . envVars } ) ;
298
- this . chrome = chrome ;
296
+ this . chromeProcess = this . spawn ( execPath , this . flags , {
297
+ // On non-windows platforms, `detached: true` makes child process a leader of a new
298
+ // process group, making it possible to kill child process tree with `.kill(-pid)` command.
299
+ // @see https://nodejs.org/api/child_process.html#child_process_options_detached
300
+ detached : process . platform !== 'win32' ,
301
+ stdio : [ 'ignore' , this . outFile , this . errFile ] ,
302
+ env : this . envVars
303
+ } ) ;
299
304
300
- if ( chrome . pid ) {
301
- this . fs . writeFileSync ( this . pidFile , chrome . pid . toString ( ) ) ;
305
+ if ( this . chromeProcess . pid ) {
306
+ this . fs . writeFileSync ( this . pidFile , this . chromeProcess . pid . toString ( ) ) ;
302
307
}
303
308
304
- log . verbose ( 'ChromeLauncher' , `Chrome running with pid ${ chrome . pid } on port ${ this . port } .` ) ;
305
- return chrome . pid ;
309
+ log . verbose (
310
+ 'ChromeLauncher' ,
311
+ `Chrome running with pid ${ this . chromeProcess . pid } on port ${ this . port } .` ) ;
312
+ return this . chromeProcess . pid ;
306
313
} ) ( ) ;
307
314
308
315
const pid = await spawnPromise ;
@@ -373,28 +380,30 @@ class Launcher {
373
380
}
374
381
375
382
kill ( ) {
376
- return new Promise < void > ( ( resolve , reject ) => {
377
- if ( this . chrome ) {
378
- this . chrome . on ( 'close' , ( ) => {
379
- delete this . chrome ;
383
+ return new Promise < void > ( ( resolve ) => {
384
+ if ( this . chromeProcess ) {
385
+ this . chromeProcess . on ( 'close' , ( ) => {
386
+ delete this . chromeProcess ;
380
387
this . destroyTmp ( ) . then ( resolve ) ;
381
388
} ) ;
382
389
383
- log . log ( 'ChromeLauncher' , `Killing Chrome instance ${ this . chrome . pid } ` ) ;
390
+ log . log ( 'ChromeLauncher' , `Killing Chrome instance ${ this . chromeProcess . pid } ` ) ;
384
391
try {
385
392
if ( isWindows ) {
386
- // While pipe is the default, stderr also gets printed to process.stderr
387
- // if you don't explicitly set `stdio`
388
- execSync ( `taskkill /pid ${ this . chrome . pid } /T /F` , { stdio : 'pipe' } ) ;
393
+ // https://github.com/GoogleChrome/chrome-launcher/issues/266
394
+ const taskkillProc = spawnSync (
395
+ `taskkill /pid ${ this . chromeProcess . pid } /T /F` , { shell : true , encoding : 'utf-8' } ) ;
396
+
397
+ const { stderr} = taskkillProc ;
398
+ if ( stderr ) log . error ( 'ChromeLauncher' , `taskkill stderr` , stderr ) ;
389
399
} else {
390
- if ( this . chrome . pid ) {
391
- process . kill ( - this . chrome . pid ) ;
400
+ if ( this . chromeProcess . pid ) {
401
+ process . kill ( - this . chromeProcess . pid , 'SIGKILL' ) ;
392
402
}
393
403
}
394
404
} catch ( err ) {
395
405
const message = `Chrome could not be killed ${ err . message } ` ;
396
406
log . warn ( 'ChromeLauncher' , message ) ;
397
- reject ( new Error ( message ) ) ;
398
407
}
399
408
} else {
400
409
// fail silently as we did not start chrome
0 commit comments