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:observatory/service_common.dart'; |
12 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_common.dart'; |
16 | 15 |
17 /// Description of a VM target. | 16 class _HtmlWebSocket implements CommonWebSocket { |
18 class WebSocketVMTarget { | 17 WebSocket _webSocket; |
19 // Last time this VM has been connected to. | |
20 int lastConnectionTime = 0; | |
21 bool get hasEverConnected => lastConnectionTime > 0; | |
22 | 18 |
23 // Chrome VM or standalone; | 19 void connect(String address, |
24 bool chrome = false; | 20 void onOpen(), |
25 bool get standalone => !chrome; | 21 void onMessage(dynamic data), |
26 | 22 void onError(), |
27 // User defined name. | 23 void onClose()) { |
28 String name; | 24 _webSocket = new WebSocket(address); |
29 // Network address of VM. | 25 _webSocket.onClose.listen((CloseEvent) => onClose()); |
30 String networkAddress; | 26 _webSocket.onError.listen((Event) => onError()); |
31 | 27 _webSocket.onOpen.listen((Event) => onOpen()); |
32 WebSocketVMTarget(this.networkAddress) { | 28 _webSocket.onMessage.listen((MessageEvent event) => onMessage(event.data)); |
33 name = networkAddress; | |
34 } | 29 } |
35 | 30 |
36 WebSocketVMTarget.fromMap(Map json) { | 31 bool get isOpen => _webSocket.readyState == WebSocket.OPEN; |
37 lastConnectionTime = json['lastConnectionTime']; | 32 |
38 chrome = json['chrome']; | 33 void send(dynamic data) { |
39 name = json['name']; | 34 _webSocket.send(data); |
40 networkAddress = json['networkAddress']; | |
41 if (name == null) { | |
42 name = networkAddress; | |
43 } | |
44 } | 35 } |
45 | 36 |
46 Map toJson() { | 37 void close() { |
47 return { | 38 _webSocket.close(); |
48 'lastConnectionTime': lastConnectionTime, | |
49 'chrome': chrome, | |
50 'name': name, | |
51 'networkAddress': networkAddress, | |
52 }; | |
53 } | 39 } |
54 } | 40 } |
55 | 41 |
56 class _WebSocketRequest { | |
57 final String id; | |
58 final Completer<String> completer; | |
59 _WebSocketRequest(this.id) | |
60 : completer = new Completer<String>(); | |
61 } | |
62 | |
63 /// The [WebSocketVM] communicates with a Dart VM over WebSocket. The Dart VM | 42 /// The [WebSocketVM] communicates with a Dart VM over WebSocket. The Dart VM |
64 /// can be embedded in Chromium or standalone. In the case of Chromium, we | 43 /// can be embedded in Chromium or standalone. In the case of Chromium, we |
65 /// make the service requests via the Chrome Remote Debugging Protocol. | 44 /// make the service requests via the Chrome Remote Debugging Protocol. |
66 class WebSocketVM extends VM { | 45 class WebSocketVM extends CommonWebSocketVM { |
67 final Completer _connected = new Completer(); | 46 WebSocketVM(WebSocketVMTarget target) : super(target, new _HtmlWebSocket()); |
68 final Completer _disconnected = new Completer(); | |
69 final WebSocketVMTarget target; | |
70 final Map<String, _WebSocketRequest> _delayedRequests = | |
71 new Map<String, _WebSocketRequest>(); | |
72 final Map<String, _WebSocketRequest> _pendingRequests = | |
73 new Map<String, _WebSocketRequest>(); | |
74 int _requestSerial = 0; | |
75 WebSocket _webSocket; | |
76 | |
77 WebSocketVM(this.target) { | |
78 assert(target != null); | |
79 } | |
80 | |
81 void _notifyConnect() { | |
82 if (!_connected.isCompleted) { | |
83 Logger.root.info('WebSocketVM connection opened: ${target.networkAddress}'
); | |
84 _connected.complete(this); | |
85 } | |
86 } | |
87 Future get onConnect => _connected.future; | |
88 void _notifyDisconnect() { | |
89 if (!_disconnected.isCompleted) { | |
90 Logger.root.info('WebSocketVM connection error: ${target.networkAddress}')
; | |
91 _disconnected.complete(this); | |
92 } | |
93 } | |
94 Future get onDisconnect => _disconnected.future; | |
95 | |
96 void disconnect() { | |
97 if (_webSocket != null) { | |
98 _webSocket.close(); | |
99 } | |
100 _cancelAllRequests(); | |
101 _notifyDisconnect(); | |
102 } | |
103 | |
104 Future<String> getString(String id) { | |
105 if (_webSocket == null) { | |
106 // Create a WebSocket. | |
107 _webSocket = new WebSocket(target.networkAddress); | |
108 _webSocket.onClose.listen(_onClose); | |
109 _webSocket.onError.listen(_onError); | |
110 _webSocket.onOpen.listen(_onOpen); | |
111 _webSocket.onMessage.listen(_onMessage); | |
112 } | |
113 return _makeRequest(id); | |
114 } | |
115 | |
116 /// Add a request for [id] to pending requests. | |
117 Future<String> _makeRequest(String id) { | |
118 assert(_webSocket != null); | |
119 // Create request. | |
120 String serial = (_requestSerial++).toString(); | |
121 var request = new _WebSocketRequest(id); | |
122 if (_webSocket.readyState == WebSocket.OPEN) { | |
123 // Already connected, send request immediately. | |
124 _sendRequest(serial, request); | |
125 } else { | |
126 // Not connected yet, add to delayed requests. | |
127 _delayedRequests[serial] = request; | |
128 } | |
129 return request.completer.future; | |
130 } | |
131 | |
132 void _onClose(CloseEvent event) { | |
133 _cancelAllRequests(); | |
134 _notifyDisconnect(); | |
135 } | |
136 | |
137 // WebSocket error event handler. | |
138 void _onError(Event) { | |
139 _cancelAllRequests(); | |
140 _notifyDisconnect(); | |
141 } | |
142 | |
143 // WebSocket open event handler. | |
144 void _onOpen(Event) { | |
145 target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch; | |
146 _sendAllDelayedRequests(); | |
147 _notifyConnect(); | |
148 } | |
149 | |
150 // WebSocket message event handler. | |
151 void _onMessage(MessageEvent event) { | |
152 var map = JSON.decode(event.data); | |
153 if (map == null) { | |
154 Logger.root.severe('WebSocketVM got empty message'); | |
155 return; | |
156 } | |
157 // Extract serial and response. | |
158 var serial; | |
159 var response; | |
160 if (target.chrome) { | |
161 if (map['method'] != 'Dart.observatoryData') { | |
162 // ignore devtools protocol spam. | |
163 return; | |
164 } | |
165 serial = map['params']['id'].toString(); | |
166 response = map['params']['data']; | |
167 } else { | |
168 serial = map['seq']; | |
169 response = map['response']; | |
170 } | |
171 if (serial == null) { | |
172 // Messages without sequence numbers are asynchronous events | |
173 // from the vm. | |
174 postEventMessage(response); | |
175 return; | |
176 } | |
177 // Complete request. | |
178 var request = _pendingRequests.remove(serial); | |
179 if (request == null) { | |
180 Logger.root.severe('Received unexpected message: ${map}'); | |
181 return; | |
182 } | |
183 request.completer.complete(response); | |
184 } | |
185 | |
186 String _generateNetworkError(String userMessage) { | |
187 return JSON.encode({ | |
188 'type': 'ServiceException', | |
189 'id': '', | |
190 'kind': 'NetworkException', | |
191 'message': userMessage | |
192 }); | |
193 } | |
194 | |
195 void _cancelRequests(Map<String, _WebSocketRequest> requests) { | |
196 requests.forEach((String serial, _WebSocketRequest request) { | |
197 request.completer.complete( | |
198 _generateNetworkError('WebSocket disconnected')); | |
199 }); | |
200 requests.clear(); | |
201 } | |
202 | |
203 /// Cancel all pending and delayed requests by completing them with an error. | |
204 void _cancelAllRequests() { | |
205 if (_pendingRequests.length > 0) { | |
206 Logger.root.info('Cancelling all pending requests.'); | |
207 _cancelRequests(_pendingRequests); | |
208 } | |
209 if (_delayedRequests.length > 0) { | |
210 Logger.root.info('Cancelling all delayed requests.'); | |
211 _cancelRequests(_delayedRequests); | |
212 } | |
213 } | |
214 | |
215 /// Send all delayed requests. | |
216 void _sendAllDelayedRequests() { | |
217 assert(_webSocket != null); | |
218 if (_delayedRequests.length == 0) { | |
219 return; | |
220 } | |
221 Logger.root.info('Sending all delayed requests.'); | |
222 // Send all delayed requests. | |
223 _delayedRequests.forEach(_sendRequest); | |
224 // Clear all delayed requests. | |
225 _delayedRequests.clear(); | |
226 } | |
227 | |
228 /// Send the request over WebSocket. | |
229 void _sendRequest(String serial, _WebSocketRequest request) { | |
230 assert (_webSocket.readyState == WebSocket.OPEN); | |
231 if (!request.id.endsWith('/profile/tag')) { | |
232 Logger.root.info('GET ${request.id} from ${target.networkAddress}'); | |
233 } | |
234 // Mark request as pending. | |
235 assert(_pendingRequests.containsKey(serial) == false); | |
236 _pendingRequests[serial] = request; | |
237 var message; | |
238 // Encode message. | |
239 if (target.chrome) { | |
240 message = JSON.encode({ | |
241 'id': int.parse(serial), | |
242 'method': 'Dart.observatoryQuery', | |
243 'params': { | |
244 'id': serial, | |
245 'query': request.id | |
246 } | |
247 }); | |
248 } else { | |
249 message = JSON.encode({'seq': serial, 'request': request.id}); | |
250 } | |
251 // Send message. | |
252 _webSocket.send(message); | |
253 } | |
254 } | 47 } |
255 | 48 |
256 // A VM that communicates with the service via posting messages from DevTools. | 49 // A VM that communicates with the service via posting messages from DevTools. |
257 class PostMessageVM extends VM { | 50 class PostMessageVM extends VM { |
258 final Completer _connected = new Completer(); | 51 final Completer _connected = new Completer(); |
259 final Completer _disconnected = new Completer(); | 52 final Completer _disconnected = new Completer(); |
260 void disconnect() { /* nope */ } | 53 void disconnect() { /* nope */ } |
261 Future get onConnect => _connected.future; | 54 Future get onConnect => _connected.future; |
262 Future get onDisconnect => _disconnected.future; | 55 Future get onDisconnect => _disconnected.future; |
263 final Map<String, Completer> _pendingRequests = | 56 final Map<String, Completer> _pendingRequests = |
(...skipping 24 matching lines...) Expand all Loading... |
288 message['id'] = idString; | 81 message['id'] = idString; |
289 message['method'] = 'observatoryQuery'; | 82 message['method'] = 'observatoryQuery'; |
290 message['query'] = '$path'; | 83 message['query'] = '$path'; |
291 _requestSerial++; | 84 _requestSerial++; |
292 var completer = new Completer(); | 85 var completer = new Completer(); |
293 _pendingRequests[idString] = completer; | 86 _pendingRequests[idString] = completer; |
294 window.parent.postMessage(JSON.encode(message), '*'); | 87 window.parent.postMessage(JSON.encode(message), '*'); |
295 return completer.future; | 88 return completer.future; |
296 } | 89 } |
297 } | 90 } |
OLD | NEW |