Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1795)

Unified Diff: runtime/observatory/lib/src/elements/script_inset.dart

Issue 2277543004: Converted Observatory script-inset & source-inset elements (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Fixed long line Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 fbbef3aa34921cc9b15b0ade0b5c1a8d94bb776c..d968fe09bb17a26d8d05699a9df151aa085481f0 100644
--- a/runtime/observatory/lib/src/elements/script_inset.dart
+++ b/runtime/observatory/lib/src/elements/script_inset.dart
@@ -6,1374 +6,1341 @@ library script_inset_element;
import 'dart:async';
import 'dart:html';
-import 'dart:math';
-import 'observatory_element.dart';
-import 'service_ref.dart';
+import 'package:observatory/app.dart';
import 'package:observatory/models.dart' as M;
-import 'package:observatory/service.dart';
+import 'package:observatory/service.dart' as S;
+import 'package:observatory/src/elements/helpers/any_ref.dart';
+import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
+import 'package:observatory/src/elements/helpers/tag.dart';
+import 'package:observatory/src/elements/helpers/uris.dart';
import 'package:observatory/utils.dart';
-import 'package:polymer/polymer.dart';
-import 'package:logging/logging.dart';
-const nbsp = "\u00A0";
-
-void addInfoBox(Element content, Function infoBoxGenerator) {
- var infoBox;
- var show = false;
- var originalBackground = content.style.backgroundColor;
- buildInfoBox() {
- infoBox = infoBoxGenerator();
- infoBox.style.position = 'absolute';
- infoBox.style.padding = '1em';
- infoBox.style.border = 'solid black 2px';
- infoBox.style.zIndex = '10';
- infoBox.style.backgroundColor = 'white';
- infoBox.style.cursor = 'auto';
- // Don't inherit pre formating from the script lines.
- infoBox.style.whiteSpace = 'normal';
- content.append(infoBox);
+class ScriptInsetElement extends HtmlElement implements Renderable {
+ static const tag = const Tag<ScriptInsetElement>('script-inset-wrapped');
+
+ RenderingScheduler _r;
+
+ Stream<RenderedEvent<ScriptInsetElement>> get onRendered => _r.onRendered;
+
+
+ M.IsolateRef _isolate;
+ M.ScriptRef _script;
+ M.Script _loadedScript;
+ M.ScriptRepository _scripts;
+ M.InstanceRepository _instances;
+ M.EventRepository _events;
+ StreamSubscription _subscription;
+ int _startPos;
+ int _endPos;
+ int _currentPos;
+ bool _inDebuggerContext;
+ Iterable _variables;
+
+ M.IsolateRef get isolate => _isolate;
+ M.ScriptRef get script => _script;
+
+ factory ScriptInsetElement(M.IsolateRef isolate, M.ScriptRef script,
+ M.ScriptRepository scripts,
+ M.InstanceRepository instances,
+ M.EventRepository events,
+ {int startPos, int endPos, int currentPos,
+ bool inDebuggerContext: false,
+ Iterable variables: const [],
+ RenderingQueue queue}) {
+ assert(isolate != null);
+ assert(script != null);
+ assert(scripts != null);
+ assert(instances != null);
+ assert(events != null);
+ assert(inDebuggerContext != null);
+ assert(variables != null);
+ ScriptInsetElement e = document.createElement(tag.name);
+ e._r = new RenderingScheduler(e, queue: queue);
+ e._isolate = isolate;
+ e._script = script;
+ e._scripts = scripts;
+ e._instances = instances;
+ e._events = events;
+ e._startPos = startPos;
+ e._endPos = endPos;
+ e._currentPos = currentPos;
+ e._inDebuggerContext = inDebuggerContext;
+ e._variables = new List.unmodifiable(variables);
+ return e;
}
- content.onClick.listen((event) {
- show = !show;
- if (infoBox == null) buildInfoBox(); // Created lazily on the first click.
- infoBox.style.display = show ? 'block' : 'none';
- content.style.backgroundColor = show ? 'white' : originalBackground;
- });
- // Causes infoBox to be positioned relative to the bottom-left of content.
- content.style.display = 'inline-block';
- content.style.cursor = 'pointer';
-}
-
-
-void addLink(Element content, String target) {
- // Ick, destructive but still compatible with also adding an info box.
- var a = new AnchorElement(href: target);
- a.text = content.text;
- content.text = '';
- content.append(a);
-}
+ ScriptInsetElement.created() : super.created();
+ @override
+ void attached() {
+ super.attached();
+ _r.enable();
+ _subscription = _events.onDebugEvent
+ .where((e) => (e is M.BreakpointAddedEvent) ||
+ (e is M.BreakpointResolvedEvent) ||
+ (e is M.BreakpointRemovedEvent))
+ .map((e) => e.breakpoint)
+ .listen((M.Breakpoint b) {
+ final loc = b.location;
+ int line;
+ if (loc.script.id == script.id) {
+ if (loc.tokenPos != null) {
+ line = _loadedScript.tokenToLine(loc.tokenPos);
+ } else {
+ line = loc.line;
+ }
+ } else {
+ line = loc.line;
+ }
+ if ((line == null) || ((line >= _startLine) && (line <= _endLine))) {
+ _r.dirty();
+ }
+ });
+ _refresh();
+ }
-abstract class Annotation implements Comparable<Annotation> {
- int line;
- int columnStart;
- int columnStop;
- int get priority;
+ @override
+ void detached() {
+ super.detached();
+ children = [];
+ _r.disable(notify: true);
+ _subscription.cancel();
+ }
- void applyStyleTo(element);
+ void render() {
+ if (_loadedScript == null) {
+ children = [new SpanElement()..text = 'Loading...'];
+ } else {
+ final table = linesTable();
+ var firstBuild = false;
+ if (container == null) {
+ // Indirect to avoid deleting the style element.
+ container = new DivElement();
- int compareTo(Annotation other) {
- if (line == other.line) {
- if (columnStart == other.columnStart) {
- return priority.compareTo(other.priority);
+ firstBuild = true;
+ }
+ children = [container];
+ container.children.clear();
+ container.children.add(table);
+ _makeCssClassUncopyable(table, "noCopy");
+ if (firstBuild) {
+ _scrollToCurrentPos();
}
- return columnStart.compareTo(other.columnStart);
}
- return line.compareTo(other.line);
}
- Element table() {
- var e = new DivElement();
- e.style.display = "table";
- e.style.color = "#333";
- e.style.font = "400 14px 'Montserrat', sans-serif";
- return e;
+ Future _refresh() async {
+ _loadedScript = await _scripts.get(_isolate, _script.id);
+ await _refreshSourceReport();
+ await _computeAnnotations();
+ _r.dirty();
}
- Element row([content]) {
- var e = new DivElement();
- e.style.display = "table-row";
- if (content is String) e.text = content;
- if (content is Element) e.children.add(content);
- return e;
- }
+ ButtonElement _refreshButton;
+ ButtonElement _toggleProfileButton;
- Element cell(content) {
- var e = new DivElement();
- e.style.display = "table-cell";
- e.style.padding = "3px";
- if (content is String) e.text = content;
- if (content is Element) e.children.add(content);
- return e;
+ int _currentLine;
+ int _currentCol;
+ int _startLine;
+ int _endLine;
+
+ Map<int, List<S.ServiceMap>> _rangeMap = {};
+ Set _callSites = new Set<S.CallSite>();
+ Set _possibleBreakpointLines = new Set<int>();
+ Map<int, ScriptLineProfile> _profileMap = {};
+
+ var _annotations = [];
+ var _annotationsCursor;
+
+ bool _includeProfile = false;
+
+ String makeLineClass(int line) {
+ return 'script-inset-line-$line';
}
- Element serviceRef(object) {
- AnyServiceRefElement e = new Element.tag("any-service-ref");
- e.ref = object;
- return e;
+ void _scrollToCurrentPos() {
+ var lines = getElementsByClassName(makeLineClass(_currentLine));
+ if (lines.length > 0) {
+ lines[0].scrollIntoView();
+ }
}
-}
-class CurrentExecutionAnnotation extends Annotation {
- int priority = 0; // highest priority.
+ Element a(String text) => new AnchorElement()..text = text;
+ Element span(String text) => new SpanElement()..text = text;
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
- }
- element.classes.add("currentCol");
- element.title = "Current execution";
+ Element hitsCurrent(Element element) {
+ element.classes.add('hitsCurrent');
+ element.title = "";
+ return element;
+ }
+ Element hitsUnknown(Element element) {
+ element.classes.add('hitsNone');
+ element.title = "";
+ return element;
+ }
+ Element hitsNotExecuted(Element element) {
+ element.classes.add('hitsNotExecuted');
+ element.title = "Line did not execute";
+ return element;
+ }
+ Element hitsExecuted(Element element) {
+ element.classes.add('hitsExecuted');
+ 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;
}
-}
-class BreakpointAnnotation extends Annotation {
- Breakpoint bpt;
- int priority = 1;
+ Element container;
- BreakpointAnnotation(this.bpt) {
- var script = bpt.location.script;
- var location = bpt.location;
- if (location.tokenPos != null) {
- var pos = location.tokenPos;
- line = script.tokenToLine(pos);
- columnStart = script.tokenToCol(pos) - 1; // tokenToCol is 1-origin.
- } else if (location is UnresolvedSourceLocation) {
- line = location.line;
- columnStart = location.column;
- if (columnStart == null) {
- columnStart = 0;
- }
+ // Build _rangeMap and _callSites from a source report.
+ Future _refreshSourceReport() async {
+ var reports = [S.Isolate.kCallSitesReport,
+ S.Isolate.kPossibleBreakpointsReport];
+ if (_includeProfile) {
+ reports.add(S.Isolate.kProfileReport);
}
- var length = script.guessTokenLength(line, columnStart);
- if (length == null) {
- length = 1;
+ S.Isolate isolate = _isolate as S.Isolate;
+ var sourceReport = await isolate.getSourceReport(
+ reports,
+ script, _startPos, _endPos);
+ _possibleBreakpointLines = S.getPossibleBreakpointLines(sourceReport,
+ script);
+ _rangeMap.clear();
+ _callSites.clear();
+ _profileMap.clear();
+ for (var range in sourceReport['ranges']) {
+ int startLine = _loadedScript.tokenToLine(range['startPos']);
+ int endLine = _loadedScript.tokenToLine(range['endPos']);
+ // TODO(turnidge): Track down the root cause of null startLine/endLine.
+ if ((startLine != null) && (endLine != null)) {
+ for (var line = startLine; line <= endLine; line++) {
+ var rangeList = _rangeMap[line];
+ if (rangeList == null) {
+ _rangeMap[line] = [range];
+ } else {
+ rangeList.add(range);
+ }
+ }
+ }
+ if (_includeProfile && range['profile'] != null) {
+ List positions = range['profile']['positions'];
+ List exclusiveTicks = range['profile']['exclusiveTicks'];
+ List inclusiveTicks = range['profile']['inclusiveTicks'];
+ int sampleCount = range['profile']['metadata']['sampleCount'];
+ assert(positions.length == exclusiveTicks.length);
+ assert(positions.length == inclusiveTicks.length);
+ for (int i = 0; i < positions.length; i++) {
+ if (positions[i] is String) {
+ // String positions are classifying token positions.
+ // TODO(johnmccutchan): Add classifier data to UI.
+ continue;
+ }
+ int line = _loadedScript.tokenToLine(positions[i]);
+ ScriptLineProfile lineProfile = _profileMap[line];
+ if (lineProfile == null) {
+ lineProfile = new ScriptLineProfile(line, sampleCount);
+ _profileMap[line] = lineProfile;
+ }
+ lineProfile.process(exclusiveTicks[i], inclusiveTicks[i]);
+ }
+ }
+ if (range['compiled']) {
+ var rangeCallSites = range['callSites'];
+ if (rangeCallSites != null) {
+ for (var callSiteMap in rangeCallSites) {
+ _callSites.add(new S.CallSite.fromMap(callSiteMap, script));
+ }
+ }
+ }
}
- columnStop = columnStart + length;
}
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
- }
- var script = bpt.location.script;
- var pos = bpt.location.tokenPos;
- int line = script.tokenToLine(pos);
- int column = script.tokenToCol(pos);
- if (bpt.resolved) {
- element.classes.add("resolvedBreakAnnotation");
- } else {
- element.classes.add("unresolvedBreakAnnotation");
+ Future _computeAnnotations() async {
+ _startLine = (_startPos != null
+ ? _loadedScript.tokenToLine(_startPos)
+ : 1 + _loadedScript.lineOffset);
+ _currentLine = (_currentPos != null
+ ? _loadedScript.tokenToLine(_currentPos)
+ : null);
+ _currentCol = (_currentPos != null
+ ? (_loadedScript.tokenToCol(_currentPos))
+ : null);
+ if (_currentCol != null) {
+ _currentCol--; // make this 0-based.
}
- element.title = "Breakpoint ${bpt.number} at ${line}:${column}";
- }
-}
-class LibraryAnnotation extends Annotation {
- Library target;
- String url;
- int priority = 2;
+ S.Script script = _loadedScript as S.Script;
- LibraryAnnotation(this.target, this.url);
+ _endLine = (_endPos != null
+ ? _loadedScript.tokenToLine(_endPos)
+ : script.lines.length + _loadedScript.lineOffset);
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
+ if (_startLine == null || _endLine == null) {
+ return;
}
- element.title = "library ${target.uri}";
- addLink(element, url);
- }
-}
-class PartAnnotation extends Annotation {
- Script part;
- String url;
- int priority = 2;
+ _annotations.clear();
- PartAnnotation(this.part, this.url);
+ addCurrentExecutionAnnotation();
+ addBreakpointAnnotations();
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
+ if (!_inDebuggerContext && script.library != null) {
+ await loadDeclarationsOfLibrary(script.library);
+ addLibraryAnnotations();
+ addDependencyAnnotations();
+ addPartAnnotations();
+ addClassAnnotations();
+ addFieldAnnotations();
+ addFunctionAnnotations();
+ addCallSiteAnnotations();
}
- element.title = "script ${part.uri}";
- addLink(element, url);
- }
-}
-class LocalVariableAnnotation extends Annotation {
- final value;
- int priority = 2;
+ addLocalVariableAnnotations();
- LocalVariableAnnotation(LocalVarLocation location, this.value) {
- line = location.line;
- columnStart = location.column;
- columnStop = location.endColumn;
+ _annotations.sort();
}
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
+ void addCurrentExecutionAnnotation() {
+ if (_currentLine != null) {
+ var a = new CurrentExecutionAnnotation(_isolate, _instances, _r.queue);
+ a.line = _currentLine;
+ a.columnStart = _currentCol;
+ S.Script script = _loadedScript as S.Script;
+ var length = script.guessTokenLength(_currentLine, _currentCol);
+ if (length == null) {
+ length = 1;
+ }
+ a.columnStop = _currentCol + length;
+ _annotations.add(a);
}
- element.style.fontWeight = "bold";
- element.title = "${value.shortName}";
}
-}
-class CallSiteAnnotation extends Annotation {
- CallSite callSite;
- int priority = 2;
-
- CallSiteAnnotation(this.callSite) {
- line = callSite.line;
- columnStart = callSite.column - 1; // Call site is 1-origin.
- var tokenLength = callSite.script.guessTokenLength(line, columnStart);
- if (tokenLength == null) {
- tokenLength = callSite.name.length; // Approximate.
- if (callSite.name.startsWith("get:") ||
- callSite.name.startsWith("set:")) tokenLength -= 4;
+ void addBreakpointAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ for (var line = _startLine; line <= _endLine; line++) {
+ var bpts = script.getLine(line).breakpoints;
+ if (bpts != null) {
+ for (var bpt in bpts) {
+ if (bpt.location != null) {
+ _annotations.add(new BreakpointAnnotation(_isolate, _instances,
+ _r.queue, bpt));
+ }
+ }
+ }
}
- columnStop = columnStart + tokenLength;
}
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
- }
- element.style.fontWeight = "bold";
- element.title = "Call site: ${callSite.name}";
-
- addInfoBox(element, () {
- var details = table();
- if (callSite.entries.isEmpty) {
- details.append(row('Call of "${callSite.name}" did not execute'));
- } else {
- var r = row();
- r.append(cell("Container"));
- r.append(cell("Count"));
- r.append(cell("Target"));
- details.append(r);
+ Future loadDeclarationsOfLibrary(S.Library lib) {
+ return lib.load().then((lib) {
+ var loads = [];
+ for (var func in lib.functions) {
+ loads.add(func.load());
+ }
+ for (var field in lib.variables) {
+ loads.add(field.load());
+ }
+ for (var cls in lib.classes) {
+ loads.add(loadDeclarationsOfClass(cls));
+ }
+ return Future.wait(loads);
+ });
+ }
- for (var entry in callSite.entries) {
- var r = row();
- r.append(cell(serviceRef(entry.receiver)));
- r.append(cell(entry.count.toString()));
- r.append(cell(serviceRef(entry.target)));
- details.append(r);
- }
+ Future loadDeclarationsOfClass(S.Class cls) {
+ return cls.load().then((cls) {
+ var loads = [];
+ for (var func in cls.functions) {
+ loads.add(func.load());
}
- return details;
+ for (var field in cls.fields) {
+ loads.add(field.load());
+ }
+ return Future.wait(loads);
});
}
-}
-abstract class DeclarationAnnotation extends Annotation {
- String url;
- int priority = 2;
+ void addLibraryAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ for (S.ScriptLine line in script.lines) {
+ // TODO(rmacnak): Use a real scanner.
+ var pattern = new RegExp("library ${script.library.name}");
+ var match = pattern.firstMatch(line.text);
+ if (match != null) {
+ var anno = new LibraryAnnotation(_isolate, _instances, _r.queue,
+ _loadedScript.library,
+ Uris.inspect(isolate, object: _loadedScript.library));
+ anno.line = line.line;
+ anno.columnStart = match.start + 8;
+ anno.columnStop = match.end;
+ _annotations.add(anno);
+ }
+ // TODO(rmacnak): Use a real scanner.
+ pattern = new RegExp("part of ${script.library.name}");
+ match = pattern.firstMatch(line.text);
+ if (match != null) {
+ var anno = new LibraryAnnotation(_isolate, _instances, _r.queue,
+ _loadedScript.library,
+ Uris.inspect(isolate, object: _loadedScript.library));
+ anno.line = line.line;
+ anno.columnStart = match.start + 8;
+ anno.columnStop = match.end;
+ _annotations.add(anno);
+ }
+ }
+ }
- DeclarationAnnotation(decl, this.url) {
- assert(decl.loaded);
- SourceLocation location = decl.location;
- if (location == null) {
- line = 0;
- columnStart = 0;
- columnStop = 0;
- return;
+ M.Library resolveDependency(String relativeUri) {
+ S.Script script = _loadedScript as S.Script;
+ // This isn't really correct: we need to ask the embedder to do the
+ // uri canonicalization for us, but Observatory isn't in a position
+ // to invoke the library tag handler. Handle the most common cases.
+ var targetUri = Uri.parse(_loadedScript.library.uri).resolve(relativeUri);
+ for (M.Library l in script.isolate.libraries) {
+ if (targetUri.toString() == l.uri) {
+ return l;
+ }
+ }
+ if (targetUri.scheme == 'package') {
+ targetUri = "packages/${targetUri.path}";
+ for (M.Library l in script.isolate.libraries) {
+ if (targetUri.toString() == l.uri) {
+ return l;
+ }
+ }
}
- Script script = location.script;
- line = script.tokenToLine(location.tokenPos);
- columnStart = script.tokenToCol(location.tokenPos);
- if ((line == null) || (columnStart == null)) {
- line = 0;
- columnStart = 0;
- columnStop = 0;
- } else {
- columnStart--; // 1-origin -> 0-origin.
+ print("Could not resolve library dependency: $relativeUri");
+ return null;
+ }
- // The method's token position is at the beginning of the method
- // declaration, which may be a return type annotation, metadata, static
- // modifier, etc. Try to scan forward to position this annotation on the
- // function's name instead.
- var lineSource = script.getLine(line).text;
- var betterStart = lineSource.indexOf(decl.name, columnStart);
- if (betterStart != -1) {
- columnStart = betterStart;
+ void addDependencyAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ // TODO(rmacnak): Use a real scanner.
+ var patterns = [
+ new RegExp("import '(.*)'"),
+ new RegExp('import "(.*)"'),
+ new RegExp("export '(.*)'"),
+ new RegExp('export "(.*)"'),
+ ];
+ for (S.ScriptLine line in script.lines) {
+ for (var pattern in patterns) {
+ var match = pattern.firstMatch(line.text);
+ if (match != null) {
+ M.Library target = resolveDependency(match[1]);
+ if (target != null) {
+ var anno = new LibraryAnnotation(_isolate, _instances, _r.queue,
+ target, Uris.inspect(isolate, object: target));
+ anno.line = line.line;
+ anno.columnStart = match.start + 8;
+ anno.columnStop = match.end - 1;
+ _annotations.add(anno);
+ }
+ }
}
- columnStop = columnStart + decl.name.length;
}
}
-}
-
-class ClassDeclarationAnnotation extends DeclarationAnnotation {
- Class klass;
- ClassDeclarationAnnotation(Class cls, String url)
- : klass = cls,
- super(cls, url);
-
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
+ M.Script resolvePart(String relativeUri) {
+ S.Script script = _loadedScript as S.Script;
+ var rootUri = Uri.parse(script.library.uri);
+ if (rootUri.scheme == 'dart') {
+ // The relative paths from dart:* libraries to their parts are not valid.
+ rootUri = new Uri.directory(script.library.uri);
}
- element.title = "class ${klass.name}";
- addLink(element, url);
+ var targetUri = rootUri.resolve(relativeUri);
+ for (M.Script s in script.library.scripts) {
+ if (targetUri.toString() == s.uri) {
+ return s;
+ }
+ }
+ print("Could not resolve part: $relativeUri");
+ return null;
}
-}
-
-class FieldDeclarationAnnotation extends DeclarationAnnotation {
- Field field;
- FieldDeclarationAnnotation(Field fld, String url)
- : field = fld,
- super(fld, url);
-
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
+ void addPartAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ // TODO(rmacnak): Use a real scanner.
+ var patterns = [
+ new RegExp("part '(.*)'"),
+ new RegExp('part "(.*)"'),
+ ];
+ for (S.ScriptLine line in script.lines) {
+ for (var pattern in patterns) {
+ var match = pattern.firstMatch(line.text);
+ if (match != null) {
+ S.Script part = resolvePart(match[1]);
+ if (part != null) {
+ var anno = new PartAnnotation(_isolate, _instances, _r.queue, part,
+ Uris.inspect(isolate, object: part));
+ anno.line = line.line;
+ anno.columnStart = match.start + 6;
+ anno.columnStop = match.end - 1;
+ _annotations.add(anno);
+ }
+ }
+ }
}
- var tooltip = "field ${field.name}";
- element.title = tooltip;
- addLink(element, url);
}
-}
-
-class FunctionDeclarationAnnotation extends DeclarationAnnotation {
- ServiceFunction function;
-
- FunctionDeclarationAnnotation(ServiceFunction func, String url)
- : function = func,
- super(func, url);
- void applyStyleTo(element) {
- if (element == null) {
- return; // TODO(rmacnak): Handling overlapping annotations.
- }
- var tooltip = "method ${function.name}";
- if (function.isOptimizable == false) {
- tooltip += "\nUnoptimizable!";
- }
- if (function.isInlinable == false) {
- tooltip += "\nNot inlinable!";
- }
- if (function.deoptimizations > 0) {
- tooltip += "\nDeoptimized ${function.deoptimizations} times!";
+ void addClassAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ for (var cls in script.library.classes) {
+ if ((cls.location != null) && (cls.location.script == script)) {
+ var a = new ClassDeclarationAnnotation(_isolate, _instances, _r.queue,
+ cls, Uris.inspect(isolate, object: cls));
+ _annotations.add(a);
+ }
}
- element.title = tooltip;
+ }
- if (function.isOptimizable == false ||
- function.isInlinable == false ||
- function.deoptimizations >0) {
- element.style.backgroundColor = "#EEA7A7"; // Low-saturation red.
+ void addFieldAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ for (var field in script.library.variables) {
+ if ((field.location != null) && (field.location.script == script)) {
+ var a = new FieldDeclarationAnnotation(_isolate, _instances, _r.queue,
+ field, Uris.inspect(isolate, object: field));
+ _annotations.add(a);
+ }
+ }
+ for (var cls in script.library.classes) {
+ for (var field in cls.fields) {
+ if ((field.location != null) && (field.location.script == script)) {
+ var a = new FieldDeclarationAnnotation(_isolate, _instances, _r.queue,
+ field, Uris.inspect(isolate, object: field));
+ _annotations.add(a);
+ }
+ }
}
-
- addLink(element, url);
}
-}
-
-class ScriptLineProfile {
- ScriptLineProfile(this.line, this.sampleCount);
- static const kHotThreshold = 0.05; // 5%.
- static const kMediumThreshold = 0.02; // 2%.
-
- final int line;
- final int sampleCount;
-
- int selfTicks = 0;
- int totalTicks = 0;
-
- void process(int exclusive, int inclusive) {
- selfTicks += exclusive;
- totalTicks += inclusive;
+ void addFunctionAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ for (var func in script.library.functions) {
+ if ((func.location != null) &&
+ (func.location.script == script) &&
+ (func.kind != M.FunctionKind.implicitGetter) &&
+ (func.kind != M.FunctionKind.implicitSetter)) {
+ // We annotate a field declaration with the field instead of the
+ // implicit getter or setter.
+ var a = new FunctionDeclarationAnnotation(_isolate, _instances,
+ _r.queue, func, Uris.inspect(isolate, object: func));
+ _annotations.add(a);
+ }
+ }
+ for (var cls in script.library.classes) {
+ S.Script script = _loadedScript as S.Script;
+ for (var func in cls.functions) {
+ if ((func.location != null) &&
+ (func.location.script == script) &&
+ (func.kind != M.FunctionKind.implicitGetter) &&
+ (func.kind != M.FunctionKind.implicitSetter)) {
+ // We annotate a field declaration with the field instead of the
+ // implicit getter or setter.
+ var a = new FunctionDeclarationAnnotation(_isolate, _instances,
+ _r.queue, func, Uris.inspect(isolate, object: func));
+ _annotations.add(a);
+ }
+ }
+ }
}
- String get formattedSelfTicks {
- return Utils.formatPercent(selfTicks, sampleCount);
+ void addCallSiteAnnotations() {
+ for (var callSite in _callSites) {
+ _annotations.add(new CallSiteAnnotation(_isolate, _instances, _r.queue,
+ callSite));
+ }
}
- String get formattedTotalTicks {
- return Utils.formatPercent(totalTicks, sampleCount);
- }
+ void addLocalVariableAnnotations() {
+ S.Script script = _loadedScript as S.Script;
+ // We have local variable information.
+ if (_variables != null) {
+ // For each variable.
+ for (var variable in _variables) {
+ // Find variable usage locations.
+ var locations = script.scanForLocalVariableLocations(
+ variable['name'],
+ variable['_tokenPos'],
+ variable['_endTokenPos']);
- double _percent(bool self) {
- if (sampleCount == 0) {
- return 0.0;
- }
- if (self) {
- return selfTicks / sampleCount;
- } else {
- return totalTicks / sampleCount;
+ // Annotate locations.
+ for (var location in locations) {
+ _annotations.add(new LocalVariableAnnotation(_isolate, _instances,
+ _r.queue, location,
+ variable['value']));
+ }
+ }
}
}
- bool isHot(bool self) => _percent(self) > kHotThreshold;
- bool isMedium(bool self) => _percent(self) > kMediumThreshold;
-}
+ ButtonElement _newRefreshButton() {
+ var button = new ButtonElement();
+ button.classes = const ['refresh'];
+ button.onClick.listen((_) async {
+ button.disabled = true;
+ await _refresh();
+ button.disabled = false;
+ });
+ button.title = 'Refresh coverage';
+ button.text = '↺';
+ return button;
+ }
-/// Box with script source code in it.
-@CustomTag('script-inset')
-class ScriptInsetElement extends ObservatoryElement {
- @published Script script;
- @published int startPos;
- @published int endPos;
+ ButtonElement _newToggleProfileButton() {
+ ButtonElement button = new ButtonElement();
+ button.classes = _includeProfile ? const ['toggle-profile', 'enabled']
+ : const ['toggle-profile'];
+ button.title = 'Toggle CPU profile information';
+ button.onClick.listen((_) async {
+ _includeProfile = !_includeProfile;
+ button.classes.toggle('enabled');
+ button.disabled = true;
+ _refresh();
+ button.disabled = false;
+ });
+ button.text = '🔥';
+ return button;
+ }
- /// Set the height to make the script inset scroll. Otherwise it
- /// will show from startPos to endPos.
- @published String height = null;
+ Element linesTable() {
+ S.Script script = _loadedScript as S.Script;
+ var table = new DivElement();
+ table.classes.add("sourceTable");
- @published int currentPos;
- @published bool inDebuggerContext = false;
- @published ObservableList variables;
+ _refreshButton = _newRefreshButton();
+ _toggleProfileButton = _newToggleProfileButton();
+ table.append(_refreshButton);
+ table.append(_toggleProfileButton);
- @published Element scroller;
- RefreshButtonElement _refreshButton;
- ToggleButtonElement _toggleProfileButton;
+ if (_startLine == null || _endLine == null) {
+ return table;
+ }
- int _currentLine;
- int _currentCol;
- int _startLine;
- int _endLine;
+ var endLine = (_endPos != null
+ ? _loadedScript.tokenToLine(_endPos)
+ : script.lines.length + _loadedScript.lineOffset);
+ var lineNumPad = endLine.toString().length;
- Map<int, List<ServiceMap>> _rangeMap = {};
- Set _callSites = new Set<CallSite>();
- Set _possibleBreakpointLines = new Set<int>();
- Map<int, ScriptLineProfile> _profileMap = {};
+ _annotationsCursor = 0;
+
+ int blankLineCount = 0;
+ for (int i = _startLine; i <= _endLine; i++) {
+ var line = script.getLine(i);
+ if (line.isBlank) {
+ // Try to introduce elipses if there are 4 or more contiguous
+ // blank lines.
+ blankLineCount++;
+ } else {
+ if (blankLineCount > 0) {
+ int firstBlank = i - blankLineCount;
+ int lastBlank = i - 1;
+ if (blankLineCount < 4) {
+ // Too few blank lines for an elipsis.
+ for (int j = firstBlank; j <= lastBlank; j++) {
+ table.append(lineElement(script.getLine(j), lineNumPad));
+ }
+ } else {
+ // Add an elipsis for the skipped region.
+ table.append(lineElement(script.getLine(firstBlank), lineNumPad));
+ table.append(lineElement(null, lineNumPad));
+ table.append(lineElement(script.getLine(lastBlank), lineNumPad));
+ }
+ blankLineCount = 0;
+ }
+ table.append(lineElement(line, lineNumPad));
+ }
+ }
- var annotations = [];
- var annotationsCursor;
+ return table;
+ }
- StreamSubscription _scriptChangeSubscription;
- Future<StreamSubscription> _debugSubscriptionFuture;
- StreamSubscription _scrollSubscription;
+ // Assumes annotations are sorted.
+ Annotation nextAnnotationOnLine(int line) {
+ if (_annotationsCursor >= _annotations.length) return null;
+ var annotation = _annotations[_annotationsCursor];
- bool hasLoadedLibraryDeclarations = false;
- bool _includeProfile = false;
+ // Fast-forward past any annotations before the first line that
+ // we are displaying.
+ while (annotation.line < line) {
+ _annotationsCursor++;
+ if (_annotationsCursor >= _annotations.length) return null;
+ annotation = _annotations[_annotationsCursor];
+ }
- String makeLineId(int line) {
- return 'line-$line';
+ // Next annotation is for a later line, don't advance past it.
+ if (annotation.line != line) return null;
+ _annotationsCursor++;
+ return annotation;
}
- void _scrollToCurrentPos() {
- var line = shadowRoot.getElementById(makeLineId(_currentLine));
- if (line != null) {
- line.scrollIntoView();
+ Element lineElement(S.ScriptLine line, int lineNumPad) {
+ var e = new DivElement();
+ e.classes.add("sourceRow");
+ e.append(lineBreakpointElement(line));
+ e.append(lineNumberElement(line, lineNumPad));
+ if (_includeProfile) {
+ e.append(lineProfileElement(line, false));
+ e.append(lineProfileElement(line, true));
}
+ e.append(lineSourceElement(line));
+ return e;
}
- void attached() {
- super.attached();
- _debugSubscriptionFuture =
- app.vm.listenEventStream(VM.kDebugStream, _onDebugEvent);
- if (scroller != null) {
- _scrollSubscription = scroller.onScroll.listen(_onScroll);
+ Element lineProfileElement(S.ScriptLine line, bool self) {
+ var e = span('');
+ e.classes.add('noCopy');
+ if (self) {
+ e.title = 'Self %';
} else {
- _scrollSubscription = window.onScroll.listen(_onScroll);
+ e.title = 'Total %';
}
- }
- 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();
- _scriptChangeSubscription = null;
+ if (line == null) {
+ e.classes.add('notSourceProfile');
+ e.text = nbsp;
+ return e;
}
- super.detached();
- }
- void _onScroll(event) {
- if (_refreshButton != null) {
- var newTop = _buttonTop(_refreshButton);
- if (_refreshButton.style.top != newTop) {
- _refreshButton.style.top = '${newTop}px';
- }
- }
- if (_toggleProfileButton != null) {
- var newTop = _buttonTop(_toggleProfileButton);
- if (_toggleProfileButton.style.top != newTop) {
- _toggleProfileButton.style.top = '${newTop}px';
- }
+ var ranges = _rangeMap[line.line];
+ if ((ranges == null) || ranges.isEmpty) {
+ e.classes.add('notSourceProfile');
+ e.text = nbsp;
+ return e;
}
- }
- void _onDebugEvent(event) {
- if (script == null) {
- return;
+ ScriptLineProfile lineProfile = _profileMap[line.line];
+ if (lineProfile == null) {
+ e.classes.add('noProfile');
+ e.text = nbsp;
+ return e;
}
- switch (event.kind) {
- case ServiceEvent.kBreakpointAdded:
- case ServiceEvent.kBreakpointResolved:
- case ServiceEvent.kBreakpointRemoved:
- var loc = event.breakpoint.location;
- if (loc.script == script) {
- int line;
- if (loc.tokenPos != null) {
- line = script.tokenToLine(loc.tokenPos);
- } else {
- line = loc.line;
- }
- if ((line >= _startLine) && (line <= _endLine)) {
- _updateTask.queue();
- }
- }
- break;
- default:
- // Ignore.
- break;
+
+ if (self) {
+ e.text = lineProfile.formattedSelfTicks;
+ } else {
+ e.text = lineProfile.formattedTotalTicks;
}
- }
- void currentPosChanged(oldValue) {
- _updateTask.queue();
- _scrollToCurrentPos();
- }
+ if (lineProfile.isHot(self)) {
+ e.classes.add('hotProfile');
+ } else if (lineProfile.isMedium(self)) {
+ e.classes.add('mediumProfile');
+ } else {
+ e.classes.add('coldProfile');
+ }
- void startPosChanged(oldValue) {
- _updateTask.queue();
+ return e;
}
- void endPosChanged(oldValue) {
- _updateTask.queue();
- }
+ Element lineBreakpointElement(S.ScriptLine line) {
+ var e = new DivElement();
+ if (line == null || !_possibleBreakpointLines.contains(line.line)) {
+ e.classes.add('noCopy');
+ e.classes.add("emptyBreakpoint");
+ e.text = nbsp;
+ return e;
+ }
- void scriptChanged(oldValue) {
- _updateTask.queue();
- }
+ e.text = 'B';
+ var busy = false;
+ void update() {
+ e.classes.clear();
+ e.classes.add('noCopy');
+ if (busy) {
+ e.classes.add("busyBreakpoint");
+ } else if (line.breakpoints != null) {
+ bool resolved = false;
+ for (var bpt in line.breakpoints) {
+ if (bpt.resolved) {
+ resolved = true;
+ break;
+ }
+ }
+ if (resolved) {
+ e.classes.add("resolvedBreakpoint");
+ } else {
+ e.classes.add("unresolvedBreakpoint");
+ }
+ } else {
+ e.classes.add("possibleBreakpoint");
+ }
+ }
- void variablesChanged(oldValue) {
- _updateTask.queue();
+ line.changes.listen((_) => update());
+ e.onClick.listen((event) {
+ if (busy) {
+ return;
+ }
+ busy = true;
+ if (line.breakpoints == null) {
+ // No breakpoint. Add it.
+ line.script.isolate.addBreakpoint(line.script, line.line)
+ .catchError((e, st) {
+ if (e is! S.ServerRpcException ||
+ (e as S.ServerRpcException).code !=
+ S.ServerRpcException.kCannotAddBreakpoint) {
+ ObservatoryApplication.app.handleException(e, st);
+ }})
+ .whenComplete(() {
+ busy = false;
+ update();
+ });
+ } else {
+ // Existing breakpoint. Remove it.
+ List pending = [];
+ for (var bpt in line.breakpoints) {
+ pending.add(line.script.isolate.removeBreakpoint(bpt));
+ }
+ Future.wait(pending).then((_) {
+ busy = false;
+ update();
+ });
+ }
+ update();
+ });
+ update();
+ return e;
}
- Element a(String text) => new AnchorElement()..text = text;
- Element span(String text) => new SpanElement()..text = text;
-
- Element hitsCurrent(Element element) {
- element.classes.add('hitsCurrent');
- element.title = "";
- return element;
- }
- Element hitsUnknown(Element element) {
- element.classes.add('hitsNone');
- element.title = "";
- return element;
- }
- Element hitsNotExecuted(Element element) {
- element.classes.add('hitsNotExecuted');
- element.title = "Line did not execute";
- return element;
- }
- Element hitsExecuted(Element element) {
- element.classes.add('hitsExecuted');
- 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 reports = [Isolate.kCallSitesReport,
- Isolate.kPossibleBreakpointsReport];
- if (_includeProfile) {
- reports.add(Isolate.kProfileReport);
+ Element lineNumberElement(S.ScriptLine line, int lineNumPad) {
+ 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);
+ return e;
}
- var sourceReport = await script.isolate.getSourceReport(
- reports,
- script, startPos, endPos);
- _possibleBreakpointLines = getPossibleBreakpointLines(sourceReport, script);
- _rangeMap.clear();
- _callSites.clear();
- _profileMap.clear();
- for (var range in sourceReport['ranges']) {
- int startLine = script.tokenToLine(range['startPos']);
- int endLine = script.tokenToLine(range['endPos']);
- // TODO(turnidge): Track down the root cause of null startLine/endLine.
- if ((startLine != null) && (endLine != null)) {
- for (var line = startLine; line <= endLine; line++) {
- var rangeList = _rangeMap[line];
- if (rangeList == null) {
- _rangeMap[line] = [range];
- } else {
- rangeList.add(range);
- }
- }
- }
- if (_includeProfile && range['profile'] != null) {
- List positions = range['profile']['positions'];
- List exclusiveTicks = range['profile']['exclusiveTicks'];
- List inclusiveTicks = range['profile']['inclusiveTicks'];
- int sampleCount = range['profile']['metadata']['sampleCount'];
- assert(positions.length == exclusiveTicks.length);
- assert(positions.length == inclusiveTicks.length);
- for (int i = 0; i < positions.length; i++) {
- if (positions[i] is String) {
- // String positions are classifying token positions.
- // TODO(johnmccutchan): Add classifier data to UI.
- continue;
- }
- int line = script.tokenToLine(positions[i]);
- ScriptLineProfile lineProfile = _profileMap[line];
- if (lineProfile == null) {
- lineProfile = new ScriptLineProfile(line, sampleCount);
- _profileMap[line] = lineProfile;
- }
- lineProfile.process(exclusiveTicks[i], inclusiveTicks[i]);
- }
- }
+ var ranges = _rangeMap[lineNumber];
+ if ((ranges == null) || ranges.isEmpty) {
+ // This line is not code.
+ hitsUnknown(e);
+ return e;
+ }
+ bool compiled = true;
+ bool hasCallInfo = false;
+ bool executed = false;
+ for (var range in ranges) {
if (range['compiled']) {
- var rangeCallSites = range['callSites'];
- if (rangeCallSites != null) {
- for (var callSiteMap in rangeCallSites) {
- _callSites.add(new CallSite.fromMap(callSiteMap, script));
+ 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;
+ }
+ }
}
}
- }
- }
- }
-
- Task _updateTask;
- Future update() async {
- assert(_updateTask != null);
- if (script == null) {
- // We may have previously had a script.
- if (container != null) {
- container.children.clear();
- }
- return;
- }
- if (!script.loaded) {
- await script.load();
- }
- if (_scriptChangeSubscription == null) {
- _scriptChangeSubscription = script.changes.listen((_) => update());
- }
- await _refreshSourceReport();
-
- computeAnnotations();
-
- var table = linesTable();
- var firstBuild = false;
- if (container == null) {
- // Indirect to avoid deleting the style element.
- container = new DivElement();
- shadowRoot.append(container);
- firstBuild = true;
- }
- container.children.clear();
- container.children.add(table);
- makeCssClassUncopyable(table, "noCopy");
- if (firstBuild) {
- _scrollToCurrentPos();
- }
- }
-
- void computeAnnotations() {
- _startLine = (startPos != null
- ? script.tokenToLine(startPos)
- : 1 + script.lineOffset);
- _currentLine = (currentPos != null
- ? script.tokenToLine(currentPos)
- : null);
- _currentCol = (currentPos != null
- ? (script.tokenToCol(currentPos))
- : null);
- if (_currentCol != null) {
- _currentCol--; // make this 0-based.
- }
-
- _endLine = (endPos != null
- ? script.tokenToLine(endPos)
- : script.lines.length + script.lineOffset);
-
- if (_startLine == null || _endLine == null) {
- return;
- }
-
- annotations.clear();
-
- addCurrentExecutionAnnotation();
- addBreakpointAnnotations();
-
- if (!inDebuggerContext && script.library != null) {
- if (hasLoadedLibraryDeclarations) {
- addLibraryAnnotations();
- addDependencyAnnotations();
- addPartAnnotations();
- addClassAnnotations();
- addFieldAnnotations();
- addFunctionAnnotations();
- addCallSiteAnnotations();
} else {
- loadDeclarationsOfLibrary(script.library).then((_) {
- hasLoadedLibraryDeclarations = true;
- update();
- });
- }
- }
-
- addLocalVariableAnnotations();
-
- annotations.sort();
- }
-
- void addCurrentExecutionAnnotation() {
- if (_currentLine != null) {
- var a = new CurrentExecutionAnnotation();
- a.line = _currentLine;
- a.columnStart = _currentCol;
- var length = script.guessTokenLength(_currentLine, _currentCol);
- if (length == null) {
- length = 1;
+ // If any range isn't compiled, show the line as not compiled.
+ // This is necessary so that nested functions appear to be uncompiled.
+ compiled = false;
}
- a.columnStop = _currentCol + length;
- annotations.add(a);
}
- }
-
- void addBreakpointAnnotations() {
- for (var line = _startLine; line <= _endLine; line++) {
- var bpts = script.getLine(line).breakpoints;
- if (bpts != null) {
- for (var bpt in bpts) {
- if (bpt.location != null) {
- annotations.add(new BreakpointAnnotation(bpt));
- }
- }
- }
+ if (executed) {
+ hitsExecuted(e);
+ } else if (hasCallInfo) {
+ hitsNotExecuted(e);
+ } else if (compiled) {
+ hitsCompiled(e);
+ } else {
+ hitsNotCompiled(e);
}
+ return e;
}
- Future loadDeclarationsOfLibrary(Library lib) {
- return lib.load().then((lib) {
- var loads = [];
- for (var func in lib.functions) {
- loads.add(func.load());
- }
- for (var field in lib.variables) {
- loads.add(field.load());
- }
- for (var cls in lib.classes) {
- loads.add(loadDeclarationsOfClass(cls));
- }
- return Future.wait(loads);
- });
- }
+ Element lineSourceElement(S.ScriptLine line) {
+ var e = new DivElement();
+ e.classes.add("sourceItem");
- Future loadDeclarationsOfClass(Class cls) {
- return cls.load().then((cls) {
- var loads = [];
- for (var func in cls.functions) {
- loads.add(func.load());
- }
- for (var field in cls.fields) {
- loads.add(field.load());
+ if (line != null) {
+ e.classes.add(makeLineClass(line.line));
+ if (line.line == _currentLine) {
+ e.classes.add("currentLine");
}
- return Future.wait(loads);
- });
- }
- String inspectLink(ServiceObject ref) {
- return gotoLink('/inspect', ref);
- }
+ var position = 0;
+ consumeUntil(var stop) {
+ if (stop <= position) {
+ return null; // Empty gap between annotations/boundries.
+ }
+ if (stop > line.text.length) {
+ // Approximated token length can run past the end of the line.
+ stop = line.text.length;
+ }
- void addLibraryAnnotations() {
- for (ScriptLine line in script.lines) {
- // TODO(rmacnak): Use a real scanner.
- var pattern = new RegExp("library ${script.library.name}");
- var match = pattern.firstMatch(line.text);
- if (match != null) {
- var anno = new LibraryAnnotation(script.library,
- inspectLink(script.library));
- anno.line = line.line;
- anno.columnStart = match.start + 8;
- anno.columnStop = match.end;
- annotations.add(anno);
- }
- // TODO(rmacnak): Use a real scanner.
- pattern = new RegExp("part of ${script.library.name}");
- match = pattern.firstMatch(line.text);
- if (match != null) {
- var anno = new LibraryAnnotation(script.library,
- inspectLink(script.library));
- anno.line = line.line;
- anno.columnStart = match.start + 8;
- anno.columnStop = match.end;
- annotations.add(anno);
+ var chunk = line.text.substring(position, stop);
+ var chunkNode = span(chunk);
+ e.append(chunkNode);
+ position = stop;
+ return chunkNode;
}
- }
- }
- Library resolveDependency(String relativeUri) {
- // This isn't really correct: we need to ask the embedder to do the
- // uri canonicalization for us, but Observatory isn't in a position
- // to invoke the library tag handler. Handle the most common cases.
- var targetUri = Uri.parse(script.library.uri).resolve(relativeUri);
- for (Library l in script.isolate.libraries) {
- if (targetUri.toString() == l.uri) {
- return l;
- }
- }
- if (targetUri.scheme == 'package') {
- targetUri = "packages/${targetUri.path}";
- for (Library l in script.isolate.libraries) {
- if (targetUri.toString() == l.uri) {
- return l;
- }
+ // TODO(rmacnak): Tolerate overlapping annotations.
+ var annotation;
+ while ((annotation = nextAnnotationOnLine(line.line)) != null) {
+ consumeUntil(annotation.columnStart);
+ annotation.applyStyleTo(consumeUntil(annotation.columnStop));
}
+ consumeUntil(line.text.length);
}
- Logger.root.info("Could not resolve library dependency: $relativeUri");
- return null;
- }
+ // So blank lines are included when copying script to the clipboard.
+ e.append(span('\n'));
- void addDependencyAnnotations() {
- // TODO(rmacnak): Use a real scanner.
- var patterns = [
- new RegExp("import '(.*)'"),
- new RegExp('import "(.*)"'),
- new RegExp("export '(.*)'"),
- new RegExp('export "(.*)"'),
- ];
- for (ScriptLine line in script.lines) {
- for (var pattern in patterns) {
- var match = pattern.firstMatch(line.text);
- if (match != null) {
- Library target = resolveDependency(match[1]);
- if (target != null) {
- var anno = new LibraryAnnotation(target, inspectLink(target));
- anno.line = line.line;
- anno.columnStart = match.start + 8;
- anno.columnStop = match.end - 1;
- annotations.add(anno);
- }
- }
- }
- }
+ return e;
}
- Script resolvePart(String relativeUri) {
- var rootUri = Uri.parse(script.library.uri);
- if (rootUri.scheme == 'dart') {
- // The relative paths from dart:* libraries to their parts are not valid.
- rootUri = new Uri.directory(script.library.uri);
- }
- var targetUri = rootUri.resolve(relativeUri);
- for (Script s in script.library.scripts) {
- if (targetUri.toString() == s.uri) {
- return s;
+ /// Exclude nodes from being copied, for example the line numbers and
+ /// breakpoint toggles in script insets. Must be called after [root]'s
+ /// children have been added, and only supports one node at a time.
+ static void _makeCssClassUncopyable(Element root, String className) {
+ var noCopyNodes = root.getElementsByClassName(className);
+ for (var node in noCopyNodes) {
+ node.style.setProperty('-moz-user-select', 'none');
+ node.style.setProperty('-khtml-user-select', 'none');
+ node.style.setProperty('-webkit-user-select', 'none');
+ node.style.setProperty('-ms-user-select', 'none');
+ node.style.setProperty('user-select', 'none');
+ }
+ root.onCopy.listen((event) {
+ // Mark the nodes as hidden before the copy happens, then mark them as
+ // visible on the next event loop turn.
+ for (var node in noCopyNodes) {
+ node.style.visibility = 'hidden';
}
- }
- Logger.root.info("Could not resolve part: $relativeUri");
- return null;
- }
-
- void addPartAnnotations() {
- // TODO(rmacnak): Use a real scanner.
- var patterns = [
- new RegExp("part '(.*)'"),
- new RegExp('part "(.*)"'),
- ];
- for (ScriptLine line in script.lines) {
- for (var pattern in patterns) {
- var match = pattern.firstMatch(line.text);
- if (match != null) {
- Script part = resolvePart(match[1]);
- if (part != null) {
- var anno = new PartAnnotation(part, inspectLink(part));
- anno.line = line.line;
- anno.columnStart = match.start + 6;
- anno.columnStop = match.end - 1;
- annotations.add(anno);
- }
+ Timer.run(() {
+ for (var node in noCopyNodes) {
+ node.style.visibility = 'visible';
}
- }
- }
+ });
+ });
}
+}
- void addClassAnnotations() {
- for (var cls in script.library.classes) {
- if ((cls.location != null) && (cls.location.script == script)) {
- var a = new ClassDeclarationAnnotation(cls, inspectLink(cls));
- annotations.add(a);
- }
- }
- }
+const nbsp = "\u00A0";
- void addFieldAnnotations() {
- for (var field in script.library.variables) {
- if ((field.location != null) && (field.location.script == script)) {
- var a = new FieldDeclarationAnnotation(field, inspectLink(field));
- annotations.add(a);
- }
- }
- for (var cls in script.library.classes) {
- for (var field in cls.fields) {
- if ((field.location != null) && (field.location.script == script)) {
- var a = new FieldDeclarationAnnotation(field, inspectLink(field));
- annotations.add(a);
- }
- }
- }
+void addInfoBox(Element content, Function infoBoxGenerator) {
+ var infoBox;
+ var show = false;
+ var originalBackground = content.style.backgroundColor;
+ buildInfoBox() {
+ infoBox = infoBoxGenerator();
+ infoBox.style.position = 'absolute';
+ infoBox.style.padding = '1em';
+ infoBox.style.border = 'solid black 2px';
+ infoBox.style.zIndex = '10';
+ infoBox.style.backgroundColor = 'white';
+ infoBox.style.cursor = 'auto';
+ // Don't inherit pre formating from the script lines.
+ infoBox.style.whiteSpace = 'normal';
+ content.append(infoBox);
}
+ content.onClick.listen((event) {
+ show = !show;
+ if (infoBox == null) buildInfoBox(); // Created lazily on the first click.
+ infoBox.style.display = show ? 'block' : 'none';
+ content.style.backgroundColor = show ? 'white' : originalBackground;
+ });
+
+ // Causes infoBox to be positioned relative to the bottom-left of content.
+ content.style.display = 'inline-block';
+ content.style.cursor = 'pointer';
+}
- void addFunctionAnnotations() {
- for (var func in script.library.functions) {
- if ((func.location != null) &&
- (func.location.script == script) &&
- (func.kind != M.FunctionKind.implicitGetter) &&
- (func.kind != M.FunctionKind.implicitSetter)) {
- // We annotate a field declaration with the field instead of the
- // implicit getter or setter.
- var a = new FunctionDeclarationAnnotation(func, inspectLink(func));
- annotations.add(a);
- }
- }
- for (var cls in script.library.classes) {
- for (var func in cls.functions) {
- if ((func.location != null) &&
- (func.location.script == script) &&
- (func.kind != M.FunctionKind.implicitGetter) &&
- (func.kind != M.FunctionKind.implicitSetter)) {
- // We annotate a field declaration with the field instead of the
- // implicit getter or setter.
- var a = new FunctionDeclarationAnnotation(func, inspectLink(func));
- annotations.add(a);
- }
- }
- }
- }
- void addCallSiteAnnotations() {
- for (var callSite in _callSites) {
- annotations.add(new CallSiteAnnotation(callSite));
- }
- }
+void addLink(Element content, String target) {
+ // Ick, destructive but still compatible with also adding an info box.
+ var a = new AnchorElement(href: target);
+ a.text = content.text;
+ content.text = '';
+ content.append(a);
+}
- void addLocalVariableAnnotations() {
- // We have local variable information.
- if (variables != null) {
- // For each variable.
- for (var variable in variables) {
- // Find variable usage locations.
- var locations = script.scanForLocalVariableLocations(
- variable['name'],
- variable['_tokenPos'],
- variable['_endTokenPos']);
- // Annotate locations.
- for (var location in locations) {
- annotations.add(new LocalVariableAnnotation(location,
- variable['value']));
- }
+abstract class Annotation implements Comparable<Annotation> {
+ M.IsolateRef _isolate;
+ M.InstanceRepository _instances;
+ RenderingQueue queue;
+ int line;
+ int columnStart;
+ int columnStop;
+ int get priority;
+
+ Annotation(this._isolate, this._instances, this.queue);
+
+ void applyStyleTo(element);
+
+ int compareTo(Annotation other) {
+ if (line == other.line) {
+ if (columnStart == other.columnStart) {
+ return priority.compareTo(other.priority);
}
+ return columnStart.compareTo(other.columnStart);
}
+ return line.compareTo(other.line);
}
- int _buttonTop(Element element) {
- if (element == null) {
- return 5;
- }
- const padding = 5;
- // TODO (cbernaschina) check if this is needed.
- const navbarHeight = 40;
- var rect = getBoundingClientRect();
- var buttonHeight = element.clientHeight;
- return min(max(0, navbarHeight - rect.top) + padding,
- rect.height - (buttonHeight + padding));
+ Element table() {
+ var e = new DivElement();
+ e.style.display = "table";
+ e.style.color = "#333";
+ e.style.font = "400 14px 'Montserrat', sans-serif";
+ return e;
}
- RefreshButtonElement _newRefreshButton() {
- var button = new Element.tag('refresh-button');
- button.style.position = 'absolute';
- button.style.display = 'inline-block';
- button.style.top = '${_buttonTop(null)}px';
- button.style.right = '5px';
- button.callback = _refresh;
- button.title = 'Refresh coverage';
- return button;
+ Element row([content]) {
+ var e = new DivElement();
+ e.style.display = "table-row";
+ if (content is String) e.text = content;
+ if (content is Element) e.children.add(content);
+ return e;
}
- ToggleButtonElement _newToggleProfileButton() {
- ToggleButtonElement button = new Element.tag('toggle-button');
- button.style.position = 'absolute';
- button.style.display = 'inline-block';
- button.style.top = '${_buttonTop(null)}px';
- button.style.right = '30px';
- button.title = 'Toggle CPU profile information';
- final String enabledColor = 'black';
- final String disabledColor = 'rgba(0, 0, 0 ,.3)';
- button.callback = (enabled) async {
- _includeProfile = enabled;
- if (button.children.length > 0) {
- var content = button.children[0];
- if (enabled) {
- content.style.color = enabledColor;
- } else {
- content.style.color = disabledColor;
- }
- }
- await update();
- };
- button.children.add(new Element.tag('icon-whatshot'));
- button.children[0].style.color = disabledColor;
- button.enabled = _includeProfile;
- return button;
+ Element cell(content) {
+ var e = new DivElement();
+ e.style.display = "table-cell";
+ e.style.padding = "3px";
+ if (content is String) e.text = content;
+ if (content is Element) e.children.add(content);
+ return e;
}
- Element linesTable() {
- var table = new DivElement();
- table.classes.add("sourceTable");
+ Element serviceRef(object) {
+ return anyRef(_isolate, object, _instances, queue: queue);
+ }
+}
- _refreshButton = _newRefreshButton();
- _toggleProfileButton = _newToggleProfileButton();
- table.append(_refreshButton);
- table.append(_toggleProfileButton);
+class CurrentExecutionAnnotation extends Annotation {
+ int priority = 0; // highest priority.
- if (_startLine == null || _endLine == null) {
- return table;
- }
+ CurrentExecutionAnnotation(M.IsolateRef isolate,
+ M.InstanceRepository instances,
+ RenderingQueue queue)
+ : super(isolate, instances, queue);
- var endLine = (endPos != null
- ? script.tokenToLine(endPos)
- : script.lines.length + script.lineOffset);
- var lineNumPad = endLine.toString().length;
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
+ }
+ element.classes.add("currentCol");
+ element.title = "Current execution";
+ }
+}
- annotationsCursor = 0;
+class BreakpointAnnotation extends Annotation {
+ M.Breakpoint bpt;
+ int priority = 1;
- int blankLineCount = 0;
- for (int i = _startLine; i <= _endLine; i++) {
- var line = script.getLine(i);
- if (line.isBlank) {
- // Try to introduce elipses if there are 4 or more contiguous
- // blank lines.
- blankLineCount++;
- } else {
- if (blankLineCount > 0) {
- int firstBlank = i - blankLineCount;
- int lastBlank = i - 1;
- if (blankLineCount < 4) {
- // Too few blank lines for an elipsis.
- for (int j = firstBlank; j <= lastBlank; j++) {
- table.append(lineElement(script.getLine(j), lineNumPad));
- }
- } else {
- // Add an elipsis for the skipped region.
- table.append(lineElement(script.getLine(firstBlank), lineNumPad));
- table.append(lineElement(null, lineNumPad));
- table.append(lineElement(script.getLine(lastBlank), lineNumPad));
- }
- blankLineCount = 0;
- }
- table.append(lineElement(line, lineNumPad));
+ BreakpointAnnotation(M.IsolateRef isolate, M.InstanceRepository instances,
+ RenderingQueue queue, this.bpt)
+ : super(isolate, instances, queue) {
+ var script = bpt.location.script;
+ var location = bpt.location;
+ if (location.tokenPos != null) {
+ var pos = location.tokenPos;
+ line = script.tokenToLine(pos);
+ columnStart = script.tokenToCol(pos) - 1; // tokenToCol is 1-origin.
+ } else if (location is M.UnresolvedSourceLocation) {
+ line = location.line;
+ columnStart = location.column;
+ if (columnStart == null) {
+ columnStart = 0;
}
}
-
- return table;
+ var length = script.guessTokenLength(line, columnStart);
+ if (length == null) {
+ length = 1;
+ }
+ columnStop = columnStart + length;
}
- // Assumes annotations are sorted.
- Annotation nextAnnotationOnLine(int line) {
- if (annotationsCursor >= annotations.length) return null;
- var annotation = annotations[annotationsCursor];
-
- // Fast-forward past any annotations before the first line that
- // we are displaying.
- while (annotation.line < line) {
- annotationsCursor++;
- if (annotationsCursor >= annotations.length) return null;
- annotation = annotations[annotationsCursor];
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
}
-
- // Next annotation is for a later line, don't advance past it.
- if (annotation.line != line) return null;
- annotationsCursor++;
- return annotation;
+ var script = bpt.location.script;
+ var pos = bpt.location.tokenPos;
+ int line = script.tokenToLine(pos);
+ int column = script.tokenToCol(pos);
+ if (bpt.resolved) {
+ element.classes.add("resolvedBreakAnnotation");
+ } else {
+ element.classes.add("unresolvedBreakAnnotation");
+ }
+ element.title = "Breakpoint ${bpt.number} at ${line}:${column}";
}
+}
- Element lineElement(ScriptLine line, int lineNumPad) {
- var e = new DivElement();
- e.classes.add("sourceRow");
- e.append(lineBreakpointElement(line));
- e.append(lineNumberElement(line, lineNumPad));
- if (_includeProfile) {
- e.append(lineProfileElement(line, false));
- e.append(lineProfileElement(line, true));
+class LibraryAnnotation extends Annotation {
+ S.Library target;
+ String url;
+ int priority = 2;
+
+ LibraryAnnotation(M.IsolateRef isolate, M.InstanceRepository instances,
+ RenderingQueue queue, this.target, this.url)
+ : super(isolate, instances, queue);
+
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
}
- e.append(lineSourceElement(line));
- return e;
+ element.title = "library ${target.uri}";
+ addLink(element, url);
}
+}
- Element lineProfileElement(ScriptLine line, bool self) {
- var e = span('');
- e.classes.add('noCopy');
- if (self) {
- e.title = 'Self %';
- } else {
- e.title = 'Total %';
- }
+class PartAnnotation extends Annotation {
+ S.Script part;
+ String url;
+ int priority = 2;
- if (line == null) {
- e.classes.add('notSourceProfile');
- e.text = nbsp;
- return e;
- }
+ PartAnnotation(M.IsolateRef isolate, M.InstanceRepository instances,
+ RenderingQueue queue, this.part, this.url)
+ : super(isolate, instances, queue);
- var ranges = _rangeMap[line.line];
- if ((ranges == null) || ranges.isEmpty) {
- e.classes.add('notSourceProfile');
- e.text = nbsp;
- return e;
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
}
+ element.title = "script ${part.uri}";
+ addLink(element, url);
+ }
+}
- ScriptLineProfile lineProfile = _profileMap[line.line];
- if (lineProfile == null) {
- e.classes.add('noProfile');
- e.text = nbsp;
- return e;
- }
+class LocalVariableAnnotation extends Annotation {
+ final value;
+ int priority = 2;
- if (self) {
- e.text = lineProfile.formattedSelfTicks;
- } else {
- e.text = lineProfile.formattedTotalTicks;
- }
+ LocalVariableAnnotation(M.IsolateRef isolate, M.InstanceRepository instances,
+ RenderingQueue queue, S.LocalVarLocation location,
+ this.value): super(isolate, instances, queue) {
+ line = location.line;
+ columnStart = location.column;
+ columnStop = location.endColumn;
+ }
- if (lineProfile.isHot(self)) {
- e.classes.add('hotProfile');
- } else if (lineProfile.isMedium(self)) {
- e.classes.add('mediumProfile');
- } else {
- e.classes.add('coldProfile');
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
}
+ element.style.fontWeight = "bold";
+ element.title = "${value.shortName}";
+ }
+}
- return e;
+class CallSiteAnnotation extends Annotation {
+ S.CallSite callSite;
+ int priority = 2;
+
+ CallSiteAnnotation(M.IsolateRef isolate, M.InstanceRepository instances,
+ RenderingQueue queue, this.callSite)
+ : super(isolate, instances, queue) {
+ line = callSite.line;
+ columnStart = callSite.column - 1; // Call site is 1-origin.
+ var tokenLength = callSite.script.guessTokenLength(line, columnStart);
+ if (tokenLength == null) {
+ tokenLength = callSite.name.length; // Approximate.
+ if (callSite.name.startsWith("get:") ||
+ callSite.name.startsWith("set:")) tokenLength -= 4;
+ }
+ columnStop = columnStart + tokenLength;
}
- Element lineBreakpointElement(ScriptLine line) {
- var e = new DivElement();
- if (line == null || !_possibleBreakpointLines.contains(line.line)) {
- e.classes.add('noCopy');
- e.classes.add("emptyBreakpoint");
- e.text = nbsp;
- return e;
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
}
+ element.style.fontWeight = "bold";
+ element.title = "Call site: ${callSite.name}";
- e.text = 'B';
- var busy = false;
- void update() {
- e.classes.clear();
- e.classes.add('noCopy');
- if (busy) {
- e.classes.add("busyBreakpoint");
- } else if (line.breakpoints != null) {
- bool resolved = false;
- for (var bpt in line.breakpoints) {
- if (bpt.resolved) {
- resolved = true;
- break;
- }
- }
- if (resolved) {
- e.classes.add("resolvedBreakpoint");
- } else {
- e.classes.add("unresolvedBreakpoint");
- }
+ addInfoBox(element, () {
+ var details = table();
+ if (callSite.entries.isEmpty) {
+ details.append(row('Call of "${callSite.name}" did not execute'));
} else {
- e.classes.add("possibleBreakpoint");
- }
- }
+ var r = row();
+ r.append(cell("Container"));
+ r.append(cell("Count"));
+ r.append(cell("Target"));
+ details.append(r);
- line.changes.listen((_) => update());
- e.onClick.listen((event) {
- if (busy) {
- return;
- }
- busy = true;
- if (line.breakpoints == null) {
- // No breakpoint. Add it.
- line.script.isolate.addBreakpoint(line.script, line.line)
- .catchError((e, st) {
- if (e is! ServerRpcException ||
- (e as ServerRpcException).code !=
- ServerRpcException.kCannotAddBreakpoint) {
- app.handleException(e, st);
- }})
- .whenComplete(() {
- busy = false;
- update();
- });
- } else {
- // Existing breakpoint. Remove it.
- List pending = [];
- for (var bpt in line.breakpoints) {
- pending.add(line.script.isolate.removeBreakpoint(bpt));
+ for (var entry in callSite.entries) {
+ var r = row();
+ r.append(cell(serviceRef(entry.receiver)));
+ r.append(cell(entry.count.toString()));
+ r.append(cell(serviceRef(entry.target)));
+ details.append(r);
}
- Future.wait(pending).then((_) {
- busy = false;
- update();
- });
}
- update();
+ return details;
});
- update();
- return e;
}
+}
- Element lineNumberElement(ScriptLine line, int lineNumPad) {
- 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);
- return e;
- }
- var ranges = _rangeMap[lineNumber];
- if ((ranges == null) || ranges.isEmpty) {
- // This line is not code.
- hitsUnknown(e);
- return e;
- }
- bool compiled = true;
- bool hasCallInfo = false;
- bool executed = false;
- for (var range in ranges) {
- if (range['compiled']) {
- 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;
- }
- }
- }
- }
- } else {
- // If any range isn't compiled, show the line as not compiled.
- // This is necessary so that nested functions appear to be uncompiled.
- compiled = false;
- }
- }
- if (executed) {
- hitsExecuted(e);
- } else if (hasCallInfo) {
- hitsNotExecuted(e);
- } else if (compiled) {
- hitsCompiled(e);
- } else {
- hitsNotCompiled(e);
+abstract class DeclarationAnnotation extends Annotation {
+ String url;
+ int priority = 2;
+
+ DeclarationAnnotation(M.IsolateRef isolate, M.InstanceRepository instances,
+ RenderingQueue queue, decl, this.url)
+ : super(isolate, instances, queue) {
+ assert(decl.loaded);
+ S.SourceLocation location = decl.location;
+ if (location == null) {
+ line = 0;
+ columnStart = 0;
+ columnStop = 0;
+ return;
}
- return e;
- }
- Element lineSourceElement(ScriptLine line) {
- var e = new DivElement();
- e.classes.add("sourceItem");
+ S.Script script = location.script;
+ line = script.tokenToLine(location.tokenPos);
+ columnStart = script.tokenToCol(location.tokenPos);
+ if ((line == null) || (columnStart == null)) {
+ line = 0;
+ columnStart = 0;
+ columnStop = 0;
+ } else {
+ columnStart--; // 1-origin -> 0-origin.
- if (line != null) {
- if (line.line == _currentLine) {
- e.classes.add("currentLine");
+ // The method's token position is at the beginning of the method
+ // declaration, which may be a return type annotation, metadata, static
+ // modifier, etc. Try to scan forward to position this annotation on the
+ // function's name instead.
+ var lineSource = script.getLine(line).text;
+ var betterStart = lineSource.indexOf(decl.name, columnStart);
+ if (betterStart != -1) {
+ columnStart = betterStart;
}
+ columnStop = columnStart + decl.name.length;
+ }
+ }
+}
- e.id = makeLineId(line.line);
-
- var position = 0;
- consumeUntil(var stop) {
- if (stop <= position) {
- return null; // Empty gap between annotations/boundries.
- }
- if (stop > line.text.length) {
- // Approximated token length can run past the end of the line.
- stop = line.text.length;
- }
+class ClassDeclarationAnnotation extends DeclarationAnnotation {
+ S.Class klass;
- var chunk = line.text.substring(position, stop);
- var chunkNode = span(chunk);
- e.append(chunkNode);
- position = stop;
- return chunkNode;
- }
+ ClassDeclarationAnnotation(M.IsolateRef isolate,
+ M.InstanceRepository instances,
+ RenderingQueue queue, S.Class cls, String url)
+ : klass = cls,
+ super(isolate, instances, queue, cls, url);
- // TODO(rmacnak): Tolerate overlapping annotations.
- var annotation;
- while ((annotation = nextAnnotationOnLine(line.line)) != null) {
- consumeUntil(annotation.columnStart);
- annotation.applyStyleTo(consumeUntil(annotation.columnStop));
- }
- consumeUntil(line.text.length);
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
}
+ element.title = "class ${klass.name}";
+ addLink(element, url);
+ }
+}
- // So blank lines are included when copying script to the clipboard.
- e.append(span('\n'));
+class FieldDeclarationAnnotation extends DeclarationAnnotation {
+ S.Field field;
- return e;
- }
+ FieldDeclarationAnnotation(M.IsolateRef isolate,
+ M.InstanceRepository instances,
+ RenderingQueue queue, S.Field fld, String url)
+ : field = fld,
+ super(isolate, instances, queue, fld, url);
- ScriptInsetElement.created()
- : super.created() {
- _updateTask = new Task(update);
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
+ }
+ var tooltip = "field ${field.name}";
+ element.title = tooltip;
+ addLink(element, url);
}
}
-@CustomTag('refresh-button')
-class RefreshButtonElement extends PolymerElement {
- RefreshButtonElement.created() : super.created();
+class FunctionDeclarationAnnotation extends DeclarationAnnotation {
+ S.ServiceFunction function;
- @published var callback = null;
- bool busy = false;
+ FunctionDeclarationAnnotation(M.IsolateRef isolate,
+ M.InstanceRepository instances,
+ RenderingQueue queue, S.ServiceFunction func,
+ String url)
+ : function = func,
+ super(isolate, instances, queue, func, url);
- Future buttonClick(var event, var b, var c) async {
- if (busy) {
- return;
+ void applyStyleTo(element) {
+ if (element == null) {
+ return; // TODO(rmacnak): Handling overlapping annotations.
+ }
+ var tooltip = "method ${function.name}";
+ if (function.isOptimizable == false) {
+ tooltip += "\nUnoptimizable!";
+ }
+ if (function.isInlinable == false) {
+ tooltip += "\nNot inlinable!";
}
- busy = true;
- if (callback != null) {
- await callback();
+ if (function.deoptimizations > 0) {
+ tooltip += "\nDeoptimized ${function.deoptimizations} times!";
+ }
+ element.title = tooltip;
+
+ if (function.isOptimizable == false ||
+ function.isInlinable == false ||
+ function.deoptimizations >0) {
+ element.style.backgroundColor = "#EEA7A7"; // Low-saturation red.
}
- busy = false;
+
+ addLink(element, url);
}
}
+class ScriptLineProfile {
+ ScriptLineProfile(this.line, this.sampleCount);
+
+ static const kHotThreshold = 0.05; // 5%.
+ static const kMediumThreshold = 0.02; // 2%.
-@CustomTag('toggle-button')
-class ToggleButtonElement extends PolymerElement {
- ToggleButtonElement.created() : super.created();
+ final int line;
+ final int sampleCount;
- @published var callback = null;
- @observable bool enabled = false;
+ int selfTicks = 0;
+ int totalTicks = 0;
- Future buttonClick(var event, var b, var c) async {
- enabled = !enabled;
- if (callback != null) {
- await callback(enabled);
- }
+ void process(int exclusive, int inclusive) {
+ selfTicks += exclusive;
+ totalTicks += inclusive;
+ }
+
+ String get formattedSelfTicks {
+ return Utils.formatPercent(selfTicks, sampleCount);
}
-}
+ String get formattedTotalTicks {
+ return Utils.formatPercent(totalTicks, sampleCount);
+ }
-@CustomTag('source-inset')
-class SourceInsetElement extends PolymerElement {
- SourceInsetElement.created() : super.created();
+ double _percent(bool self) {
+ if (sampleCount == 0) {
+ return 0.0;
+ }
+ if (self) {
+ return selfTicks / sampleCount;
+ } else {
+ return totalTicks / sampleCount;
+ }
+ }
- @published SourceLocation location;
- @published String height = null;
- @published int currentPos;
- @published bool inDebuggerContext = false;
- @published ObservableList variables;
- @published Element scroller;
+ bool isHot(bool self) => _percent(self) > kHotThreshold;
+ bool isMedium(bool self) => _percent(self) > kMediumThreshold;
}
« no previous file with comments | « runtime/observatory/lib/src/elements/observatory_element.dart ('k') | runtime/observatory/lib/src/elements/script_inset.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698