| // 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.'); |
| } |
| } |