| Index: runtime/bin/vmservice/client/lib/service_common.dart | 
| =================================================================== | 
| --- runtime/bin/vmservice/client/lib/service_common.dart	(revision 38598) | 
| +++ runtime/bin/vmservice/client/lib/service_common.dart	(working copy) | 
| @@ -2,11 +2,10 @@ | 
| // 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 service_html; | 
| +library service_common; | 
|  | 
| import 'dart:async'; | 
| import 'dart:convert'; | 
| -import 'dart:html'; | 
|  | 
| import 'package:logging/logging.dart'; | 
| import 'package:observatory/service.dart'; | 
| @@ -60,10 +59,23 @@ | 
| : 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 { | 
| +/// Minimal common interface for 'WebSocket' in [dart:io] and [dart:html]. | 
| +abstract class CommonWebSocket { | 
| +  void connect(String address, | 
| +               void onOpen(), | 
| +               void onMessage(dynamic data), | 
| +               void onError(), | 
| +               void onClose()); | 
| +  bool get isOpen; | 
| +  void send(dynamic data); | 
| +  void close(); | 
| +} | 
| + | 
| +/// A [CommonWebSocketVM] communicates with a Dart VM over a CommonWebSocket. | 
| +/// 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. | 
| +abstract class CommonWebSocketVM extends VM { | 
| final Completer _connected = new Completer(); | 
| final Completer _disconnected = new Completer(); | 
| final WebSocketVMTarget target; | 
| @@ -72,9 +84,11 @@ | 
| final Map<String, _WebSocketRequest> _pendingRequests = | 
| new Map<String, _WebSocketRequest>(); | 
| int _requestSerial = 0; | 
| -  WebSocket _webSocket; | 
| +  bool _hasInitiatedConnect = false; | 
|  | 
| -  WebSocketVM(this.target) { | 
| +  CommonWebSocket _webSocket; | 
| + | 
| +  CommonWebSocketVM(this.target, this._webSocket) { | 
| assert(target != null); | 
| } | 
|  | 
| @@ -94,7 +108,7 @@ | 
| Future get onDisconnect => _disconnected.future; | 
|  | 
| void disconnect() { | 
| -    if (_webSocket != null) { | 
| +    if (_hasInitiatedConnect) { | 
| _webSocket.close(); | 
| } | 
| _cancelAllRequests(); | 
| @@ -102,24 +116,21 @@ | 
| } | 
|  | 
| 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); | 
| +    if (!_hasInitiatedConnect) { | 
| +      _hasInitiatedConnect = true; | 
| +      _webSocket.connect( | 
| +          target.networkAddress, _onOpen, _onMessage, _onError, _onClose); | 
| } | 
| return _makeRequest(id); | 
| } | 
|  | 
| /// Add a request for [id] to pending requests. | 
| Future<String> _makeRequest(String id) { | 
| -    assert(_webSocket != null); | 
| +    assert(_hasInitiatedConnect); | 
| // Create request. | 
| String serial = (_requestSerial++).toString(); | 
| var request = new _WebSocketRequest(id); | 
| -    if (_webSocket.readyState == WebSocket.OPEN) { | 
| +    if (_webSocket.isOpen) { | 
| // Already connected, send request immediately. | 
| _sendRequest(serial, request); | 
| } else { | 
| @@ -129,27 +140,28 @@ | 
| return request.completer.future; | 
| } | 
|  | 
| -  void _onClose(CloseEvent event) { | 
| +  void _onClose() { | 
| _cancelAllRequests(); | 
| _notifyDisconnect(); | 
| } | 
|  | 
| // WebSocket error event handler. | 
| -  void _onError(Event) { | 
| +  void _onError() { | 
| _cancelAllRequests(); | 
| _notifyDisconnect(); | 
| } | 
|  | 
| // WebSocket open event handler. | 
| -  void _onOpen(Event) { | 
| +  void _onOpen() { | 
| target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch; | 
| _sendAllDelayedRequests(); | 
| _notifyConnect(); | 
| } | 
|  | 
| // WebSocket message event handler. | 
| -  void _onMessage(MessageEvent event) { | 
| -    var map = JSON.decode(event.data); | 
| +  void _onMessage(dynamic data) { | 
| +    assert(data is String);  // We don't handle binary data, yet. | 
| +    var map = JSON.decode(data); | 
| if (map == null) { | 
| Logger.root.severe('WebSocketVM got empty message'); | 
| return; | 
| @@ -214,7 +226,7 @@ | 
|  | 
| /// Send all delayed requests. | 
| void _sendAllDelayedRequests() { | 
| -    assert(_webSocket != null); | 
| +    assert(_webSocket.isOpen); | 
| if (_delayedRequests.length == 0) { | 
| return; | 
| } | 
| @@ -227,7 +239,7 @@ | 
|  | 
| /// Send the request over WebSocket. | 
| void _sendRequest(String serial, _WebSocketRequest request) { | 
| -    assert (_webSocket.readyState == WebSocket.OPEN); | 
| +    assert (_webSocket.isOpen); | 
| if (!request.id.endsWith('/profile/tag')) { | 
| Logger.root.info('GET ${request.id} from ${target.networkAddress}'); | 
| } | 
| @@ -252,46 +264,3 @@ | 
| _webSocket.send(message); | 
| } | 
| } | 
| - | 
| -// A VM that communicates with the service via posting messages from DevTools. | 
| -class PostMessageVM extends VM { | 
| -  final Completer _connected = new Completer(); | 
| -  final Completer _disconnected = new Completer(); | 
| -  void disconnect() { /* nope */ } | 
| -  Future get onConnect => _connected.future; | 
| -  Future get onDisconnect => _disconnected.future; | 
| -  final Map<String, Completer> _pendingRequests = | 
| -      new Map<String, Completer>(); | 
| -  int _requestSerial = 0; | 
| - | 
| -  PostMessageVM() : super() { | 
| -    window.onMessage.listen(_messageHandler); | 
| -    _connected.complete(this); | 
| -  } | 
| - | 
| -  void _messageHandler(msg) { | 
| -    var id = msg.data['id']; | 
| -    var name = msg.data['name']; | 
| -    var data = msg.data['data']; | 
| -    if (name != 'observatoryData') { | 
| -      return; | 
| -    } | 
| -    var completer = _pendingRequests[id]; | 
| -    assert(completer != null); | 
| -    _pendingRequests.remove(id); | 
| -    completer.complete(data); | 
| -  } | 
| - | 
| -  Future<String> getString(String path) { | 
| -    var idString = '$_requestSerial'; | 
| -    Map message = {}; | 
| -    message['id'] = idString; | 
| -    message['method'] = 'observatoryQuery'; | 
| -    message['query'] = '$path'; | 
| -    _requestSerial++; | 
| -    var completer = new Completer(); | 
| -    _pendingRequests[idString] = completer; | 
| -    window.parent.postMessage(JSON.encode(message), '*'); | 
| -    return completer.future; | 
| -  } | 
| -} | 
|  |