| Index: runtime/bin/vmservice/client/lib/service_html.dart | 
| =================================================================== | 
| --- runtime/bin/vmservice/client/lib/service_html.dart	(revision 38654) | 
| +++ runtime/bin/vmservice/client/lib/service_html.dart	(working copy) | 
| @@ -8,249 +8,42 @@ | 
| import 'dart:convert'; | 
| import 'dart:html'; | 
|  | 
| -import 'package:logging/logging.dart'; | 
| -import 'package:observatory/service.dart'; | 
| +import 'package:observatory/service_common.dart'; | 
|  | 
| // Export the service library. | 
| -export 'package:observatory/service.dart'; | 
| +export 'package:observatory/service_common.dart'; | 
|  | 
| -/// Description of a VM target. | 
| -class WebSocketVMTarget { | 
| -  // Last time this VM has been connected to. | 
| -  int lastConnectionTime = 0; | 
| -  bool get hasEverConnected => lastConnectionTime > 0; | 
| +class _HtmlWebSocket implements CommonWebSocket { | 
| +  WebSocket _webSocket; | 
|  | 
| -  // Chrome VM or standalone; | 
| -  bool chrome = false; | 
| -  bool get standalone => !chrome; | 
| - | 
| -  // User defined name. | 
| -  String name; | 
| -  // Network address of VM. | 
| -  String networkAddress; | 
| - | 
| -  WebSocketVMTarget(this.networkAddress) { | 
| -    name = networkAddress; | 
| +  void connect(String address, | 
| +               void onOpen(), | 
| +               void onMessage(dynamic data), | 
| +               void onError(), | 
| +               void onClose()) { | 
| +    _webSocket = new WebSocket(address); | 
| +    _webSocket.onClose.listen((CloseEvent) => onClose()); | 
| +    _webSocket.onError.listen((Event) => onError()); | 
| +    _webSocket.onOpen.listen((Event) => onOpen()); | 
| +    _webSocket.onMessage.listen((MessageEvent event) => onMessage(event.data)); | 
| } | 
| - | 
| -  WebSocketVMTarget.fromMap(Map json) { | 
| -    lastConnectionTime = json['lastConnectionTime']; | 
| -    chrome = json['chrome']; | 
| -    name = json['name']; | 
| -    networkAddress = json['networkAddress']; | 
| -    if (name == null) { | 
| -      name = networkAddress; | 
| -    } | 
| + | 
| +  bool get isOpen => _webSocket.readyState == WebSocket.OPEN; | 
| + | 
| +  void send(dynamic data) { | 
| +    _webSocket.send(data); | 
| } | 
| - | 
| -  Map toJson() { | 
| -    return { | 
| -      'lastConnectionTime': lastConnectionTime, | 
| -      'chrome': chrome, | 
| -      'name': name, | 
| -      'networkAddress': networkAddress, | 
| -    }; | 
| + | 
| +  void close() { | 
| +    _webSocket.close(); | 
| } | 
| } | 
|  | 
| -class _WebSocketRequest { | 
| -  final String id; | 
| -  final Completer<String> completer; | 
| -  _WebSocketRequest(this.id) | 
| -      : completer = new Completer<String>(); | 
| -} | 
| - | 
| /// The [WebSocketVM] communicates with a Dart VM over WebSocket. The Dart VM | 
| /// can be embedded in Chromium or standalone. In the case of Chromium, we | 
| /// make the service requests via the Chrome Remote Debugging Protocol. | 
| -class WebSocketVM extends VM { | 
| -  final Completer _connected = new Completer(); | 
| -  final Completer _disconnected = new Completer(); | 
| -  final WebSocketVMTarget target; | 
| -  final Map<String, _WebSocketRequest> _delayedRequests = | 
| -        new Map<String, _WebSocketRequest>(); | 
| -  final Map<String, _WebSocketRequest> _pendingRequests = | 
| -      new Map<String, _WebSocketRequest>(); | 
| -  int _requestSerial = 0; | 
| -  WebSocket _webSocket; | 
| - | 
| -  WebSocketVM(this.target) { | 
| -    assert(target != null); | 
| -  } | 
| - | 
| -  void _notifyConnect() { | 
| -    if (!_connected.isCompleted) { | 
| -      Logger.root.info('WebSocketVM connection opened: ${target.networkAddress}'); | 
| -      _connected.complete(this); | 
| -    } | 
| -  } | 
| -  Future get onConnect => _connected.future; | 
| -  void _notifyDisconnect() { | 
| -    if (!_disconnected.isCompleted) { | 
| -      Logger.root.info('WebSocketVM connection error: ${target.networkAddress}'); | 
| -      _disconnected.complete(this); | 
| -    } | 
| -  } | 
| -  Future get onDisconnect => _disconnected.future; | 
| - | 
| -  void disconnect() { | 
| -    if (_webSocket != null) { | 
| -      _webSocket.close(); | 
| -    } | 
| -    _cancelAllRequests(); | 
| -    _notifyDisconnect(); | 
| -  } | 
| - | 
| -  Future<String> getString(String id) { | 
| -    if (_webSocket == null) { | 
| -      // Create a WebSocket. | 
| -      _webSocket = new WebSocket(target.networkAddress); | 
| -      _webSocket.onClose.listen(_onClose); | 
| -      _webSocket.onError.listen(_onError); | 
| -      _webSocket.onOpen.listen(_onOpen); | 
| -      _webSocket.onMessage.listen(_onMessage); | 
| -    } | 
| -    return _makeRequest(id); | 
| -  } | 
| - | 
| -  /// Add a request for [id] to pending requests. | 
| -  Future<String> _makeRequest(String id) { | 
| -    assert(_webSocket != null); | 
| -    // Create request. | 
| -    String serial = (_requestSerial++).toString(); | 
| -    var request = new _WebSocketRequest(id); | 
| -    if (_webSocket.readyState == WebSocket.OPEN) { | 
| -      // Already connected, send request immediately. | 
| -      _sendRequest(serial, request); | 
| -    } else { | 
| -      // Not connected yet, add to delayed requests. | 
| -      _delayedRequests[serial] = request; | 
| -    } | 
| -    return request.completer.future; | 
| -  } | 
| - | 
| -  void _onClose(CloseEvent event) { | 
| -    _cancelAllRequests(); | 
| -    _notifyDisconnect(); | 
| -  } | 
| - | 
| -  // WebSocket error event handler. | 
| -  void _onError(Event) { | 
| -    _cancelAllRequests(); | 
| -    _notifyDisconnect(); | 
| -  } | 
| - | 
| -  // WebSocket open event handler. | 
| -  void _onOpen(Event) { | 
| -    target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch; | 
| -    _sendAllDelayedRequests(); | 
| -    _notifyConnect(); | 
| -  } | 
| - | 
| -  // WebSocket message event handler. | 
| -  void _onMessage(MessageEvent event) { | 
| -    var map = JSON.decode(event.data); | 
| -    if (map == null) { | 
| -      Logger.root.severe('WebSocketVM got empty message'); | 
| -      return; | 
| -    } | 
| -    // Extract serial and response. | 
| -    var serial; | 
| -    var response; | 
| -    if (target.chrome) { | 
| -      if (map['method'] != 'Dart.observatoryData') { | 
| -        // ignore devtools protocol spam. | 
| -        return; | 
| -      } | 
| -      serial = map['params']['id'].toString(); | 
| -      response = map['params']['data']; | 
| -    } else { | 
| -      serial = map['seq']; | 
| -      response = map['response']; | 
| -    } | 
| -    if (serial == null) { | 
| -      // Messages without sequence numbers are asynchronous events | 
| -      // from the vm. | 
| -      postEventMessage(response); | 
| -      return; | 
| -    } | 
| -    // Complete request. | 
| -    var request = _pendingRequests.remove(serial); | 
| -    if (request == null) { | 
| -      Logger.root.severe('Received unexpected message: ${map}'); | 
| -      return; | 
| -    } | 
| -    request.completer.complete(response); | 
| -  } | 
| - | 
| -  String _generateNetworkError(String userMessage) { | 
| -    return JSON.encode({ | 
| -      'type': 'ServiceException', | 
| -      'id': '', | 
| -      'kind': 'NetworkException', | 
| -      'message': userMessage | 
| -    }); | 
| -  } | 
| - | 
| -  void _cancelRequests(Map<String, _WebSocketRequest> requests) { | 
| -    requests.forEach((String serial, _WebSocketRequest request) { | 
| -      request.completer.complete( | 
| -          _generateNetworkError('WebSocket disconnected')); | 
| -    }); | 
| -    requests.clear(); | 
| -  } | 
| - | 
| -  /// Cancel all pending and delayed requests by completing them with an error. | 
| -  void _cancelAllRequests() { | 
| -    if (_pendingRequests.length > 0) { | 
| -      Logger.root.info('Cancelling all pending requests.'); | 
| -      _cancelRequests(_pendingRequests); | 
| -    } | 
| -    if (_delayedRequests.length > 0) { | 
| -      Logger.root.info('Cancelling all delayed requests.'); | 
| -      _cancelRequests(_delayedRequests); | 
| -    } | 
| -  } | 
| - | 
| -  /// Send all delayed requests. | 
| -  void _sendAllDelayedRequests() { | 
| -    assert(_webSocket != null); | 
| -    if (_delayedRequests.length == 0) { | 
| -      return; | 
| -    } | 
| -    Logger.root.info('Sending all delayed requests.'); | 
| -    // Send all delayed requests. | 
| -    _delayedRequests.forEach(_sendRequest); | 
| -    // Clear all delayed requests. | 
| -    _delayedRequests.clear(); | 
| -  } | 
| - | 
| -  /// Send the request over WebSocket. | 
| -  void _sendRequest(String serial, _WebSocketRequest request) { | 
| -    assert (_webSocket.readyState == WebSocket.OPEN); | 
| -    if (!request.id.endsWith('/profile/tag')) { | 
| -      Logger.root.info('GET ${request.id} from ${target.networkAddress}'); | 
| -    } | 
| -    // Mark request as pending. | 
| -    assert(_pendingRequests.containsKey(serial) == false); | 
| -    _pendingRequests[serial] = request; | 
| -    var message; | 
| -    // Encode message. | 
| -    if (target.chrome) { | 
| -      message = JSON.encode({ | 
| -        'id': int.parse(serial), | 
| -        'method': 'Dart.observatoryQuery', | 
| -        'params': { | 
| -          'id': serial, | 
| -          'query': request.id | 
| -        } | 
| -      }); | 
| -    } else { | 
| -      message = JSON.encode({'seq': serial, 'request': request.id}); | 
| -    } | 
| -    // Send message. | 
| -    _webSocket.send(message); | 
| -  } | 
| +class WebSocketVM extends CommonWebSocketVM { | 
| +  WebSocketVM(WebSocketVMTarget target) : super(target, new _HtmlWebSocket()); | 
| } | 
|  | 
| // A VM that communicates with the service via posting messages from DevTools. | 
|  |