| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library service_html; | 5 library service_common; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:convert'; | 8 import 'dart:convert'; |
| 9 import 'dart:html'; | |
| 10 | 9 |
| 11 import 'package:logging/logging.dart'; | 10 import 'package:logging/logging.dart'; |
| 12 import 'package:observatory/service.dart'; | 11 import 'package:observatory/service.dart'; |
| 13 | 12 |
| 14 // Export the service library. | 13 // Export the service library. |
| 15 export 'package:observatory/service.dart'; | 14 export 'package:observatory/service.dart'; |
| 16 | 15 |
| 17 /// Description of a VM target. | 16 /// Description of a VM target. |
| 18 class WebSocketVMTarget { | 17 class WebSocketVMTarget { |
| 19 // Last time this VM has been connected to. | 18 // Last time this VM has been connected to. |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 53 } | 52 } |
| 54 } | 53 } |
| 55 | 54 |
| 56 class _WebSocketRequest { | 55 class _WebSocketRequest { |
| 57 final String id; | 56 final String id; |
| 58 final Completer<String> completer; | 57 final Completer<String> completer; |
| 59 _WebSocketRequest(this.id) | 58 _WebSocketRequest(this.id) |
| 60 : completer = new Completer<String>(); | 59 : completer = new Completer<String>(); |
| 61 } | 60 } |
| 62 | 61 |
| 63 /// The [WebSocketVM] communicates with a Dart VM over WebSocket. The Dart VM | 62 /// Minimal common interface for 'WebSocket' in [dart:io] and [dart:html]. |
| 64 /// can be embedded in Chromium or standalone. In the case of Chromium, we | 63 abstract class CommonWebSocket { |
| 65 /// make the service requests via the Chrome Remote Debugging Protocol. | 64 void connect(String address, |
| 66 class WebSocketVM extends VM { | 65 void onOpen(), |
| 66 void onMessage(dynamic data), |
| 67 void onError(), |
| 68 void onClose()); |
| 69 bool get isOpen; |
| 70 void send(dynamic data); |
| 71 void close(); |
| 72 } |
| 73 |
| 74 /// A [CommonWebSocketVM] communicates with a Dart VM over a CommonWebSocket. |
| 75 /// The Dart VM can be embedded in Chromium or standalone. In the case of |
| 76 /// Chromium, we make the service requests via the Chrome Remote Debugging |
| 77 /// Protocol. |
| 78 abstract class CommonWebSocketVM extends VM { |
| 67 final Completer _connected = new Completer(); | 79 final Completer _connected = new Completer(); |
| 68 final Completer _disconnected = new Completer(); | 80 final Completer _disconnected = new Completer(); |
| 69 final WebSocketVMTarget target; | 81 final WebSocketVMTarget target; |
| 70 final Map<String, _WebSocketRequest> _delayedRequests = | 82 final Map<String, _WebSocketRequest> _delayedRequests = |
| 71 new Map<String, _WebSocketRequest>(); | 83 new Map<String, _WebSocketRequest>(); |
| 72 final Map<String, _WebSocketRequest> _pendingRequests = | 84 final Map<String, _WebSocketRequest> _pendingRequests = |
| 73 new Map<String, _WebSocketRequest>(); | 85 new Map<String, _WebSocketRequest>(); |
| 74 int _requestSerial = 0; | 86 int _requestSerial = 0; |
| 75 WebSocket _webSocket; | 87 bool _hasInitiatedConnect = false; |
| 76 | 88 |
| 77 WebSocketVM(this.target) { | 89 CommonWebSocket _webSocket; |
| 90 |
| 91 CommonWebSocketVM(this.target, this._webSocket) { |
| 78 assert(target != null); | 92 assert(target != null); |
| 79 } | 93 } |
| 80 | 94 |
| 81 void _notifyConnect() { | 95 void _notifyConnect() { |
| 82 if (!_connected.isCompleted) { | 96 if (!_connected.isCompleted) { |
| 83 Logger.root.info('WebSocketVM connection opened: ${target.networkAddress}'
); | 97 Logger.root.info('WebSocketVM connection opened: ${target.networkAddress}'
); |
| 84 _connected.complete(this); | 98 _connected.complete(this); |
| 85 } | 99 } |
| 86 } | 100 } |
| 87 Future get onConnect => _connected.future; | 101 Future get onConnect => _connected.future; |
| 88 void _notifyDisconnect() { | 102 void _notifyDisconnect() { |
| 89 if (!_disconnected.isCompleted) { | 103 if (!_disconnected.isCompleted) { |
| 90 Logger.root.info('WebSocketVM connection error: ${target.networkAddress}')
; | 104 Logger.root.info('WebSocketVM connection error: ${target.networkAddress}')
; |
| 91 _disconnected.complete(this); | 105 _disconnected.complete(this); |
| 92 } | 106 } |
| 93 } | 107 } |
| 94 Future get onDisconnect => _disconnected.future; | 108 Future get onDisconnect => _disconnected.future; |
| 95 | 109 |
| 96 void disconnect() { | 110 void disconnect() { |
| 97 if (_webSocket != null) { | 111 if (_hasInitiatedConnect) { |
| 98 _webSocket.close(); | 112 _webSocket.close(); |
| 99 } | 113 } |
| 100 _cancelAllRequests(); | 114 _cancelAllRequests(); |
| 101 _notifyDisconnect(); | 115 _notifyDisconnect(); |
| 102 } | 116 } |
| 103 | 117 |
| 104 Future<String> getString(String id) { | 118 Future<String> getString(String id) { |
| 105 if (_webSocket == null) { | 119 if (!_hasInitiatedConnect) { |
| 106 // Create a WebSocket. | 120 _hasInitiatedConnect = true; |
| 107 _webSocket = new WebSocket(target.networkAddress); | 121 _webSocket.connect( |
| 108 _webSocket.onClose.listen(_onClose); | 122 target.networkAddress, _onOpen, _onMessage, _onError, _onClose); |
| 109 _webSocket.onError.listen(_onError); | |
| 110 _webSocket.onOpen.listen(_onOpen); | |
| 111 _webSocket.onMessage.listen(_onMessage); | |
| 112 } | 123 } |
| 113 return _makeRequest(id); | 124 return _makeRequest(id); |
| 114 } | 125 } |
| 115 | 126 |
| 116 /// Add a request for [id] to pending requests. | 127 /// Add a request for [id] to pending requests. |
| 117 Future<String> _makeRequest(String id) { | 128 Future<String> _makeRequest(String id) { |
| 118 assert(_webSocket != null); | 129 assert(_hasInitiatedConnect); |
| 119 // Create request. | 130 // Create request. |
| 120 String serial = (_requestSerial++).toString(); | 131 String serial = (_requestSerial++).toString(); |
| 121 var request = new _WebSocketRequest(id); | 132 var request = new _WebSocketRequest(id); |
| 122 if (_webSocket.readyState == WebSocket.OPEN) { | 133 if (_webSocket.isOpen) { |
| 123 // Already connected, send request immediately. | 134 // Already connected, send request immediately. |
| 124 _sendRequest(serial, request); | 135 _sendRequest(serial, request); |
| 125 } else { | 136 } else { |
| 126 // Not connected yet, add to delayed requests. | 137 // Not connected yet, add to delayed requests. |
| 127 _delayedRequests[serial] = request; | 138 _delayedRequests[serial] = request; |
| 128 } | 139 } |
| 129 return request.completer.future; | 140 return request.completer.future; |
| 130 } | 141 } |
| 131 | 142 |
| 132 void _onClose(CloseEvent event) { | 143 void _onClose() { |
| 133 _cancelAllRequests(); | 144 _cancelAllRequests(); |
| 134 _notifyDisconnect(); | 145 _notifyDisconnect(); |
| 135 } | 146 } |
| 136 | 147 |
| 137 // WebSocket error event handler. | 148 // WebSocket error event handler. |
| 138 void _onError(Event) { | 149 void _onError() { |
| 139 _cancelAllRequests(); | 150 _cancelAllRequests(); |
| 140 _notifyDisconnect(); | 151 _notifyDisconnect(); |
| 141 } | 152 } |
| 142 | 153 |
| 143 // WebSocket open event handler. | 154 // WebSocket open event handler. |
| 144 void _onOpen(Event) { | 155 void _onOpen() { |
| 145 target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch; | 156 target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch; |
| 146 _sendAllDelayedRequests(); | 157 _sendAllDelayedRequests(); |
| 147 _notifyConnect(); | 158 _notifyConnect(); |
| 148 } | 159 } |
| 149 | 160 |
| 150 // WebSocket message event handler. | 161 // WebSocket message event handler. |
| 151 void _onMessage(MessageEvent event) { | 162 void _onMessage(dynamic data) { |
| 152 var map = JSON.decode(event.data); | 163 assert(data is String); // We don't handle binary data, yet. |
| 164 var map = JSON.decode(data); |
| 153 if (map == null) { | 165 if (map == null) { |
| 154 Logger.root.severe('WebSocketVM got empty message'); | 166 Logger.root.severe('WebSocketVM got empty message'); |
| 155 return; | 167 return; |
| 156 } | 168 } |
| 157 // Extract serial and response. | 169 // Extract serial and response. |
| 158 var serial; | 170 var serial; |
| 159 var response; | 171 var response; |
| 160 if (target.chrome) { | 172 if (target.chrome) { |
| 161 if (map['method'] != 'Dart.observatoryData') { | 173 if (map['method'] != 'Dart.observatoryData') { |
| 162 // ignore devtools protocol spam. | 174 // ignore devtools protocol spam. |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 207 _cancelRequests(_pendingRequests); | 219 _cancelRequests(_pendingRequests); |
| 208 } | 220 } |
| 209 if (_delayedRequests.length > 0) { | 221 if (_delayedRequests.length > 0) { |
| 210 Logger.root.info('Cancelling all delayed requests.'); | 222 Logger.root.info('Cancelling all delayed requests.'); |
| 211 _cancelRequests(_delayedRequests); | 223 _cancelRequests(_delayedRequests); |
| 212 } | 224 } |
| 213 } | 225 } |
| 214 | 226 |
| 215 /// Send all delayed requests. | 227 /// Send all delayed requests. |
| 216 void _sendAllDelayedRequests() { | 228 void _sendAllDelayedRequests() { |
| 217 assert(_webSocket != null); | 229 assert(_webSocket.isOpen); |
| 218 if (_delayedRequests.length == 0) { | 230 if (_delayedRequests.length == 0) { |
| 219 return; | 231 return; |
| 220 } | 232 } |
| 221 Logger.root.info('Sending all delayed requests.'); | 233 Logger.root.info('Sending all delayed requests.'); |
| 222 // Send all delayed requests. | 234 // Send all delayed requests. |
| 223 _delayedRequests.forEach(_sendRequest); | 235 _delayedRequests.forEach(_sendRequest); |
| 224 // Clear all delayed requests. | 236 // Clear all delayed requests. |
| 225 _delayedRequests.clear(); | 237 _delayedRequests.clear(); |
| 226 } | 238 } |
| 227 | 239 |
| 228 /// Send the request over WebSocket. | 240 /// Send the request over WebSocket. |
| 229 void _sendRequest(String serial, _WebSocketRequest request) { | 241 void _sendRequest(String serial, _WebSocketRequest request) { |
| 230 assert (_webSocket.readyState == WebSocket.OPEN); | 242 assert (_webSocket.isOpen); |
| 231 if (!request.id.endsWith('/profile/tag')) { | 243 if (!request.id.endsWith('/profile/tag')) { |
| 232 Logger.root.info('GET ${request.id} from ${target.networkAddress}'); | 244 Logger.root.info('GET ${request.id} from ${target.networkAddress}'); |
| 233 } | 245 } |
| 234 // Mark request as pending. | 246 // Mark request as pending. |
| 235 assert(_pendingRequests.containsKey(serial) == false); | 247 assert(_pendingRequests.containsKey(serial) == false); |
| 236 _pendingRequests[serial] = request; | 248 _pendingRequests[serial] = request; |
| 237 var message; | 249 var message; |
| 238 // Encode message. | 250 // Encode message. |
| 239 if (target.chrome) { | 251 if (target.chrome) { |
| 240 message = JSON.encode({ | 252 message = JSON.encode({ |
| 241 'id': int.parse(serial), | 253 'id': int.parse(serial), |
| 242 'method': 'Dart.observatoryQuery', | 254 'method': 'Dart.observatoryQuery', |
| 243 'params': { | 255 'params': { |
| 244 'id': serial, | 256 'id': serial, |
| 245 'query': request.id | 257 'query': request.id |
| 246 } | 258 } |
| 247 }); | 259 }); |
| 248 } else { | 260 } else { |
| 249 message = JSON.encode({'seq': serial, 'request': request.id}); | 261 message = JSON.encode({'seq': serial, 'request': request.id}); |
| 250 } | 262 } |
| 251 // Send message. | 263 // Send message. |
| 252 _webSocket.send(message); | 264 _webSocket.send(message); |
| 253 } | 265 } |
| 254 } | 266 } |
| 255 | |
| 256 // A VM that communicates with the service via posting messages from DevTools. | |
| 257 class PostMessageVM extends VM { | |
| 258 final Completer _connected = new Completer(); | |
| 259 final Completer _disconnected = new Completer(); | |
| 260 void disconnect() { /* nope */ } | |
| 261 Future get onConnect => _connected.future; | |
| 262 Future get onDisconnect => _disconnected.future; | |
| 263 final Map<String, Completer> _pendingRequests = | |
| 264 new Map<String, Completer>(); | |
| 265 int _requestSerial = 0; | |
| 266 | |
| 267 PostMessageVM() : super() { | |
| 268 window.onMessage.listen(_messageHandler); | |
| 269 _connected.complete(this); | |
| 270 } | |
| 271 | |
| 272 void _messageHandler(msg) { | |
| 273 var id = msg.data['id']; | |
| 274 var name = msg.data['name']; | |
| 275 var data = msg.data['data']; | |
| 276 if (name != 'observatoryData') { | |
| 277 return; | |
| 278 } | |
| 279 var completer = _pendingRequests[id]; | |
| 280 assert(completer != null); | |
| 281 _pendingRequests.remove(id); | |
| 282 completer.complete(data); | |
| 283 } | |
| 284 | |
| 285 Future<String> getString(String path) { | |
| 286 var idString = '$_requestSerial'; | |
| 287 Map message = {}; | |
| 288 message['id'] = idString; | |
| 289 message['method'] = 'observatoryQuery'; | |
| 290 message['query'] = '$path'; | |
| 291 _requestSerial++; | |
| 292 var completer = new Completer(); | |
| 293 _pendingRequests[idString] = completer; | |
| 294 window.parent.postMessage(JSON.encode(message), '*'); | |
| 295 return completer.future; | |
| 296 } | |
| 297 } | |
| OLD | NEW |