blob: e13e209f4286373a7e0f2c2cb84195f92f5f9ed3 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' as io;
import 'package:meta/meta.dart';
import 'utils.dart' show forwardStandardStreams;
/// Options passed to Chrome when launching it.
class ChromeOptions {
ChromeOptions({
this.userDataDirectory,
this.url,
this.windowWidth = 1024,
this.windowHeight = 1024,
this.headless,
this.debugPort,
});
/// If not null passed as `--user-data-dir`.
final String userDataDirectory;
/// If not null launches a Chrome tab at this URL.
final String url;
/// The width of the Chrome window.
///
/// This is important for screenshots and benchmarks.
final int windowWidth;
/// The height of the Chrome window.
///
/// This is important for screenshots and benchmarks.
final int windowHeight;
/// Launches code in "headless" mode, which allows running Chrome in
/// environments without a display, such as LUCI and Cirrus.
final bool headless;
/// The port Chrome will use for its debugging protocol.
///
/// If null, Chrome is launched without debugging. When running in headless
/// mode without a debug port, Chrome quits immediately. For most tests it is
/// typical to set [headless] to true and set a non-null debug port.
final int debugPort;
}
/// A function called when the Chrome process encounters an error.
typedef ChromeErrorCallback = void Function(String);
/// Manages a single Chrome process.
class Chrome {
Chrome._(this._chromeProcess, this._onError) {
// If the Chrome process quits before it was asked to quit, notify the
// error listener.
_chromeProcess.exitCode.then((int exitCode) {
if (!_isStopped) {
_onError('Chrome process exited prematurely with exit code $exitCode');
}
});
}
/// Launches Chrome with the give [options].
///
/// The [onError] callback is called with an error message when the Chrome
/// process encounters an error. In particular, [onError] is called when the
/// Chrome process exits prematurely, i.e. before [stop] is called.
static Future<Chrome> launch(ChromeOptions options, { String workingDirectory, @required ChromeErrorCallback onError }) async {
final io.ProcessResult versionResult = io.Process.runSync(_findSystemChromeExecutable(), const <String>['--version']);
print('Launching ${versionResult.stdout}');
final List<String> args = <String>[
if (options.userDataDirectory != null)
'--user-data-dir=${options.userDataDirectory}',
if (options.url != null)
options.url,
if (io.Platform.environment['CHROME_NO_SANDBOX'] == 'true')
'--no-sandbox',
if (options.headless)
'--headless',
if (options.debugPort != null)
'--remote-debugging-port=${options.debugPort}',
'--window-size=${options.windowWidth},${options.windowHeight}',
'--disable-extensions',
'--disable-popup-blocking',
// Indicates that the browser is in "browse without sign-in" (Guest session) mode.
'--bwsi',
'--no-first-run',
'--no-default-browser-check',
'--disable-default-apps',
'--disable-translate',
];
final io.Process chromeProcess = await io.Process.start(
_findSystemChromeExecutable(),
args,
workingDirectory: workingDirectory,
);
forwardStandardStreams(chromeProcess);
return Chrome._(chromeProcess, onError);
}
final io.Process _chromeProcess;
final ChromeErrorCallback _onError;
bool _isStopped = false;
/// Stops the Chrome process.
void stop() {
_isStopped = true;
_chromeProcess.kill();
}
}
String _findSystemChromeExecutable() {
// On some environments, such as the Dart HHH tester, Chrome resides in a
// non-standard location and is provided via the following environment
// variable.
final String envExecutable = io.Platform.environment['CHROME_EXECUTABLE'];
if (envExecutable != null) {
return envExecutable;
}
if (io.Platform.isLinux) {
final io.ProcessResult which =
io.Process.runSync('which', <String>['google-chrome']);
if (which.exitCode != 0) {
throw Exception('Failed to locate system Chrome installation.');
}
return (which.stdout as String).trim();
} else if (io.Platform.isMacOS) {
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
} else {
throw Exception('Web benchmarks cannot run on ${io.Platform.operatingSystem} yet.');
}
}