mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
505 lines
14 KiB
Dart
505 lines
14 KiB
Dart
// 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 'package:meta/meta.dart';
|
|
import 'package:process/process.dart';
|
|
|
|
import '../application_package.dart';
|
|
import '../base/common.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/io.dart';
|
|
import '../base/logger.dart';
|
|
import '../base/os.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/version.dart';
|
|
import '../build_info.dart';
|
|
import '../device.dart';
|
|
import '../device_port_forwarder.dart';
|
|
import '../features.dart';
|
|
import '../project.dart';
|
|
import 'chrome.dart';
|
|
|
|
class WebApplicationPackage extends ApplicationPackage {
|
|
WebApplicationPackage(this.flutterProject) : super(id: flutterProject.manifest.appName);
|
|
|
|
final FlutterProject flutterProject;
|
|
|
|
@override
|
|
String get name => flutterProject.manifest.appName;
|
|
|
|
/// The location of the web source assets.
|
|
Directory get webSourcePath => flutterProject.directory.childDirectory('web');
|
|
}
|
|
|
|
/// A web device that supports a chromium browser.
|
|
abstract class ChromiumDevice extends Device {
|
|
ChromiumDevice({
|
|
required String name,
|
|
required this.chromeLauncher,
|
|
required FileSystem fileSystem,
|
|
required Logger logger,
|
|
}) : _fileSystem = fileSystem,
|
|
_logger = logger,
|
|
super(
|
|
name,
|
|
category: Category.web,
|
|
platformType: PlatformType.web,
|
|
ephemeral: false,
|
|
);
|
|
|
|
final ChromiumLauncher chromeLauncher;
|
|
|
|
final FileSystem _fileSystem;
|
|
final Logger _logger;
|
|
|
|
/// The active chrome instance.
|
|
Chromium? _chrome;
|
|
|
|
// This device does not actually support hot reload, but the current implementation of the resident runner
|
|
// requires both supportsHotReload and supportsHotRestart to be true in order to allow hot restart.
|
|
@override
|
|
bool get supportsHotReload => true;
|
|
|
|
@override
|
|
bool get supportsHotRestart => true;
|
|
|
|
@override
|
|
bool get supportsStartPaused => true;
|
|
|
|
@override
|
|
bool get supportsFlutterExit => false;
|
|
|
|
@override
|
|
bool get supportsScreenshot => false;
|
|
|
|
@override
|
|
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
|
|
|
|
@override
|
|
void clearLogs() { }
|
|
|
|
DeviceLogReader? _logReader;
|
|
|
|
@override
|
|
DeviceLogReader getLogReader({
|
|
ApplicationPackage? app,
|
|
bool includePastLogs = false,
|
|
}) {
|
|
return _logReader ??= NoOpDeviceLogReader(app?.name);
|
|
}
|
|
|
|
@override
|
|
Future<bool> installApp(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async => true;
|
|
|
|
@override
|
|
Future<bool> isAppInstalled(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async => true;
|
|
|
|
@override
|
|
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
|
|
|
|
@override
|
|
Future<bool> get isLocalEmulator async => false;
|
|
|
|
@override
|
|
Future<String?> get emulatorId async => null;
|
|
|
|
@override
|
|
bool isSupported() => chromeLauncher.canFindExecutable();
|
|
|
|
@override
|
|
DevicePortForwarder? get portForwarder => const NoOpDevicePortForwarder();
|
|
|
|
@override
|
|
Future<LaunchResult> startApp(
|
|
covariant WebApplicationPackage package, {
|
|
String? mainPath,
|
|
String? route,
|
|
required DebuggingOptions debuggingOptions,
|
|
Map<String, Object?> platformArgs = const <String, Object?>{},
|
|
bool prebuiltApplication = false,
|
|
bool ipv6 = false,
|
|
String? userIdentifier,
|
|
}) async {
|
|
// See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart
|
|
// for the web initialization and server logic.
|
|
String url;
|
|
if (debuggingOptions.webLaunchUrl != null) {
|
|
final RegExp pattern = RegExp(r'^((http)?:\/\/)[^\s]+');
|
|
if (pattern.hasMatch(debuggingOptions.webLaunchUrl!)) {
|
|
url = debuggingOptions.webLaunchUrl!;
|
|
} else {
|
|
throwToolExit('"${debuggingOptions.webLaunchUrl}" is not a vaild HTTP URL.');
|
|
}
|
|
} else {
|
|
url = platformArgs['uri']! as String;
|
|
}
|
|
final bool launchChrome = platformArgs['no-launch-chrome'] != true;
|
|
if (launchChrome) {
|
|
_chrome = await chromeLauncher.launch(
|
|
url,
|
|
cacheDir: _fileSystem.currentDirectory
|
|
.childDirectory('.dart_tool')
|
|
.childDirectory('chrome-device'),
|
|
headless: debuggingOptions.webRunHeadless,
|
|
debugPort: debuggingOptions.webBrowserDebugPort,
|
|
webBrowserFlags: debuggingOptions.webBrowserFlags,
|
|
);
|
|
}
|
|
_logger.sendEvent('app.webLaunchUrl', <String, Object>{'url': url, 'launched': launchChrome});
|
|
return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
|
|
}
|
|
|
|
@override
|
|
Future<bool> stopApp(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async {
|
|
await _chrome?.close();
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
|
|
|
|
@override
|
|
Future<bool> uninstallApp(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async => true;
|
|
|
|
@override
|
|
bool isSupportedForProject(FlutterProject flutterProject) {
|
|
return flutterProject.web.existsSync();
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
_logReader?.dispose();
|
|
await portForwarder?.dispose();
|
|
}
|
|
}
|
|
|
|
/// The Google Chrome browser based on Chromium.
|
|
class GoogleChromeDevice extends ChromiumDevice {
|
|
GoogleChromeDevice({
|
|
required Platform platform,
|
|
required ProcessManager processManager,
|
|
required ChromiumLauncher chromiumLauncher,
|
|
required super.logger,
|
|
required super.fileSystem,
|
|
}) : _platform = platform,
|
|
_processManager = processManager,
|
|
super(
|
|
name: 'chrome',
|
|
chromeLauncher: chromiumLauncher,
|
|
);
|
|
|
|
final Platform _platform;
|
|
final ProcessManager _processManager;
|
|
|
|
@override
|
|
String get name => 'Chrome';
|
|
|
|
@override
|
|
late final Future<String> sdkNameAndVersion = _computeSdkNameAndVersion();
|
|
|
|
Future<String> _computeSdkNameAndVersion() async {
|
|
if (!isSupported()) {
|
|
return 'unknown';
|
|
}
|
|
// See https://bugs.chromium.org/p/chromium/issues/detail?id=158372
|
|
String version = 'unknown';
|
|
if (_platform.isWindows) {
|
|
if (_processManager.canRun('reg')) {
|
|
final ProcessResult result = await _processManager.run(<String>[
|
|
r'reg', 'query', r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', '/v', 'version',
|
|
]);
|
|
if (result.exitCode == 0) {
|
|
final List<String> parts = (result.stdout as String).split(RegExp(r'\s+'));
|
|
if (parts.length > 2) {
|
|
version = 'Google Chrome ${parts[parts.length - 2]}';
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
final String chrome = chromeLauncher.findExecutable();
|
|
final ProcessResult result = await _processManager.run(<String>[
|
|
chrome,
|
|
'--version',
|
|
]);
|
|
if (result.exitCode == 0) {
|
|
version = result.stdout as String;
|
|
}
|
|
}
|
|
return version.trim();
|
|
}
|
|
}
|
|
|
|
/// The Microsoft Edge browser based on Chromium.
|
|
class MicrosoftEdgeDevice extends ChromiumDevice {
|
|
MicrosoftEdgeDevice({
|
|
required ChromiumLauncher chromiumLauncher,
|
|
required super.logger,
|
|
required super.fileSystem,
|
|
required ProcessManager processManager,
|
|
}) : _processManager = processManager,
|
|
super(
|
|
name: 'edge',
|
|
chromeLauncher: chromiumLauncher,
|
|
);
|
|
|
|
final ProcessManager _processManager;
|
|
|
|
// The first version of Edge with chromium support.
|
|
static const int _kFirstChromiumEdgeMajorVersion = 79;
|
|
|
|
@override
|
|
String get name => 'Edge';
|
|
|
|
Future<bool> _meetsVersionConstraint() async {
|
|
final String rawVersion = (await sdkNameAndVersion).replaceFirst('Microsoft Edge ', '');
|
|
final Version? version = Version.parse(rawVersion);
|
|
if (version == null) {
|
|
return false;
|
|
}
|
|
return version.major >= _kFirstChromiumEdgeMajorVersion;
|
|
}
|
|
|
|
@override
|
|
late final Future<String> sdkNameAndVersion = _getSdkNameAndVersion();
|
|
|
|
Future<String> _getSdkNameAndVersion() async {
|
|
if (_processManager.canRun('reg')) {
|
|
final ProcessResult result = await _processManager.run(<String>[
|
|
r'reg', 'query', r'HKEY_CURRENT_USER\Software\Microsoft\Edge\BLBeacon', '/v', 'version',
|
|
]);
|
|
if (result.exitCode == 0) {
|
|
final List<String> parts = (result.stdout as String).split(RegExp(r'\s+'));
|
|
if (parts.length > 2) {
|
|
return 'Microsoft Edge ${parts[parts.length - 2]}';
|
|
}
|
|
}
|
|
}
|
|
// Return a non-null string so that the tool can validate the version
|
|
// does not meet the constraint above in _meetsVersionConstraint.
|
|
return '';
|
|
}
|
|
}
|
|
|
|
class WebDevices extends PollingDeviceDiscovery {
|
|
WebDevices({
|
|
required FileSystem fileSystem,
|
|
required Logger logger,
|
|
required Platform platform,
|
|
required ProcessManager processManager,
|
|
required FeatureFlags featureFlags,
|
|
}) : _featureFlags = featureFlags,
|
|
_webServerDevice = WebServerDevice(
|
|
logger: logger,
|
|
),
|
|
super('Chrome') {
|
|
final OperatingSystemUtils operatingSystemUtils = OperatingSystemUtils(
|
|
fileSystem: fileSystem,
|
|
platform: platform,
|
|
logger: logger,
|
|
processManager: processManager,
|
|
);
|
|
_chromeDevice = GoogleChromeDevice(
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
chromiumLauncher: ChromiumLauncher(
|
|
browserFinder: findChromeExecutable,
|
|
fileSystem: fileSystem,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
operatingSystemUtils: operatingSystemUtils,
|
|
logger: logger,
|
|
),
|
|
);
|
|
if (platform.isWindows) {
|
|
_edgeDevice = MicrosoftEdgeDevice(
|
|
chromiumLauncher: ChromiumLauncher(
|
|
browserFinder: findEdgeExecutable,
|
|
fileSystem: fileSystem,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
operatingSystemUtils: operatingSystemUtils,
|
|
logger: logger,
|
|
),
|
|
processManager: processManager,
|
|
logger: logger,
|
|
fileSystem: fileSystem,
|
|
);
|
|
}
|
|
}
|
|
|
|
late final GoogleChromeDevice _chromeDevice;
|
|
final WebServerDevice _webServerDevice;
|
|
MicrosoftEdgeDevice? _edgeDevice;
|
|
final FeatureFlags _featureFlags;
|
|
|
|
@override
|
|
bool get canListAnything => featureFlags.isWebEnabled;
|
|
|
|
@override
|
|
Future<List<Device>> pollingGetDevices({ Duration? timeout }) async {
|
|
if (!_featureFlags.isWebEnabled) {
|
|
return <Device>[];
|
|
}
|
|
final MicrosoftEdgeDevice? edgeDevice = _edgeDevice;
|
|
return <Device>[
|
|
if (WebServerDevice.showWebServerDevice)
|
|
_webServerDevice,
|
|
if (_chromeDevice.isSupported())
|
|
_chromeDevice,
|
|
if (edgeDevice != null && await edgeDevice._meetsVersionConstraint())
|
|
edgeDevice,
|
|
];
|
|
}
|
|
|
|
@override
|
|
bool get supportsPlatform => _featureFlags.isWebEnabled;
|
|
|
|
@override
|
|
List<String> get wellKnownIds => const <String>['chrome', 'web-server', 'edge'];
|
|
}
|
|
|
|
@visibleForTesting
|
|
String parseVersionForWindows(String input) {
|
|
return input.split(RegExp(r'\w')).last;
|
|
}
|
|
|
|
|
|
/// A special device type to allow serving for arbitrary browsers.
|
|
class WebServerDevice extends Device {
|
|
WebServerDevice({
|
|
required Logger logger,
|
|
}) : _logger = logger,
|
|
super(
|
|
'web-server',
|
|
platformType: PlatformType.web,
|
|
category: Category.web,
|
|
ephemeral: false,
|
|
);
|
|
|
|
static const String kWebServerDeviceId = 'web-server';
|
|
static bool showWebServerDevice = false;
|
|
|
|
final Logger _logger;
|
|
|
|
@override
|
|
void clearLogs() { }
|
|
|
|
@override
|
|
Future<String?> get emulatorId async => null;
|
|
|
|
DeviceLogReader? _logReader;
|
|
|
|
@override
|
|
DeviceLogReader getLogReader({
|
|
ApplicationPackage? app,
|
|
bool includePastLogs = false,
|
|
}) {
|
|
return _logReader ??= NoOpDeviceLogReader(app?.name);
|
|
}
|
|
|
|
@override
|
|
Future<bool> installApp(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async => true;
|
|
|
|
@override
|
|
Future<bool> isAppInstalled(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async => true;
|
|
|
|
@override
|
|
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
|
|
|
|
@override
|
|
bool get supportsFlutterExit => false;
|
|
|
|
@override
|
|
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
|
|
|
|
@override
|
|
Future<bool> get isLocalEmulator async => false;
|
|
|
|
@override
|
|
bool isSupported() => true;
|
|
|
|
@override
|
|
bool isSupportedForProject(FlutterProject flutterProject) {
|
|
return flutterProject.web.existsSync();
|
|
}
|
|
|
|
@override
|
|
String get name => 'Web Server';
|
|
|
|
@override
|
|
DevicePortForwarder? get portForwarder => const NoOpDevicePortForwarder();
|
|
|
|
@override
|
|
Future<String> get sdkNameAndVersion async => 'Flutter Tools';
|
|
|
|
@override
|
|
Future<LaunchResult> startApp(ApplicationPackage package, {
|
|
String? mainPath,
|
|
String? route,
|
|
required DebuggingOptions debuggingOptions,
|
|
Map<String, Object?> platformArgs = const <String, Object?>{},
|
|
bool prebuiltApplication = false,
|
|
bool ipv6 = false,
|
|
String? userIdentifier,
|
|
}) async {
|
|
final String? url = platformArgs['uri'] as String?;
|
|
if (debuggingOptions.startPaused) {
|
|
_logger.printStatus('Waiting for connection from Dart debug extension at $url', emphasis: true);
|
|
} else {
|
|
_logger.printStatus('$mainPath is being served at $url', emphasis: true);
|
|
}
|
|
_logger.printStatus(
|
|
'The web-server device requires the Dart Debug Chrome extension for debugging. '
|
|
'Consider using the Chrome or Edge devices for an improved development workflow.'
|
|
);
|
|
_logger.sendEvent('app.webLaunchUrl', <String, Object?>{'url': url, 'launched': false});
|
|
return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
|
|
}
|
|
|
|
@override
|
|
Future<bool> stopApp(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async {
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript;
|
|
|
|
@override
|
|
Future<bool> uninstallApp(
|
|
ApplicationPackage app, {
|
|
String? userIdentifier,
|
|
}) async {
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
_logReader?.dispose();
|
|
await portForwarder?.dispose();
|
|
}
|
|
}
|