blob: d29753038de8083be9f8327cc58545eabd07e411 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library coverage.src.devtools;
import 'dart:async';
import 'dart:convert' show JSON;
import 'dart:io';
import 'package:http/http.dart' as http;
class IsolateInfo {
final _Connection _connection;
final String name;
IsolateInfo(this._connection, Map json) :
name = json['name'];
Future<Map> getCoverage() =>
_connection.request('isolates/$name/coverage')
.then((resp) => resp['coverage']);
Future unpin() => _connection.request('isolates/$name/unpin');
}
/// Interface to Dart's VM Observatory
class Observatory {
final _Connection _connection;
Observatory._(this._connection);
static Future<Observatory> connect(String host, String port) {
var uri = 'http://$host:$port';
var observatory = new Observatory._(new _VmConnection(uri));
return new Future.value(observatory);
}
static Future<Observatory> connectOverDevtools(String host, String port) {
var uri = 'http://$host:$port/json';
return _DevtoolsConnection.connect(uri).then((c) => new Observatory._(c));
}
Future<Iterable<IsolateInfo>> getIsolates() =>
_connection.request('isolates')
.then((resp) => resp['members'])
.then((members) => (members == null) ? [] : members)
.then((members) => members.map((m) => new IsolateInfo(_connection, m)));
Future unpin(String isolateId) =>
_connection.request('isolates/$isolateId/unpin');
Future close() => _connection.close();
}
/// Dart Observatory connection
abstract class _Connection {
Future<Map> request(String request);
Future close();
}
/// Observatory connection over HTTP GET requests
class _VmConnection implements _Connection {
final String uri;
_VmConnection(this.uri);
Future<Map> request(String request) {
return http.get('$uri/$request')
.then((resp) => resp.body)
.then((resp) => resp.isEmpty ? {} : JSON.decode(resp));
}
Future close() => new Future.value();
}
/// Observatory connection over Chrome DevTools websocket
class _DevtoolsConnection implements _Connection {
final WebSocket _socket;
final Map<int, Completer> _pendingRequests = {};
int _requestId = 1;
_DevtoolsConnection(this._socket) {
_socket.listen(_handleResponse);
}
static Future<_Connection> connect(String uri) {
_getWebsocketDebuggerUrl(response) {
var json = JSON.decode(response.body);
if (json.length < 1) throw new StateError('No open pages');
if (json.length > 1) {
throw new UnsupportedError('Multiple page support not yet implemented');
}
var pageData = json[0];
var debuggerUrl = pageData['webSocketDebuggerUrl'];
if (debuggerUrl == null) {
throw new StateError('Unable to obtain debugger URL');
}
return debuggerUrl;
}
return http.get(uri).then((response) {
var webSocketDebuggerUrl = _getWebsocketDebuggerUrl(response);
return WebSocket.connect(webSocketDebuggerUrl)
.then((socket) => new _DevtoolsConnection(socket));
});
}
@override
Future<Map> request(String request) {
_pendingRequests[_requestId] = new Completer();
_socket.add(JSON.encode({
'id': _requestId,
'method': 'Dart.observatoryQuery',
'params': {
'id': '$_requestId',
'query': request,
},
}));
return _pendingRequests[_requestId++].future;
}
Future close() => _socket.close();
void _handleResponse(String response) {
var json = JSON.decode(response);
if (json['method'] == 'Dart.observatoryData') {
var id = int.parse(json['params']['id']);
var message = JSON.decode(json['params']['data']);
_pendingRequests.remove(id).complete(message);
}
}
}