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';
import '../plugins.dart';
import '../project.dart';
import '../reporting/reporting.dart';
import '../resident_devtools_handler.dart';
import '../resident_runner.dart';
import '../run_hot.dart';
import '../vmservice.dart';
......@@ -99,6 +100,7 @@ class ResidentWebRunner extends ResidentRunner {
@required Usage usage,
@required UrlTunneller urlTunneller,
@required FeatureFlags featureFlags,
ResidentDevtoolsHandlerFactory devtoolsHandler = createDefaultHandler,
}) : _fileSystem = fileSystem,
_logger = logger,
_systemClock = systemClock,
......@@ -112,6 +114,7 @@ class ResidentWebRunner extends ResidentRunner {
ipv6: ipv6,
stayResident: stayResident,
machine: machine,
devtoolsHandler: devtoolsHandler,
);
final FileSystem _fileSystem;
......@@ -193,6 +196,7 @@ class ResidentWebRunner extends ResidentRunner {
if (_exited) {
return;
}
await residentDevtoolsHandler.shutdown();
await _stdOutSub?.cancel();
await _stdErrSub?.cancel();
await _extensionEventSub?.cancel();
......@@ -228,6 +232,8 @@ class ResidentWebRunner extends ResidentRunner {
_logger.printStatus(message);
const String quitMessage = 'To quit, press "q".';
_logger.printStatus('For a more detailed help message, press "h". $quitMessage');
_logger.printStatus('');
printDebuggerList();
}
@override
......@@ -632,6 +638,7 @@ class ResidentWebRunner extends ResidentRunner {
final Duration elapsed = _systemClock.now().difference(start);
final String elapsedMS = getElapsedAsMilliseconds(elapsed);
_logger.printStatus('Restarted application in $elapsedMS.');
unawaited(residentDevtoolsHandler.hotRestart(flutterDevices));
// Don't track restart times for dart2js builds or web-server devices.
if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) {
......@@ -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 (debuggingOptions.vmserviceOutFile != null) {
......
......@@ -9,6 +9,7 @@ import 'dart:async';
import 'package:meta/meta.dart';
import 'base/logger.dart';
import 'build_info.dart';
import 'resident_runner.dart';
import 'vmservice.dart';
......@@ -41,7 +42,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
bool _served = false;
@override
DevToolsServerAddress get activeDevToolsServer => _devToolsLauncher?.activeDevToolsServer;
DevToolsServerAddress get activeDevToolsServer => _devToolsLauncher?.activeDevToolsServer;
// This must be guaranteed not to return a Future that fails.
@override
......@@ -59,7 +60,6 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
await _devToolsLauncher.serve();
}
await _devToolsLauncher.ready;
if (_residentRunner.reportedDebuggers) {
// Since the DevTools only just became available, we haven't had a chance to
// report their URLs yet. Do so now.
......@@ -74,16 +74,15 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
);
}
Future<void> _maybeCallDevToolsUriServiceExtension(
List<FlutterDevice> flutterDevices,
) async {
if (_devToolsLauncher?.activeDevToolsServer == null) {
return;
}
Future<void> _maybeCallDevToolsUriServiceExtension(
List<FlutterDevice> flutterDevices,
) async {
if (_devToolsLauncher?.activeDevToolsServer == null) {
return;
}
await Future.wait(<Future<void>>[
for (final FlutterDevice device in flutterDevices)
if (device.vmService != null)
_callDevToolsUriExtension(device),
if (device.vmService != null) _callDevToolsUriExtension(device),
]);
}
......@@ -108,8 +107,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
Future<List<FlutterDevice>> _devicesWithExtensions(List<FlutterDevice> flutterDevices) async {
final List<FlutterDevice> devices = await Future.wait(<Future<FlutterDevice>>[
for (final FlutterDevice device in flutterDevices)
_waitForExtensionsForDevice(device)
for (final FlutterDevice device in flutterDevices) _waitForExtensionsForDevice(device)
]);
return devices.where((FlutterDevice device) => device != null).toList();
}
......@@ -118,7 +116,10 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
Future<FlutterDevice> _waitForExtensionsForDevice(FlutterDevice flutterDevice) async {
const String extension = 'ext.flutter.connectedVmServiceUri';
try {
await flutterDevice.vmService?.findExtensionIsolate(extension);
await flutterDevice.vmService?.findExtensionIsolate(
extension,
webIsolate: flutterDevice.targetPlatform == TargetPlatform.web_javascript,
);
return flutterDevice;
} on VmServiceDisappearedException {
_logger.printTrace(
......@@ -133,8 +134,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
Future<void> _callConnectedVmServiceUriExtension(List<FlutterDevice> flutterDevices) async {
await Future.wait(<Future<void>>[
for (final FlutterDevice device in flutterDevices)
if (device.vmService != null)
_callConnectedVmServiceExtension(device),
if (device.vmService != null) _callConnectedVmServiceExtension(device),
]);
}
......@@ -160,21 +160,26 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
}
}
Future<void> _invokeRpcOnFirstView(String method, {
Future<void> _invokeRpcOnFirstView(
String method, {
@required FlutterDevice device,
@required Map<String, dynamic> params,
}) async {
if (device.targetPlatform == TargetPlatform.web_javascript) {
return device.vmService.callMethodWrapper(
method,
args: params,
);
}
final List<FlutterView> views = await device.vmService.getFlutterViews();
if (views.isEmpty) {
return;
}
await device.vmService
.invokeFlutterExtensionRpcRaw(
method,
args: params,
isolateId: views
.first.uiIsolate.id
);
await device.vmService.invokeFlutterExtensionRpcRaw(
method,
args: params,
isolateId: views.first.uiIsolate.id,
);
}
@override
......
......@@ -48,7 +48,7 @@ class FlutterDevice {
this.fileSystemRoots,
this.fileSystemScheme,
TargetModel targetModel = TargetModel.flutter,
TargetPlatform targetPlatform,
this.targetPlatform,
ResidentCompiler generator,
this.userIdentifier,
}) : assert(buildInfo.trackWidgetCreation != null),
......@@ -196,6 +196,7 @@ class FlutterDevice {
final ResidentCompiler generator;
final BuildInfo buildInfo;
final String userIdentifier;
final TargetPlatform targetPlatform;
DevFSWriter devFSWriter;
Stream<Uri> observatoryUris;
......
......@@ -41,7 +41,7 @@ class IntegrationTestTestDevice implements TestDevice {
/// 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
Future<StreamChannel<String>> start(String entrypointPath) async {
final TargetPlatform targetPlatform = await device.targetPlatform;
......@@ -76,7 +76,10 @@ class IntegrationTestTestDevice implements TestDevice {
);
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);
final Stream<String> remoteMessages = vmService.service.onExtensionEvent
......
......@@ -829,9 +829,12 @@ class FlutterVmService {
/// Looks at the list of loaded extensions for first Flutter view, as well as
/// 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
/// 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 {
await service.streamListen(vm_service.EventStreams.kIsolate);
} on vm_service.RPCError {
......@@ -849,20 +852,11 @@ class FlutterVmService {
});
try {
final List<FlutterView> flutterViews = await getFlutterViews();
if (flutterViews.isEmpty) {
throw VmServiceDisappearedException();
}
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);
final List<vm_service.IsolateRef> refs = await _getIsolateRefs(webIsolate);
for (final vm_service.IsolateRef ref in refs) {
final vm_service.Isolate isolate = await service.getIsolate(ref.id);
if (isolate.extensionRPCs.contains(extensionName)) {
return isolateRef;
return ref;
}
}
return await extensionAdded.future;
......@@ -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
/// been collected.
Future<vm_service.Isolate> getIsolateOrNull(String isolateId) {
......
......@@ -5,6 +5,7 @@
// @dart = 2.8
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/devtools_launcher.dart';
import 'package:flutter_tools/src/vmservice.dart';
......@@ -45,6 +46,21 @@ final vm_service.Isolate isolate = vm_service.Isolate(
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(
method: kListViewsMethod,
jsonResponse: <String, Object>{
......@@ -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 {
final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler(
FakeDevtoolsLauncher()..activeDevToolsServer = DevToolsServerAddress('localhost', 8080),
......@@ -313,6 +383,9 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
@override
FlutterVmService vmService;
@override
TargetPlatform targetPlatform = TargetPlatform.android_arm;
}
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