| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 timeline_page_element; | 5 library timeline_page_element; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:html'; |
| 8 import 'dart:convert'; | 9 import 'dart:convert'; |
| 9 import 'dart:html'; | 10 import 'package:observatory/service.dart' as S; |
| 10 import 'observatory_element.dart'; | 11 import 'package:observatory/service_html.dart' as SH; |
| 11 import 'package:observatory/elements.dart'; | 12 import 'package:observatory/models.dart' as M; |
| 12 import 'package:observatory/service_html.dart'; | 13 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| 13 import 'package:polymer/polymer.dart'; | 14 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 14 | 15 import 'package:observatory/src/elements/nav/bar.dart'; |
| 15 | 16 import 'package:observatory/src/elements/nav/notify.dart'; |
| 16 @CustomTag('timeline-page') | 17 import 'package:observatory/src/elements/nav/refresh.dart'; |
| 17 class TimelinePageElement extends ObservatoryElement { | 18 import 'package:observatory/src/elements/nav/top_menu.dart'; |
| 18 TimelinePageElement.created() : super.created() { | 19 import 'package:observatory/src/elements/nav/vm_menu.dart'; |
| 19 } | 20 |
| 20 | 21 enum _Profile { |
| 22 none, |
| 23 dart, |
| 24 vm, |
| 25 all, |
| 26 custom |
| 27 } |
| 28 |
| 29 class TimelinePageElement extends HtmlElement implements Renderable { |
| 30 static const tag = const Tag<TimelinePageElement>('timeline-page', |
| 31 dependencies: const [ |
| 32 NavBarElement.tag, |
| 33 NavTopMenuElement.tag, |
| 34 NavVMMenuElement.tag, |
| 35 NavRefreshElement.tag, |
| 36 NavNotifyElement.tag |
| 37 ]); |
| 38 |
| 39 RenderingScheduler<TimelinePageElement> _r; |
| 40 |
| 41 Stream<RenderedEvent<TimelinePageElement>> get onRendered => _r.onRendered; |
| 42 |
| 43 M.VM _vm; |
| 44 M.EventRepository _events; |
| 45 M.NotificationRepository _notifications; |
| 46 String _recorderName = ''; |
| 47 _Profile _profile = _Profile.none; |
| 48 final Set<String> _availableStreams = new Set<String>(); |
| 49 final Set<String> _recordedStreams = new Set<String>(); |
| 50 |
| 51 M.VMRef get vm => _vm; |
| 52 M.NotificationRepository get notifications => _notifications; |
| 53 |
| 54 factory TimelinePageElement(M.VM vm, M.EventRepository events, |
| 55 M.NotificationRepository notifications, |
| 56 {RenderingQueue queue}) { |
| 57 assert(vm != null); |
| 58 assert(events != null); |
| 59 assert(notifications != null); |
| 60 TimelinePageElement e = document.createElement(tag.name); |
| 61 e._r = new RenderingScheduler(e, queue: queue); |
| 62 e._vm = vm; |
| 63 e._events = events; |
| 64 e._notifications = notifications; |
| 65 return e; |
| 66 } |
| 67 |
| 68 TimelinePageElement.created() : super.created(); |
| 69 |
| 70 @override |
| 21 attached() { | 71 attached() { |
| 22 super.attached(); | 72 super.attached(); |
| 23 _resizeSubscription = window.onResize.listen((_) => _updateSize()); | 73 _r.enable(); |
| 24 _updateSize(); | |
| 25 _setupInitialState(); | 74 _setupInitialState(); |
| 26 } | 75 } |
| 27 | 76 |
| 77 @override |
| 28 detached() { | 78 detached() { |
| 29 super.detached(); | 79 super.detached(); |
| 30 if (_resizeSubscription != null) { | 80 _r.disable(notify: true); |
| 31 _resizeSubscription.cancel(); | 81 children = []; |
| 32 } | 82 } |
| 33 } | 83 |
| 34 | 84 IFrameElement _frame; |
| 35 Future postMessage(String method) { | 85 DivElement _content; |
| 36 IFrameElement e = $['root']; | 86 |
| 87 void render() { |
| 88 if (_frame == null) { |
| 89 _frame = new IFrameElement()..src = 'timeline.html'; |
| 90 } |
| 91 if (_content == null) { |
| 92 _content = new DivElement()..classes = ['content-centered-big']; |
| 93 } |
| 94 _content.children = [ |
| 95 new HeadingElement.h1()..text = 'Timeline settings', |
| 96 new DivElement()..classes = ['memberList'] |
| 97 ..children = [ |
| 98 new DivElement()..classes = ['memberItem'] |
| 99 ..children = [ |
| 100 new DivElement()..classes = ['memberName'] |
| 101 ..text = 'Recorder:', |
| 102 new DivElement()..classes = ['memberValue'] |
| 103 ..text = _recorderName |
| 104 ], |
| 105 new DivElement()..classes = ['memberItem'] |
| 106 ..children = [ |
| 107 new DivElement()..classes = ['memberName'] |
| 108 ..text = 'Recorded Streams Profile:', |
| 109 new DivElement()..classes = ['memberValue'] |
| 110 ..children = _createProfileSelect() |
| 111 ], |
| 112 new DivElement()..classes = ['memberItem'] |
| 113 ..children = [ |
| 114 new DivElement()..classes = ['memberName'] |
| 115 ..text = 'Recorded Streams:', |
| 116 new DivElement()..classes = ['memberValue'] |
| 117 ..children = |
| 118 _availableStreams.map(_makeStreamToggle).toList() |
| 119 ] |
| 120 ] |
| 121 ]; |
| 122 if (children.isEmpty) { |
| 123 children = [ |
| 124 new NavBarElement(queue: _r.queue) |
| 125 ..children = [ |
| 126 new NavTopMenuElement(queue: _r.queue), |
| 127 new NavVMMenuElement(_vm, _events, last: true, queue: _r.queue), |
| 128 new NavRefreshElement(queue: _r.queue) |
| 129 ..onRefresh.listen((e) async { |
| 130 e.element.disabled = true; |
| 131 await _refresh(); |
| 132 e.element.disabled = false; |
| 133 }), |
| 134 new NavRefreshElement(label: 'clear', queue: _r.queue) |
| 135 ..onRefresh.listen((e) async { |
| 136 e.element.disabled = true; |
| 137 await _clear(); |
| 138 e.element.disabled = false; |
| 139 }), |
| 140 new NavRefreshElement(label: 'save', queue: _r.queue) |
| 141 ..onRefresh.listen((e) async { |
| 142 e.element.disabled = true; |
| 143 await _save(); |
| 144 e.element.disabled = false; |
| 145 }), |
| 146 new NavRefreshElement(label: 'load', queue: _r.queue) |
| 147 ..onRefresh.listen((e) async { |
| 148 e.element.disabled = true; |
| 149 await _load(); |
| 150 e.element.disabled = false; |
| 151 }), |
| 152 new NavNotifyElement(_notifications, queue: _r.queue) |
| 153 ], |
| 154 _content, |
| 155 new DivElement()..classes = ['iframe'] |
| 156 ..children = [ |
| 157 _frame |
| 158 ] |
| 159 ]; |
| 160 } |
| 161 } |
| 162 |
| 163 List<Element> _createProfileSelect() { |
| 164 var s; |
| 165 return [ |
| 166 s = new SelectElement()..classes = ['direction-select'] |
| 167 ..value = _profileToString(_profile) |
| 168 ..children = _Profile.values.map((direction) { |
| 169 return new OptionElement(value: _profileToString(direction), |
| 170 selected: _profile == direction) |
| 171 ..text = _profileToString(direction); |
| 172 }).toList(growable: false) |
| 173 ..onChange.listen((_) { |
| 174 _profile = _Profile.values[s.selectedIndex]; |
| 175 _applyPreset(); |
| 176 _r.dirty(); |
| 177 }) |
| 178 ]; |
| 179 } |
| 180 |
| 181 String _profileToString(_Profile profile) { |
| 182 switch (profile) { |
| 183 case _Profile.none: return 'none'; |
| 184 case _Profile.dart: return 'Dart Developer'; |
| 185 case _Profile.vm: return 'VM Developer'; |
| 186 case _Profile.all: return 'All'; |
| 187 case _Profile.custom: return 'Custom'; |
| 188 } |
| 189 throw new Exception('Unkown Profile ${profile}'); |
| 190 } |
| 191 |
| 192 Future _refresh() async { |
| 193 S.VM vm = _vm as S.VM; |
| 194 await vm.reload(); |
| 195 await vm.reloadIsolates(); |
| 196 return _postMessage('refresh'); |
| 197 } |
| 198 |
| 199 Future _clear() async { |
| 200 S.VM vm = _vm as S.VM; |
| 201 await vm.invokeRpc('_clearVMTimeline', {}); |
| 202 return _postMessage('clear'); |
| 203 } |
| 204 |
| 205 Future _save() async { |
| 206 return _postMessage('save'); |
| 207 } |
| 208 |
| 209 Future _load() async { |
| 210 return _postMessage('load'); |
| 211 } |
| 212 |
| 213 Future _postMessage(String method) { |
| 214 S.VM vm = _vm as S.VM; |
| 37 var isolateIds = new List(); | 215 var isolateIds = new List(); |
| 38 for (var isolate in app.vm.isolates) { | 216 for (var isolate in vm.isolates) { |
| 39 isolateIds.add(isolate.id); | 217 isolateIds.add(isolate.id); |
| 40 } | 218 } |
| 41 var message = { | 219 var message = { |
| 42 'method': method, | 220 'method': method, |
| 43 'params': { | 221 'params': { |
| 44 'vmAddress': (app.vm as WebSocketVM).target.networkAddress, | 222 'vmAddress': (vm as SH.WebSocketVM).target.networkAddress, |
| 45 'isolateIds': isolateIds | 223 'isolateIds': isolateIds |
| 46 } | 224 } |
| 47 }; | 225 }; |
| 48 e.contentWindow.postMessage(JSON.encode(message), window.location.href); | 226 _frame.contentWindow.postMessage(JSON.encode(message), window.location.href)
; |
| 49 return null; | 227 return null; |
| 50 } | 228 } |
| 51 | 229 |
| 52 void _processFlags(ServiceMap response) { | 230 Future _setupInitialState() async { |
| 53 // Grab the recorder name. | 231 await _updateRecorderUI(); |
| 54 recorderName = response['recorderName']; | 232 await _refresh(); |
| 55 // Update the set of available streams. | |
| 56 _availableStreams.clear(); | |
| 57 response['availableStreams'].forEach( | |
| 58 (String streamName) => _availableStreams.add(streamName)); | |
| 59 // Update the set of recorded streams. | |
| 60 _recordedStreams.clear(); | |
| 61 response['recordedStreams'].forEach( | |
| 62 (String streamName) => _recordedStreams.add(streamName)); | |
| 63 } | |
| 64 | |
| 65 Future _applyStreamChanges() { | |
| 66 return app.vm.invokeRpc('_setVMTimelineFlags', { | |
| 67 'recordedStreams': '[${_recordedStreams.join(', ')}]', | |
| 68 }); | |
| 69 } | |
| 70 | |
| 71 HtmlElement _makeStreamToggle(String streamName) { | |
| 72 LabelElement label = new LabelElement(); | |
| 73 label.style.paddingLeft = '8px'; | |
| 74 SpanElement span = new SpanElement(); | |
| 75 span.text = streamName; | |
| 76 InputElement checkbox = new InputElement(); | |
| 77 checkbox.onChange.listen((_) { | |
| 78 if (checkbox.checked) { | |
| 79 _recordedStreams.add(streamName); | |
| 80 } else { | |
| 81 _recordedStreams.remove(streamName); | |
| 82 } | |
| 83 _applyStreamChanges(); | |
| 84 _updateRecorderUI(); | |
| 85 }); | |
| 86 checkbox.type = 'checkbox'; | |
| 87 checkbox.checked = _recordedStreams.contains(streamName); | |
| 88 label.children.add(checkbox); | |
| 89 label.children.add(span); | |
| 90 return label; | |
| 91 } | |
| 92 | |
| 93 void _refreshRecorderUI() { | |
| 94 DivElement e = $['streamList']; | |
| 95 e.children.clear(); | |
| 96 | |
| 97 for (String streamName in _availableStreams) { | |
| 98 e.children.add(_makeStreamToggle(streamName)); | |
| 99 } | |
| 100 | |
| 101 streamPresetSelector = streamPresetFromRecordedStreams(); | |
| 102 } | 233 } |
| 103 | 234 |
| 104 // Dart developers care about the following streams: | 235 // Dart developers care about the following streams: |
| 105 List<String> _dartPreset = | 236 List<String> _dartPreset = |
| 106 ['GC', 'Compiler', 'Dart']; | 237 ['GC', 'Compiler', 'Dart']; |
| 107 | 238 |
| 108 // VM developers care about the following streams: | 239 // VM developers care about the following streams: |
| 109 List<String> _vmPreset = | 240 List<String> _vmPreset = |
| 110 ['GC', 'Compiler', 'Dart', 'Debugger', 'Embedder', 'Isolate', 'VM']; | 241 ['GC', 'Compiler', 'Dart', 'Debugger', 'Embedder', 'Isolate', 'VM']; |
| 111 | 242 |
| 112 String streamPresetFromRecordedStreams() { | |
| 113 if (_availableStreams.length == 0) { | |
| 114 return 'None'; | |
| 115 } | |
| 116 if (_recordedStreams.length == 0) { | |
| 117 return 'None'; | |
| 118 } | |
| 119 if (_recordedStreams.length == _availableStreams.length) { | |
| 120 return 'All'; | |
| 121 } | |
| 122 if ((_vmPreset.length == _recordedStreams.length) && | |
| 123 _recordedStreams.containsAll(_vmPreset)) { | |
| 124 return 'VM'; | |
| 125 } | |
| 126 if ((_dartPreset.length == _recordedStreams.length) && | |
| 127 _recordedStreams.containsAll(_dartPreset)) { | |
| 128 return 'Dart'; | |
| 129 } | |
| 130 return 'Custom'; | |
| 131 } | |
| 132 | |
| 133 void _applyPreset() { | 243 void _applyPreset() { |
| 134 switch (streamPresetSelector) { | 244 switch (_profile) { |
| 135 case 'None': | 245 case _Profile.none: |
| 136 _recordedStreams.clear(); | 246 _recordedStreams.clear(); |
| 137 break; | 247 break; |
| 138 case 'All': | 248 case _Profile.all: |
| 139 _recordedStreams.clear(); | 249 _recordedStreams.clear(); |
| 140 _recordedStreams.addAll(_availableStreams); | 250 _recordedStreams.addAll(_availableStreams); |
| 141 break; | 251 break; |
| 142 case 'VM': | 252 case _Profile.vm: |
| 143 _recordedStreams.clear(); | 253 _recordedStreams.clear(); |
| 144 _recordedStreams.addAll(_vmPreset); | 254 _recordedStreams.addAll(_vmPreset); |
| 145 break; | 255 break; |
| 146 case 'Dart': | 256 case _Profile.dart: |
| 147 _recordedStreams.clear(); | 257 _recordedStreams.clear(); |
| 148 _recordedStreams.addAll(_dartPreset); | 258 _recordedStreams.addAll(_dartPreset); |
| 149 break; | 259 break; |
| 150 case 'Custom': | 260 case _Profile.custom: |
| 151 return; | 261 return; |
| 152 } | 262 } |
| 153 _applyStreamChanges(); | 263 _applyStreamChanges(); |
| 154 _updateRecorderUI(); | 264 _updateRecorderUI(); |
| 155 } | 265 } |
| 156 | 266 |
| 157 Future _updateRecorderUI() async { | 267 Future _updateRecorderUI() async { |
| 268 S.VM vm = _vm as S.VM; |
| 158 // Grab the current timeline flags. | 269 // Grab the current timeline flags. |
| 159 ServiceMap response = await app.vm.invokeRpc('_getVMTimelineFlags', {}); | 270 S.ServiceMap response = await vm.invokeRpc('_getVMTimelineFlags', {}); |
| 160 assert(response['type'] == 'TimelineFlags'); | 271 assert(response['type'] == 'TimelineFlags'); |
| 161 // Process them so we know available streams. | 272 // Process them so we know available streams. |
| 162 _processFlags(response); | 273 _processFlags(response); |
| 163 // Refresh the UI. | 274 // Refresh the UI. |
| 164 _refreshRecorderUI(); | 275 _r.dirty(); |
| 165 } | 276 } |
| 166 | 277 |
| 167 Future _setupInitialState() async { | 278 Element _makeStreamToggle(String streamName) { |
| 168 await _updateRecorderUI(); | 279 LabelElement label = new LabelElement(); |
| 169 SelectElement e = $['selectPreset']; | 280 label.style.paddingLeft = '8px'; |
| 170 e.onChange.listen((_) { | 281 SpanElement span = new SpanElement(); |
| 171 _applyPreset(); | 282 span.text = streamName; |
| 283 InputElement checkbox = new InputElement(); |
| 284 checkbox.onChange.listen((_) { |
| 285 if (checkbox.checked) { |
| 286 _recordedStreams.add(streamName); |
| 287 } else { |
| 288 _recordedStreams.remove(streamName); |
| 289 } |
| 290 _applyStreamChanges(); |
| 291 _updateRecorderUI(); |
| 172 }); | 292 }); |
| 173 // Finally, trigger a reload so we start with the latest timeline. | 293 checkbox.type = 'checkbox'; |
| 174 await refresh(); | 294 checkbox.checked = _recordedStreams.contains(streamName); |
| 295 label.children.add(checkbox); |
| 296 label.children.add(span); |
| 297 return label; |
| 175 } | 298 } |
| 176 | 299 |
| 177 Future refresh() async { | 300 Future _applyStreamChanges() { |
| 178 await app.vm.reload(); | 301 S.VM vm = _vm as S.VM; |
| 179 await app.vm.reloadIsolates(); | 302 return vm.invokeRpc('_setVMTimelineFlags', { |
| 180 return postMessage('refresh'); | 303 'recordedStreams': '[${_recordedStreams.join(', ')}]', |
| 304 }); |
| 181 } | 305 } |
| 182 | 306 |
| 183 Future clear() async { | 307 void _processFlags(S.ServiceMap response) { |
| 184 await app.vm.invokeRpc('_clearVMTimeline', {}); | 308 // Grab the recorder name. |
| 185 return postMessage('clear'); | 309 _recorderName = response['recorderName']; |
| 310 // Update the set of available streams. |
| 311 _availableStreams.clear(); |
| 312 response['availableStreams'].forEach( |
| 313 (String streamName) => _availableStreams.add(streamName)); |
| 314 // Update the set of recorded streams. |
| 315 _recordedStreams.clear(); |
| 316 response['recordedStreams'].forEach( |
| 317 (String streamName) => _recordedStreams.add(streamName)); |
| 318 _r.dirty(); |
| 186 } | 319 } |
| 187 | |
| 188 Future saveTimeline() async { | |
| 189 return postMessage('save'); | |
| 190 } | |
| 191 | |
| 192 Future loadTimeline() async { | |
| 193 return postMessage('load'); | |
| 194 } | |
| 195 | |
| 196 _updateSize() { | |
| 197 IFrameElement e = $['root']; | |
| 198 final totalHeight = window.innerHeight; | |
| 199 final top = e.offset.top; | |
| 200 final bottomMargin = 32; | |
| 201 final mainHeight = totalHeight - top - bottomMargin; | |
| 202 e.style.setProperty('height', '${mainHeight}px'); | |
| 203 e.style.setProperty('width', '100%'); | |
| 204 } | |
| 205 | |
| 206 | |
| 207 StreamSubscription _resizeSubscription; | |
| 208 @observable String recorderName; | |
| 209 @observable String streamPresetSelector = 'None'; | |
| 210 final Set<String> _availableStreams = new Set<String>(); | |
| 211 final Set<String> _recordedStreams = new Set<String>(); | |
| 212 } | 320 } |
| OLD | NEW |