@@ -415,7 +415,7 @@ class ChromiumLauncher {
415415 // connection is valid.
416416 if (! skipCheck) {
417417 try {
418- await _getFirstTab ( chrome);
418+ await chrome. _validateChromeConnection ( );
419419 } on Exception catch (error, stackTrace) {
420420 _logger.printError ('$error ' , stackTrace: stackTrace);
421421 await chrome.close ();
@@ -427,40 +427,6 @@ class ChromiumLauncher {
427427 return chrome;
428428 }
429429
430- /// Gets the first [chrome] tab.
431- ///
432- /// Retries getting tabs from Chrome for a few seconds and retries finding
433- /// the tab a few times. This reduces flakes caused by Chrome not returning
434- /// correct output if the call was too close to the start.
435- //
436- // TODO(ianh): remove the timeouts here, they violate our style guide.
437- // (We should just keep waiting forever, and print a warning when it's
438- // taking too long.)
439- Future <ChromeTab ?> _getFirstTab (Chromium chrome) async {
440- const Duration retryFor = Duration (seconds: 2 );
441- const int attempts = 5 ;
442-
443- for (int i = 1 ; i <= attempts; i++ ) {
444- try {
445- final List <ChromeTab > tabs =
446- await chrome.chromeConnection.getTabs (retryFor: retryFor);
447-
448- if (tabs.isNotEmpty) {
449- return tabs.first;
450- }
451- if (i == attempts) {
452- return null ;
453- }
454- } on ConnectionException catch (_) {
455- if (i == attempts) {
456- rethrow ;
457- }
458- }
459- await Future <void >.delayed (const Duration (milliseconds: 25 ));
460- }
461- return null ;
462- }
463-
464430 Future <Chromium > get connectedInstance => currentCompleter.future;
465431}
466432
@@ -483,6 +449,7 @@ class Chromium {
483449 final ChromeConnection chromeConnection;
484450 final ChromiumLauncher _chromiumLauncher;
485451 final Logger _logger;
452+ bool _hasValidChromeConnection = false ;
486453
487454 /// Resolves to browser's main process' exit code, when the browser exits.
488455 Future <int > get onExit async => _process.exitCode;
@@ -493,6 +460,41 @@ class Chromium {
493460 @visibleForTesting
494461 Process get process => _process;
495462
463+ /// Gets the first [chrome] tab in order to verify that the connection to
464+ /// the Chrome debug protocol is working properly.
465+ ///
466+ /// Retries getting tabs from Chrome for a few seconds and retries finding
467+ /// the tab a few times. This reduces flakes caused by Chrome not returning
468+ /// correct output if the call was too close to the start.
469+ //
470+ // TODO(ianh): remove the timeouts here, they violate our style guide.
471+ // (We should just keep waiting forever, and print a warning when it's
472+ // taking too long.)
473+ Future <void > _validateChromeConnection () async {
474+ const Duration retryFor = Duration (seconds: 2 );
475+ const int attempts = 5 ;
476+
477+ for (int i = 1 ; i <= attempts; i++ ) {
478+ try {
479+ final List <ChromeTab > tabs =
480+ await chromeConnection.getTabs (retryFor: retryFor);
481+
482+ if (tabs.isNotEmpty) {
483+ _hasValidChromeConnection = true ;
484+ return ;
485+ }
486+ if (i == attempts) {
487+ return ;
488+ }
489+ } on ConnectionException catch (_) {
490+ if (i == attempts) {
491+ rethrow ;
492+ }
493+ }
494+ await Future <void >.delayed (const Duration (milliseconds: 25 ));
495+ }
496+ }
497+
496498 /// Closes all connections to the browser and asks the browser to exit.
497499 Future <void > close () async {
498500 if (_logger.isVerbose) {
@@ -501,12 +503,31 @@ class Chromium {
501503 if (_chromiumLauncher.hasChromeInstance) {
502504 _chromiumLauncher.currentCompleter = Completer <Chromium >();
503505 }
506+
507+ // Send a command to shut down the browser cleanly.
508+ Duration sigtermDelay = Duration .zero;
509+ if (_hasValidChromeConnection) {
510+ final ChromeTab ? tab = await chromeConnection.getTab (
511+ (_) => true , retryFor: const Duration (seconds: 1 ));
512+ if (tab != null ) {
513+ final WipConnection wipConnection = await tab.connect ();
514+ await wipConnection.sendCommand ('Browser.close' );
515+ await wipConnection.close ();
516+ sigtermDelay = const Duration (seconds: 1 );
517+ }
518+ }
504519 chromeConnection.close ();
520+ _hasValidChromeConnection = false ;
505521
506- // Try to exit Chromium nicely using SIGTERM, before exiting it rudely using
507- // SIGKILL. Wait no longer than 5 seconds for Chromium to exit before
508- // falling back to SIGKILL, and then to a warning message.
509- ProcessSignal .sigterm.kill (_process);
522+ // If the browser close command did not shut down the process, then try to
523+ // exit Chromium using SIGTERM.
524+ await _process.exitCode.timeout (sigtermDelay, onTimeout: () {
525+ ProcessSignal .sigterm.kill (_process);
526+ return 0 ;
527+ });
528+ // If the process still has not ended, then use SIGKILL. Wait up to 5
529+ // seconds for Chromium to exit before falling back to SIGKILL and then to
530+ // a warning message.
510531 await _process.exitCode.timeout (const Duration (seconds: 5 ), onTimeout: () {
511532 _logger.printWarning (
512533 'Failed to exit Chromium (pid: ${_process .pid }) using SIGTERM. Will try '
0 commit comments