diff --git a/packages/flutter_tools/lib/src/resident_devtools_handler.dart b/packages/flutter_tools/lib/src/resident_devtools_handler.dart index 32f6968433..a48fcd9768 100644 --- a/packages/flutter_tools/lib/src/resident_devtools_handler.dart +++ b/packages/flutter_tools/lib/src/resident_devtools_handler.dart @@ -8,23 +8,25 @@ import 'dart:async'; -import 'package:browser_launcher/browser_launcher.dart'; import 'package:meta/meta.dart'; +import 'base/io.dart'; import 'base/logger.dart'; import 'build_info.dart'; import 'resident_runner.dart'; import 'vmservice.dart'; +import 'web/chrome.dart'; typedef ResidentDevtoolsHandlerFactory = - ResidentDevtoolsHandler Function(DevtoolsLauncher?, ResidentRunner, Logger); + ResidentDevtoolsHandler Function(DevtoolsLauncher?, ResidentRunner, Logger, ChromiumLauncher); ResidentDevtoolsHandler createDefaultHandler( DevtoolsLauncher? launcher, ResidentRunner runner, Logger logger, + ChromiumLauncher chromiumLauncher, ) { - return FlutterResidentDevtoolsHandler(launcher, runner, logger); + return FlutterResidentDevtoolsHandler(launcher, runner, logger, chromiumLauncher); } /// Helper class to manage the life-cycle of devtools and its interaction with @@ -66,12 +68,18 @@ abstract class ResidentDevtoolsHandler { } class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { - FlutterResidentDevtoolsHandler(this._devToolsLauncher, this._residentRunner, this._logger); + FlutterResidentDevtoolsHandler( + this._devToolsLauncher, + this._residentRunner, + this._logger, + this._chromiumLauncher, + ); static const Duration launchInBrowserTimeout = Duration(seconds: 15); final DevtoolsLauncher? _devToolsLauncher; final ResidentRunner _residentRunner; + final ChromiumLauncher _chromiumLauncher; final Logger _logger; bool _shutdown = false; @@ -184,11 +192,14 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { queryParameters: {'uri': '${device!.vmService!.httpAddress}'}, ) .toString(); - _logger.printStatus( - 'Launching Flutter DevTools for ' - '${device.device!.displayName} at $devToolsUrl', - ); - unawaited(Chrome.start([devToolsUrl])); + _logger.printStatus('Launching Flutter DevTools for ${device.device!.name} at $devToolsUrl'); + + _chromiumLauncher.launch(devToolsUrl).catchError((Object e) { + _logger.printError('Failed to launch web browser: $e'); + throw ProcessException('Chrome', [ + devToolsUrl, + ], 'Failed to launch browser for dev tools'); + }).ignore(); } launchedInBrowser = true; } @@ -316,6 +327,7 @@ NoOpDevtoolsHandler createNoOpHandler( DevtoolsLauncher? launcher, ResidentRunner runner, Logger logger, + ChromiumLauncher? chromiumLauncher, ) { return NoOpDevtoolsHandler(); } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index b2b42ad71e..2cd801fd7a 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -41,6 +41,7 @@ import 'run_cold.dart'; import 'run_hot.dart'; import 'sksl_writer.dart'; import 'vmservice.dart'; +import 'web/chrome.dart'; class FlutterDevice { FlutterDevice( @@ -1048,7 +1049,19 @@ abstract class ResidentRunner extends ResidentHandlers { artifactDirectory.createSync(recursive: true); } // TODO(bkonyi): remove when ready to serve DevTools from DDS. - _residentDevtoolsHandler = devtoolsHandler(DevtoolsLauncher.instance, this, globals.logger); + _residentDevtoolsHandler = devtoolsHandler( + DevtoolsLauncher.instance, + this, + globals.logger, + ChromiumLauncher( + fileSystem: globals.fs, + platform: globals.platform, + processManager: globals.processManager, + operatingSystemUtils: globals.os, + browserFinder: findChromeExecutable, + logger: globals.logger, + ), + ); } @override diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 9a4cce26a2..3b426f7a54 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: # https://github.com/flutter/flutter/blob/main/docs/infra/Updating-dependencies-in-Flutter.md archive: 3.6.1 args: 2.6.0 - browser_launcher: 1.1.3 dds: 5.0.0 dwds: 24.3.2 completion: 1.0.1 @@ -71,6 +70,7 @@ dependencies: _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + browser_launcher: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_collection: 5.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_value: 8.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" diff --git a/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart b/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart index 566dbaa996..ca254cdf6e 100644 --- a/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/dds.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; @@ -14,6 +16,7 @@ import 'package:flutter_tools/src/devtools_launcher.dart'; import 'package:flutter_tools/src/resident_devtools_handler.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/vmservice.dart'; +import 'package:flutter_tools/src/web/chrome.dart'; import 'package:test/fake.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart'; @@ -58,6 +61,7 @@ void main() { null, FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); await handler.serveAndAnnounceDevTools(flutterDevices: []); @@ -72,8 +76,8 @@ void main() { FakeDevtoolsLauncher(), FakeResidentRunner()..supportsServiceProtocol = false, BufferLogger.test(), + _FakeChromiumLauncher(), ); - await handler.serveAndAnnounceDevTools(flutterDevices: []); expect(handler.activeDevToolsServer, null); @@ -94,6 +98,7 @@ void main() { launcher, FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); await handler.serveAndAnnounceDevTools( @@ -114,6 +119,7 @@ void main() { ..devToolsUrl = Uri.parse('http://localhost:8080'), FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); // VM Service is intentionally null @@ -132,6 +138,7 @@ void main() { ..devToolsUrl = Uri.parse('http://localhost:8080'), FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [ @@ -170,6 +177,7 @@ void main() { FakeDevtoolsLauncher()..activeDevToolsServer = null, FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [], @@ -188,6 +196,7 @@ void main() { ..devToolsUrl = Uri.parse('http://localhost:8080'), FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [ @@ -228,6 +237,7 @@ void main() { FakeDevtoolsLauncher()..activeDevToolsServer = DevToolsServerAddress('localhost', 8080), FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [ @@ -258,6 +268,7 @@ void main() { ..devToolsUrl = Uri.parse('http://localhost:8080'), FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); final FakeVmServiceHost vmServiceHost = FakeVmServiceHost( @@ -314,6 +325,7 @@ void main() { null, FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); handler.launchDevToolsInBrowser(flutterDevices: []); @@ -328,6 +340,7 @@ void main() { FakeDevtoolsLauncher(), FakeResidentRunner()..supportsServiceProtocol = false, BufferLogger.test(), + _FakeChromiumLauncher(), ); handler.launchDevToolsInBrowser(flutterDevices: []); @@ -349,6 +362,7 @@ void main() { ..readyCompleter = completer, FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); expect(handler.launchDevToolsInBrowser(flutterDevices: []), isTrue); @@ -369,12 +383,27 @@ void main() { ..activeDevToolsServer = DevToolsServerAddress('localhost', 8080), FakeResidentRunner(), BufferLogger.test(), + _FakeChromiumLauncher(), ); expect(handler.launchDevToolsInBrowser(flutterDevices: []), isTrue); expect(handler.launchedInBrowser, isTrue); }); + testWithoutContext('launchDevToolsInBrowser fails without Chrome installed', () async { + final FlutterResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler( + FakeDevtoolsLauncher() + ..devToolsUrl = Uri(host: 'localhost', port: 8080) + ..activeDevToolsServer = DevToolsServerAddress('localhost', 8080), + FakeResidentRunner(), + BufferLogger.test(), + _ThrowingChromiumLauncher(), + ); + + expect(handler.launchedInBrowser, isFalse); + expect(handler.launchDevToolsInBrowser(flutterDevices: []), isTrue); + }); + testWithoutContext( 'Converts a VM Service URI with a query parameter to a pretty display string', () { @@ -443,3 +472,19 @@ class FakeDartDevelopmentService extends Fake implements DartDevelopmentService disposed = true; } } + +class _ThrowingChromiumLauncher extends Fake implements ChromiumLauncher { + @override + Future launch( + String url, { + bool headless = false, + int? debugPort, + bool skipCheck = false, + Directory? cacheDir, + List webBrowserFlags = const [], + }) async { + throw ProcessException('ChromiumLauncher', [url]); + } +} + +class _FakeChromiumLauncher extends Fake implements ChromiumLauncher {}