blob: 7717131ae1ea43b9b23013ca31191b3c1bbf031f [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. 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:async';
import 'dart:io';
import 'package:browser_launcher/browser_launcher.dart' as browser_launcher;
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
// ignore: import_of_legacy_library_into_null_safe
import '../command/configuration.dart';
import 'utils.dart';
var _logger = Logger('ChromeLauncher');
var _currentCompleter = Completer<Chrome>();
/// A class for managing an instance of Chrome.
class Chrome {
final int debugPort;
final browser_launcher.Chrome _chrome;
final ChromeConnection chromeConnection;
Chrome._(
this.debugPort,
this._chrome,
) : chromeConnection = _chrome.chromeConnection;
Future<void> close() async {
if (_currentCompleter.isCompleted) _currentCompleter = Completer<Chrome>();
await _chrome.close();
}
/// Connects to an instance of Chrome with an open debug port.
static Future<Chrome> fromExisting(int port) async => _connect(
Chrome._(port, await browser_launcher.Chrome.fromExisting(port)));
static Future<Chrome> get connectedInstance => _currentCompleter.future;
/// Starts Chrome with the remote debug port enabled.
///
/// Each url in [urls] will be loaded in a separate tab.
/// Enables chrome devtools with port [port] if specified.
/// Uses a copy of [userDataDir] to sign into the default
/// user profile, or starts a new session without sign in,
/// if not specified.
static Future<Chrome> start(List<String> urls,
{required int port, String? userDataDir}) async {
var signIn = false;
String? dir;
// Re-using the directory causes flakiness on Windows, so on that platform
// pass null to have it create a directory.
// Issue: https://github.com/dart-lang/webdev/issues/1545
if (!Platform.isWindows) {
var userDataTemp = path.join(Directory.current.absolute.path,
'.dart_tool', 'webdev', 'chrome_user_data');
var userDataCopy = path.join(Directory.current.absolute.path,
'.dart_tool', 'webdev', 'chrome_user_data_copy');
if (userDataDir != null) {
signIn = true;
dir = userDataCopy;
var stopwatch = Stopwatch()..start();
try {
_logger.info('Copying user data directory...');
_logger.warning(
'Copying user data directory might take >12s on the first '
'use of --$userDataDirFlag, and ~2-3s on subsequent runs. '
'Run without --$userDataDirFlag to improve performance.');
Directory(dir).createSync(recursive: true);
await updatePath(
path.join(userDataDir, 'Default'), path.join(dir, 'Default'));
_logger.info(
'Copied user data directory in ${stopwatch.elapsedMilliseconds} ms');
} catch (e, s) {
dir = userDataTemp;
signIn = false;
if (Directory(dir).existsSync()) {
Directory(dir).deleteSync(recursive: true);
}
_logger.severe('Failed to copy user data directory', e, s);
_logger.severe('Launching with temp profile instead.');
rethrow;
}
}
if (!signIn) {
dir = userDataTemp;
Directory(dir).createSync(recursive: true);
}
}
_logger.info('Starting chrome with user data directory: $dir');
var chrome = await browser_launcher.Chrome.startWithDebugPort(urls,
debugPort: port, userDataDir: dir, signIn: signIn);
return _connect(Chrome._(chrome.debugPort, chrome));
}
static Future<Chrome> _connect(Chrome chrome) async {
if (_currentCompleter.isCompleted) {
throw ChromeError('Only one instance of chrome can be started.');
}
// The connection is lazy. Try a simple call to make sure the provided
// connection is valid.
try {
await chrome._chrome.chromeConnection.getTabs();
} catch (e) {
await chrome.close();
throw ChromeError(
'Unable to connect to Chrome debug port: ${chrome.debugPort}\n $e');
}
_currentCompleter.complete(chrome);
return chrome;
}
}
class ChromeError extends Error {
final String details;
ChromeError(this.details);
@override
String toString() {
return 'ChromeError: $details';
}
}
String? autoDetectChromeUserDataDirectory() {
Directory directory;
if (Platform.isMacOS) {
var home = Platform.environment['HOME']!;
directory = Directory(
path.join(home, 'Library', 'Application Support', 'Google', 'Chrome'));
} else if (Platform.isLinux) {
var home = Platform.environment['HOME']!;
directory = Directory(path.join(home, '.config', 'google-chrome'));
} else {
_logger.warning('Auto detecting chrome user data directory option is not '
'supported for ${Platform.operatingSystem}');
return null;
}
if (directory.existsSync()) {
_logger.info('Auto detected chrome user data directory: ${directory.path}');
return directory.path;
}
_logger.warning('Cannot automatically detect chrome user data directory. '
'Directory does not exist: ${directory.path}');
return null;
}