Chromium Code Reviews| 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_html; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:convert'; | 8 import 'dart:convert'; |
| 9 import 'dart:html'; | 9 import 'dart:html'; |
| 10 | 10 |
| 11 import 'package:logging/logging.dart'; | 11 import 'package:logging/logging.dart'; |
| 12 import 'package:observatory/service.dart'; | 12 import 'package:observatory/service.dart'; |
| 13 | 13 |
| 14 // Export the service library. | 14 // Export the service library. |
| 15 export 'package:observatory/service.dart'; | 15 export 'package:observatory/service.dart'; |
| 16 | 16 |
| 17 class HttpVM extends VM { | 17 /// Description of a VM target. |
| 18 String host; | 18 class NetworkVMTarget { |
| 19 | 19 // Last time this VM has been connected to. |
| 20 bool runningInJavaScript() => identical(1.0, 1); | 20 int lastConnectionTime = 0; |
| 21 | 21 bool get everConnected => lastConnectionTime > 0; |
|
turnidge
2014/07/01 00:27:47
Maybe "hasConnected" or "hasEverConnected"?
Cutch
2014/07/01 17:14:51
Done.
| |
| 22 HttpVM() : super() { | 22 |
| 23 if (runningInJavaScript()) { | 23 // Chrome VM or standalone; |
| 24 // When we are running as JavaScript use the same hostname:port | 24 bool chrome = false; |
| 25 // that the Observatory is loaded from. | 25 bool get standalone => !chrome; |
| 26 host = 'http://${window.location.host}/'; | 26 |
| 27 // User defined name. | |
| 28 String name; | |
| 29 // Network address of VM. | |
| 30 String networkAddress; | |
| 31 | |
| 32 NetworkVMTarget(this.networkAddress) { | |
| 33 name = networkAddress; | |
| 34 } | |
| 35 | |
| 36 NetworkVMTarget.fromMap(Map json) { | |
| 37 lastConnectionTime = json['lastConnectionTime']; | |
| 38 chrome = json['chrome']; | |
| 39 name = json['name']; | |
| 40 networkAddress = json['networkAddress']; | |
| 41 if (name == null) { | |
| 42 name = networkAddress; | |
| 43 } | |
| 44 } | |
| 45 | |
| 46 Map toJson() { | |
| 47 return { | |
| 48 'lastConnectionTime': lastConnectionTime, | |
| 49 'chrome': chrome, | |
| 50 'name': name, | |
| 51 'networkAddress': networkAddress, | |
| 52 }; | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 class _WebSocketRequest { | |
| 57 final String id; | |
| 58 final Completer<String> completer; | |
| 59 _WebSocketRequest(this.id) | |
| 60 : completer = new Completer<String>(); | |
| 61 } | |
| 62 | |
|
turnidge
2014/07/01 00:27:47
Maybe a class-level comment about NetworkVM.
Also
Cutch
2014/07/01 17:14:51
Done.
| |
| 63 class NetworkVM extends VM { | |
| 64 final Completer _connected = new Completer(); | |
| 65 final Completer _disconnected = new Completer(); | |
| 66 final NetworkVMTarget target; | |
| 67 final Map<String, _WebSocketRequest> _pendingRequests = | |
| 68 new Map<String, _WebSocketRequest>(); | |
| 69 int _requestSerial = 0; | |
| 70 WebSocket _webSocket; | |
| 71 | |
| 72 NetworkVM(this.target) { | |
| 73 assert(target != null); | |
| 74 } | |
| 75 | |
| 76 void _notifyConnect() { | |
| 77 if (!_connected.isCompleted) { | |
| 78 Logger.root.info('NetworkVM connection opened: ${target.networkAddress}'); | |
| 79 _connected.complete(this); | |
| 80 } | |
| 81 } | |
| 82 Future get onConnect => _connected.future; | |
| 83 void _notifyDisconnect() { | |
| 84 if (!_disconnected.isCompleted) { | |
| 85 Logger.root.info('NetworkVM connection error: ${target.networkAddress}'); | |
| 86 _disconnected.complete(this); | |
| 87 } | |
| 88 } | |
| 89 Future get onDisconnect => _disconnected.future; | |
| 90 | |
| 91 void disconnect() { | |
| 92 if (_webSocket != null) { | |
| 93 _webSocket.close(); | |
| 94 } | |
| 95 _cancelAllPendingRequests(); | |
| 96 _notifyDisconnect(); | |
| 97 } | |
| 98 | |
| 99 Future<String> getString(String id) { | |
| 100 if (_webSocket == null) { | |
| 101 // Create a WebSocket. | |
| 102 _webSocket = new WebSocket(target.networkAddress); | |
| 103 _webSocket.onClose.listen(_onClose); | |
| 104 _webSocket.onError.listen(_onError); | |
| 105 _webSocket.onOpen.listen(_onOpen); | |
| 106 _webSocket.onMessage.listen(_onMessage); | |
| 107 } | |
| 108 return _makeRequest(id); | |
| 109 } | |
| 110 | |
| 111 void _onClose(CloseEvent event) { | |
| 112 _cancelAllPendingRequests(); | |
| 113 _notifyDisconnect(); | |
| 114 } | |
| 115 | |
| 116 // WebSocket error event handler. | |
| 117 void _onError(Event) { | |
| 118 _cancelAllPendingRequests(); | |
| 119 _notifyDisconnect(); | |
| 120 } | |
| 121 | |
| 122 // WebSocket open event handler. | |
| 123 void _onOpen(Event) { | |
| 124 target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch; | |
| 125 _notifyConnect(); | |
| 126 _sendAllPendingRequests(); | |
|
turnidge
2014/07/01 00:27:47
I'm wondering if we should move the call to _sendA
turnidge
2014/07/01 00:30:02
This comment got garbled during editing -- it shou
Cutch
2014/07/01 17:14:51
Good catch. I've gone ahead and split pendingReque
| |
| 127 } | |
| 128 | |
| 129 // WebSocket message event handler. | |
| 130 void _onMessage(MessageEvent event) { | |
| 131 var map = JSON.decode(event.data); | |
| 132 if (map == null) { | |
| 133 Logger.root.severe('NetworkVM got empty message'); | |
| 134 return; | |
| 135 } | |
| 136 // Extract serial and response. | |
| 137 var serial; | |
| 138 var response; | |
| 139 if (target.chrome) { | |
| 140 if (map['method'] != 'Dart.observatoryData') { | |
| 141 // ignore devtools protocol spam. | |
| 142 return; | |
| 143 } | |
| 144 serial = map['params']['id'].toString(); | |
| 145 response = map['params']['data']; | |
| 27 } else { | 146 } else { |
| 28 // Otherwise, assume we are running from the Dart Editor and | 147 serial = map['seq']; |
| 29 // want to connect on the default port. | 148 response = map['response']; |
|
turnidge
2014/07/01 00:27:47
Should we consider changing our naming to be consi
Cutch
2014/07/01 17:14:51
The ['params']['id'] and ['params']['data'] are di
| |
| 30 host = 'http://127.0.0.1:8181/'; | 149 } |
| 31 } | 150 // Complete request. |
| 32 } | 151 var request = _pendingRequests.remove(serial); |
| 33 | 152 if (request == null) { |
| 34 Future<String> getString(String id) { | 153 Logger.root.severe('Received unexpected message: ${map}'); |
| 35 // Ensure we don't request host//id. | 154 return; |
| 36 if (host.endsWith('/') && id.startsWith('/')) { | 155 } |
| 37 id = id.substring(1); | 156 request.completer.complete(response); |
| 38 } | 157 } |
| 39 Logger.root.info('Fetching $id from $host'); | 158 |
| 40 return HttpRequest.request(host + id, | 159 String _generateNetworkError(String userMessage) { |
| 41 requestHeaders: { | 160 return JSON.encode({ |
| 42 'Observatory-Version': '1.0' | 161 'type': 'ServiceException', |
| 43 }).then((HttpRequest request) { | 162 'id': '', |
| 44 return request.responseText; | 163 'kind': 'NetworkException', |
| 45 }).catchError((error) { | 164 'message': userMessage |
| 46 // If we get an error here, the network request has failed. | |
| 47 Logger.root.severe('HttpRequest.request failed.'); | |
| 48 var request = error.target; | |
| 49 return JSON.encode({ | |
| 50 'type': 'ServiceException', | |
| 51 'id': '', | |
| 52 'response': request.responseText, | |
| 53 'kind': 'NetworkException', | |
| 54 'message': 'Could not connect to service (${request.statusText}). ' | |
| 55 'Check that you started the VM with the following flags: ' | |
| 56 '--observe' | |
| 57 }); | |
| 58 }); | 165 }); |
| 59 } | 166 } |
| 167 | |
| 168 /// Cancel all pending requests by completing them with an error. | |
| 169 void _cancelAllPendingRequests() { | |
| 170 if (_pendingRequests.length == 0) { | |
| 171 return; | |
| 172 } | |
| 173 Logger.root.info('Cancelling all pending requests.'); | |
| 174 _pendingRequests.forEach((String serial, _WebSocketRequest request) { | |
| 175 request.completer.complete( | |
| 176 _generateNetworkError('WebSocket disconnected')); | |
| 177 }); | |
| 178 _pendingRequests.clear(); | |
| 179 } | |
| 180 | |
| 181 /// Send all pending requests. | |
| 182 void _sendAllPendingRequests() { | |
| 183 assert(_webSocket != null); | |
| 184 if (_pendingRequests.length == 0) { | |
| 185 return; | |
| 186 } | |
| 187 Logger.root.info('Sending all pending requests.'); | |
| 188 _pendingRequests.forEach(_sendRequest); | |
| 189 } | |
| 190 | |
| 191 /// Send the request over WebSocket. | |
| 192 void _sendRequest(String serial, _WebSocketRequest request) { | |
| 193 assert (_webSocket.readyState == WebSocket.OPEN); | |
| 194 if (!request.id.endsWith('/profile/tag')) { | |
| 195 Logger.root.info('GET ${request.id} from ${target.networkAddress}'); | |
| 196 } | |
| 197 var message; | |
| 198 // Encode message. | |
| 199 if (target.chrome) { | |
| 200 message = JSON.encode({ | |
| 201 'id': int.parse(serial), | |
| 202 'method': 'Dart.observatoryQuery', | |
| 203 'params': { | |
| 204 'id': serial, | |
| 205 'query': request.id | |
| 206 } | |
| 207 }); | |
| 208 } else { | |
| 209 message = JSON.encode({'seq': serial, 'request': request.id}); | |
| 210 } | |
| 211 // Send message. | |
| 212 _webSocket.send(message); | |
| 213 } | |
| 214 | |
| 215 /// Add a request for [id] to pending requests. | |
| 216 Future<String> _makeRequest(String id) { | |
|
turnidge
2014/07/01 00:27:47
I would move this function closer to getString --
Cutch
2014/07/01 17:14:51
Done.
| |
| 217 assert(_webSocket != null); | |
| 218 // Create request. | |
| 219 String serial = (_requestSerial++).toString(); | |
| 220 var request = new _WebSocketRequest(id); | |
| 221 _pendingRequests[serial] = request; | |
| 222 if (_webSocket.readyState == WebSocket.OPEN) { | |
| 223 // Already connected, send request immediately. | |
| 224 _sendRequest(serial, request); | |
| 225 } | |
| 226 return request.completer.future; | |
| 227 } | |
| 60 } | 228 } |
| 61 | 229 |
| 62 class WebSocketVM extends VM { | 230 // A VM that communicates with the service via posting messages from DevTools. |
| 63 final Map<int, Completer> _pendingRequests = | 231 class PostMessageVM extends VM { |
| 64 new Map<int, Completer>(); | 232 final Completer _connected = new Completer(); |
| 65 int _requestSerial = 0; | 233 final Completer _disconnected = new Completer(); |
| 66 | 234 void disconnect() { /* nope */ } |
| 67 String _host; | 235 Future get onConnect => _connected.future; |
| 68 Future<WebSocket> _socketFuture; | 236 Future get onDisconnect => _disconnected.future; |
| 69 | |
| 70 bool runningInJavaScript() => identical(1.0, 1); | |
| 71 | |
| 72 WebSocketVM() : super() { | |
| 73 if (runningInJavaScript()) { | |
| 74 // When we are running as JavaScript use the same hostname:port | |
| 75 // that the Observatory is loaded from. | |
| 76 _host = 'ws://${window.location.host}/ws'; | |
| 77 } else { | |
| 78 // Otherwise, assume we are running from the Dart Editor and | |
| 79 // want to connect on the default port. | |
| 80 _host = 'ws://127.0.0.1:8181/ws'; | |
| 81 } | |
| 82 | |
| 83 var completer = new Completer<WebSocket>(); | |
| 84 _socketFuture = completer.future; | |
| 85 var socket = new WebSocket(_host); | |
| 86 socket.onOpen.first.then((_) { | |
| 87 socket.onMessage.listen(_handleMessage); | |
| 88 socket.onClose.first.then((_) { | |
| 89 _socketFuture = null; | |
| 90 }); | |
| 91 completer.complete(socket); | |
| 92 }); | |
| 93 socket.onError.first.then((_) { | |
| 94 _socketFuture = null; | |
| 95 }); | |
| 96 } | |
| 97 | |
| 98 void _handleMessage(MessageEvent event) { | |
| 99 var map = JSON.decode(event.data); | |
| 100 int seq = map['seq']; | |
| 101 var response = map['response']; | |
| 102 var completer = _pendingRequests.remove(seq); | |
| 103 if (completer == null) { | |
| 104 Logger.root.severe('Received unexpected message: ${map}'); | |
| 105 } else { | |
| 106 completer.complete(response); | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 Future<String> getString(String id) { | |
| 111 if (_socketFuture == null) { | |
| 112 var errorResponse = JSON.encode({ | |
| 113 'type': 'ServiceException', | |
| 114 'id': '', | |
| 115 'response': '', | |
| 116 'kind': 'NetworkException', | |
| 117 'message': 'Could not connect to service. Check that you started t he' | |
| 118 ' VM with the following flags:\n --enable-vm-service' | |
| 119 ' --pause-isolates-on-exit' | |
| 120 }); | |
| 121 return new Future.value(errorResponse); | |
| 122 } | |
| 123 return _socketFuture.then((socket) { | |
| 124 int seq = _requestSerial++; | |
| 125 if (!id.endsWith('/profile/tag')) { | |
| 126 Logger.root.info('Fetching $id from $_host'); | |
| 127 } | |
| 128 var completer = new Completer<String>(); | |
| 129 _pendingRequests[seq] = completer; | |
| 130 var message = JSON.encode({'seq': seq, 'request': id}); | |
| 131 socket.send(message); | |
| 132 return completer.future; | |
| 133 }); | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 class DartiumVM extends VM { | |
| 138 final Map<String, Completer> _pendingRequests = | 237 final Map<String, Completer> _pendingRequests = |
| 139 new Map<String, Completer>(); | 238 new Map<String, Completer>(); |
| 140 int _requestSerial = 0; | 239 int _requestSerial = 0; |
| 141 | 240 |
| 142 DartiumVM() : super() { | 241 PostMessageVM() : super() { |
| 143 window.onMessage.listen(_messageHandler); | 242 window.onMessage.listen(_messageHandler); |
| 144 Logger.root.info('Connected to DartiumVM'); | 243 _connected.complete(this); |
| 145 } | 244 } |
| 146 | 245 |
| 147 void _messageHandler(msg) { | 246 void _messageHandler(msg) { |
| 148 var id = msg.data['id']; | 247 var id = msg.data['id']; |
| 149 var name = msg.data['name']; | 248 var name = msg.data['name']; |
| 150 var data = msg.data['data']; | 249 var data = msg.data['data']; |
| 151 if (name != 'observatoryData') { | 250 if (name != 'observatoryData') { |
| 152 return; | 251 return; |
| 153 } | 252 } |
| 154 var completer = _pendingRequests[id]; | 253 var completer = _pendingRequests[id]; |
| 155 assert(completer != null); | 254 assert(completer != null); |
| 156 _pendingRequests.remove(id); | 255 _pendingRequests.remove(id); |
| 157 completer.complete(data); | 256 completer.complete(data); |
| 158 } | 257 } |
| 159 | 258 |
| 160 Future<String> getString(String path) { | 259 Future<String> getString(String path) { |
| 161 var idString = '$_requestSerial'; | 260 var idString = '$_requestSerial'; |
| 162 Map message = {}; | 261 Map message = {}; |
| 163 message['id'] = idString; | 262 message['id'] = idString; |
| 164 message['method'] = 'observatoryQuery'; | 263 message['method'] = 'observatoryQuery'; |
| 165 message['query'] = '$path'; | 264 message['query'] = '$path'; |
| 166 _requestSerial++; | 265 _requestSerial++; |
| 167 var completer = new Completer(); | 266 var completer = new Completer(); |
| 168 _pendingRequests[idString] = completer; | 267 _pendingRequests[idString] = completer; |
| 169 window.parent.postMessage(JSON.encode(message), '*'); | 268 window.parent.postMessage(JSON.encode(message), '*'); |
| 170 return completer.future; | 269 return completer.future; |
| 171 } | 270 } |
| 172 } | 271 } |
| OLD | NEW |