Chromium Code Reviews| Index: runtime/observatory/lib/src/elements/script_inset.dart |
| diff --git a/runtime/observatory/lib/src/elements/script_inset.dart b/runtime/observatory/lib/src/elements/script_inset.dart |
| index 47bbff7b4f3d4066834e0250192e0e19594d5546..bc5f25a426da5046d6aa57cfe3b01c4e736b8899 100644 |
| --- a/runtime/observatory/lib/src/elements/script_inset.dart |
| +++ b/runtime/observatory/lib/src/elements/script_inset.dart |
| @@ -6,6 +6,7 @@ library script_inset_element; |
| import 'dart:async'; |
| import 'dart:html'; |
| +import 'dart:math'; |
| import 'observatory_element.dart'; |
| import 'service_ref.dart'; |
| import 'package:observatory/service.dart'; |
| @@ -244,7 +245,7 @@ class CallSiteAnnotation extends Annotation { |
| for (var entry in callSite.entries) { |
| var r = row(); |
| - r.append(cell(serviceRef(entry.receiverContainer))); |
| + r.append(cell(serviceRef(entry.receiver))); |
| r.append(cell(entry.count.toString())); |
| r.append(cell(serviceRef(entry.target))); |
| details.append(r); |
| @@ -374,16 +375,23 @@ class ScriptInsetElement extends ObservatoryElement { |
| @published bool inDebuggerContext = false; |
| @published ObservableList variables; |
| + @published Element scroller; |
| + RefreshButtonElement _refreshButton; |
| + |
| int _currentLine; |
| int _currentCol; |
| int _startLine; |
| int _endLine; |
| + Map<int, List<ServiceMap>> _rangeMap = {}; |
| + Set _callSites = new Set<CallSite>(); |
| + |
| var annotations = []; |
| var annotationsCursor; |
| StreamSubscription _scriptChangeSubscription; |
| Future<StreamSubscription> _debugSubscriptionFuture; |
| + StreamSubscription _scrollSubscription; |
| bool hasLoadedLibraryDeclarations = false; |
| @@ -402,11 +410,20 @@ class ScriptInsetElement extends ObservatoryElement { |
| super.attached(); |
| _debugSubscriptionFuture = |
| app.vm.listenEventStream(VM.kDebugStream, _onDebugEvent); |
| + if (scroller != null) { |
| + _scrollSubscription = scroller.onScroll.listen(_onScroll); |
| + } else { |
| + _scrollSubscription = window.onScroll.listen(_onScroll); |
| + } |
| } |
| void detached() { |
| cancelFutureSubscription(_debugSubscriptionFuture); |
| _debugSubscriptionFuture = null; |
| + if (_scrollSubscription != null) { |
| + _scrollSubscription.cancel(); |
| + _scrollSubscription = null; |
| + } |
| if (_scriptChangeSubscription != null) { |
| // Don't leak. If only Dart and Javascript exposed weak references... |
| _scriptChangeSubscription.cancel(); |
| @@ -415,6 +432,14 @@ class ScriptInsetElement extends ObservatoryElement { |
| super.detached(); |
| } |
| + void _onScroll(event) { |
| + var currentTop = _refreshButton.style.top; |
| + var newTop = _refreshButtonTop(); |
| + if (currentTop != newTop) { |
| + _refreshButton.style.top = '${newTop}px'; |
| + } |
| + } |
| + |
| void _onDebugEvent(event) { |
| if (script == null) { |
| return; |
| @@ -486,11 +511,51 @@ class ScriptInsetElement extends ObservatoryElement { |
| element.title = "Line did execute"; |
| return element; |
| } |
| + Element hitsCompiled(Element element) { |
| + element.classes.add('hitsCompiled'); |
| + element.title = "Line in compiled function"; |
| + return element; |
| + } |
| + Element hitsNotCompiled(Element element) { |
| + element.classes.add('hitsNotCompiled'); |
| + element.title = "Line in uncompiled function"; |
| + return element; |
| + } |
| Element container; |
| + Future _refresh() async { |
| + await update(); |
| + } |
| + |
| + // Build _rangeMap and _callSites from a source report. |
| + Future _refreshSourceReport() async { |
| + var sourceReport = await script.isolate.getSourceReport( |
| + [Isolate.kCallSitesReport], script, startPos, endPos); |
| + _rangeMap.clear(); |
| + _callSites.clear(); |
| + for (var range in sourceReport['ranges']) { |
| + int startLine = script.tokenToLine(range['startPos']); |
| + int endLine = script.tokenToLine(range['endPos']); |
| + for (var line = startLine; line <= endLine; line++) { |
| + var rangeList = _rangeMap[line]; |
| + if (rangeList == null) { |
| + _rangeMap[line] = [range]; |
| + } else { |
| + rangeList.add(range); |
| + } |
| + } |
| + var rangeCallSites = range['callSites']; |
| + if (rangeCallSites != null) { |
| + for (var callSiteMap in rangeCallSites) { |
| + _callSites.add(new CallSite.fromMap(callSiteMap, script)); |
| + } |
| + } |
| + } |
| + } |
| + |
| Task _updateTask; |
| - void update() { |
| + Future update() async { |
| assert(_updateTask != null); |
| if (script == null) { |
| // We may have previously had a script. |
| @@ -500,13 +565,12 @@ class ScriptInsetElement extends ObservatoryElement { |
| return; |
| } |
| if (!script.loaded) { |
| - script.load().then((_) => update()); |
| - return; |
| + await script.load(); |
| } |
| - |
| if (_scriptChangeSubscription == null) { |
| _scriptChangeSubscription = script.changes.listen((_) => update()); |
| } |
| + await _refreshSourceReport(); |
| computeAnnotations(); |
| @@ -803,7 +867,7 @@ class ScriptInsetElement extends ObservatoryElement { |
| } |
| void addCallSiteAnnotations() { |
| - for (var callSite in script.callSites) { |
| + for (var callSite in _callSites) { |
| annotations.add(new CallSiteAnnotation(callSite)); |
| } |
| } |
| @@ -828,10 +892,35 @@ class ScriptInsetElement extends ObservatoryElement { |
| } |
| } |
| + int _refreshButtonTop() { |
| + if (_refreshButton == null) { |
| + return 5; |
| + } |
| + const padding = 5; |
| + const navbarHeight = 40; |
|
Cutch
2016/01/14 21:37:40
The navbarHeight constant is hardcoded in two plac
|
| + var rect = getBoundingClientRect(); |
| + var buttonHeight = _refreshButton.clientHeight; |
| + return min(max(0, navbarHeight - rect.top) + padding, |
| + rect.height - (buttonHeight + padding)); |
| + } |
| + |
| + RefreshButtonElement _newRefreshButton() { |
| + var button = new Element.tag('refresh-button'); |
| + button.style.position = 'absolute'; |
| + button.style.display = 'inline-block'; |
| + button.style.top = '${_refreshButtonTop()}px'; |
| + button.style.right = '5px'; |
| + button.callback = _refresh; |
| + return button; |
| + } |
| + |
| Element linesTable() { |
| var table = new DivElement(); |
| table.classes.add("sourceTable"); |
| + _refreshButton = _newRefreshButton(); |
| + table.append(_refreshButton); |
| + |
| if (_startLine == null || _endLine == null) { |
| return table; |
| } |
| @@ -973,17 +1062,48 @@ class ScriptInsetElement extends ObservatoryElement { |
| var lineNumber = line == null ? "..." : line.line; |
| var e = span("$nbsp${lineNumber.toString().padLeft(lineNumPad,nbsp)}$nbsp"); |
| e.classes.add('noCopy'); |
| - |
| if (lineNumber == _currentLine) { |
| hitsCurrent(e); |
| - } else if ((line == null) || (line.hits == null)) { |
| + return e; |
| + } |
| + var ranges = _rangeMap[lineNumber]; |
| + if ((ranges == null) || ranges.isEmpty) { |
| + // This line is not code. |
| hitsUnknown(e); |
| - } else if (line.hits == 0) { |
| + return e; |
| + } |
| + bool compiled = false; |
| + bool hasCallInfo = false; |
| + bool executed = false; |
| + for (var range in ranges) { |
| + if (range['compiled']) { |
| + compiled = true; |
| + for (var callSite in range['callSites']) { |
| + var callLine = line.script.tokenToLine(callSite['tokenPos']); |
| + if (lineNumber == callLine) { |
| + // The call site is on the current line. |
| + hasCallInfo = true; |
| + for (var cacheEntry in callSite['cacheEntries']) { |
| + if (cacheEntry['count'] > 0) { |
| + // If any call site on the line has been executed, we |
| + // mark the line as executed. |
| + executed = true; |
| + break; |
| + } |
| + } |
| + } |
| + } |
| + } |
| + } |
| + if (executed) { |
| + hitsExecuted(e); |
| + } else if (hasCallInfo) { |
| hitsNotExecuted(e); |
| + } else if (compiled) { |
| + hitsCompiled(e); |
| } else { |
| - hitsExecuted(e); |
| + hitsNotCompiled(e); |
| } |
| - |
| return e; |
| } |
| @@ -1036,6 +1156,26 @@ class ScriptInsetElement extends ObservatoryElement { |
| } |
| } |
| +@CustomTag('refresh-button') |
| +class RefreshButtonElement extends PolymerElement { |
| + RefreshButtonElement.created() : super.created(); |
| + |
| + @published var callback = null; |
| + bool busy = false; |
| + |
| + Future buttonClick(var event, var b, var c) async { |
| + if (busy) { |
| + return; |
| + } |
| + busy = true; |
| + if (callback != null) { |
| + await callback(); |
| + } |
| + busy = false; |
| + } |
| +} |
| + |
| + |
| @CustomTag('source-inset') |
| class SourceInsetElement extends PolymerElement { |
| SourceInsetElement.created() : super.created(); |
| @@ -1045,4 +1185,5 @@ class SourceInsetElement extends PolymerElement { |
| @published int currentPos; |
| @published bool inDebuggerContext = false; |
| @published ObservableList variables; |
| + @published Element scroller; |
| } |