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 |