Unverified Commit 5a5eb2d9 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] connect devtools deeplink URLs for web target platform / debug mode (#80389)

parent 2a8dba4a
...@@ -35,6 +35,7 @@ import '../platform_plugins.dart'; ...@@ -35,6 +35,7 @@ import '../platform_plugins.dart';
import '../plugins.dart'; import '../plugins.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/reporting.dart'; import '../reporting/reporting.dart';
import '../resident_devtools_handler.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
import '../run_hot.dart'; import '../run_hot.dart';
import '../vmservice.dart'; import '../vmservice.dart';
...@@ -99,6 +100,7 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -99,6 +100,7 @@ class ResidentWebRunner extends ResidentRunner {
@required Usage usage, @required Usage usage,
@required UrlTunneller urlTunneller, @required UrlTunneller urlTunneller,
@required FeatureFlags featureFlags, @required FeatureFlags featureFlags,
ResidentDevtoolsHandlerFactory devtoolsHandler = createDefaultHandler,
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_logger = logger, _logger = logger,
_systemClock = systemClock, _systemClock = systemClock,
...@@ -112,6 +114,7 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -112,6 +114,7 @@ class ResidentWebRunner extends ResidentRunner {
ipv6: ipv6, ipv6: ipv6,
stayResident: stayResident, stayResident: stayResident,
machine: machine, machine: machine,
devtoolsHandler: devtoolsHandler,
); );
final FileSystem _fileSystem; final FileSystem _fileSystem;
...@@ -193,6 +196,7 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -193,6 +196,7 @@ class ResidentWebRunner extends ResidentRunner {
if (_exited) { if (_exited) {
return; return;
} }
await residentDevtoolsHandler.shutdown();
await _stdOutSub?.cancel(); await _stdOutSub?.cancel();
await _stdErrSub?.cancel(); await _stdErrSub?.cancel();
await _extensionEventSub?.cancel(); await _extensionEventSub?.cancel();
...@@ -228,6 +232,8 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -228,6 +232,8 @@ class ResidentWebRunner extends ResidentRunner {
_logger.printStatus(message); _logger.printStatus(message);
const String quitMessage = 'To quit, press "q".'; const String quitMessage = 'To quit, press "q".';
_logger.printStatus('For a more detailed help message, press "h". $quitMessage'); _logger.printStatus('For a more detailed help message, press "h". $quitMessage');
_logger.printStatus('');
printDebuggerList();
} }
@override @override
...@@ -632,6 +638,7 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -632,6 +638,7 @@ class ResidentWebRunner extends ResidentRunner {
final Duration elapsed = _systemClock.now().difference(start); final Duration elapsed = _systemClock.now().difference(start);
final String elapsedMS = getElapsedAsMilliseconds(elapsed); final String elapsedMS = getElapsedAsMilliseconds(elapsed);
_logger.printStatus('Restarted application in $elapsedMS.'); _logger.printStatus('Restarted application in $elapsedMS.');
unawaited(residentDevtoolsHandler.hotRestart(flutterDevices));
// Don't track restart times for dart2js builds or web-server devices. // Don't track restart times for dart2js builds or web-server devices.
if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) { if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) {
...@@ -844,6 +851,13 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -844,6 +851,13 @@ class ResidentWebRunner extends ResidentRunner {
} }
}); });
} }
if (enableDevTools) {
// The method below is guaranteed never to return a failing future.
unawaited(residentDevtoolsHandler.serveAndAnnounceDevTools(
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
flutterDevices: flutterDevices,
));
}
} }
if (websocketUri != null) { if (websocketUri != null) {
if (debuggingOptions.vmserviceOutFile != null) { if (debuggingOptions.vmserviceOutFile != null) {
......
...@@ -9,6 +9,7 @@ import 'dart:async'; ...@@ -9,6 +9,7 @@ import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'base/logger.dart'; import 'base/logger.dart';
import 'build_info.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
import 'vmservice.dart'; import 'vmservice.dart';
...@@ -41,7 +42,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { ...@@ -41,7 +42,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
bool _served = false; bool _served = false;
@override @override
DevToolsServerAddress get activeDevToolsServer => _devToolsLauncher?.activeDevToolsServer; DevToolsServerAddress get activeDevToolsServer => _devToolsLauncher?.activeDevToolsServer;
// This must be guaranteed not to return a Future that fails. // This must be guaranteed not to return a Future that fails.
@override @override
...@@ -59,7 +60,6 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { ...@@ -59,7 +60,6 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
await _devToolsLauncher.serve(); await _devToolsLauncher.serve();
} }
await _devToolsLauncher.ready; await _devToolsLauncher.ready;
if (_residentRunner.reportedDebuggers) { if (_residentRunner.reportedDebuggers) {
// Since the DevTools only just became available, we haven't had a chance to // Since the DevTools only just became available, we haven't had a chance to
// report their URLs yet. Do so now. // report their URLs yet. Do so now.
...@@ -74,16 +74,15 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { ...@@ -74,16 +74,15 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
); );
} }
Future<void> _maybeCallDevToolsUriServiceExtension( Future<void> _maybeCallDevToolsUriServiceExtension(
List<FlutterDevice> flutterDevices, List<FlutterDevice> flutterDevices,
) async { ) async {
if (_devToolsLauncher?.activeDevToolsServer == null) { if (_devToolsLauncher?.activeDevToolsServer == null) {
return; return;
} }
await Future.wait(<Future<void>>[ await Future.wait(<Future<void>>[
for (final FlutterDevice device in flutterDevices) for (final FlutterDevice device in flutterDevices)
if (device.vmService != null) if (device.vmService != null) _callDevToolsUriExtension(device),
_callDevToolsUriExtension(device),
]); ]);
} }
...@@ -108,8 +107,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { ...@@ -108,8 +107,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
Future<List<FlutterDevice>> _devicesWithExtensions(List<FlutterDevice> flutterDevices) async { Future<List<FlutterDevice>> _devicesWithExtensions(List<FlutterDevice> flutterDevices) async {
final List<FlutterDevice> devices = await Future.wait(<Future<FlutterDevice>>[ final List<FlutterDevice> devices = await Future.wait(<Future<FlutterDevice>>[
for (final FlutterDevice device in flutterDevices) for (final FlutterDevice device in flutterDevices) _waitForExtensionsForDevice(device)
_waitForExtensionsForDevice(device)
]); ]);
return devices.where((FlutterDevice device) => device != null).toList(); return devices.where((FlutterDevice device) => device != null).toList();
} }
...@@ -118,7 +116,10 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { ...@@ -118,7 +116,10 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
Future<FlutterDevice> _waitForExtensionsForDevice(FlutterDevice flutterDevice) async { Future<FlutterDevice> _waitForExtensionsForDevice(FlutterDevice flutterDevice) async {
const String extension = 'ext.flutter.connectedVmServiceUri'; const String extension = 'ext.flutter.connectedVmServiceUri';
try { try {
await flutterDevice.vmService?.findExtensionIsolate(extension); await flutterDevice.vmService?.findExtensionIsolate(
extension,
webIsolate: flutterDevice.targetPlatform == TargetPlatform.web_javascript,
);
return flutterDevice; return flutterDevice;
} on VmServiceDisappearedException { } on VmServiceDisappearedException {
_logger.printTrace( _logger.printTrace(
...@@ -133,8 +134,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { ...@@ -133,8 +134,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
Future<void> _callConnectedVmServiceUriExtension(List<FlutterDevice> flutterDevices) async { Future<void> _callConnectedVmServiceUriExtension(List<FlutterDevice> flutterDevices) async {
await Future.wait(<Future<void>>[ await Future.wait(<Future<void>>[
for (final FlutterDevice device in flutterDevices) for (final FlutterDevice device in flutterDevices)
if (device.vmService != null) if (device.vmService != null) _callConnectedVmServiceExtension(device),
_callConnectedVmServiceExtension(device),
]); ]);
} }
...@@ -160,21 +160,26 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { ...@@ -160,21 +160,26 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
} }
} }
Future<void> _invokeRpcOnFirstView(String method, { Future<void> _invokeRpcOnFirstView(
String method, {
@required FlutterDevice device, @required FlutterDevice device,
@required Map<String, dynamic> params, @required Map<String, dynamic> params,
}) async { }) async {
if (device.targetPlatform == TargetPlatform.web_javascript) {
return device.vmService.callMethodWrapper(
method,
args: params,
);
}
final List<FlutterView> views = await device.vmService.getFlutterViews(); final List<FlutterView> views = await device.vmService.getFlutterViews();
if (views.isEmpty) { if (views.isEmpty) {
return; return;
} }
await device.vmService await device.vmService.invokeFlutterExtensionRpcRaw(
.invokeFlutterExtensionRpcRaw( method,
method, args: params,
args: params, isolateId: views.first.uiIsolate.id,
isolateId: views );
.first.uiIsolate.id
);
} }
@override @override
......
...@@ -48,7 +48,7 @@ class FlutterDevice { ...@@ -48,7 +48,7 @@ class FlutterDevice {
this.fileSystemRoots, this.fileSystemRoots,
this.fileSystemScheme, this.fileSystemScheme,
TargetModel targetModel = TargetModel.flutter, TargetModel targetModel = TargetModel.flutter,
TargetPlatform targetPlatform, this.targetPlatform,
ResidentCompiler generator, ResidentCompiler generator,
this.userIdentifier, this.userIdentifier,
}) : assert(buildInfo.trackWidgetCreation != null), }) : assert(buildInfo.trackWidgetCreation != null),
...@@ -196,6 +196,7 @@ class FlutterDevice { ...@@ -196,6 +196,7 @@ class FlutterDevice {
final ResidentCompiler generator; final ResidentCompiler generator;
final BuildInfo buildInfo; final BuildInfo buildInfo;
final String userIdentifier; final String userIdentifier;
final TargetPlatform targetPlatform;
DevFSWriter devFSWriter; DevFSWriter devFSWriter;
Stream<Uri> observatoryUris; Stream<Uri> observatoryUris;
......
...@@ -41,7 +41,7 @@ class IntegrationTestTestDevice implements TestDevice { ...@@ -41,7 +41,7 @@ class IntegrationTestTestDevice implements TestDevice {
/// Starts the device. /// Starts the device.
/// ///
/// [entrypointPath] must be a path to an uncompiled source file. /// [entrypointPath] must be a path to an un-compiled source file.
@override @override
Future<StreamChannel<String>> start(String entrypointPath) async { Future<StreamChannel<String>> start(String entrypointPath) async {
final TargetPlatform targetPlatform = await device.targetPlatform; final TargetPlatform targetPlatform = await device.targetPlatform;
...@@ -76,7 +76,10 @@ class IntegrationTestTestDevice implements TestDevice { ...@@ -76,7 +76,10 @@ class IntegrationTestTestDevice implements TestDevice {
); );
globals.printTrace('test $id: Finding the correct isolate with the integration test service extension'); globals.printTrace('test $id: Finding the correct isolate with the integration test service extension');
final vm_service.IsolateRef isolateRef = await vmService.findExtensionIsolate(kIntegrationTestMethod); final vm_service.IsolateRef isolateRef = await vmService.findExtensionIsolate(
kIntegrationTestMethod,
webIsolate: targetPlatform == TargetPlatform.web_javascript,
);
await vmService.service.streamListen(vm_service.EventStreams.kExtension); await vmService.service.streamListen(vm_service.EventStreams.kExtension);
final Stream<String> remoteMessages = vmService.service.onExtensionEvent final Stream<String> remoteMessages = vmService.service.onExtensionEvent
......
...@@ -829,9 +829,12 @@ class FlutterVmService { ...@@ -829,9 +829,12 @@ class FlutterVmService {
/// Looks at the list of loaded extensions for first Flutter view, as well as /// Looks at the list of loaded extensions for first Flutter view, as well as
/// the stream of added extensions to avoid races. /// the stream of added extensions to avoid races.
/// ///
/// If [webIsolate] is true, this uses the VM Service isolate list instead of
/// the `_flutter.listViews` method, which is not implemented by DWDS.
///
/// Throws a [VmServiceDisappearedException] should the VM Service disappear /// Throws a [VmServiceDisappearedException] should the VM Service disappear
/// while making calls to it. /// while making calls to it.
Future<vm_service.IsolateRef> findExtensionIsolate(String extensionName) async { Future<vm_service.IsolateRef> findExtensionIsolate(String extensionName, {bool webIsolate = false}) async {
try { try {
await service.streamListen(vm_service.EventStreams.kIsolate); await service.streamListen(vm_service.EventStreams.kIsolate);
} on vm_service.RPCError { } on vm_service.RPCError {
...@@ -849,20 +852,11 @@ class FlutterVmService { ...@@ -849,20 +852,11 @@ class FlutterVmService {
}); });
try { try {
final List<FlutterView> flutterViews = await getFlutterViews(); final List<vm_service.IsolateRef> refs = await _getIsolateRefs(webIsolate);
if (flutterViews.isEmpty) { for (final vm_service.IsolateRef ref in refs) {
throw VmServiceDisappearedException(); final vm_service.Isolate isolate = await service.getIsolate(ref.id);
}
for (final FlutterView flutterView in flutterViews) {
final vm_service.IsolateRef isolateRef = flutterView.uiIsolate;
if (isolateRef == null) {
continue;
}
final vm_service.Isolate isolate = await service.getIsolate(isolateRef.id);
if (isolate.extensionRPCs.contains(extensionName)) { if (isolate.extensionRPCs.contains(extensionName)) {
return isolateRef; return ref;
} }
} }
return await extensionAdded.future; return await extensionAdded.future;
...@@ -876,6 +870,28 @@ class FlutterVmService { ...@@ -876,6 +870,28 @@ class FlutterVmService {
} }
} }
Future<List<vm_service.IsolateRef>> _getIsolateRefs(bool webIsolate) async {
if (webIsolate) {
final List<vm_service.IsolateRef> refs = (await service.getVM()).isolates;
if (refs.isEmpty) {
throw VmServiceDisappearedException();
}
return refs;
}
final List<FlutterView> flutterViews = await getFlutterViews();
if (flutterViews.isEmpty) {
throw VmServiceDisappearedException();
}
final List<vm_service.IsolateRef> refs = <vm_service.IsolateRef>[];
for (final FlutterView flutterView in flutterViews) {
if (flutterView.uiIsolate != null) {
refs.add(flutterView.uiIsolate);
}
}
return refs;
}
/// Attempt to retrieve the isolate with id [isolateId], or `null` if it has /// Attempt to retrieve the isolate with id [isolateId], or `null` if it has
/// been collected. /// been collected.
Future<vm_service.Isolate> getIsolateOrNull(String isolateId) { Future<vm_service.Isolate> getIsolateOrNull(String isolateId) {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// @dart = 2.8 // @dart = 2.8
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/devtools_launcher.dart'; import 'package:flutter_tools/src/devtools_launcher.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
...@@ -45,6 +46,21 @@ final vm_service.Isolate isolate = vm_service.Isolate( ...@@ -45,6 +46,21 @@ final vm_service.Isolate isolate = vm_service.Isolate(
extensionRPCs: <String>['ext.flutter.connectedVmServiceUri'], extensionRPCs: <String>['ext.flutter.connectedVmServiceUri'],
); );
final vm_service.VM fakeVM = vm_service.VM(
isolates: <vm_service.IsolateRef>[isolate],
pid: 1,
hostCPU: '',
isolateGroups: <vm_service.IsolateGroupRef>[],
targetCPU: '',
startTime: 0,
name: 'dart',
architectureBits: 64,
operatingSystem: '',
version: '',
systemIsolateGroups: <vm_service.IsolateGroupRef>[],
systemIsolates: <vm_service.IsolateRef>[],
);
final FakeVmServiceRequest listViews = FakeVmServiceRequest( final FakeVmServiceRequest listViews = FakeVmServiceRequest(
method: kListViewsMethod, method: kListViewsMethod,
jsonResponse: <String, Object>{ jsonResponse: <String, Object>{
...@@ -175,6 +191,60 @@ void main() { ...@@ -175,6 +191,60 @@ void main() {
); );
}); });
testWithoutContext('serveAndAnnounceDevTools with web device', () async {
final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler(
FakeDevtoolsLauncher()..activeDevToolsServer = DevToolsServerAddress('localhost', 8080),
FakeResidentRunner(),
BufferLogger.test(),
);
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
}
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
args: <String, Object>{},
),
FakeVmServiceRequest(
method: 'getIsolate',
jsonResponse: isolate.toJson(),
args: <String, Object>{
'isolateId': '1',
},
),
const FakeVmServiceRequest(
method: 'streamCancel',
args: <String, Object>{
'streamId': 'Isolate',
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.activeDevToolsServerAddress',
args: <String, Object>{
'value': 'http://localhost:8080',
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.connectedVmServiceUri',
args: <String, Object>{
'value': 'http://localhost:1234',
},
),
], httpAddress: Uri.parse('http://localhost:1234'));
final FakeFlutterDevice device = FakeFlutterDevice()
..vmService = fakeVmServiceHost.vmService
..targetPlatform = TargetPlatform.web_javascript;
await handler.serveAndAnnounceDevTools(
flutterDevices: <FlutterDevice>[device],
);
});
testWithoutContext('serveAndAnnounceDevTools with skips calling service extensions when VM service disappears', () async { testWithoutContext('serveAndAnnounceDevTools with skips calling service extensions when VM service disappears', () async {
final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler( final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler(
FakeDevtoolsLauncher()..activeDevToolsServer = DevToolsServerAddress('localhost', 8080), FakeDevtoolsLauncher()..activeDevToolsServer = DevToolsServerAddress('localhost', 8080),
...@@ -313,6 +383,9 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { ...@@ -313,6 +383,9 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
@override @override
FlutterVmService vmService; FlutterVmService vmService;
@override
TargetPlatform targetPlatform = TargetPlatform.android_arm;
} }
class FakeDevice extends Fake implements Device {} class FakeDevice extends Fake implements Device {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment