| // 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/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/logger.dart'; |
| import '../base/os.dart'; |
| import '../base/platform.dart'; |
| import '../build_info.dart'; |
| import '../device.dart'; |
| import '../features.dart'; |
| import '../globals.dart' as globals; |
| 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, |
| Logger logger, |
| }) : _fileSystem = fileSystem, |
| _logger = logger, |
| super( |
| name, |
| category: Category.web, |
| platformType: PlatformType.web, |
| ephemeral: false, |
| ); |
| |
| final ChromiumLauncher chromeLauncher; |
| |
| final FileSystem _fileSystem; |
| Logger _logger; |
| |
| /// The active chrome instance. |
| Chromium _chrome; |
| |
| // TODO(jonahwilliams): this is technically false, but requires some refactoring |
| // to allow hot mode restart only devices. |
| @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 |
| void clearLogs() { } |
| |
| DeviceLogReader _logReader; |
| |
| @override |
| DeviceLogReader getLogReader({ |
| ApplicationPackage app, |
| bool includePastLogs = false, |
| }) { |
| return _logReader ??= NoOpDeviceLogReader(app?.name); |
| } |
| |
| @override |
| Future<bool> installApp(ApplicationPackage app) async => true; |
| |
| @override |
| Future<bool> isAppInstalled(ApplicationPackage app) 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, |
| DebuggingOptions debuggingOptions, |
| Map<String, Object> platformArgs, |
| bool prebuiltApplication = false, |
| bool ipv6 = false, |
| }) async { |
| _logger ??= globals.logger; |
| // See [ResidentWebRunner.run] in flutter_tools/lib/src/resident_web_runner.dart |
| // for the web initialization and server logic. |
| final String 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, |
| ); |
| } |
| |
| _logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': launchChrome}); |
| return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null); |
| } |
| |
| @override |
| Future<bool> stopApp(ApplicationPackage app) async { |
| await _chrome?.close(); |
| return true; |
| } |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript; |
| |
| @override |
| Future<bool> uninstallApp(ApplicationPackage app) 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 Logger logger, |
| @required FileSystem fileSystem, |
| }) : _platform = platform, |
| _processManager = processManager, |
| super( |
| name: 'chrome', |
| chromeLauncher: chromiumLauncher, |
| logger: logger, |
| fileSystem: fileSystem, |
| ); |
| |
| final Platform _platform; |
| final ProcessManager _processManager; |
| |
| @override |
| String get name => 'Chrome'; |
| |
| @override |
| Future<String> get sdkNameAndVersion async => _sdkNameAndVersion ??= await _computeSdkNameAndVersion(); |
| |
| String _sdkNameAndVersion; |
| 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) { |
| 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. |
| // This is not currently used, see https://github.com/flutter/flutter/issues/55322 |
| class MicrosoftEdgeDevice extends ChromiumDevice { |
| MicrosoftEdgeDevice({ |
| @required ChromiumLauncher chromiumLauncher, |
| @required Logger logger, |
| @required FileSystem fileSystem, |
| }) : super( |
| name: 'edge', |
| chromeLauncher: chromiumLauncher, |
| logger: logger, |
| fileSystem: fileSystem, |
| ); |
| |
| @override |
| String get name => 'Edge'; |
| |
| @override |
| Future<String> get sdkNameAndVersion async => '<?>'; |
| } |
| |
| class WebDevices extends PollingDeviceDiscovery { |
| WebDevices({ |
| @required FileSystem fileSystem, |
| // TODO(jonahwilliams): the logger is overriden by the daemon command |
| // to support IDE integration. Accessing the global logger too early |
| // will grab the old stdout logger. |
| Logger logger, |
| @required Platform platform, |
| @required ProcessManager processManager, |
| @required FeatureFlags featureFlags, |
| }) : _featureFlags = featureFlags, |
| 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, |
| logger: logger, |
| platform: platform, |
| processManager: processManager, |
| operatingSystemUtils: operatingSystemUtils, |
| ), |
| ); |
| _webServerDevice = WebServerDevice( |
| logger: logger, |
| ); |
| } |
| |
| GoogleChromeDevice _chromeDevice; |
| WebServerDevice _webServerDevice; |
| final FeatureFlags _featureFlags; |
| |
| @override |
| bool get canListAnything => featureFlags.isWebEnabled; |
| |
| @override |
| Future<List<Device>> pollingGetDevices({ Duration timeout }) async { |
| if (!_featureFlags.isWebEnabled) { |
| return <Device>[]; |
| } |
| return <Device>[ |
| _webServerDevice, |
| if (_chromeDevice.isSupported()) |
| _chromeDevice, |
| ]; |
| } |
| |
| @override |
| bool get supportsPlatform => _featureFlags.isWebEnabled; |
| } |
| |
| @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({ |
| Logger logger, |
| }) : _logger = logger, |
| super( |
| 'web-server', |
| platformType: PlatformType.web, |
| category: Category.web, |
| ephemeral: false, |
| ); |
| |
| Logger _logger; |
| |
| @override |
| void clearLogs() { } |
| |
| @override |
| Future<String> get emulatorId => null; |
| |
| DeviceLogReader _logReader; |
| |
| @override |
| DeviceLogReader getLogReader({ |
| ApplicationPackage app, |
| bool includePastLogs = false, |
| }) { |
| return _logReader ??= NoOpDeviceLogReader(app?.name); |
| } |
| |
| @override |
| Future<bool> installApp(ApplicationPackage app) async => true; |
| |
| @override |
| Future<bool> isAppInstalled(ApplicationPackage app) async => true; |
| |
| @override |
| Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true; |
| |
| @override |
| bool get supportsFlutterExit => false; |
| |
| @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, |
| DebuggingOptions debuggingOptions, |
| Map<String, Object> platformArgs, |
| bool prebuiltApplication = false, |
| bool ipv6 = false, |
| }) async { |
| _logger ??= globals.logger; |
| 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.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false}); |
| return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null); |
| } |
| |
| @override |
| Future<bool> stopApp(ApplicationPackage app) async { |
| return true; |
| } |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.web_javascript; |
| |
| @override |
| Future<bool> uninstallApp(ApplicationPackage app) async { |
| return true; |
| } |
| |
| @override |
| Future<void> dispose() async { |
| _logReader?.dispose(); |
| await portForwarder?.dispose(); |
| } |
| } |