Commit 3a012b32 authored by John McCutchan's avatar John McCutchan Committed by GitHub

vmservice redux (#5437)

parent 53dd5dbd
...@@ -143,7 +143,7 @@ class RunCommand extends RunCommandBase { ...@@ -143,7 +143,7 @@ class RunCommand extends RunCommandBase {
return 1; return 1;
} }
} else { } else {
if (argResults['control-pipe']) { if (argResults['control-pipe'] != null) {
printError('--control-pipe requires --hot'); printError('--control-pipe requires --hot');
return 1; return 1;
} }
......
...@@ -95,40 +95,39 @@ class TraceCommand extends FlutterCommand { ...@@ -95,40 +95,39 @@ class TraceCommand extends FlutterCommand {
} }
class Tracing { class Tracing {
Tracing(this.observatory); Tracing(this.vmService);
static Future<Tracing> connect(int port) { static Future<Tracing> connect(int port) {
return VMService.connect(port).then((VMService observatory) => new Tracing(observatory)); return VMService.connect(port).then((VMService observatory) => new Tracing(observatory));
} }
final VMService observatory; final VMService vmService;
Future<Null> startTracing() async { Future<Null> startTracing() async {
await observatory.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']); await vmService.vm.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
await observatory.clearVMTimeline(); await vmService.vm.clearVMTimeline();
} }
/// Stops tracing; optionally wait for first frame. /// Stops tracing; optionally wait for first frame.
Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({ Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({
bool waitForFirstFrame: false bool waitForFirstFrame: false
}) async { }) async {
Response timeline; Map<String, dynamic> timeline;
if (!waitForFirstFrame) { if (!waitForFirstFrame) {
// Stop tracing immediately and get the timeline // Stop tracing immediately and get the timeline
await observatory.setVMTimelineFlags(<String>[]); await vmService.vm.setVMTimelineFlags(<String>[]);
timeline = await observatory.getVMTimeline(); timeline = await vmService.vm.getVMTimeline();
} else { } else {
Completer<Null> whenFirstFrameRendered = new Completer<Null>(); Completer<Null> whenFirstFrameRendered = new Completer<Null>();
observatory.onTimelineEvent.listen((Event timelineEvent) { vmService.onTimelineEvent.listen((ServiceEvent timelineEvent) {
List<Map<String, dynamic>> events = timelineEvent['timelineEvents']; List<Map<String, dynamic>> events = timelineEvent.timelineEvents;
for (Map<String, dynamic> event in events) { for (Map<String, dynamic> event in events) {
if (event['name'] == kFirstUsefulFrameEventName) if (event['name'] == kFirstUsefulFrameEventName)
whenFirstFrameRendered.complete(); whenFirstFrameRendered.complete();
} }
}); });
await observatory.streamListen('Timeline');
await whenFirstFrameRendered.future.timeout( await whenFirstFrameRendered.future.timeout(
const Duration(seconds: 10), const Duration(seconds: 10),
...@@ -142,12 +141,12 @@ class Tracing { ...@@ -142,12 +141,12 @@ class Tracing {
} }
); );
timeline = await observatory.getVMTimeline(); timeline = await vmService.vm.getVMTimeline();
await observatory.setVMTimelineFlags(<String>[]); await vmService.vm.setVMTimelineFlags(<String>[]);
} }
return timeline.response; return timeline;
} }
} }
......
...@@ -106,22 +106,22 @@ abstract class DevFSOperations { ...@@ -106,22 +106,22 @@ abstract class DevFSOperations {
} }
/// An implementation of [DevFSOperations] that speaks to the /// An implementation of [DevFSOperations] that speaks to the
/// service protocol. /// vm service.
class ServiceProtocolDevFSOperations implements DevFSOperations { class ServiceProtocolDevFSOperations implements DevFSOperations {
final VMService serviceProtocol; final VMService vmService;
ServiceProtocolDevFSOperations(this.serviceProtocol); ServiceProtocolDevFSOperations(this.vmService);
@override @override
Future<Uri> create(String fsName) async { Future<Uri> create(String fsName) async {
Response response = await serviceProtocol.createDevFS(fsName); Map<String, dynamic> response = await vmService.vm.createDevFS(fsName);
return Uri.parse(response['uri']); return Uri.parse(response['uri']);
} }
@override @override
Future<dynamic> destroy(String fsName) async { Future<dynamic> destroy(String fsName) async {
await serviceProtocol.sendRequest('_deleteDevFS', await vmService.vm.invokeRpcRaw('_deleteDevFS',
<String, dynamic> { 'fsName': fsName }); <String, dynamic> { 'fsName': fsName });
} }
@override @override
...@@ -134,12 +134,12 @@ class ServiceProtocolDevFSOperations implements DevFSOperations { ...@@ -134,12 +134,12 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
} }
String fileContents = BASE64.encode(bytes); String fileContents = BASE64.encode(bytes);
try { try {
return await serviceProtocol.sendRequest('_writeDevFSFile', return await vmService.vm.invokeRpcRaw('_writeDevFSFile',
<String, dynamic> { <String, dynamic> {
'fsName': fsName, 'fsName': fsName,
'path': entry.devicePath, 'path': entry.devicePath,
'fileContents': fileContents 'fileContents': fileContents
}); });
} catch (e) { } catch (e) {
printTrace('DevFS: Failed to write ${entry.devicePath}: $e'); printTrace('DevFS: Failed to write ${entry.devicePath}: $e');
} }
...@@ -155,12 +155,12 @@ class ServiceProtocolDevFSOperations implements DevFSOperations { ...@@ -155,12 +155,12 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
String devicePath, String devicePath,
String contents) async { String contents) async {
String fileContents = BASE64.encode(UTF8.encode(contents)); String fileContents = BASE64.encode(UTF8.encode(contents));
return await serviceProtocol.sendRequest('_writeDevFSFile', return await vmService.vm.invokeRpcRaw('_writeDevFSFile',
<String, dynamic> { <String, dynamic> {
'fsName': fsName, 'fsName': fsName,
'path': devicePath, 'path': devicePath,
'fileContents': fileContents 'fileContents': fileContents
}); });
} }
} }
...@@ -251,7 +251,8 @@ class DevFS { ...@@ -251,7 +251,8 @@ class DevFS {
final Set<DevFSEntry> _deletedEntries = new Set<DevFSEntry>(); final Set<DevFSEntry> _deletedEntries = new Set<DevFSEntry>();
final Set<DevFSEntry> dirtyAssetEntries = new Set<DevFSEntry>(); final Set<DevFSEntry> dirtyAssetEntries = new Set<DevFSEntry>();
final List<Future<Response>> _pendingOperations = new List<Future<Response>>(); final List<Future<Map<String, dynamic>>> _pendingOperations =
new List<Future<Map<String, dynamic>>>();
int _bytes = 0; int _bytes = 0;
int get bytes => _bytes; int get bytes => _bytes;
...@@ -358,7 +359,8 @@ class DevFS { ...@@ -358,7 +359,8 @@ class DevFS {
if (_deletedEntries.length > 0) { if (_deletedEntries.length > 0) {
status = logger.startProgress('Removing deleted files...'); status = logger.startProgress('Removing deleted files...');
for (DevFSEntry entry in _deletedEntries) { for (DevFSEntry entry in _deletedEntries) {
Future<Response> operation = _operations.deleteFile(fsName, entry); Future<Map<String, dynamic>> operation =
_operations.deleteFile(fsName, entry);
if (operation != null) if (operation != null)
_pendingOperations.add(operation); _pendingOperations.add(operation);
} }
...@@ -382,7 +384,8 @@ class DevFS { ...@@ -382,7 +384,8 @@ class DevFS {
} else { } else {
// Make service protocol requests for each. // Make service protocol requests for each.
for (DevFSEntry entry in _dirtyEntries) { for (DevFSEntry entry in _dirtyEntries) {
Future<Response> operation = _operations.writeFile(fsName, entry); Future<Map<String, dynamic>> operation =
_operations.writeFile(fsName, entry);
if (operation != null) if (operation != null)
_pendingOperations.add(operation); _pendingOperations.add(operation);
} }
......
...@@ -23,7 +23,6 @@ import 'devfs.dart'; ...@@ -23,7 +23,6 @@ import 'devfs.dart';
import 'vmservice.dart'; import 'vmservice.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
import 'toolchain.dart'; import 'toolchain.dart';
import 'view.dart';
String getDevFSLoaderScript() { String getDevFSLoaderScript() {
return path.absolute(path.join(Cache.flutterRoot, return path.absolute(path.join(Cache.flutterRoot,
...@@ -80,18 +79,18 @@ class StartupDependencySetBuilder { ...@@ -80,18 +79,18 @@ class StartupDependencySetBuilder {
class FirstFrameTimer { class FirstFrameTimer {
FirstFrameTimer(this.serviceProtocol); FirstFrameTimer(this.vmService);
void start() { void start() {
stopwatch.reset(); stopwatch.reset();
stopwatch.start(); stopwatch.start();
_subscription = serviceProtocol.onExtensionEvent.listen(_onExtensionEvent); _subscription = vmService.onExtensionEvent.listen(_onExtensionEvent);
} }
/// Returns a Future which completes after the first frame event is received. /// Returns a Future which completes after the first frame event is received.
Future<Null> firstFrame() => _completer.future; Future<Null> firstFrame() => _completer.future;
void _onExtensionEvent(Event event) { void _onExtensionEvent(ServiceEvent event) {
if (event.extensionKind == 'Flutter.FirstFrame') if (event.extensionKind == 'Flutter.FirstFrame')
_stop(); _stop();
} }
...@@ -108,10 +107,10 @@ class FirstFrameTimer { ...@@ -108,10 +107,10 @@ class FirstFrameTimer {
return stopwatch.elapsed; return stopwatch.elapsed;
} }
final VMService serviceProtocol; final VMService vmService;
final Stopwatch stopwatch = new Stopwatch(); final Stopwatch stopwatch = new Stopwatch();
final Completer<Null> _completer = new Completer<Null>(); final Completer<Null> _completer = new Completer<Null>();
StreamSubscription<Event> _subscription; StreamSubscription<ServiceEvent> _subscription;
} }
class HotRunner extends ResidentRunner { class HotRunner extends ResidentRunner {
...@@ -294,8 +293,8 @@ class HotRunner extends ResidentRunner { ...@@ -294,8 +293,8 @@ class HotRunner extends ResidentRunner {
return 3; return 3;
} }
await viewManager.refresh(); await vmService.vm.refreshViews();
printStatus('Connected to view \'${viewManager.mainView}\'.'); printStatus('Connected to view \'${vmService.vm.mainView}\'.');
printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...'); printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
_loaderShowMessage('Launching...'); _loaderShowMessage('Launching...');
...@@ -332,13 +331,13 @@ class HotRunner extends ResidentRunner { ...@@ -332,13 +331,13 @@ class HotRunner extends ResidentRunner {
} }
void _loaderShowMessage(String message, { int progress, int max }) { void _loaderShowMessage(String message, { int progress, int max }) {
serviceProtocol.flutterLoaderShowMessage(serviceProtocol.firstIsolateId, message); currentView.uiIsolate.flutterLoaderShowMessage(message);
if (progress != null) { if (progress != null) {
serviceProtocol.flutterLoaderSetProgress(serviceProtocol.firstIsolateId, progress.toDouble()); currentView.uiIsolate.flutterLoaderSetProgress(progress.toDouble());
serviceProtocol.flutterLoaderSetProgressMax(serviceProtocol.firstIsolateId, max?.toDouble() ?? 0.0); currentView.uiIsolate.flutterLoaderSetProgressMax(max?.toDouble() ?? 0.0);
} else { } else {
serviceProtocol.flutterLoaderSetProgress(serviceProtocol.firstIsolateId, 0.0); currentView.uiIsolate.flutterLoaderSetProgress(0.0);
serviceProtocol.flutterLoaderSetProgressMax(serviceProtocol.firstIsolateId, -1.0); currentView.uiIsolate.flutterLoaderSetProgressMax(-1.0);
} }
} }
...@@ -346,7 +345,7 @@ class HotRunner extends ResidentRunner { ...@@ -346,7 +345,7 @@ class HotRunner extends ResidentRunner {
Future<Uri> _initDevFS() { Future<Uri> _initDevFS() {
String fsName = path.basename(_projectRootPath); String fsName = path.basename(_projectRootPath);
_devFS = new DevFS(serviceProtocol, _devFS = new DevFS(vmService,
fsName, fsName,
new Directory(_projectRootPath)); new Directory(_projectRootPath));
return _devFS.create(); return _devFS.create();
...@@ -377,11 +376,10 @@ class HotRunner extends ResidentRunner { ...@@ -377,11 +376,10 @@ class HotRunner extends ResidentRunner {
Future<Null> _evictDirtyAssets() async { Future<Null> _evictDirtyAssets() async {
if (_devFS.dirtyAssetEntries.length == 0) if (_devFS.dirtyAssetEntries.length == 0)
return; return;
if (serviceProtocol.firstIsolateId == null) if (currentView.uiIsolate == null)
throw 'Application isolate not found'; throw 'Application isolate not found';
for (DevFSEntry entry in _devFS.dirtyAssetEntries) { for (DevFSEntry entry in _devFS.dirtyAssetEntries) {
await serviceProtocol.flutterEvictAsset(serviceProtocol.firstIsolateId, await currentView.uiIsolate.flutterEvictAsset(entry.assetPath);
entry.assetPath);
} }
} }
...@@ -400,7 +398,7 @@ class HotRunner extends ResidentRunner { ...@@ -400,7 +398,7 @@ class HotRunner extends ResidentRunner {
Future<Null> _launchInView(String entryPath, Future<Null> _launchInView(String entryPath,
String packagesPath, String packagesPath,
String assetsDirectoryPath) async { String assetsDirectoryPath) async {
FlutterView view = viewManager.mainView; FlutterView view = vmService.vm.mainView;
return view.runFromSource(entryPath, packagesPath, assetsDirectoryPath); return view.runFromSource(entryPath, packagesPath, assetsDirectoryPath);
} }
...@@ -419,7 +417,7 @@ class HotRunner extends ResidentRunner { ...@@ -419,7 +417,7 @@ class HotRunner extends ResidentRunner {
} }
Future<Null> _restartFromSources() async { Future<Null> _restartFromSources() async {
FirstFrameTimer firstFrameTimer = new FirstFrameTimer(serviceProtocol); FirstFrameTimer firstFrameTimer = new FirstFrameTimer(vmService);
firstFrameTimer.start(); firstFrameTimer.start();
await _updateDevFS(); await _updateDevFS();
await _launchFromDevFS(_package, _mainPath); await _launchFromDevFS(_package, _mainPath);
...@@ -459,16 +457,16 @@ class HotRunner extends ResidentRunner { ...@@ -459,16 +457,16 @@ class HotRunner extends ResidentRunner {
} }
Future<bool> _reloadSources() async { Future<bool> _reloadSources() async {
if (serviceProtocol.firstIsolateId == null) if (currentView.uiIsolate == null)
throw 'Application isolate not found'; throw 'Application isolate not found';
FirstFrameTimer firstFrameTimer = new FirstFrameTimer(serviceProtocol); FirstFrameTimer firstFrameTimer = new FirstFrameTimer(vmService);
firstFrameTimer.start(); firstFrameTimer.start();
if (_devFS != null) if (_devFS != null)
await _updateDevFS(); await _updateDevFS();
Status reloadStatus = logger.startProgress('Performing hot reload...'); Status reloadStatus = logger.startProgress('Performing hot reload...');
try { try {
Map<String, dynamic> reloadReport = Map<String, dynamic> reloadReport =
await serviceProtocol.reloadSources(serviceProtocol.firstIsolateId); await currentView.uiIsolate.reloadSources();
reloadStatus.stop(showElapsedTime: true); reloadStatus.stop(showElapsedTime: true);
if (!_printReloadReport(reloadReport)) { if (!_printReloadReport(reloadReport)) {
// Reload failed. // Reload failed.
...@@ -477,16 +475,16 @@ class HotRunner extends ResidentRunner { ...@@ -477,16 +475,16 @@ class HotRunner extends ResidentRunner {
} else { } else {
flutterUsage.sendEvent('hot', 'reload'); flutterUsage.sendEvent('hot', 'reload');
} }
} catch (errorMessage) { } catch (errorMessage, st) {
reloadStatus.stop(showElapsedTime: true); reloadStatus.stop(showElapsedTime: true);
printError('Hot reload failed:\n$errorMessage'); printError('Hot reload failed:\n$errorMessage\n$st');
return false; return false;
} }
await _evictDirtyAssets(); await _evictDirtyAssets();
Status reassembleStatus = Status reassembleStatus =
logger.startProgress('Reassembling application...'); logger.startProgress('Reassembling application...');
try { try {
await serviceProtocol.flutterReassemble(serviceProtocol.firstIsolateId); await currentView.uiIsolate.flutterReassemble();
} catch (_) { } catch (_) {
reassembleStatus.stop(showElapsedTime: true); reassembleStatus.stop(showElapsedTime: true);
printError('Reassembling application failed.'); printError('Reassembling application failed.');
......
...@@ -12,7 +12,6 @@ import 'build_info.dart'; ...@@ -12,7 +12,6 @@ import 'build_info.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart'; import 'globals.dart';
import 'vmservice.dart'; import 'vmservice.dart';
import 'view.dart';
// Shared code between different resident application runners. // Shared code between different resident application runners.
abstract class ResidentRunner { abstract class ResidentRunner {
...@@ -28,8 +27,8 @@ abstract class ResidentRunner { ...@@ -28,8 +27,8 @@ abstract class ResidentRunner {
final bool usesTerminalUI; final bool usesTerminalUI;
final Completer<int> _finished = new Completer<int>(); final Completer<int> _finished = new Completer<int>();
VMService serviceProtocol; VMService vmService;
ViewManager viewManager; FlutterView currentView;
StreamSubscription<String> _loggingSubscription; StreamSubscription<String> _loggingSubscription;
/// Start the app and keep the process running during its lifetime. /// Start the app and keep the process running during its lifetime.
...@@ -48,11 +47,11 @@ abstract class ResidentRunner { ...@@ -48,11 +47,11 @@ abstract class ResidentRunner {
} }
Future<Null> _debugDumpApp() async { Future<Null> _debugDumpApp() async {
await serviceProtocol.flutterDebugDumpApp(serviceProtocol.firstIsolateId); await currentView.uiIsolate.flutterDebugDumpApp();
} }
Future<Null> _debugDumpRenderTree() async { Future<Null> _debugDumpRenderTree() async {
await serviceProtocol.flutterDebugDumpRenderTree(serviceProtocol.firstIsolateId); await currentView.uiIsolate.flutterDebugDumpRenderTree();
} }
void registerSignalHandlers() { void registerSignalHandlers() {
...@@ -90,22 +89,23 @@ abstract class ResidentRunner { ...@@ -90,22 +89,23 @@ abstract class ResidentRunner {
if (!debuggingOptions.debuggingEnabled) { if (!debuggingOptions.debuggingEnabled) {
return new Future<Null>.error('Error the service protocol is not enabled.'); return new Future<Null>.error('Error the service protocol is not enabled.');
} }
serviceProtocol = await VMService.connect(port); vmService = await VMService.connect(port);
printTrace('Connected to service protocol on port $port'); printTrace('Connected to service protocol on port $port');
serviceProtocol.populateIsolateInfo(); await vmService.getVM();
serviceProtocol.onExtensionEvent.listen((Event event) { vmService.onExtensionEvent.listen((ServiceEvent event) {
printTrace(event.toString()); printTrace(event.toString());
}); });
serviceProtocol.onIsolateEvent.listen((Event event) { vmService.onIsolateEvent.listen((ServiceEvent event) {
printTrace(event.toString()); printTrace(event.toString());
}); });
// Setup view manager and refresh the view list. // Refresh the view list.
viewManager = new ViewManager(serviceProtocol); await vmService.vm.refreshViews();
await viewManager.refresh(); currentView = vmService.vm.mainView;
assert(currentView != null);
// Listen for service protocol connection to close. // Listen for service protocol connection to close.
serviceProtocol.done.whenComplete(() { vmService.done.whenComplete(() {
appFinished(); appFinished();
}); });
} }
...@@ -175,9 +175,10 @@ abstract class ResidentRunner { ...@@ -175,9 +175,10 @@ abstract class ResidentRunner {
Future<Null> preStop() async { } Future<Null> preStop() async { }
Future<Null> stopApp() async { Future<Null> stopApp() async {
if (serviceProtocol != null && !serviceProtocol.isClosed) { if (vmService != null && !vmService.isClosed) {
if (serviceProtocol.isolates.isNotEmpty) { if ((currentView != null) && (currentView.uiIsolate != null)) {
serviceProtocol.flutterExit(serviceProtocol.firstIsolateId); // TODO(johnmccutchan): Wait for the exit command to complete.
currentView.uiIsolate.flutterExit();
await new Future<Null>.delayed(new Duration(milliseconds: 100)); await new Future<Null>.delayed(new Duration(milliseconds: 100));
} }
} }
......
...@@ -59,17 +59,17 @@ class RunAndStayResident extends ResidentRunner { ...@@ -59,17 +59,17 @@ class RunAndStayResident extends ResidentRunner {
@override @override
Future<bool> restart({ bool fullRestart: false }) async { Future<bool> restart({ bool fullRestart: false }) async {
if (serviceProtocol == null) { if (vmService == null) {
printError('Debugging is not enabled.'); printError('Debugging is not enabled.');
return false; return false;
} else { } else {
Status status = logger.startProgress('Re-starting application...'); Status status = logger.startProgress('Re-starting application...');
Future<Event> extensionAddedEvent; Future<ServiceEvent> extensionAddedEvent;
if (device.restartSendsFrameworkInitEvent) { if (device.restartSendsFrameworkInitEvent) {
extensionAddedEvent = serviceProtocol.onExtensionEvent extensionAddedEvent = vmService.onExtensionEvent
.where((Event event) => event.extensionKind == 'Flutter.FrameworkInitialization') .where((ServiceEvent event) => event.extensionKind == 'Flutter.FrameworkInitialization')
.first; .first;
} }
...@@ -77,7 +77,7 @@ class RunAndStayResident extends ResidentRunner { ...@@ -77,7 +77,7 @@ class RunAndStayResident extends ResidentRunner {
_package, _package,
_result, _result,
mainPath: _mainPath, mainPath: _mainPath,
observatory: serviceProtocol observatory: vmService
); );
status.stop(showElapsedTime: true); status.stop(showElapsedTime: true);
...@@ -178,16 +178,20 @@ class RunAndStayResident extends ResidentRunner { ...@@ -178,16 +178,20 @@ class RunAndStayResident extends ResidentRunner {
if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.debuggingEnabled) {
await connectToServiceProtocol(_result.observatoryPort); await connectToServiceProtocol(_result.observatoryPort);
if (benchmark) if (benchmark) {
await serviceProtocol.waitFirstIsolate; await vmService.getVM();
}
} }
printStatus('Application running.'); printStatus('Application running.');
if (serviceProtocol != null && traceStartup) { await vmService.vm.refreshViews();
printStatus('Connected to view \'${vmService.vm.mainView}\'.');
if (vmService != null && traceStartup) {
printStatus('Downloading startup trace info...'); printStatus('Downloading startup trace info...');
try { try {
await downloadStartupTrace(serviceProtocol); await downloadStartupTrace(vmService);
} catch(error) { } catch(error) {
printError(error); printError(error);
return 2; return 2;
......
// Copyright 2016 The Chromium 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 'dart:async';
import 'globals.dart';
import 'vmservice.dart';
/// Peered to a Android/iOS FlutterView widget on a device.
class FlutterView {
FlutterView(this.viewId, this.viewManager);
final String viewId;
final ViewManager viewManager;
String _uiIsolateId;
String get uiIsolateId => _uiIsolateId;
Future<Null> runFromSource(String entryPath,
String packagesPath,
String assetsDirectoryPath) async {
return viewManager._runFromSource(this,
entryPath,
packagesPath,
assetsDirectoryPath);
}
@override
String toString() => viewId;
@override
bool operator ==(FlutterView other) {
return other.viewId == viewId;
}
@override
int get hashCode => viewId.hashCode;
}
/// Manager of FlutterViews.
class ViewManager {
ViewManager(this.serviceProtocol);
final VMService serviceProtocol;
Future<Null> refresh() async {
List<Map<String, String>> viewList = await serviceProtocol.getViewList();
for (Map<String, String> viewDescription in viewList) {
FlutterView view = new FlutterView(viewDescription['id'], this);
if (!views.contains(view)) {
// Canonicalize views against the view set.
views.add(view);
}
}
}
// TODO(johnmccutchan): Report errors when running failed.
Future<Null> _runFromSource(FlutterView view,
String entryPath,
String packagesPath,
String assetsDirectoryPath) async {
final String viewId = await serviceProtocol.getFirstViewId();
// When this completer completes the isolate is running.
final Completer<Null> completer = new Completer<Null>();
final StreamSubscription<Event> subscription =
serviceProtocol.onIsolateEvent.listen((Event event) {
// TODO(johnmccutchan): Listen to the debug stream and catch initial
// launch errors.
if (event.kind == 'IsolateRunnable') {
printTrace('Isolate is runnable.');
completer.complete(null);
}
});
await serviceProtocol.runInView(viewId,
entryPath,
packagesPath,
assetsDirectoryPath);
await completer.future;
await subscription.cancel();
}
// TODO(johnmccutchan): Remove this accessor and make the runner multi-view
// aware.
FlutterView get mainView {
return views.first;
}
final Set<FlutterView> views = new Set<FlutterView>();
}
...@@ -11,23 +11,17 @@ import 'package:web_socket_channel/io.dart'; ...@@ -11,23 +11,17 @@ import 'package:web_socket_channel/io.dart';
import 'globals.dart'; import 'globals.dart';
// TODO(johnmccutchan): Rename this class to ServiceProtocol or VmService. /// A connection to the Dart VM Service.
class VMService { class VMService {
VMService._(this.peer, this.port, this.httpAddress) { VMService._(this.peer, this.port, this.httpAddress) {
_vm = new VM._empty(this);
peer.registerMethod('streamNotify', (rpc.Parameters event) { peer.registerMethod('streamNotify', (rpc.Parameters event) {
_handleStreamNotify(event.asMap); _handleStreamNotify(event.asMap);
}); });
onIsolateEvent.listen((Event event) {
if (event.kind == 'IsolateStart') {
_addIsolate(event.isolate);
} else if (event.kind == 'IsolateExit') {
String removedId = event.isolate.id;
isolates.removeWhere((IsolateRef ref) => ref.id == removedId);
}
});
} }
/// Connect to '127.0.0.1' at [port].
static Future<VMService> connect(int port) async { static Future<VMService> connect(int port) async {
Uri uri = new Uri(scheme: 'ws', host: '127.0.0.1', port: port, path: 'ws'); Uri uri = new Uri(scheme: 'ws', host: '127.0.0.1', port: port, path: 'ws');
WebSocket ws = await WebSocket.connect(uri.toString()); WebSocket ws = await WebSocket.connect(uri.toString());
...@@ -37,158 +31,572 @@ class VMService { ...@@ -37,158 +31,572 @@ class VMService {
return new VMService._(peer, port, httpAddress); return new VMService._(peer, port, httpAddress);
} }
final Uri httpAddress; final Uri httpAddress;
final rpc.Peer peer;
final int port; final int port;
final rpc.Peer peer;
List<IsolateRef> isolates = <IsolateRef>[]; VM _vm;
Completer<IsolateRef> _waitFirstIsolateCompleter; /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
VM get vm => _vm;
Map<String, StreamController<Event>> _eventControllers = <String, StreamController<Event>>{}; final Map<String, StreamController<ServiceEvent>> _eventControllers =
<String, StreamController<ServiceEvent>>{};
Set<String> _listeningFor = new Set<String>(); Set<String> _listeningFor = new Set<String>();
bool get isClosed => peer.isClosed; bool get isClosed => peer.isClosed;
Future<Null> get done => peer.done; Future<Null> get done => peer.done;
String get firstIsolateId => isolates.isEmpty ? null : isolates.first.id;
// Events // Events
Stream<ServiceEvent> get onDebugEvent => onEvent('Debug');
Stream<Event> get onExtensionEvent => onEvent('Extension'); Stream<ServiceEvent> get onExtensionEvent => onEvent('Extension');
// IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
Stream<Event> get onIsolateEvent => onEvent('Isolate'); Stream<ServiceEvent> get onIsolateEvent => onEvent('Isolate');
Stream<Event> get onTimelineEvent => onEvent('Timeline'); Stream<ServiceEvent> get onTimelineEvent => onEvent('Timeline');
// TODO(johnmccutchan): Add FlutterView events.
// Listen for a specific event name. // Listen for a specific event name.
Stream<Event> onEvent(String streamId) { Stream<ServiceEvent> onEvent(String streamId) {
streamListen(streamId); _streamListen(streamId);
return _getEventController(streamId).stream; return _getEventController(streamId).stream;
} }
StreamController<Event> _getEventController(String eventName) { StreamController<ServiceEvent> _getEventController(String eventName) {
StreamController<Event> controller = _eventControllers[eventName]; StreamController<ServiceEvent> controller = _eventControllers[eventName];
if (controller == null) { if (controller == null) {
controller = new StreamController<Event>.broadcast(); controller = new StreamController<ServiceEvent>.broadcast();
_eventControllers[eventName] = controller; _eventControllers[eventName] = controller;
} }
return controller; return controller;
} }
void _handleStreamNotify(Map<String, dynamic> data) { void _handleStreamNotify(Map<String, dynamic> data) {
Event event = new Event(data['event']); final String streamId = data['streamId'];
_getEventController(data['streamId']).add(event); final Map<String, dynamic> eventData = data['event'];
final Map<String, dynamic> eventIsolate = eventData['isolate'];
ServiceEvent event;
if (eventIsolate != null) {
// getFromMap creates the Isolate if necessary.
Isolate isolate = vm.getFromMap(eventIsolate);
event = new ServiceObject._fromMap(isolate, eventData);
if (event.kind == ServiceEvent.kIsolateExit) {
vm._isolateCache.remove(isolate.id);
vm._buildIsolateList();
} else if (event.kind == ServiceEvent.kIsolateRunnable) {
// Force reload once the isolate becomes runnable so that we
// update the root library.
isolate.reload();
}
} else {
// The event doesn't have an isolate, so it is owned by the VM.
event = new ServiceObject._fromMap(vm, eventData);
}
_getEventController(streamId).add(event);
} }
Future<Null> populateIsolateInfo() async { Future<Null> _streamListen(String streamId) async {
// Calling this has the side effect of populating the isolate information. if (!_listeningFor.contains(streamId)) {
await waitFirstIsolate; _listeningFor.add(streamId);
await peer.sendRequest('streamListen',
<String, dynamic>{ 'streamId': streamId });
}
} }
Future<IsolateRef> get waitFirstIsolate async { /// Reloads the VM.
if (isolates.isNotEmpty) Future<VM> getVM() {
return isolates.first; return _vm.reload();
}
}
_waitFirstIsolateCompleter ??= new Completer<IsolateRef>(); /// An error that is thrown when constructing/updating a service object.
class VMServiceObjectLoadError {
VMServiceObjectLoadError(this.message, this.map);
final String message;
final Map<String, dynamic> map;
}
getVM().then((VM vm) { bool _isServiceMap(Map<String, dynamic> m) {
for (IsolateRef isolate in vm.isolates) return (m != null) && (m['type'] != null);
_addIsolate(isolate); }
}); bool _hasRef(String type) => (type != null) && type.startsWith('@');
String _stripRef(String type) => (_hasRef(type) ? type.substring(1) : type);
/// Given a raw response from the service protocol and a [ServiceObjectOwner],
/// recursively walk the response and replace values that are service maps with
/// actual [ServiceObject]s. During the upgrade the owner is given a chance
/// to return a cached / canonicalized object.
void _upgradeCollection(dynamic collection,
ServiceObjectOwner owner) {
if (collection is ServiceMap) {
return;
}
if (collection is Map) {
_upgradeMap(collection, owner);
} else if (collection is List) {
_upgradeList(collection, owner);
}
}
void _upgradeMap(Map<String, dynamic> map, ServiceObjectOwner owner) {
map.forEach((String k, dynamic v) {
if ((v is Map) && _isServiceMap(v)) {
map[k] = owner.getFromMap(v);
} else if (v is List) {
_upgradeList(v, owner);
} else if (v is Map) {
_upgradeMap(v, owner);
}
});
}
return _waitFirstIsolateCompleter.future; void _upgradeList(List<dynamic> list, ServiceObjectOwner owner) {
for (int i = 0; i < list.length; i++) {
dynamic v = list[i];
if ((v is Map) && _isServiceMap(v)) {
list[i] = owner.getFromMap(v);
} else if (v is List) {
_upgradeList(v, owner);
} else if (v is Map) {
_upgradeMap(v, owner);
}
}
}
/// Base class of all objects received over the service protocol.
abstract class ServiceObject {
ServiceObject._empty(this._owner);
/// Factory constructor given a [ServiceObjectOwner] and a service map,
/// upgrade the map into a proper [ServiceObject]. This function always
/// returns a new instance and does not interact with caches.
factory ServiceObject._fromMap(ServiceObjectOwner owner,
Map<String, dynamic> map) {
if (map == null)
return null;
if (!_isServiceMap(map))
throw new VMServiceObjectLoadError("Expected a service map", map);
String type = _stripRef(map['type']);
ServiceObject serviceObject;
switch (type) {
case 'Event':
serviceObject = new ServiceEvent._empty(owner);
break;
case 'FlutterView':
serviceObject = new FlutterView._empty(owner.vm);
break;
case 'Isolate':
serviceObject = new Isolate._empty(owner.vm);
break;
default:
printTrace("Unsupported service object type: $type");
}
if (serviceObject == null) {
// If we don't have a model object for this service object type, as a
// fallback return a ServiceMap object.
serviceObject = new ServiceMap._empty(owner);
}
// We have now constructed an emtpy service object, call update to
// populate it.
serviceObject.update(map);
return serviceObject;
} }
// Requests final ServiceObjectOwner _owner;
ServiceObjectOwner get owner => _owner;
/// The id of this object.
String get id => _id;
String _id;
/// The user-level type of this object.
String get type => _type;
String _type;
/// The vm-level type of this object. Usually the same as [type].
String get vmType => _vmType;
String _vmType;
/// Is it safe to cache this object?
bool _canCache = false;
bool get canCache => _canCache;
/// Has this object been fully loaded?
bool get loaded => _loaded;
bool _loaded = false;
/// Is this object immutable after it is [loaded]?
bool get immutable => false;
Future<Response> sendRequest(String method, [Map<String, dynamic> args]) { String get name => _name;
return peer.sendRequest(method, args).then((dynamic result) => new Response(result)); String _name;
String get vmName => _vmName;
String _vmName;
/// If this is not already loaded, load it. Otherwise reload.
Future<ServiceObject> load() async {
if (loaded) {
return this;
}
return reload();
} }
Future<Null> streamListen(String streamId) async { /// Fetch this object from vmService and return the response directly.
if (!_listeningFor.contains(streamId)) { Future<Map<String, dynamic>> _fetchDirect() {
_listeningFor.add(streamId); Map<String, dynamic> params = <String, dynamic>{
sendRequest('streamListen', <String, dynamic>{ 'streamId': streamId }); 'objectId': id,
};
return _owner.isolate.invokeRpcRaw('getObject', params);
}
Future<ServiceObject> _inProgressReload;
/// Reload the service object (if possible).
Future<ServiceObject> reload() async {
bool hasId = (id != null) && (id != '');
bool isVM = this is VM;
// We should always reload the VM.
// We can't reload objects without an id.
// We shouldn't reload an immutable and already loaded object.
bool skipLoad = !isVM && (!hasId || (immutable && loaded));
if (skipLoad) {
return this;
}
if (_inProgressReload == null) {
Completer<ServiceObject> completer = new Completer<ServiceObject>();
_inProgressReload = completer.future;
try {
Map<String, dynamic> response = await _fetchDirect();
if (_stripRef(response['type']) == 'Sentinel') {
// An object may have been collected.
completer.complete(new ServiceObject._fromMap(owner, response));
} else {
update(response);
completer.complete(this);
}
} catch (e, st) {
completer.completeError(e, st);
}
_inProgressReload = null;
} }
return _inProgressReload;
} }
Future<VM> getVM() { /// Update [this] using [map] as a source. [map] can be a service reference.
return peer.sendRequest('getVM').then((dynamic result) { void update(Map<String, dynamic> map) {
return new VM(result); // Don't allow the type to change on an object update.
}); final bool mapIsRef = _hasRef(map['type']);
final String mapType = _stripRef(map['type']);
if ((_type != null) && (_type != mapType)) {
throw new VMServiceObjectLoadError("ServiceObject types must not change",
map);
}
_type = mapType;
_vmType = map.containsKey('_vmType') ? _stripRef(map['_vmType']) : _type;
_canCache = map['fixedId'] == true;
if ((_id != null) && (_id != map['id']) && _canCache) {
throw new VMServiceObjectLoadError("ServiceObject id changed", map);
}
_id = map['id'];
// Copy name properties.
_name = map['name'];
_vmName = map.containsKey('_vmName') ? map['_vmName'] : _name;
// We have now updated all common properties, let the subclasses update
// their specific properties.
_update(map, mapIsRef);
} }
Future<Map<String, dynamic>> reloadSources(String isolateId) async { /// Implemented by subclasses to populate their model.
try { void _update(Map<String, dynamic> map, bool mapIsRef);
Response response = }
await sendRequest('_reloadSources',
<String, dynamic>{ 'isolateId': isolateId }); class ServiceEvent extends ServiceObject {
return response.response; /// The possible 'kind' values.
} catch (e) { static const String kVMUpdate = 'VMUpdate';
return new Future<Map<String, dynamic>>.error(e.data['details']); static const String kIsolateStart = 'IsolateStart';
static const String kIsolateRunnable = 'IsolateRunnable';
static const String kIsolateExit = 'IsolateExit';
static const String kIsolateUpdate = 'IsolateUpdate';
static const String kIsolateReload = 'IsolateReload';
static const String kIsolateSpawn = 'IsolateSpawn';
static const String kServiceExtensionAdded = 'ServiceExtensionAdded';
static const String kPauseStart = 'PauseStart';
static const String kPauseExit = 'PauseExit';
static const String kPauseBreakpoint = 'PauseBreakpoint';
static const String kPauseInterrupted = 'PauseInterrupted';
static const String kPauseException = 'PauseException';
static const String kNone = 'None';
static const String kResume = 'Resume';
static const String kBreakpointAdded = 'BreakpointAdded';
static const String kBreakpointResolved = 'BreakpointResolved';
static const String kBreakpointRemoved = 'BreakpointRemoved';
static const String kGraph = '_Graph';
static const String kGC = 'GC';
static const String kInspect = 'Inspect';
static const String kDebuggerSettingsUpdate = '_DebuggerSettingsUpdate';
static const String kConnectionClosed = 'ConnectionClosed';
static const String kLogging = '_Logging';
static const String kExtension = 'Extension';
ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner);
String _kind;
String get kind => _kind;
DateTime _timestamp;
DateTime get timestmap => _timestamp;
String _extensionKind;
String get extensionKind => _extensionKind;
Map<String, dynamic> _extensionData;
Map<String, dynamic> get extensionData => _extensionData;
List<Map<String, dynamic>> _timelineEvents;
List<Map<String, dynamic>> get timelineEvents => _timelineEvents;
@override
void _update(Map<String, dynamic> map, bool mapIsRef) {
_loaded = true;
_upgradeCollection(map, owner);
_kind = map['kind'];
assert(map['isolate'] == null || owner == map['isolate']);
_timestamp =
new DateTime.fromMillisecondsSinceEpoch(map['timestamp']);
if (map['extensionKind'] != null) {
_extensionKind = map['extensionKind'];
_extensionData = map['extensionData'];
} }
_timelineEvents = map['timelineEvents'];
} }
Future<List<Map<String, String>>> getViewList() async { bool get isPauseEvent {
Map<String, dynamic> response = await peer.sendRequest('_flutter.listViews'); return (kind == kPauseStart ||
List<Map<String, String>> views = response['views']; kind == kPauseExit ||
return views; kind == kPauseBreakpoint ||
kind == kPauseInterrupted ||
kind == kPauseException ||
kind == kNone);
} }
}
/// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache
/// and/or canonicalize service objets received over the wire.
abstract class ServiceObjectOwner extends ServiceObject {
ServiceObjectOwner._empty(ServiceObjectOwner owner) : super._empty(owner);
/// Returns the owning VM.
VM get vm => null;
/// Returns the owning isolate (if any).
Isolate get isolate => null;
/// Returns the vmService connection.
VMService get vmService => null;
Future<String> getFirstViewId() async { /// Builds a [ServiceObject] corresponding to the [id] from [map].
List<Map<String, String>> views = await getViewList(); /// The result may come from the cache. The result will not necessarily
return views[0]['id']; /// be [loaded].
ServiceObject getFromMap(Map<String, dynamic> map);
}
/// There is only one instance of the VM class. The VM class owns [Isolate]
/// and [FlutterView] objects.
class VM extends ServiceObjectOwner {
VM._empty(this._vmService) : super._empty(null);
/// Connection to the VMService.
final VMService _vmService;
@override
VMService get vmService => _vmService;
@override
VM get vm => this;
@override
Future<Map<String, dynamic>> _fetchDirect() async {
return invokeRpcRaw('getVM', <String, dynamic> {});
} }
Future<Null> runInView(String viewId, @override
String main, void _update(Map<String, dynamic> map, bool mapIsRef) {
String packages, if (mapIsRef)
String assetsDirectory) async { return;
await peer.sendRequest('_flutter.runInView',
<String, dynamic> { // Upgrade the collection. A side effect of this call is that any new
'viewId': viewId, // isolates in the map are created and added to the isolate cache.
'mainScript': main, _upgradeCollection(map, this);
'packagesFile': packages, _loaded = true;
'assetDirectory': assetsDirectory
}); // TODO(johnmccutchan): Extract any properties we care about here.
return null;
// Remove any isolates which are now dead from the isolate cache.
_removeDeadIsolates(map['isolates']);
} }
Future<Response> clearVMTimeline() => sendRequest('_clearVMTimeline'); final Map<String, ServiceObject> _cache = new Map<String,ServiceObject>();
final Map<String,Isolate> _isolateCache = new Map<String,Isolate>();
Future<Response> setVMTimelineFlags(List<String> recordedStreams) { /// The list of live isolates, ordered by isolate start time.
assert(recordedStreams != null); final List<Isolate> isolates = new List<Isolate>();
return sendRequest('_setVMTimelineFlags', <String, dynamic> { /// The set of live views.
'recordedStreams': recordedStreams final Map<String, FlutterView> _viewCache = new Map<String, FlutterView>();
int _compareIsolates(Isolate a, Isolate b) {
DateTime aStart = a.startTime;
DateTime bStart = b.startTime;
if (aStart == null) {
if (bStart == null) {
return 0;
} else {
return 1;
}
}
if (bStart == null) {
return -1;
}
return aStart.compareTo(bStart);
}
void _buildIsolateList() {
List<Isolate> isolateList = _isolateCache.values.toList();
isolateList.sort(_compareIsolates);
isolates.clear();
isolates.addAll(isolateList);
}
void _removeDeadIsolates(List<Isolate> newIsolates) {
// Build a set of new isolates.
Set<String> newIsolateSet = new Set<String>();
newIsolates.forEach((Isolate iso) => newIsolateSet.add(iso.id));
// Remove any old isolates which no longer exist.
List<String> toRemove = <String>[];
_isolateCache.forEach((String id, _) {
if (!newIsolateSet.contains(id)) {
toRemove.add(id);
}
}); });
toRemove.forEach((String id) => _isolateCache.remove(id));
_buildIsolateList();
}
@override
ServiceObject getFromMap(Map<String, dynamic> map) {
if (map == null) {
return null;
}
String type = _stripRef(map['type']);
if (type == 'VM') {
// Update this VM object.
update(map);
return this;
}
String mapId = map['id'];
switch (type) {
case 'Isolate': {
// Check cache.
Isolate isolate = _isolateCache[mapId];
if (isolate == null) {
// Add new isolate to the cache.
isolate = new ServiceObject._fromMap(this, map);
_isolateCache[mapId] = isolate;
_buildIsolateList();
// Eagerly load the isolate.
isolate.load().catchError((dynamic e, StackTrace stack) {
printTrace('Eagerly loading an isolate failed: $e\n$stack');
});
} else {
// Existing isolate, update data.
isolate.update(map);
}
return isolate;
}
break;
case 'FlutterView': {
FlutterView view = _viewCache[mapId];
if (view == null) {
// Add new view to the cache.
view = new ServiceObject._fromMap(this, map);
_viewCache[mapId] = view;
} else {
view.update(map);
}
return view;
}
break;
default:
throw new VMServiceObjectLoadError(
'VM.getFromMap called for something other than an isolate', map);
}
} }
Future<Response> getVMTimeline() => sendRequest('_getVMTimeline'); // Note that this function does not reload the isolate if it found
// in the cache.
Future<Isolate> getIsolate(String isolateId) {
if (!loaded) {
// Trigger a VM load, then get the isolate. Ignore any errors.
return load().then((_) => getIsolate(isolateId)).catchError((_) => null);
}
return new Future<Isolate>.value(_isolateCache[isolateId]);
}
// DevFS / VM virtual file system methods /// Invoke the RPC and return the raw response.
Future<Map<String, dynamic>> invokeRpcRaw(
String method, [Map<String, dynamic> params]) async {
if (params == null) {
params = <String, dynamic>{};
}
Map<String, dynamic> result =
await _vmService.peer.sendRequest(method, params);
return result;
}
/// Create a new file system. /// Invoke the RPC and return a ServiceObject response.
Future<CreateDevFSResponse> createDevFS(String fsName) async { Future<ServiceObject> invokeRpc(
Response response = await sendRequest('_createDevFS', <String, dynamic> { 'fsName': fsName }); String method, [Map<String, dynamic> params]) async {
return new CreateDevFSResponse(response.response); Map<String, dynamic> response = await invokeRpcRaw(method, params);
ServiceObject serviceObject = new ServiceObject._fromMap(this, response);
if ((serviceObject != null) && (serviceObject._canCache)) {
String serviceObjectId = serviceObject.id;
_cache.putIfAbsent(serviceObjectId, () => serviceObject);
}
return serviceObject;
} }
/// List the available file systems. /// Create a new development file system on the device.
Future<List<String>> listDevFS() { Future<Map<String, dynamic>> createDevFS(String fsName) async {
return sendRequest('_listDevFS').then((Response response) { Map<String, dynamic> response =
return response.response['fsNames']; await invokeRpcRaw('_createDevFS', <String, dynamic> {
}); 'fsName': fsName
});
return response;
}
/// List the development file system son the device.
Future<List<String>> listDevFS() async {
Map<String, dynamic> response =
await invokeRpcRaw('_listDevFS', <String, dynamic>{});
return response['fsNames'];
} }
// Write one file into a file system. // Write one file into a file system.
Future<Response> writeDevFSFile(String fsName, { Future<Map<String, dynamic>> writeDevFSFile(String fsName, {
String path, String path,
List<int> fileContents List<int> fileContents
}) { }) {
assert(path != null); assert(path != null);
assert(fileContents != null); assert(fileContents != null);
return sendRequest('_writeDevFSFile', <String, dynamic> { return invokeRpcRaw('_writeDevFSFile', <String, dynamic> {
'fsName': fsName, 'fsName': fsName,
'path': path, 'path': path,
'fileContents': BASE64.encode(fileContents) 'fileContents': BASE64.encode(fileContents)
...@@ -197,159 +605,292 @@ class VMService { ...@@ -197,159 +605,292 @@ class VMService {
// Read one file from a file system. // Read one file from a file system.
Future<List<int>> readDevFSFile(String fsName, String path) { Future<List<int>> readDevFSFile(String fsName, String path) {
return sendRequest('_readDevFSFile', <String, dynamic> { return invokeRpcRaw('_readDevFSFile', <String, dynamic> {
'fsName': fsName, 'fsName': fsName,
'path': path 'path': path
}).then((Response response) { }).then((Map<String, dynamic> response) {
return BASE64.decode(response.response['fileContents']); return BASE64.decode(response['fileContents']);
}); });
} }
/// The complete list of a file system. /// The complete list of a file system.
Future<List<String>> listDevFSFiles(String fsName) { Future<List<String>> listDevFSFiles(String fsName) {
return sendRequest('_listDevFSFiles', <String, dynamic> { return invokeRpcRaw('_listDevFSFiles', <String, dynamic> {
'fsName': fsName 'fsName': fsName
}).then((Response response) { }).then((Map<String, dynamic> response) {
return response.response['files']; return response['files'];
}); });
} }
/// Delete an existing file system. /// Delete an existing file system.
Future<Response> deleteDevFS(String fsName) { Future<Map<String, dynamic>> deleteDevFS(String fsName) {
return sendRequest('_deleteDevFS', <String, dynamic> { 'fsName': fsName }); return invokeRpcRaw('_deleteDevFS', <String, dynamic> { 'fsName': fsName });
} }
// Flutter extension methods. Future<ServiceMap> runInView(String viewId,
String main,
String packages,
String assetsDirectory) {
return invokeRpc('_flutter.runInView',
<String, dynamic> {
'viewId': viewId,
'mainScript': main,
'packagesFile': packages,
'assetDirectory': assetsDirectory
});
}
Future<Response> flutterDebugDumpApp(String isolateId) { Future<Map<String, dynamic>> clearVMTimeline() {
return peer.sendRequest('ext.flutter.debugDumpApp', <String, dynamic>{ return invokeRpcRaw('_clearVMTimeline', <String, dynamic>{});
'isolateId': isolateId
}).then((dynamic result) => new Response(result));
} }
Future<Response> flutterDebugDumpRenderTree(String isolateId) { Future<Map<String, dynamic>> setVMTimelineFlags(
return peer.sendRequest('ext.flutter.debugDumpRenderTree', <String, dynamic>{ List<String> recordedStreams) {
'isolateId': isolateId assert(recordedStreams != null);
}).then((dynamic result) => new Response(result));
return invokeRpcRaw('_setVMTimelineFlags', <String, dynamic> {
'recordedStreams': recordedStreams
});
} }
// Loader page extension methods. Future<Map<String, dynamic>> getVMTimeline() {
return invokeRpcRaw('_getVMTimeline', <String, dynamic> {});
}
Future<Response> flutterLoaderShowMessage(String isolateId, String message) { Future<Null> refreshViews() async {
return peer.sendRequest('ext.flutter.loaderShowMessage', <String, dynamic>{ await vmService.vm.invokeRpc('_flutter.listViews');
'isolateId': isolateId,
'value': message
}).then(
(dynamic result) => new Response(result),
onError: (dynamic exception) { printTrace('ext.flutter.loaderShowMessage: $exception'); }
);
} }
Future<Response> flutterLoaderSetProgress(String isolateId, double progress) { FlutterView get mainView {
return peer.sendRequest('ext.flutter.loaderSetProgress', <String, dynamic>{ return _viewCache.values.first;
'isolateId': isolateId,
'loaderSetProgress': progress
}).then(
(dynamic result) => new Response(result),
onError: (dynamic exception) { printTrace('ext.flutter.loaderSetProgress: $exception'); }
);
} }
}
Future<Response> flutterLoaderSetProgressMax(String isolateId, double max) { /// An isolate running inside the VM. Instances of the Isolate class are always
return peer.sendRequest('ext.flutter.loaderSetProgressMax', <String, dynamic>{ /// canonicalized.
'isolateId': isolateId, class Isolate extends ServiceObjectOwner {
'loaderSetProgressMax': max Isolate._empty(ServiceObjectOwner owner) : super._empty(owner);
}).then(
(dynamic result) => new Response(result), @override
onError: (dynamic exception) { printTrace('ext.flutter.loaderSetProgressMax: $exception'); } VM get vm => owner;
);
@override
VMService get vmService => vm.vmService;
@override
Isolate get isolate => this;
DateTime startTime;
final Map<String, ServiceObject> _cache = new Map<String, ServiceObject>();
@override
ServiceObject getFromMap(Map<String, dynamic> map) {
if (map == null) {
return null;
}
String mapType = _stripRef(map['type']);
if (mapType == 'Isolate') {
// There are sometimes isolate refs in ServiceEvents.
return vm.getFromMap(map);
}
String mapId = map['id'];
ServiceObject serviceObject = (mapId != null) ? _cache[mapId] : null;
if (serviceObject != null) {
serviceObject.update(map);
return serviceObject;
}
// Build the object from the map directly.
serviceObject = new ServiceObject._fromMap(this, map);
if ((serviceObject != null) && serviceObject.canCache) {
_cache[mapId] = serviceObject;
}
return serviceObject;
} }
/// Causes the application to pick up any changed code. @override
Future<Response> flutterReassemble(String isolateId) { Future<Map<String, dynamic>> _fetchDirect() {
return peer.sendRequest('ext.flutter.reassemble', <String, dynamic>{ return invokeRpcRaw('getIsolate', <String, dynamic>{});
'isolateId': isolateId
}).then((dynamic result) => new Response(result));
} }
Future<Response> flutterEvictAsset(String isolateId, String assetPath) { /// Invoke the RPC and return the raw response.
return peer.sendRequest('ext.flutter.evict', <String, dynamic>{ Future<Map<String, dynamic>> invokeRpcRaw(
'isolateId': isolateId, String method, [Map<String, dynamic> params]) {
'value': assetPath // Inject the 'isolateId' parameter.
}).then((dynamic result) => new Response(result)); if (params == null) {
params = <String, dynamic>{
'isolateId': id
};
} else {
params['isolateId'] = id;
}
return vm.invokeRpcRaw(method, params);
} }
Future<Response> flutterExit(String isolateId) { /// Invoke the RPC and return a ServiceObject response.
return peer Future<ServiceObject> invokeRpc(
.sendRequest('ext.flutter.exit', <String, dynamic>{ 'isolateId': isolateId }) String method, Map<String, dynamic> params) async {
.then((dynamic result) => new Response(result)) Map<String, dynamic> response = await invokeRpcRaw(method, params);
.timeout(new Duration(seconds: 2), onTimeout: () => null); return getFromMap(response);
} }
void _addIsolate(IsolateRef isolate) { @override
if (!isolates.contains(isolate)) { void _update(Map<String, dynamic> map, bool mapIsRef) {
isolates.add(isolate); if (mapIsRef) {
return;
}
_loaded = true;
int startTimeMillis = map['startTime'];
startTime = new DateTime.fromMillisecondsSinceEpoch(startTimeMillis);
if (_waitFirstIsolateCompleter != null) { // TODO(johnmccutchan): Extract any properties we care about here.
_waitFirstIsolateCompleter.complete(isolate); _upgradeCollection(map, this);
_waitFirstIsolateCompleter = null; }
}
Future<Map<String, dynamic>> reloadSources() async {
try {
Map<String, dynamic> response = await invokeRpcRaw('_reloadSources');
return response;
} catch (e) {
return new Future<Map<String, dynamic>>.error(e.data['details']);
} }
} }
}
class Response { // Flutter extension methods.
Response(this.response);
final Map<String, dynamic> response; Future<Map<String, dynamic>> flutterDebugDumpApp() {
return invokeRpcRaw('ext.flutter.debugDumpApp');
}
String get type => response['type']; Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
return invokeRpcRaw('ext.flutter.debugDumpRenderTree');
}
dynamic operator[](String key) => response[key]; // Loader page extension methods.
@override Future<Map<String, dynamic>> flutterLoaderShowMessage(String message) {
String toString() => response.toString(); return invokeRpcRaw('ext.flutter.loaderShowMessage', <String, dynamic> {
} 'value': message
});
}
class CreateDevFSResponse extends Response { Future<Map<String, dynamic>> flutterLoaderSetProgress(double progress) {
CreateDevFSResponse(Map<String, dynamic> response) : super(response); return invokeRpcRaw('ext.flutter.loaderSetProgress', <String, dynamic>{
'loaderSetProgress': progress
});
}
String get name => response['name']; Future<Map<String, dynamic>> flutterLoaderSetProgressMax(double max) {
String get uri => response['uri']; return invokeRpcRaw('ext.flutter.loaderSetProgressMax', <String, dynamic>{
} 'loaderSetProgressMax': max
});
}
class VM extends Response { /// Causes the application to pick up any changed code.
VM(Map<String, dynamic> response) : super(response); Future<Map<String, dynamic>> flutterReassemble() {
return invokeRpcRaw('ext.flutter.reassemble');
}
Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) {
return invokeRpcRaw('ext.flutter.evict', <String, dynamic>{
'value': assetPath
});
}
List<IsolateRef> get isolates => response['isolates'].map((dynamic ref) => new IsolateRef(ref)).toList(); Future<Map<String, dynamic>> flutterExit() {
return invokeRpcRaw('ext.flutter.exit').timeout(
const Duration(seconds: 2), onTimeout: () => null);
}
} }
class Event extends Response { class ServiceMap extends ServiceObject implements Map<String, dynamic> {
Event(Map<String, dynamic> response) : super(response); ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner);
final Map<String, dynamic> _map = new Map<String, dynamic>();
String get kind => response['kind']; @override
IsolateRef get isolate => new IsolateRef.from(response['isolate']); void _update(Map<String, dynamic> map, bool mapIsRef) {
_loaded = !mapIsRef;
_upgradeCollection(map, owner);
_map.clear();
_map.addAll(map);
}
/// Only valid for [kind] == `Extension`. // Forward Map interface calls.
String get extensionKind => response['extensionKind']; @override
void addAll(Map<String, dynamic> other) => _map.addAll(other);
@override
void clear() => _map.clear();
@override
bool containsValue(dynamic v) => _map.containsValue(v);
@override
bool containsKey(String k) => _map.containsKey(k);
@override
void forEach(Function f) => _map.forEach(f);
@override
dynamic putIfAbsent(String key, Function ifAbsent) => _map.putIfAbsent(key, ifAbsent);
@override
void remove(String key) => _map.remove(key);
@override
dynamic operator [](String k) => _map[k];
@override
void operator []=(String k, dynamic v) => _map[k] = v;
@override
bool get isEmpty => _map.isEmpty;
@override
bool get isNotEmpty => _map.isNotEmpty;
@override
Iterable<String> get keys => _map.keys;
@override
Iterable<dynamic> get values => _map.values;
@override
int get length => _map.length;
@override
String toString() => _map.toString();
} }
class IsolateRef extends Response { /// Peered to a Android/iOS FlutterView widget on a device.
IsolateRef(Map<String, dynamic> response) : super(response); class FlutterView extends ServiceObject {
factory IsolateRef.from(dynamic ref) => ref == null ? null : new IsolateRef(ref); FlutterView._empty(ServiceObjectOwner owner) : super._empty(owner);
String get id => response['id']; Isolate _uiIsolate;
Isolate get uiIsolate => _uiIsolate;
@override @override
bool operator ==(dynamic other) { void _update(Map<String, dynamic> map, bool mapIsRef) {
if (identical(this, other)) _loaded = !mapIsRef;
return true; _upgradeCollection(map, owner);
if (other is! IsolateRef) _uiIsolate = map['isolate'];
return false; }
final IsolateRef typedOther = other;
return id == typedOther.id; // TODO(johnmccutchan): Report errors when running failed.
Future<Null> runFromSource(String entryPath,
String packagesPath,
String assetsDirectoryPath) async {
final String viewId = id;
// When this completer completes the isolate is running.
final Completer<Null> completer = new Completer<Null>();
final StreamSubscription<ServiceEvent> subscription =
owner.vm.vmService.onIsolateEvent.listen((ServiceEvent event) {
// TODO(johnmccutchan): Listen to the debug stream and catch initial
// launch errors.
if (event.kind == ServiceEvent.kIsolateRunnable) {
printTrace('Isolate is runnable.');
completer.complete(null);
}
});
await owner.vm.runInView(viewId,
entryPath,
packagesPath,
assetsDirectoryPath);
await completer.future;
await subscription.cancel();
} }
bool get hasIsolate => _uiIsolate != null;
@override @override
int get hashCode => id.hashCode; String toString() => id;
} }
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