Index: runtime/observatory/lib/src/elements/timeline_page.dart |
diff --git a/runtime/observatory/lib/src/elements/timeline_page.dart b/runtime/observatory/lib/src/elements/timeline_page.dart |
index 28f3046e7cc77f0dd24ac1bec0fbaab52d533bbf..8e7b3ef144da8c56af6b08bbfc40d16af3c92000 100644 |
--- a/runtime/observatory/lib/src/elements/timeline_page.dart |
+++ b/runtime/observatory/lib/src/elements/timeline_page.dart |
@@ -5,100 +5,231 @@ |
library timeline_page_element; |
import 'dart:async'; |
-import 'dart:convert'; |
import 'dart:html'; |
-import 'observatory_element.dart'; |
-import 'package:observatory/elements.dart'; |
-import 'package:observatory/service_html.dart'; |
-import 'package:polymer/polymer.dart'; |
+import 'dart:convert'; |
+import 'package:observatory/service.dart' as S; |
+import 'package:observatory/service_html.dart' as SH; |
+import 'package:observatory/models.dart' as M; |
+import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
+import 'package:observatory/src/elements/helpers/tag.dart'; |
+import 'package:observatory/src/elements/nav/bar.dart'; |
+import 'package:observatory/src/elements/nav/notify.dart'; |
+import 'package:observatory/src/elements/nav/refresh.dart'; |
+import 'package:observatory/src/elements/nav/top_menu.dart'; |
+import 'package:observatory/src/elements/nav/vm_menu.dart'; |
+ |
+enum _Profile { |
+ none, |
+ dart, |
+ vm, |
+ all, |
+ custom |
+} |
+class TimelinePageElement extends HtmlElement implements Renderable { |
+ static const tag = const Tag<TimelinePageElement>('timeline-page', |
+ dependencies: const [ |
+ NavBarElement.tag, |
+ NavTopMenuElement.tag, |
+ NavVMMenuElement.tag, |
+ NavRefreshElement.tag, |
+ NavNotifyElement.tag |
+ ]); |
-@CustomTag('timeline-page') |
-class TimelinePageElement extends ObservatoryElement { |
- TimelinePageElement.created() : super.created() { |
+ RenderingScheduler<TimelinePageElement> _r; |
+ |
+ Stream<RenderedEvent<TimelinePageElement>> get onRendered => _r.onRendered; |
+ |
+ M.VM _vm; |
+ M.EventRepository _events; |
+ M.NotificationRepository _notifications; |
+ String _recorderName = ''; |
+ _Profile _profile = _Profile.none; |
+ final Set<String> _availableStreams = new Set<String>(); |
+ final Set<String> _recordedStreams = new Set<String>(); |
+ |
+ M.VMRef get vm => _vm; |
+ M.NotificationRepository get notifications => _notifications; |
+ |
+ factory TimelinePageElement(M.VM vm, M.EventRepository events, |
+ M.NotificationRepository notifications, |
+ {RenderingQueue queue}) { |
+ assert(vm != null); |
+ assert(events != null); |
+ assert(notifications != null); |
+ TimelinePageElement e = document.createElement(tag.name); |
+ e._r = new RenderingScheduler(e, queue: queue); |
+ e._vm = vm; |
+ e._events = events; |
+ e._notifications = notifications; |
+ return e; |
} |
+ TimelinePageElement.created() : super.created(); |
+ |
+ @override |
attached() { |
super.attached(); |
- _resizeSubscription = window.onResize.listen((_) => _updateSize()); |
- _updateSize(); |
+ _r.enable(); |
_setupInitialState(); |
} |
+ @override |
detached() { |
super.detached(); |
- if (_resizeSubscription != null) { |
- _resizeSubscription.cancel(); |
+ _r.disable(notify: true); |
+ children = []; |
+ } |
+ |
+ IFrameElement _frame; |
+ DivElement _content; |
+ |
+ void render() { |
+ if (_frame == null) { |
+ _frame = new IFrameElement()..src = 'timeline.html'; |
+ } |
+ if (_content == null) { |
+ _content = new DivElement()..classes = ['content-centered-big']; |
+ } |
+ _content.children = [ |
+ new HeadingElement.h1()..text = 'Timeline settings', |
+ new DivElement()..classes = ['memberList'] |
+ ..children = [ |
+ new DivElement()..classes = ['memberItem'] |
+ ..children = [ |
+ new DivElement()..classes = ['memberName'] |
+ ..text = 'Recorder:', |
+ new DivElement()..classes = ['memberValue'] |
+ ..text = _recorderName |
+ ], |
+ new DivElement()..classes = ['memberItem'] |
+ ..children = [ |
+ new DivElement()..classes = ['memberName'] |
+ ..text = 'Recorded Streams Profile:', |
+ new DivElement()..classes = ['memberValue'] |
+ ..children = _createProfileSelect() |
+ ], |
+ new DivElement()..classes = ['memberItem'] |
+ ..children = [ |
+ new DivElement()..classes = ['memberName'] |
+ ..text = 'Recorded Streams:', |
+ new DivElement()..classes = ['memberValue'] |
+ ..children = |
+ _availableStreams.map(_makeStreamToggle).toList() |
+ ] |
+ ] |
+ ]; |
+ if (children.isEmpty) { |
+ children = [ |
+ new NavBarElement(queue: _r.queue) |
+ ..children = [ |
+ new NavTopMenuElement(queue: _r.queue), |
+ new NavVMMenuElement(_vm, _events, last: true, queue: _r.queue), |
+ new NavRefreshElement(queue: _r.queue) |
+ ..onRefresh.listen((e) async { |
+ e.element.disabled = true; |
+ await _refresh(); |
+ e.element.disabled = false; |
+ }), |
+ new NavRefreshElement(label: 'clear', queue: _r.queue) |
+ ..onRefresh.listen((e) async { |
+ e.element.disabled = true; |
+ await _clear(); |
+ e.element.disabled = false; |
+ }), |
+ new NavRefreshElement(label: 'save', queue: _r.queue) |
+ ..onRefresh.listen((e) async { |
+ e.element.disabled = true; |
+ await _save(); |
+ e.element.disabled = false; |
+ }), |
+ new NavRefreshElement(label: 'load', queue: _r.queue) |
+ ..onRefresh.listen((e) async { |
+ e.element.disabled = true; |
+ await _load(); |
+ e.element.disabled = false; |
+ }), |
+ new NavNotifyElement(_notifications, queue: _r.queue) |
+ ], |
+ _content, |
+ new DivElement()..classes = ['iframe'] |
+ ..children = [ |
+ _frame |
+ ] |
+ ]; |
} |
} |
- Future postMessage(String method) { |
- IFrameElement e = $['root']; |
- var isolateIds = new List(); |
- for (var isolate in app.vm.isolates) { |
- isolateIds.add(isolate.id); |
+ List<Element> _createProfileSelect() { |
+ var s; |
+ return [ |
+ s = new SelectElement()..classes = ['direction-select'] |
+ ..value = _profileToString(_profile) |
+ ..children = _Profile.values.map((direction) { |
+ return new OptionElement(value: _profileToString(direction), |
+ selected: _profile == direction) |
+ ..text = _profileToString(direction); |
+ }).toList(growable: false) |
+ ..onChange.listen((_) { |
+ _profile = _Profile.values[s.selectedIndex]; |
+ _applyPreset(); |
+ _r.dirty(); |
+ }) |
+ ]; |
+ } |
+ |
+ String _profileToString(_Profile profile) { |
+ switch (profile) { |
+ case _Profile.none: return 'none'; |
+ case _Profile.dart: return 'Dart Developer'; |
+ case _Profile.vm: return 'VM Developer'; |
+ case _Profile.all: return 'All'; |
+ case _Profile.custom: return 'Custom'; |
} |
- var message = { |
- 'method': method, |
- 'params': { |
- 'vmAddress': (app.vm as WebSocketVM).target.networkAddress, |
- 'isolateIds': isolateIds |
- } |
- }; |
- e.contentWindow.postMessage(JSON.encode(message), window.location.href); |
- return null; |
+ throw new Exception('Unkown Profile ${profile}'); |
} |
- void _processFlags(ServiceMap response) { |
- // Grab the recorder name. |
- recorderName = response['recorderName']; |
- // Update the set of available streams. |
- _availableStreams.clear(); |
- response['availableStreams'].forEach( |
- (String streamName) => _availableStreams.add(streamName)); |
- // Update the set of recorded streams. |
- _recordedStreams.clear(); |
- response['recordedStreams'].forEach( |
- (String streamName) => _recordedStreams.add(streamName)); |
+ Future _refresh() async { |
+ S.VM vm = _vm as S.VM; |
+ await vm.reload(); |
+ await vm.reloadIsolates(); |
+ return _postMessage('refresh'); |
} |
- Future _applyStreamChanges() { |
- return app.vm.invokeRpc('_setVMTimelineFlags', { |
- 'recordedStreams': '[${_recordedStreams.join(', ')}]', |
- }); |
+ Future _clear() async { |
+ S.VM vm = _vm as S.VM; |
+ await vm.invokeRpc('_clearVMTimeline', {}); |
+ return _postMessage('clear'); |
} |
- HtmlElement _makeStreamToggle(String streamName) { |
- LabelElement label = new LabelElement(); |
- label.style.paddingLeft = '8px'; |
- SpanElement span = new SpanElement(); |
- span.text = streamName; |
- InputElement checkbox = new InputElement(); |
- checkbox.onChange.listen((_) { |
- if (checkbox.checked) { |
- _recordedStreams.add(streamName); |
- } else { |
- _recordedStreams.remove(streamName); |
- } |
- _applyStreamChanges(); |
- _updateRecorderUI(); |
- }); |
- checkbox.type = 'checkbox'; |
- checkbox.checked = _recordedStreams.contains(streamName); |
- label.children.add(checkbox); |
- label.children.add(span); |
- return label; |
+ Future _save() async { |
+ return _postMessage('save'); |
} |
- void _refreshRecorderUI() { |
- DivElement e = $['streamList']; |
- e.children.clear(); |
+ Future _load() async { |
+ return _postMessage('load'); |
+ } |
- for (String streamName in _availableStreams) { |
- e.children.add(_makeStreamToggle(streamName)); |
+ Future _postMessage(String method) { |
+ S.VM vm = _vm as S.VM; |
+ var isolateIds = new List(); |
+ for (var isolate in vm.isolates) { |
+ isolateIds.add(isolate.id); |
} |
+ var message = { |
+ 'method': method, |
+ 'params': { |
+ 'vmAddress': (vm as SH.WebSocketVM).target.networkAddress, |
+ 'isolateIds': isolateIds |
+ } |
+ }; |
+ _frame.contentWindow.postMessage(JSON.encode(message), window.location.href); |
+ return null; |
+ } |
- streamPresetSelector = streamPresetFromRecordedStreams(); |
+ Future _setupInitialState() async { |
+ await _updateRecorderUI(); |
+ await _refresh(); |
} |
// Dart developers care about the following streams: |
@@ -109,45 +240,24 @@ class TimelinePageElement extends ObservatoryElement { |
List<String> _vmPreset = |
['GC', 'Compiler', 'Dart', 'Debugger', 'Embedder', 'Isolate', 'VM']; |
- String streamPresetFromRecordedStreams() { |
- if (_availableStreams.length == 0) { |
- return 'None'; |
- } |
- if (_recordedStreams.length == 0) { |
- return 'None'; |
- } |
- if (_recordedStreams.length == _availableStreams.length) { |
- return 'All'; |
- } |
- if ((_vmPreset.length == _recordedStreams.length) && |
- _recordedStreams.containsAll(_vmPreset)) { |
- return 'VM'; |
- } |
- if ((_dartPreset.length == _recordedStreams.length) && |
- _recordedStreams.containsAll(_dartPreset)) { |
- return 'Dart'; |
- } |
- return 'Custom'; |
- } |
- |
void _applyPreset() { |
- switch (streamPresetSelector) { |
- case 'None': |
+ switch (_profile) { |
+ case _Profile.none: |
_recordedStreams.clear(); |
break; |
- case 'All': |
+ case _Profile.all: |
_recordedStreams.clear(); |
_recordedStreams.addAll(_availableStreams); |
break; |
- case 'VM': |
+ case _Profile.vm: |
_recordedStreams.clear(); |
_recordedStreams.addAll(_vmPreset); |
break; |
- case 'Dart': |
+ case _Profile.dart: |
_recordedStreams.clear(); |
_recordedStreams.addAll(_dartPreset); |
break; |
- case 'Custom': |
+ case _Profile.custom: |
return; |
} |
_applyStreamChanges(); |
@@ -155,58 +265,56 @@ class TimelinePageElement extends ObservatoryElement { |
} |
Future _updateRecorderUI() async { |
+ S.VM vm = _vm as S.VM; |
// Grab the current timeline flags. |
- ServiceMap response = await app.vm.invokeRpc('_getVMTimelineFlags', {}); |
+ S.ServiceMap response = await vm.invokeRpc('_getVMTimelineFlags', {}); |
assert(response['type'] == 'TimelineFlags'); |
// Process them so we know available streams. |
_processFlags(response); |
// Refresh the UI. |
- _refreshRecorderUI(); |
+ _r.dirty(); |
} |
- Future _setupInitialState() async { |
- await _updateRecorderUI(); |
- SelectElement e = $['selectPreset']; |
- e.onChange.listen((_) { |
- _applyPreset(); |
+ Element _makeStreamToggle(String streamName) { |
+ LabelElement label = new LabelElement(); |
+ label.style.paddingLeft = '8px'; |
+ SpanElement span = new SpanElement(); |
+ span.text = streamName; |
+ InputElement checkbox = new InputElement(); |
+ checkbox.onChange.listen((_) { |
+ if (checkbox.checked) { |
+ _recordedStreams.add(streamName); |
+ } else { |
+ _recordedStreams.remove(streamName); |
+ } |
+ _applyStreamChanges(); |
+ _updateRecorderUI(); |
}); |
- // Finally, trigger a reload so we start with the latest timeline. |
- await refresh(); |
- } |
- |
- Future refresh() async { |
- await app.vm.reload(); |
- await app.vm.reloadIsolates(); |
- return postMessage('refresh'); |
- } |
- |
- Future clear() async { |
- await app.vm.invokeRpc('_clearVMTimeline', {}); |
- return postMessage('clear'); |
- } |
- |
- Future saveTimeline() async { |
- return postMessage('save'); |
+ checkbox.type = 'checkbox'; |
+ checkbox.checked = _recordedStreams.contains(streamName); |
+ label.children.add(checkbox); |
+ label.children.add(span); |
+ return label; |
} |
- Future loadTimeline() async { |
- return postMessage('load'); |
+ Future _applyStreamChanges() { |
+ S.VM vm = _vm as S.VM; |
+ return vm.invokeRpc('_setVMTimelineFlags', { |
+ 'recordedStreams': '[${_recordedStreams.join(', ')}]', |
+ }); |
} |
- _updateSize() { |
- IFrameElement e = $['root']; |
- final totalHeight = window.innerHeight; |
- final top = e.offset.top; |
- final bottomMargin = 32; |
- final mainHeight = totalHeight - top - bottomMargin; |
- e.style.setProperty('height', '${mainHeight}px'); |
- e.style.setProperty('width', '100%'); |
+ void _processFlags(S.ServiceMap response) { |
+ // Grab the recorder name. |
+ _recorderName = response['recorderName']; |
+ // Update the set of available streams. |
+ _availableStreams.clear(); |
+ response['availableStreams'].forEach( |
+ (String streamName) => _availableStreams.add(streamName)); |
+ // Update the set of recorded streams. |
+ _recordedStreams.clear(); |
+ response['recordedStreams'].forEach( |
+ (String streamName) => _recordedStreams.add(streamName)); |
+ _r.dirty(); |
} |
- |
- |
- StreamSubscription _resizeSubscription; |
- @observable String recorderName; |
- @observable String streamPresetSelector = 'None'; |
- final Set<String> _availableStreams = new Set<String>(); |
- final Set<String> _recordedStreams = new Set<String>(); |
} |