| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library script_inset_element; | 5 library script_inset_element; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:html'; | 8 import 'dart:html'; |
| 9 import 'dart:math'; | 9 import 'package:observatory/app.dart'; |
| 10 import 'observatory_element.dart'; | |
| 11 import 'service_ref.dart'; | |
| 12 import 'package:observatory/models.dart' as M; | 10 import 'package:observatory/models.dart' as M; |
| 13 import 'package:observatory/service.dart'; | 11 import 'package:observatory/service.dart' as S; |
| 12 import 'package:observatory/src/elements/helpers/any_ref.dart'; |
| 13 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| 14 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 15 import 'package:observatory/src/elements/helpers/uris.dart'; |
| 14 import 'package:observatory/utils.dart'; | 16 import 'package:observatory/utils.dart'; |
| 15 import 'package:polymer/polymer.dart'; | 17 |
| 16 import 'package:logging/logging.dart'; | 18 class ScriptInsetElement extends HtmlElement implements Renderable { |
| 17 | 19 static const tag = const Tag<ScriptInsetElement>('script-inset-wrapped'); |
| 18 const nbsp = "\u00A0"; | 20 |
| 19 | 21 RenderingScheduler _r; |
| 20 void addInfoBox(Element content, Function infoBoxGenerator) { | 22 |
| 21 var infoBox; | 23 Stream<RenderedEvent<ScriptInsetElement>> get onRendered => _r.onRendered; |
| 22 var show = false; | 24 |
| 23 var originalBackground = content.style.backgroundColor; | 25 |
| 24 buildInfoBox() { | 26 M.IsolateRef _isolate; |
| 25 infoBox = infoBoxGenerator(); | 27 M.ScriptRef _script; |
| 26 infoBox.style.position = 'absolute'; | 28 M.Script _loadedScript; |
| 27 infoBox.style.padding = '1em'; | 29 M.ScriptRepository _scripts; |
| 28 infoBox.style.border = 'solid black 2px'; | 30 M.InstanceRepository _instances; |
| 29 infoBox.style.zIndex = '10'; | 31 M.EventRepository _events; |
| 30 infoBox.style.backgroundColor = 'white'; | 32 StreamSubscription _subscription; |
| 31 infoBox.style.cursor = 'auto'; | 33 int _startPos; |
| 32 // Don't inherit pre formating from the script lines. | 34 int _endPos; |
| 33 infoBox.style.whiteSpace = 'normal'; | 35 int _currentPos; |
| 34 content.append(infoBox); | 36 bool _inDebuggerContext; |
| 35 } | 37 Iterable _variables; |
| 36 content.onClick.listen((event) { | 38 |
| 37 show = !show; | 39 M.IsolateRef get isolate => _isolate; |
| 38 if (infoBox == null) buildInfoBox(); // Created lazily on the first click. | 40 M.ScriptRef get script => _script; |
| 39 infoBox.style.display = show ? 'block' : 'none'; | 41 |
| 40 content.style.backgroundColor = show ? 'white' : originalBackground; | 42 factory ScriptInsetElement(M.IsolateRef isolate, M.ScriptRef script, |
| 41 }); | 43 M.ScriptRepository scripts, |
| 42 | 44 M.InstanceRepository instances, |
| 43 // Causes infoBox to be positioned relative to the bottom-left of content. | 45 M.EventRepository events, |
| 44 content.style.display = 'inline-block'; | 46 {int startPos, int endPos, int currentPos, |
| 45 content.style.cursor = 'pointer'; | 47 bool inDebuggerContext: false, |
| 46 } | 48 Iterable variables: const [], |
| 47 | 49 RenderingQueue queue}) { |
| 48 | 50 assert(isolate != null); |
| 49 void addLink(Element content, String target) { | 51 assert(script != null); |
| 50 // Ick, destructive but still compatible with also adding an info box. | 52 assert(scripts != null); |
| 51 var a = new AnchorElement(href: target); | 53 assert(instances != null); |
| 52 a.text = content.text; | 54 assert(events != null); |
| 53 content.text = ''; | 55 assert(inDebuggerContext != null); |
| 54 content.append(a); | 56 assert(variables != null); |
| 55 } | 57 ScriptInsetElement e = document.createElement(tag.name); |
| 56 | 58 e._r = new RenderingScheduler(e, queue: queue); |
| 57 | 59 e._isolate = isolate; |
| 58 abstract class Annotation implements Comparable<Annotation> { | 60 e._script = script; |
| 59 int line; | 61 e._scripts = scripts; |
| 60 int columnStart; | 62 e._instances = instances; |
| 61 int columnStop; | 63 e._events = events; |
| 62 int get priority; | 64 e._startPos = startPos; |
| 63 | 65 e._endPos = endPos; |
| 64 void applyStyleTo(element); | 66 e._currentPos = currentPos; |
| 65 | 67 e._inDebuggerContext = inDebuggerContext; |
| 66 int compareTo(Annotation other) { | 68 e._variables = new List.unmodifiable(variables); |
| 67 if (line == other.line) { | |
| 68 if (columnStart == other.columnStart) { | |
| 69 return priority.compareTo(other.priority); | |
| 70 } | |
| 71 return columnStart.compareTo(other.columnStart); | |
| 72 } | |
| 73 return line.compareTo(other.line); | |
| 74 } | |
| 75 | |
| 76 Element table() { | |
| 77 var e = new DivElement(); | |
| 78 e.style.display = "table"; | |
| 79 e.style.color = "#333"; | |
| 80 e.style.font = "400 14px 'Montserrat', sans-serif"; | |
| 81 return e; | 69 return e; |
| 82 } | 70 } |
| 83 | 71 |
| 84 Element row([content]) { | 72 ScriptInsetElement.created() : super.created(); |
| 85 var e = new DivElement(); | 73 |
| 86 e.style.display = "table-row"; | 74 @override |
| 87 if (content is String) e.text = content; | 75 void attached() { |
| 88 if (content is Element) e.children.add(content); | 76 super.attached(); |
| 89 return e; | 77 _r.enable(); |
| 90 } | 78 _subscription = _events.onDebugEvent |
| 91 | 79 .where((e) => (e is M.BreakpointAddedEvent) || |
| 92 Element cell(content) { | 80 (e is M.BreakpointResolvedEvent) || |
| 93 var e = new DivElement(); | 81 (e is M.BreakpointRemovedEvent)) |
| 94 e.style.display = "table-cell"; | 82 .map((e) => e.breakpoint) |
| 95 e.style.padding = "3px"; | 83 .listen((M.Breakpoint b) { |
| 96 if (content is String) e.text = content; | 84 final loc = b.location; |
| 97 if (content is Element) e.children.add(content); | 85 int line; |
| 98 return e; | 86 if (loc.script.id == script.id) { |
| 99 } | 87 if (loc.tokenPos != null) { |
| 100 | 88 line = _loadedScript.tokenToLine(loc.tokenPos); |
| 101 Element serviceRef(object) { | 89 } else { |
| 102 AnyServiceRefElement e = new Element.tag("any-service-ref"); | 90 line = loc.line; |
| 103 e.ref = object; | 91 } |
| 104 return e; | 92 } else { |
| 105 } | 93 line = loc.line; |
| 106 } | 94 } |
| 107 | 95 if ((line == null) || ((line >= _startLine) && (line <= _endLine))) { |
| 108 class CurrentExecutionAnnotation extends Annotation { | 96 _r.dirty(); |
| 109 int priority = 0; // highest priority. | 97 } |
| 110 | 98 }); |
| 111 void applyStyleTo(element) { | 99 _refresh(); |
| 112 if (element == null) { | 100 } |
| 113 return; // TODO(rmacnak): Handling overlapping annotations. | 101 |
| 114 } | 102 @override |
| 115 element.classes.add("currentCol"); | 103 void detached() { |
| 116 element.title = "Current execution"; | 104 super.detached(); |
| 117 } | 105 children = []; |
| 118 } | 106 _r.disable(notify: true); |
| 119 | 107 _subscription.cancel(); |
| 120 class BreakpointAnnotation extends Annotation { | 108 } |
| 121 Breakpoint bpt; | 109 |
| 122 int priority = 1; | 110 void render() { |
| 123 | 111 if (_loadedScript == null) { |
| 124 BreakpointAnnotation(this.bpt) { | 112 children = [new SpanElement()..text = 'Loading...']; |
| 125 var script = bpt.location.script; | |
| 126 var location = bpt.location; | |
| 127 if (location.tokenPos != null) { | |
| 128 var pos = location.tokenPos; | |
| 129 line = script.tokenToLine(pos); | |
| 130 columnStart = script.tokenToCol(pos) - 1; // tokenToCol is 1-origin. | |
| 131 } else if (location is UnresolvedSourceLocation) { | |
| 132 line = location.line; | |
| 133 columnStart = location.column; | |
| 134 if (columnStart == null) { | |
| 135 columnStart = 0; | |
| 136 } | |
| 137 } | |
| 138 var length = script.guessTokenLength(line, columnStart); | |
| 139 if (length == null) { | |
| 140 length = 1; | |
| 141 } | |
| 142 columnStop = columnStart + length; | |
| 143 } | |
| 144 | |
| 145 void applyStyleTo(element) { | |
| 146 if (element == null) { | |
| 147 return; // TODO(rmacnak): Handling overlapping annotations. | |
| 148 } | |
| 149 var script = bpt.location.script; | |
| 150 var pos = bpt.location.tokenPos; | |
| 151 int line = script.tokenToLine(pos); | |
| 152 int column = script.tokenToCol(pos); | |
| 153 if (bpt.resolved) { | |
| 154 element.classes.add("resolvedBreakAnnotation"); | |
| 155 } else { | 113 } else { |
| 156 element.classes.add("unresolvedBreakAnnotation"); | 114 final table = linesTable(); |
| 157 } | 115 var firstBuild = false; |
| 158 element.title = "Breakpoint ${bpt.number} at ${line}:${column}"; | 116 if (container == null) { |
| 159 } | 117 // Indirect to avoid deleting the style element. |
| 160 } | 118 container = new DivElement(); |
| 161 | 119 |
| 162 class LibraryAnnotation extends Annotation { | 120 firstBuild = true; |
| 163 Library target; | 121 } |
| 164 String url; | 122 children = [container]; |
| 165 int priority = 2; | 123 container.children.clear(); |
| 166 | 124 container.children.add(table); |
| 167 LibraryAnnotation(this.target, this.url); | 125 _makeCssClassUncopyable(table, "noCopy"); |
| 168 | 126 if (firstBuild) { |
| 169 void applyStyleTo(element) { | 127 _scrollToCurrentPos(); |
| 170 if (element == null) { | 128 } |
| 171 return; // TODO(rmacnak): Handling overlapping annotations. | 129 } |
| 172 } | 130 } |
| 173 element.title = "library ${target.uri}"; | 131 |
| 174 addLink(element, url); | 132 Future _refresh() async { |
| 175 } | 133 _loadedScript = await _scripts.get(_isolate, _script.id); |
| 176 } | 134 await _refreshSourceReport(); |
| 177 | 135 await _computeAnnotations(); |
| 178 class PartAnnotation extends Annotation { | 136 _r.dirty(); |
| 179 Script part; | 137 } |
| 180 String url; | 138 |
| 181 int priority = 2; | 139 ButtonElement _refreshButton; |
| 182 | 140 ButtonElement _toggleProfileButton; |
| 183 PartAnnotation(this.part, this.url); | |
| 184 | |
| 185 void applyStyleTo(element) { | |
| 186 if (element == null) { | |
| 187 return; // TODO(rmacnak): Handling overlapping annotations. | |
| 188 } | |
| 189 element.title = "script ${part.uri}"; | |
| 190 addLink(element, url); | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 class LocalVariableAnnotation extends Annotation { | |
| 195 final value; | |
| 196 int priority = 2; | |
| 197 | |
| 198 LocalVariableAnnotation(LocalVarLocation location, this.value) { | |
| 199 line = location.line; | |
| 200 columnStart = location.column; | |
| 201 columnStop = location.endColumn; | |
| 202 } | |
| 203 | |
| 204 void applyStyleTo(element) { | |
| 205 if (element == null) { | |
| 206 return; // TODO(rmacnak): Handling overlapping annotations. | |
| 207 } | |
| 208 element.style.fontWeight = "bold"; | |
| 209 element.title = "${value.shortName}"; | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 class CallSiteAnnotation extends Annotation { | |
| 214 CallSite callSite; | |
| 215 int priority = 2; | |
| 216 | |
| 217 CallSiteAnnotation(this.callSite) { | |
| 218 line = callSite.line; | |
| 219 columnStart = callSite.column - 1; // Call site is 1-origin. | |
| 220 var tokenLength = callSite.script.guessTokenLength(line, columnStart); | |
| 221 if (tokenLength == null) { | |
| 222 tokenLength = callSite.name.length; // Approximate. | |
| 223 if (callSite.name.startsWith("get:") || | |
| 224 callSite.name.startsWith("set:")) tokenLength -= 4; | |
| 225 } | |
| 226 columnStop = columnStart + tokenLength; | |
| 227 } | |
| 228 | |
| 229 void applyStyleTo(element) { | |
| 230 if (element == null) { | |
| 231 return; // TODO(rmacnak): Handling overlapping annotations. | |
| 232 } | |
| 233 element.style.fontWeight = "bold"; | |
| 234 element.title = "Call site: ${callSite.name}"; | |
| 235 | |
| 236 addInfoBox(element, () { | |
| 237 var details = table(); | |
| 238 if (callSite.entries.isEmpty) { | |
| 239 details.append(row('Call of "${callSite.name}" did not execute')); | |
| 240 } else { | |
| 241 var r = row(); | |
| 242 r.append(cell("Container")); | |
| 243 r.append(cell("Count")); | |
| 244 r.append(cell("Target")); | |
| 245 details.append(r); | |
| 246 | |
| 247 for (var entry in callSite.entries) { | |
| 248 var r = row(); | |
| 249 r.append(cell(serviceRef(entry.receiver))); | |
| 250 r.append(cell(entry.count.toString())); | |
| 251 r.append(cell(serviceRef(entry.target))); | |
| 252 details.append(r); | |
| 253 } | |
| 254 } | |
| 255 return details; | |
| 256 }); | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 abstract class DeclarationAnnotation extends Annotation { | |
| 261 String url; | |
| 262 int priority = 2; | |
| 263 | |
| 264 DeclarationAnnotation(decl, this.url) { | |
| 265 assert(decl.loaded); | |
| 266 SourceLocation location = decl.location; | |
| 267 if (location == null) { | |
| 268 line = 0; | |
| 269 columnStart = 0; | |
| 270 columnStop = 0; | |
| 271 return; | |
| 272 } | |
| 273 | |
| 274 Script script = location.script; | |
| 275 line = script.tokenToLine(location.tokenPos); | |
| 276 columnStart = script.tokenToCol(location.tokenPos); | |
| 277 if ((line == null) || (columnStart == null)) { | |
| 278 line = 0; | |
| 279 columnStart = 0; | |
| 280 columnStop = 0; | |
| 281 } else { | |
| 282 columnStart--; // 1-origin -> 0-origin. | |
| 283 | |
| 284 // The method's token position is at the beginning of the method | |
| 285 // declaration, which may be a return type annotation, metadata, static | |
| 286 // modifier, etc. Try to scan forward to position this annotation on the | |
| 287 // function's name instead. | |
| 288 var lineSource = script.getLine(line).text; | |
| 289 var betterStart = lineSource.indexOf(decl.name, columnStart); | |
| 290 if (betterStart != -1) { | |
| 291 columnStart = betterStart; | |
| 292 } | |
| 293 columnStop = columnStart + decl.name.length; | |
| 294 } | |
| 295 } | |
| 296 } | |
| 297 | |
| 298 class ClassDeclarationAnnotation extends DeclarationAnnotation { | |
| 299 Class klass; | |
| 300 | |
| 301 ClassDeclarationAnnotation(Class cls, String url) | |
| 302 : klass = cls, | |
| 303 super(cls, url); | |
| 304 | |
| 305 void applyStyleTo(element) { | |
| 306 if (element == null) { | |
| 307 return; // TODO(rmacnak): Handling overlapping annotations. | |
| 308 } | |
| 309 element.title = "class ${klass.name}"; | |
| 310 addLink(element, url); | |
| 311 } | |
| 312 } | |
| 313 | |
| 314 class FieldDeclarationAnnotation extends DeclarationAnnotation { | |
| 315 Field field; | |
| 316 | |
| 317 FieldDeclarationAnnotation(Field fld, String url) | |
| 318 : field = fld, | |
| 319 super(fld, url); | |
| 320 | |
| 321 void applyStyleTo(element) { | |
| 322 if (element == null) { | |
| 323 return; // TODO(rmacnak): Handling overlapping annotations. | |
| 324 } | |
| 325 var tooltip = "field ${field.name}"; | |
| 326 element.title = tooltip; | |
| 327 addLink(element, url); | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 class FunctionDeclarationAnnotation extends DeclarationAnnotation { | |
| 332 ServiceFunction function; | |
| 333 | |
| 334 FunctionDeclarationAnnotation(ServiceFunction func, String url) | |
| 335 : function = func, | |
| 336 super(func, url); | |
| 337 | |
| 338 void applyStyleTo(element) { | |
| 339 if (element == null) { | |
| 340 return; // TODO(rmacnak): Handling overlapping annotations. | |
| 341 } | |
| 342 var tooltip = "method ${function.name}"; | |
| 343 if (function.isOptimizable == false) { | |
| 344 tooltip += "\nUnoptimizable!"; | |
| 345 } | |
| 346 if (function.isInlinable == false) { | |
| 347 tooltip += "\nNot inlinable!"; | |
| 348 } | |
| 349 if (function.deoptimizations > 0) { | |
| 350 tooltip += "\nDeoptimized ${function.deoptimizations} times!"; | |
| 351 } | |
| 352 element.title = tooltip; | |
| 353 | |
| 354 if (function.isOptimizable == false || | |
| 355 function.isInlinable == false || | |
| 356 function.deoptimizations >0) { | |
| 357 element.style.backgroundColor = "#EEA7A7"; // Low-saturation red. | |
| 358 } | |
| 359 | |
| 360 addLink(element, url); | |
| 361 } | |
| 362 } | |
| 363 | |
| 364 class ScriptLineProfile { | |
| 365 ScriptLineProfile(this.line, this.sampleCount); | |
| 366 | |
| 367 static const kHotThreshold = 0.05; // 5%. | |
| 368 static const kMediumThreshold = 0.02; // 2%. | |
| 369 | |
| 370 final int line; | |
| 371 final int sampleCount; | |
| 372 | |
| 373 int selfTicks = 0; | |
| 374 int totalTicks = 0; | |
| 375 | |
| 376 void process(int exclusive, int inclusive) { | |
| 377 selfTicks += exclusive; | |
| 378 totalTicks += inclusive; | |
| 379 } | |
| 380 | |
| 381 String get formattedSelfTicks { | |
| 382 return Utils.formatPercent(selfTicks, sampleCount); | |
| 383 } | |
| 384 | |
| 385 String get formattedTotalTicks { | |
| 386 return Utils.formatPercent(totalTicks, sampleCount); | |
| 387 } | |
| 388 | |
| 389 double _percent(bool self) { | |
| 390 if (sampleCount == 0) { | |
| 391 return 0.0; | |
| 392 } | |
| 393 if (self) { | |
| 394 return selfTicks / sampleCount; | |
| 395 } else { | |
| 396 return totalTicks / sampleCount; | |
| 397 } | |
| 398 } | |
| 399 | |
| 400 bool isHot(bool self) => _percent(self) > kHotThreshold; | |
| 401 bool isMedium(bool self) => _percent(self) > kMediumThreshold; | |
| 402 } | |
| 403 | |
| 404 /// Box with script source code in it. | |
| 405 @CustomTag('script-inset') | |
| 406 class ScriptInsetElement extends ObservatoryElement { | |
| 407 @published Script script; | |
| 408 @published int startPos; | |
| 409 @published int endPos; | |
| 410 | |
| 411 /// Set the height to make the script inset scroll. Otherwise it | |
| 412 /// will show from startPos to endPos. | |
| 413 @published String height = null; | |
| 414 | |
| 415 @published int currentPos; | |
| 416 @published bool inDebuggerContext = false; | |
| 417 @published ObservableList variables; | |
| 418 | |
| 419 @published Element scroller; | |
| 420 RefreshButtonElement _refreshButton; | |
| 421 ToggleButtonElement _toggleProfileButton; | |
| 422 | 141 |
| 423 int _currentLine; | 142 int _currentLine; |
| 424 int _currentCol; | 143 int _currentCol; |
| 425 int _startLine; | 144 int _startLine; |
| 426 int _endLine; | 145 int _endLine; |
| 427 | 146 |
| 428 Map<int, List<ServiceMap>> _rangeMap = {}; | 147 Map<int, List<S.ServiceMap>> _rangeMap = {}; |
| 429 Set _callSites = new Set<CallSite>(); | 148 Set _callSites = new Set<S.CallSite>(); |
| 430 Set _possibleBreakpointLines = new Set<int>(); | 149 Set _possibleBreakpointLines = new Set<int>(); |
| 431 Map<int, ScriptLineProfile> _profileMap = {}; | 150 Map<int, ScriptLineProfile> _profileMap = {}; |
| 432 | 151 |
| 433 var annotations = []; | 152 var _annotations = []; |
| 434 var annotationsCursor; | 153 var _annotationsCursor; |
| 435 | 154 |
| 436 StreamSubscription _scriptChangeSubscription; | |
| 437 Future<StreamSubscription> _debugSubscriptionFuture; | |
| 438 StreamSubscription _scrollSubscription; | |
| 439 | |
| 440 bool hasLoadedLibraryDeclarations = false; | |
| 441 bool _includeProfile = false; | 155 bool _includeProfile = false; |
| 442 | 156 |
| 443 String makeLineId(int line) { | 157 String makeLineClass(int line) { |
| 444 return 'line-$line'; | 158 return 'script-inset-line-$line'; |
| 445 } | 159 } |
| 446 | 160 |
| 447 void _scrollToCurrentPos() { | 161 void _scrollToCurrentPos() { |
| 448 var line = shadowRoot.getElementById(makeLineId(_currentLine)); | 162 var lines = getElementsByClassName(makeLineClass(_currentLine)); |
| 449 if (line != null) { | 163 if (lines.length > 0) { |
| 450 line.scrollIntoView(); | 164 lines[0].scrollIntoView(); |
| 451 } | 165 } |
| 452 } | |
| 453 | |
| 454 void attached() { | |
| 455 super.attached(); | |
| 456 _debugSubscriptionFuture = | |
| 457 app.vm.listenEventStream(VM.kDebugStream, _onDebugEvent); | |
| 458 if (scroller != null) { | |
| 459 _scrollSubscription = scroller.onScroll.listen(_onScroll); | |
| 460 } else { | |
| 461 _scrollSubscription = window.onScroll.listen(_onScroll); | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 void detached() { | |
| 466 cancelFutureSubscription(_debugSubscriptionFuture); | |
| 467 _debugSubscriptionFuture = null; | |
| 468 if (_scrollSubscription != null) { | |
| 469 _scrollSubscription.cancel(); | |
| 470 _scrollSubscription = null; | |
| 471 } | |
| 472 if (_scriptChangeSubscription != null) { | |
| 473 // Don't leak. If only Dart and Javascript exposed weak references... | |
| 474 _scriptChangeSubscription.cancel(); | |
| 475 _scriptChangeSubscription = null; | |
| 476 } | |
| 477 super.detached(); | |
| 478 } | |
| 479 | |
| 480 void _onScroll(event) { | |
| 481 if (_refreshButton != null) { | |
| 482 var newTop = _buttonTop(_refreshButton); | |
| 483 if (_refreshButton.style.top != newTop) { | |
| 484 _refreshButton.style.top = '${newTop}px'; | |
| 485 } | |
| 486 } | |
| 487 if (_toggleProfileButton != null) { | |
| 488 var newTop = _buttonTop(_toggleProfileButton); | |
| 489 if (_toggleProfileButton.style.top != newTop) { | |
| 490 _toggleProfileButton.style.top = '${newTop}px'; | |
| 491 } | |
| 492 } | |
| 493 } | |
| 494 | |
| 495 void _onDebugEvent(event) { | |
| 496 if (script == null) { | |
| 497 return; | |
| 498 } | |
| 499 switch (event.kind) { | |
| 500 case ServiceEvent.kBreakpointAdded: | |
| 501 case ServiceEvent.kBreakpointResolved: | |
| 502 case ServiceEvent.kBreakpointRemoved: | |
| 503 var loc = event.breakpoint.location; | |
| 504 if (loc.script == script) { | |
| 505 int line; | |
| 506 if (loc.tokenPos != null) { | |
| 507 line = script.tokenToLine(loc.tokenPos); | |
| 508 } else { | |
| 509 line = loc.line; | |
| 510 } | |
| 511 if ((line >= _startLine) && (line <= _endLine)) { | |
| 512 _updateTask.queue(); | |
| 513 } | |
| 514 } | |
| 515 break; | |
| 516 default: | |
| 517 // Ignore. | |
| 518 break; | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 void currentPosChanged(oldValue) { | |
| 523 _updateTask.queue(); | |
| 524 _scrollToCurrentPos(); | |
| 525 } | |
| 526 | |
| 527 void startPosChanged(oldValue) { | |
| 528 _updateTask.queue(); | |
| 529 } | |
| 530 | |
| 531 void endPosChanged(oldValue) { | |
| 532 _updateTask.queue(); | |
| 533 } | |
| 534 | |
| 535 void scriptChanged(oldValue) { | |
| 536 _updateTask.queue(); | |
| 537 } | |
| 538 | |
| 539 void variablesChanged(oldValue) { | |
| 540 _updateTask.queue(); | |
| 541 } | 166 } |
| 542 | 167 |
| 543 Element a(String text) => new AnchorElement()..text = text; | 168 Element a(String text) => new AnchorElement()..text = text; |
| 544 Element span(String text) => new SpanElement()..text = text; | 169 Element span(String text) => new SpanElement()..text = text; |
| 545 | 170 |
| 546 Element hitsCurrent(Element element) { | 171 Element hitsCurrent(Element element) { |
| 547 element.classes.add('hitsCurrent'); | 172 element.classes.add('hitsCurrent'); |
| 548 element.title = ""; | 173 element.title = ""; |
| 549 return element; | 174 return element; |
| 550 } | 175 } |
| (...skipping 18 matching lines...) Expand all Loading... |
| 569 return element; | 194 return element; |
| 570 } | 195 } |
| 571 Element hitsNotCompiled(Element element) { | 196 Element hitsNotCompiled(Element element) { |
| 572 element.classes.add('hitsNotCompiled'); | 197 element.classes.add('hitsNotCompiled'); |
| 573 element.title = "Line in uncompiled function"; | 198 element.title = "Line in uncompiled function"; |
| 574 return element; | 199 return element; |
| 575 } | 200 } |
| 576 | 201 |
| 577 Element container; | 202 Element container; |
| 578 | 203 |
| 579 Future _refresh() async { | |
| 580 await update(); | |
| 581 } | |
| 582 | |
| 583 // Build _rangeMap and _callSites from a source report. | 204 // Build _rangeMap and _callSites from a source report. |
| 584 Future _refreshSourceReport() async { | 205 Future _refreshSourceReport() async { |
| 585 var reports = [Isolate.kCallSitesReport, | 206 var reports = [S.Isolate.kCallSitesReport, |
| 586 Isolate.kPossibleBreakpointsReport]; | 207 S.Isolate.kPossibleBreakpointsReport]; |
| 587 if (_includeProfile) { | 208 if (_includeProfile) { |
| 588 reports.add(Isolate.kProfileReport); | 209 reports.add(S.Isolate.kProfileReport); |
| 589 } | 210 } |
| 590 var sourceReport = await script.isolate.getSourceReport( | 211 S.Isolate isolate = _isolate as S.Isolate; |
| 212 var sourceReport = await isolate.getSourceReport( |
| 591 reports, | 213 reports, |
| 592 script, startPos, endPos); | 214 script, _startPos, _endPos); |
| 593 _possibleBreakpointLines = getPossibleBreakpointLines(sourceReport, script); | 215 _possibleBreakpointLines = S.getPossibleBreakpointLines(sourceReport, |
| 216 script); |
| 594 _rangeMap.clear(); | 217 _rangeMap.clear(); |
| 595 _callSites.clear(); | 218 _callSites.clear(); |
| 596 _profileMap.clear(); | 219 _profileMap.clear(); |
| 597 for (var range in sourceReport['ranges']) { | 220 for (var range in sourceReport['ranges']) { |
| 598 int startLine = script.tokenToLine(range['startPos']); | 221 int startLine = _loadedScript.tokenToLine(range['startPos']); |
| 599 int endLine = script.tokenToLine(range['endPos']); | 222 int endLine = _loadedScript.tokenToLine(range['endPos']); |
| 600 // TODO(turnidge): Track down the root cause of null startLine/endLine. | 223 // TODO(turnidge): Track down the root cause of null startLine/endLine. |
| 601 if ((startLine != null) && (endLine != null)) { | 224 if ((startLine != null) && (endLine != null)) { |
| 602 for (var line = startLine; line <= endLine; line++) { | 225 for (var line = startLine; line <= endLine; line++) { |
| 603 var rangeList = _rangeMap[line]; | 226 var rangeList = _rangeMap[line]; |
| 604 if (rangeList == null) { | 227 if (rangeList == null) { |
| 605 _rangeMap[line] = [range]; | 228 _rangeMap[line] = [range]; |
| 606 } else { | 229 } else { |
| 607 rangeList.add(range); | 230 rangeList.add(range); |
| 608 } | 231 } |
| 609 } | 232 } |
| 610 } | 233 } |
| 611 if (_includeProfile && range['profile'] != null) { | 234 if (_includeProfile && range['profile'] != null) { |
| 612 List positions = range['profile']['positions']; | 235 List positions = range['profile']['positions']; |
| 613 List exclusiveTicks = range['profile']['exclusiveTicks']; | 236 List exclusiveTicks = range['profile']['exclusiveTicks']; |
| 614 List inclusiveTicks = range['profile']['inclusiveTicks']; | 237 List inclusiveTicks = range['profile']['inclusiveTicks']; |
| 615 int sampleCount = range['profile']['metadata']['sampleCount']; | 238 int sampleCount = range['profile']['metadata']['sampleCount']; |
| 616 assert(positions.length == exclusiveTicks.length); | 239 assert(positions.length == exclusiveTicks.length); |
| 617 assert(positions.length == inclusiveTicks.length); | 240 assert(positions.length == inclusiveTicks.length); |
| 618 for (int i = 0; i < positions.length; i++) { | 241 for (int i = 0; i < positions.length; i++) { |
| 619 if (positions[i] is String) { | 242 if (positions[i] is String) { |
| 620 // String positions are classifying token positions. | 243 // String positions are classifying token positions. |
| 621 // TODO(johnmccutchan): Add classifier data to UI. | 244 // TODO(johnmccutchan): Add classifier data to UI. |
| 622 continue; | 245 continue; |
| 623 } | 246 } |
| 624 int line = script.tokenToLine(positions[i]); | 247 int line = _loadedScript.tokenToLine(positions[i]); |
| 625 ScriptLineProfile lineProfile = _profileMap[line]; | 248 ScriptLineProfile lineProfile = _profileMap[line]; |
| 626 if (lineProfile == null) { | 249 if (lineProfile == null) { |
| 627 lineProfile = new ScriptLineProfile(line, sampleCount); | 250 lineProfile = new ScriptLineProfile(line, sampleCount); |
| 628 _profileMap[line] = lineProfile; | 251 _profileMap[line] = lineProfile; |
| 629 } | 252 } |
| 630 lineProfile.process(exclusiveTicks[i], inclusiveTicks[i]); | 253 lineProfile.process(exclusiveTicks[i], inclusiveTicks[i]); |
| 631 } | 254 } |
| 632 } | 255 } |
| 633 if (range['compiled']) { | 256 if (range['compiled']) { |
| 634 var rangeCallSites = range['callSites']; | 257 var rangeCallSites = range['callSites']; |
| 635 if (rangeCallSites != null) { | 258 if (rangeCallSites != null) { |
| 636 for (var callSiteMap in rangeCallSites) { | 259 for (var callSiteMap in rangeCallSites) { |
| 637 _callSites.add(new CallSite.fromMap(callSiteMap, script)); | 260 _callSites.add(new S.CallSite.fromMap(callSiteMap, script)); |
| 638 } | 261 } |
| 639 } | 262 } |
| 640 } | 263 } |
| 641 } | 264 } |
| 642 } | 265 } |
| 643 | 266 |
| 644 Task _updateTask; | 267 Future _computeAnnotations() async { |
| 645 Future update() async { | 268 _startLine = (_startPos != null |
| 646 assert(_updateTask != null); | 269 ? _loadedScript.tokenToLine(_startPos) |
| 647 if (script == null) { | 270 : 1 + _loadedScript.lineOffset); |
| 648 // We may have previously had a script. | 271 _currentLine = (_currentPos != null |
| 649 if (container != null) { | 272 ? _loadedScript.tokenToLine(_currentPos) |
| 650 container.children.clear(); | |
| 651 } | |
| 652 return; | |
| 653 } | |
| 654 if (!script.loaded) { | |
| 655 await script.load(); | |
| 656 } | |
| 657 if (_scriptChangeSubscription == null) { | |
| 658 _scriptChangeSubscription = script.changes.listen((_) => update()); | |
| 659 } | |
| 660 await _refreshSourceReport(); | |
| 661 | |
| 662 computeAnnotations(); | |
| 663 | |
| 664 var table = linesTable(); | |
| 665 var firstBuild = false; | |
| 666 if (container == null) { | |
| 667 // Indirect to avoid deleting the style element. | |
| 668 container = new DivElement(); | |
| 669 shadowRoot.append(container); | |
| 670 firstBuild = true; | |
| 671 } | |
| 672 container.children.clear(); | |
| 673 container.children.add(table); | |
| 674 makeCssClassUncopyable(table, "noCopy"); | |
| 675 if (firstBuild) { | |
| 676 _scrollToCurrentPos(); | |
| 677 } | |
| 678 } | |
| 679 | |
| 680 void computeAnnotations() { | |
| 681 _startLine = (startPos != null | |
| 682 ? script.tokenToLine(startPos) | |
| 683 : 1 + script.lineOffset); | |
| 684 _currentLine = (currentPos != null | |
| 685 ? script.tokenToLine(currentPos) | |
| 686 : null); | 273 : null); |
| 687 _currentCol = (currentPos != null | 274 _currentCol = (_currentPos != null |
| 688 ? (script.tokenToCol(currentPos)) | 275 ? (_loadedScript.tokenToCol(_currentPos)) |
| 689 : null); | 276 : null); |
| 690 if (_currentCol != null) { | 277 if (_currentCol != null) { |
| 691 _currentCol--; // make this 0-based. | 278 _currentCol--; // make this 0-based. |
| 692 } | 279 } |
| 693 | 280 |
| 694 _endLine = (endPos != null | 281 S.Script script = _loadedScript as S.Script; |
| 695 ? script.tokenToLine(endPos) | 282 |
| 696 : script.lines.length + script.lineOffset); | 283 _endLine = (_endPos != null |
| 284 ? _loadedScript.tokenToLine(_endPos) |
| 285 : script.lines.length + _loadedScript.lineOffset); |
| 697 | 286 |
| 698 if (_startLine == null || _endLine == null) { | 287 if (_startLine == null || _endLine == null) { |
| 699 return; | 288 return; |
| 700 } | 289 } |
| 701 | 290 |
| 702 annotations.clear(); | 291 _annotations.clear(); |
| 703 | 292 |
| 704 addCurrentExecutionAnnotation(); | 293 addCurrentExecutionAnnotation(); |
| 705 addBreakpointAnnotations(); | 294 addBreakpointAnnotations(); |
| 706 | 295 |
| 707 if (!inDebuggerContext && script.library != null) { | 296 if (!_inDebuggerContext && script.library != null) { |
| 708 if (hasLoadedLibraryDeclarations) { | 297 await loadDeclarationsOfLibrary(script.library); |
| 709 addLibraryAnnotations(); | 298 addLibraryAnnotations(); |
| 710 addDependencyAnnotations(); | 299 addDependencyAnnotations(); |
| 711 addPartAnnotations(); | 300 addPartAnnotations(); |
| 712 addClassAnnotations(); | 301 addClassAnnotations(); |
| 713 addFieldAnnotations(); | 302 addFieldAnnotations(); |
| 714 addFunctionAnnotations(); | 303 addFunctionAnnotations(); |
| 715 addCallSiteAnnotations(); | 304 addCallSiteAnnotations(); |
| 716 } else { | |
| 717 loadDeclarationsOfLibrary(script.library).then((_) { | |
| 718 hasLoadedLibraryDeclarations = true; | |
| 719 update(); | |
| 720 }); | |
| 721 } | |
| 722 } | 305 } |
| 723 | 306 |
| 724 addLocalVariableAnnotations(); | 307 addLocalVariableAnnotations(); |
| 725 | 308 |
| 726 annotations.sort(); | 309 _annotations.sort(); |
| 727 } | 310 } |
| 728 | 311 |
| 729 void addCurrentExecutionAnnotation() { | 312 void addCurrentExecutionAnnotation() { |
| 730 if (_currentLine != null) { | 313 if (_currentLine != null) { |
| 731 var a = new CurrentExecutionAnnotation(); | 314 var a = new CurrentExecutionAnnotation(_isolate, _instances, _r.queue); |
| 732 a.line = _currentLine; | 315 a.line = _currentLine; |
| 733 a.columnStart = _currentCol; | 316 a.columnStart = _currentCol; |
| 317 S.Script script = _loadedScript as S.Script; |
| 734 var length = script.guessTokenLength(_currentLine, _currentCol); | 318 var length = script.guessTokenLength(_currentLine, _currentCol); |
| 735 if (length == null) { | 319 if (length == null) { |
| 736 length = 1; | 320 length = 1; |
| 737 } | 321 } |
| 738 a.columnStop = _currentCol + length; | 322 a.columnStop = _currentCol + length; |
| 739 annotations.add(a); | 323 _annotations.add(a); |
| 740 } | 324 } |
| 741 } | 325 } |
| 742 | 326 |
| 743 void addBreakpointAnnotations() { | 327 void addBreakpointAnnotations() { |
| 328 S.Script script = _loadedScript as S.Script; |
| 744 for (var line = _startLine; line <= _endLine; line++) { | 329 for (var line = _startLine; line <= _endLine; line++) { |
| 745 var bpts = script.getLine(line).breakpoints; | 330 var bpts = script.getLine(line).breakpoints; |
| 746 if (bpts != null) { | 331 if (bpts != null) { |
| 747 for (var bpt in bpts) { | 332 for (var bpt in bpts) { |
| 748 if (bpt.location != null) { | 333 if (bpt.location != null) { |
| 749 annotations.add(new BreakpointAnnotation(bpt)); | 334 _annotations.add(new BreakpointAnnotation(_isolate, _instances, |
| 335 _r.queue, bpt)); |
| 750 } | 336 } |
| 751 } | 337 } |
| 752 } | 338 } |
| 753 } | 339 } |
| 754 } | 340 } |
| 755 | 341 |
| 756 Future loadDeclarationsOfLibrary(Library lib) { | 342 Future loadDeclarationsOfLibrary(S.Library lib) { |
| 757 return lib.load().then((lib) { | 343 return lib.load().then((lib) { |
| 758 var loads = []; | 344 var loads = []; |
| 759 for (var func in lib.functions) { | 345 for (var func in lib.functions) { |
| 760 loads.add(func.load()); | 346 loads.add(func.load()); |
| 761 } | 347 } |
| 762 for (var field in lib.variables) { | 348 for (var field in lib.variables) { |
| 763 loads.add(field.load()); | 349 loads.add(field.load()); |
| 764 } | 350 } |
| 765 for (var cls in lib.classes) { | 351 for (var cls in lib.classes) { |
| 766 loads.add(loadDeclarationsOfClass(cls)); | 352 loads.add(loadDeclarationsOfClass(cls)); |
| 767 } | 353 } |
| 768 return Future.wait(loads); | 354 return Future.wait(loads); |
| 769 }); | 355 }); |
| 770 } | 356 } |
| 771 | 357 |
| 772 Future loadDeclarationsOfClass(Class cls) { | 358 Future loadDeclarationsOfClass(S.Class cls) { |
| 773 return cls.load().then((cls) { | 359 return cls.load().then((cls) { |
| 774 var loads = []; | 360 var loads = []; |
| 775 for (var func in cls.functions) { | 361 for (var func in cls.functions) { |
| 776 loads.add(func.load()); | 362 loads.add(func.load()); |
| 777 } | 363 } |
| 778 for (var field in cls.fields) { | 364 for (var field in cls.fields) { |
| 779 loads.add(field.load()); | 365 loads.add(field.load()); |
| 780 } | 366 } |
| 781 return Future.wait(loads); | 367 return Future.wait(loads); |
| 782 }); | 368 }); |
| 783 } | 369 } |
| 784 | 370 |
| 785 String inspectLink(ServiceObject ref) { | |
| 786 return gotoLink('/inspect', ref); | |
| 787 } | |
| 788 | |
| 789 void addLibraryAnnotations() { | 371 void addLibraryAnnotations() { |
| 790 for (ScriptLine line in script.lines) { | 372 S.Script script = _loadedScript as S.Script; |
| 373 for (S.ScriptLine line in script.lines) { |
| 791 // TODO(rmacnak): Use a real scanner. | 374 // TODO(rmacnak): Use a real scanner. |
| 792 var pattern = new RegExp("library ${script.library.name}"); | 375 var pattern = new RegExp("library ${script.library.name}"); |
| 793 var match = pattern.firstMatch(line.text); | 376 var match = pattern.firstMatch(line.text); |
| 794 if (match != null) { | 377 if (match != null) { |
| 795 var anno = new LibraryAnnotation(script.library, | 378 var anno = new LibraryAnnotation(_isolate, _instances, _r.queue, |
| 796 inspectLink(script.library)); | 379 _loadedScript.library, |
| 380 Uris.inspect(isolate, object: _loadedScript.library)); |
| 797 anno.line = line.line; | 381 anno.line = line.line; |
| 798 anno.columnStart = match.start + 8; | 382 anno.columnStart = match.start + 8; |
| 799 anno.columnStop = match.end; | 383 anno.columnStop = match.end; |
| 800 annotations.add(anno); | 384 _annotations.add(anno); |
| 801 } | 385 } |
| 802 // TODO(rmacnak): Use a real scanner. | 386 // TODO(rmacnak): Use a real scanner. |
| 803 pattern = new RegExp("part of ${script.library.name}"); | 387 pattern = new RegExp("part of ${script.library.name}"); |
| 804 match = pattern.firstMatch(line.text); | 388 match = pattern.firstMatch(line.text); |
| 805 if (match != null) { | 389 if (match != null) { |
| 806 var anno = new LibraryAnnotation(script.library, | 390 var anno = new LibraryAnnotation(_isolate, _instances, _r.queue, |
| 807 inspectLink(script.library)); | 391 _loadedScript.library, |
| 392 Uris.inspect(isolate, object: _loadedScript.library)); |
| 808 anno.line = line.line; | 393 anno.line = line.line; |
| 809 anno.columnStart = match.start + 8; | 394 anno.columnStart = match.start + 8; |
| 810 anno.columnStop = match.end; | 395 anno.columnStop = match.end; |
| 811 annotations.add(anno); | 396 _annotations.add(anno); |
| 812 } | 397 } |
| 813 } | 398 } |
| 814 } | 399 } |
| 815 | 400 |
| 816 Library resolveDependency(String relativeUri) { | 401 M.Library resolveDependency(String relativeUri) { |
| 402 S.Script script = _loadedScript as S.Script; |
| 817 // This isn't really correct: we need to ask the embedder to do the | 403 // This isn't really correct: we need to ask the embedder to do the |
| 818 // uri canonicalization for us, but Observatory isn't in a position | 404 // uri canonicalization for us, but Observatory isn't in a position |
| 819 // to invoke the library tag handler. Handle the most common cases. | 405 // to invoke the library tag handler. Handle the most common cases. |
| 820 var targetUri = Uri.parse(script.library.uri).resolve(relativeUri); | 406 var targetUri = Uri.parse(_loadedScript.library.uri).resolve(relativeUri); |
| 821 for (Library l in script.isolate.libraries) { | 407 for (M.Library l in script.isolate.libraries) { |
| 822 if (targetUri.toString() == l.uri) { | 408 if (targetUri.toString() == l.uri) { |
| 823 return l; | 409 return l; |
| 824 } | 410 } |
| 825 } | 411 } |
| 826 if (targetUri.scheme == 'package') { | 412 if (targetUri.scheme == 'package') { |
| 827 targetUri = "packages/${targetUri.path}"; | 413 targetUri = "packages/${targetUri.path}"; |
| 828 for (Library l in script.isolate.libraries) { | 414 for (M.Library l in script.isolate.libraries) { |
| 829 if (targetUri.toString() == l.uri) { | 415 if (targetUri.toString() == l.uri) { |
| 830 return l; | 416 return l; |
| 831 } | 417 } |
| 832 } | 418 } |
| 833 } | 419 } |
| 834 | 420 |
| 835 Logger.root.info("Could not resolve library dependency: $relativeUri"); | 421 print("Could not resolve library dependency: $relativeUri"); |
| 836 return null; | 422 return null; |
| 837 } | 423 } |
| 838 | 424 |
| 839 void addDependencyAnnotations() { | 425 void addDependencyAnnotations() { |
| 426 S.Script script = _loadedScript as S.Script; |
| 840 // TODO(rmacnak): Use a real scanner. | 427 // TODO(rmacnak): Use a real scanner. |
| 841 var patterns = [ | 428 var patterns = [ |
| 842 new RegExp("import '(.*)'"), | 429 new RegExp("import '(.*)'"), |
| 843 new RegExp('import "(.*)"'), | 430 new RegExp('import "(.*)"'), |
| 844 new RegExp("export '(.*)'"), | 431 new RegExp("export '(.*)'"), |
| 845 new RegExp('export "(.*)"'), | 432 new RegExp('export "(.*)"'), |
| 846 ]; | 433 ]; |
| 847 for (ScriptLine line in script.lines) { | 434 for (S.ScriptLine line in script.lines) { |
| 848 for (var pattern in patterns) { | 435 for (var pattern in patterns) { |
| 849 var match = pattern.firstMatch(line.text); | 436 var match = pattern.firstMatch(line.text); |
| 850 if (match != null) { | 437 if (match != null) { |
| 851 Library target = resolveDependency(match[1]); | 438 M.Library target = resolveDependency(match[1]); |
| 852 if (target != null) { | 439 if (target != null) { |
| 853 var anno = new LibraryAnnotation(target, inspectLink(target)); | 440 var anno = new LibraryAnnotation(_isolate, _instances, _r.queue, |
| 441 target, Uris.inspect(isolate, object: target)); |
| 854 anno.line = line.line; | 442 anno.line = line.line; |
| 855 anno.columnStart = match.start + 8; | 443 anno.columnStart = match.start + 8; |
| 856 anno.columnStop = match.end - 1; | 444 anno.columnStop = match.end - 1; |
| 857 annotations.add(anno); | 445 _annotations.add(anno); |
| 858 } | 446 } |
| 859 } | 447 } |
| 860 } | 448 } |
| 861 } | 449 } |
| 862 } | 450 } |
| 863 | 451 |
| 864 Script resolvePart(String relativeUri) { | 452 M.Script resolvePart(String relativeUri) { |
| 453 S.Script script = _loadedScript as S.Script; |
| 865 var rootUri = Uri.parse(script.library.uri); | 454 var rootUri = Uri.parse(script.library.uri); |
| 866 if (rootUri.scheme == 'dart') { | 455 if (rootUri.scheme == 'dart') { |
| 867 // The relative paths from dart:* libraries to their parts are not valid. | 456 // The relative paths from dart:* libraries to their parts are not valid. |
| 868 rootUri = new Uri.directory(script.library.uri); | 457 rootUri = new Uri.directory(script.library.uri); |
| 869 } | 458 } |
| 870 var targetUri = rootUri.resolve(relativeUri); | 459 var targetUri = rootUri.resolve(relativeUri); |
| 871 for (Script s in script.library.scripts) { | 460 for (M.Script s in script.library.scripts) { |
| 872 if (targetUri.toString() == s.uri) { | 461 if (targetUri.toString() == s.uri) { |
| 873 return s; | 462 return s; |
| 874 } | 463 } |
| 875 } | 464 } |
| 876 Logger.root.info("Could not resolve part: $relativeUri"); | 465 print("Could not resolve part: $relativeUri"); |
| 877 return null; | 466 return null; |
| 878 } | 467 } |
| 879 | 468 |
| 880 void addPartAnnotations() { | 469 void addPartAnnotations() { |
| 470 S.Script script = _loadedScript as S.Script; |
| 881 // TODO(rmacnak): Use a real scanner. | 471 // TODO(rmacnak): Use a real scanner. |
| 882 var patterns = [ | 472 var patterns = [ |
| 883 new RegExp("part '(.*)'"), | 473 new RegExp("part '(.*)'"), |
| 884 new RegExp('part "(.*)"'), | 474 new RegExp('part "(.*)"'), |
| 885 ]; | 475 ]; |
| 886 for (ScriptLine line in script.lines) { | 476 for (S.ScriptLine line in script.lines) { |
| 887 for (var pattern in patterns) { | 477 for (var pattern in patterns) { |
| 888 var match = pattern.firstMatch(line.text); | 478 var match = pattern.firstMatch(line.text); |
| 889 if (match != null) { | 479 if (match != null) { |
| 890 Script part = resolvePart(match[1]); | 480 S.Script part = resolvePart(match[1]); |
| 891 if (part != null) { | 481 if (part != null) { |
| 892 var anno = new PartAnnotation(part, inspectLink(part)); | 482 var anno = new PartAnnotation(_isolate, _instances, _r.queue, part, |
| 483 Uris.inspect(isolate, object: part)); |
| 893 anno.line = line.line; | 484 anno.line = line.line; |
| 894 anno.columnStart = match.start + 6; | 485 anno.columnStart = match.start + 6; |
| 895 anno.columnStop = match.end - 1; | 486 anno.columnStop = match.end - 1; |
| 896 annotations.add(anno); | 487 _annotations.add(anno); |
| 897 } | 488 } |
| 898 } | 489 } |
| 899 } | 490 } |
| 900 } | 491 } |
| 901 } | 492 } |
| 902 | 493 |
| 903 void addClassAnnotations() { | 494 void addClassAnnotations() { |
| 495 S.Script script = _loadedScript as S.Script; |
| 904 for (var cls in script.library.classes) { | 496 for (var cls in script.library.classes) { |
| 905 if ((cls.location != null) && (cls.location.script == script)) { | 497 if ((cls.location != null) && (cls.location.script == script)) { |
| 906 var a = new ClassDeclarationAnnotation(cls, inspectLink(cls)); | 498 var a = new ClassDeclarationAnnotation(_isolate, _instances, _r.queue, |
| 907 annotations.add(a); | 499 cls, Uris.inspect(isolate, object: cls)); |
| 500 _annotations.add(a); |
| 908 } | 501 } |
| 909 } | 502 } |
| 910 } | 503 } |
| 911 | 504 |
| 912 void addFieldAnnotations() { | 505 void addFieldAnnotations() { |
| 506 S.Script script = _loadedScript as S.Script; |
| 913 for (var field in script.library.variables) { | 507 for (var field in script.library.variables) { |
| 914 if ((field.location != null) && (field.location.script == script)) { | 508 if ((field.location != null) && (field.location.script == script)) { |
| 915 var a = new FieldDeclarationAnnotation(field, inspectLink(field)); | 509 var a = new FieldDeclarationAnnotation(_isolate, _instances, _r.queue, |
| 916 annotations.add(a); | 510 field, Uris.inspect(isolate, object: field)); |
| 511 _annotations.add(a); |
| 917 } | 512 } |
| 918 } | 513 } |
| 919 for (var cls in script.library.classes) { | 514 for (var cls in script.library.classes) { |
| 920 for (var field in cls.fields) { | 515 for (var field in cls.fields) { |
| 921 if ((field.location != null) && (field.location.script == script)) { | 516 if ((field.location != null) && (field.location.script == script)) { |
| 922 var a = new FieldDeclarationAnnotation(field, inspectLink(field)); | 517 var a = new FieldDeclarationAnnotation(_isolate, _instances, _r.queue, |
| 923 annotations.add(a); | 518 field, Uris.inspect(isolate, object: field)); |
| 519 _annotations.add(a); |
| 924 } | 520 } |
| 925 } | 521 } |
| 926 } | 522 } |
| 927 } | 523 } |
| 928 | 524 |
| 929 void addFunctionAnnotations() { | 525 void addFunctionAnnotations() { |
| 526 S.Script script = _loadedScript as S.Script; |
| 930 for (var func in script.library.functions) { | 527 for (var func in script.library.functions) { |
| 931 if ((func.location != null) && | 528 if ((func.location != null) && |
| 932 (func.location.script == script) && | 529 (func.location.script == script) && |
| 933 (func.kind != M.FunctionKind.implicitGetter) && | 530 (func.kind != M.FunctionKind.implicitGetter) && |
| 934 (func.kind != M.FunctionKind.implicitSetter)) { | 531 (func.kind != M.FunctionKind.implicitSetter)) { |
| 935 // We annotate a field declaration with the field instead of the | 532 // We annotate a field declaration with the field instead of the |
| 936 // implicit getter or setter. | 533 // implicit getter or setter. |
| 937 var a = new FunctionDeclarationAnnotation(func, inspectLink(func)); | 534 var a = new FunctionDeclarationAnnotation(_isolate, _instances, |
| 938 annotations.add(a); | 535 _r.queue, func, Uris.inspect(isolate, object: func)); |
| 536 _annotations.add(a); |
| 939 } | 537 } |
| 940 } | 538 } |
| 941 for (var cls in script.library.classes) { | 539 for (var cls in script.library.classes) { |
| 540 S.Script script = _loadedScript as S.Script; |
| 942 for (var func in cls.functions) { | 541 for (var func in cls.functions) { |
| 943 if ((func.location != null) && | 542 if ((func.location != null) && |
| 944 (func.location.script == script) && | 543 (func.location.script == script) && |
| 945 (func.kind != M.FunctionKind.implicitGetter) && | 544 (func.kind != M.FunctionKind.implicitGetter) && |
| 946 (func.kind != M.FunctionKind.implicitSetter)) { | 545 (func.kind != M.FunctionKind.implicitSetter)) { |
| 947 // We annotate a field declaration with the field instead of the | 546 // We annotate a field declaration with the field instead of the |
| 948 // implicit getter or setter. | 547 // implicit getter or setter. |
| 949 var a = new FunctionDeclarationAnnotation(func, inspectLink(func)); | 548 var a = new FunctionDeclarationAnnotation(_isolate, _instances, |
| 950 annotations.add(a); | 549 _r.queue, func, Uris.inspect(isolate, object: func)); |
| 550 _annotations.add(a); |
| 951 } | 551 } |
| 952 } | 552 } |
| 953 } | 553 } |
| 954 } | 554 } |
| 955 | 555 |
| 956 void addCallSiteAnnotations() { | 556 void addCallSiteAnnotations() { |
| 957 for (var callSite in _callSites) { | 557 for (var callSite in _callSites) { |
| 958 annotations.add(new CallSiteAnnotation(callSite)); | 558 _annotations.add(new CallSiteAnnotation(_isolate, _instances, _r.queue, |
| 559 callSite)); |
| 959 } | 560 } |
| 960 } | 561 } |
| 961 | 562 |
| 962 void addLocalVariableAnnotations() { | 563 void addLocalVariableAnnotations() { |
| 564 S.Script script = _loadedScript as S.Script; |
| 963 // We have local variable information. | 565 // We have local variable information. |
| 964 if (variables != null) { | 566 if (_variables != null) { |
| 965 // For each variable. | 567 // For each variable. |
| 966 for (var variable in variables) { | 568 for (var variable in _variables) { |
| 967 // Find variable usage locations. | 569 // Find variable usage locations. |
| 968 var locations = script.scanForLocalVariableLocations( | 570 var locations = script.scanForLocalVariableLocations( |
| 969 variable['name'], | 571 variable['name'], |
| 970 variable['_tokenPos'], | 572 variable['_tokenPos'], |
| 971 variable['_endTokenPos']); | 573 variable['_endTokenPos']); |
| 972 | 574 |
| 973 // Annotate locations. | 575 // Annotate locations. |
| 974 for (var location in locations) { | 576 for (var location in locations) { |
| 975 annotations.add(new LocalVariableAnnotation(location, | 577 _annotations.add(new LocalVariableAnnotation(_isolate, _instances, |
| 976 variable['value'])); | 578 _r.queue, location, |
| 977 } | 579 variable['value'])); |
| 978 } | 580 } |
| 979 } | 581 } |
| 980 } | 582 } |
| 981 | 583 } |
| 982 int _buttonTop(Element element) { | 584 |
| 983 if (element == null) { | 585 ButtonElement _newRefreshButton() { |
| 984 return 5; | 586 var button = new ButtonElement(); |
| 985 } | 587 button.classes = const ['refresh']; |
| 986 const padding = 5; | 588 button.onClick.listen((_) async { |
| 987 // TODO (cbernaschina) check if this is needed. | 589 button.disabled = true; |
| 988 const navbarHeight = 40; | 590 await _refresh(); |
| 989 var rect = getBoundingClientRect(); | 591 button.disabled = false; |
| 990 var buttonHeight = element.clientHeight; | 592 }); |
| 991 return min(max(0, navbarHeight - rect.top) + padding, | |
| 992 rect.height - (buttonHeight + padding)); | |
| 993 } | |
| 994 | |
| 995 RefreshButtonElement _newRefreshButton() { | |
| 996 var button = new Element.tag('refresh-button'); | |
| 997 button.style.position = 'absolute'; | |
| 998 button.style.display = 'inline-block'; | |
| 999 button.style.top = '${_buttonTop(null)}px'; | |
| 1000 button.style.right = '5px'; | |
| 1001 button.callback = _refresh; | |
| 1002 button.title = 'Refresh coverage'; | 593 button.title = 'Refresh coverage'; |
| 594 button.text = '↺'; |
| 1003 return button; | 595 return button; |
| 1004 } | 596 } |
| 1005 | 597 |
| 1006 ToggleButtonElement _newToggleProfileButton() { | 598 ButtonElement _newToggleProfileButton() { |
| 1007 ToggleButtonElement button = new Element.tag('toggle-button'); | 599 ButtonElement button = new ButtonElement(); |
| 1008 button.style.position = 'absolute'; | 600 button.classes = _includeProfile ? const ['toggle-profile', 'enabled'] |
| 1009 button.style.display = 'inline-block'; | 601 : const ['toggle-profile']; |
| 1010 button.style.top = '${_buttonTop(null)}px'; | |
| 1011 button.style.right = '30px'; | |
| 1012 button.title = 'Toggle CPU profile information'; | 602 button.title = 'Toggle CPU profile information'; |
| 1013 final String enabledColor = 'black'; | 603 button.onClick.listen((_) async { |
| 1014 final String disabledColor = 'rgba(0, 0, 0 ,.3)'; | 604 _includeProfile = !_includeProfile; |
| 1015 button.callback = (enabled) async { | 605 button.classes.toggle('enabled'); |
| 1016 _includeProfile = enabled; | 606 button.disabled = true; |
| 1017 if (button.children.length > 0) { | 607 _refresh(); |
| 1018 var content = button.children[0]; | 608 button.disabled = false; |
| 1019 if (enabled) { | 609 }); |
| 1020 content.style.color = enabledColor; | 610 button.text = '🔥'; |
| 1021 } else { | |
| 1022 content.style.color = disabledColor; | |
| 1023 } | |
| 1024 } | |
| 1025 await update(); | |
| 1026 }; | |
| 1027 button.children.add(new Element.tag('icon-whatshot')); | |
| 1028 button.children[0].style.color = disabledColor; | |
| 1029 button.enabled = _includeProfile; | |
| 1030 return button; | 611 return button; |
| 1031 } | 612 } |
| 1032 | 613 |
| 1033 Element linesTable() { | 614 Element linesTable() { |
| 615 S.Script script = _loadedScript as S.Script; |
| 1034 var table = new DivElement(); | 616 var table = new DivElement(); |
| 1035 table.classes.add("sourceTable"); | 617 table.classes.add("sourceTable"); |
| 1036 | 618 |
| 1037 _refreshButton = _newRefreshButton(); | 619 _refreshButton = _newRefreshButton(); |
| 1038 _toggleProfileButton = _newToggleProfileButton(); | 620 _toggleProfileButton = _newToggleProfileButton(); |
| 1039 table.append(_refreshButton); | 621 table.append(_refreshButton); |
| 1040 table.append(_toggleProfileButton); | 622 table.append(_toggleProfileButton); |
| 1041 | 623 |
| 1042 if (_startLine == null || _endLine == null) { | 624 if (_startLine == null || _endLine == null) { |
| 1043 return table; | 625 return table; |
| 1044 } | 626 } |
| 1045 | 627 |
| 1046 var endLine = (endPos != null | 628 var endLine = (_endPos != null |
| 1047 ? script.tokenToLine(endPos) | 629 ? _loadedScript.tokenToLine(_endPos) |
| 1048 : script.lines.length + script.lineOffset); | 630 : script.lines.length + _loadedScript.lineOffset); |
| 1049 var lineNumPad = endLine.toString().length; | 631 var lineNumPad = endLine.toString().length; |
| 1050 | 632 |
| 1051 annotationsCursor = 0; | 633 _annotationsCursor = 0; |
| 1052 | 634 |
| 1053 int blankLineCount = 0; | 635 int blankLineCount = 0; |
| 1054 for (int i = _startLine; i <= _endLine; i++) { | 636 for (int i = _startLine; i <= _endLine; i++) { |
| 1055 var line = script.getLine(i); | 637 var line = script.getLine(i); |
| 1056 if (line.isBlank) { | 638 if (line.isBlank) { |
| 1057 // Try to introduce elipses if there are 4 or more contiguous | 639 // Try to introduce elipses if there are 4 or more contiguous |
| 1058 // blank lines. | 640 // blank lines. |
| 1059 blankLineCount++; | 641 blankLineCount++; |
| 1060 } else { | 642 } else { |
| 1061 if (blankLineCount > 0) { | 643 if (blankLineCount > 0) { |
| (...skipping 14 matching lines...) Expand all Loading... |
| 1076 } | 658 } |
| 1077 table.append(lineElement(line, lineNumPad)); | 659 table.append(lineElement(line, lineNumPad)); |
| 1078 } | 660 } |
| 1079 } | 661 } |
| 1080 | 662 |
| 1081 return table; | 663 return table; |
| 1082 } | 664 } |
| 1083 | 665 |
| 1084 // Assumes annotations are sorted. | 666 // Assumes annotations are sorted. |
| 1085 Annotation nextAnnotationOnLine(int line) { | 667 Annotation nextAnnotationOnLine(int line) { |
| 1086 if (annotationsCursor >= annotations.length) return null; | 668 if (_annotationsCursor >= _annotations.length) return null; |
| 1087 var annotation = annotations[annotationsCursor]; | 669 var annotation = _annotations[_annotationsCursor]; |
| 1088 | 670 |
| 1089 // Fast-forward past any annotations before the first line that | 671 // Fast-forward past any annotations before the first line that |
| 1090 // we are displaying. | 672 // we are displaying. |
| 1091 while (annotation.line < line) { | 673 while (annotation.line < line) { |
| 1092 annotationsCursor++; | 674 _annotationsCursor++; |
| 1093 if (annotationsCursor >= annotations.length) return null; | 675 if (_annotationsCursor >= _annotations.length) return null; |
| 1094 annotation = annotations[annotationsCursor]; | 676 annotation = _annotations[_annotationsCursor]; |
| 1095 } | 677 } |
| 1096 | 678 |
| 1097 // Next annotation is for a later line, don't advance past it. | 679 // Next annotation is for a later line, don't advance past it. |
| 1098 if (annotation.line != line) return null; | 680 if (annotation.line != line) return null; |
| 1099 annotationsCursor++; | 681 _annotationsCursor++; |
| 1100 return annotation; | 682 return annotation; |
| 1101 } | 683 } |
| 1102 | 684 |
| 1103 Element lineElement(ScriptLine line, int lineNumPad) { | 685 Element lineElement(S.ScriptLine line, int lineNumPad) { |
| 1104 var e = new DivElement(); | 686 var e = new DivElement(); |
| 1105 e.classes.add("sourceRow"); | 687 e.classes.add("sourceRow"); |
| 1106 e.append(lineBreakpointElement(line)); | 688 e.append(lineBreakpointElement(line)); |
| 1107 e.append(lineNumberElement(line, lineNumPad)); | 689 e.append(lineNumberElement(line, lineNumPad)); |
| 1108 if (_includeProfile) { | 690 if (_includeProfile) { |
| 1109 e.append(lineProfileElement(line, false)); | 691 e.append(lineProfileElement(line, false)); |
| 1110 e.append(lineProfileElement(line, true)); | 692 e.append(lineProfileElement(line, true)); |
| 1111 } | 693 } |
| 1112 e.append(lineSourceElement(line)); | 694 e.append(lineSourceElement(line)); |
| 1113 return e; | 695 return e; |
| 1114 } | 696 } |
| 1115 | 697 |
| 1116 Element lineProfileElement(ScriptLine line, bool self) { | 698 Element lineProfileElement(S.ScriptLine line, bool self) { |
| 1117 var e = span(''); | 699 var e = span(''); |
| 1118 e.classes.add('noCopy'); | 700 e.classes.add('noCopy'); |
| 1119 if (self) { | 701 if (self) { |
| 1120 e.title = 'Self %'; | 702 e.title = 'Self %'; |
| 1121 } else { | 703 } else { |
| 1122 e.title = 'Total %'; | 704 e.title = 'Total %'; |
| 1123 } | 705 } |
| 1124 | 706 |
| 1125 if (line == null) { | 707 if (line == null) { |
| 1126 e.classes.add('notSourceProfile'); | 708 e.classes.add('notSourceProfile'); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 1152 e.classes.add('hotProfile'); | 734 e.classes.add('hotProfile'); |
| 1153 } else if (lineProfile.isMedium(self)) { | 735 } else if (lineProfile.isMedium(self)) { |
| 1154 e.classes.add('mediumProfile'); | 736 e.classes.add('mediumProfile'); |
| 1155 } else { | 737 } else { |
| 1156 e.classes.add('coldProfile'); | 738 e.classes.add('coldProfile'); |
| 1157 } | 739 } |
| 1158 | 740 |
| 1159 return e; | 741 return e; |
| 1160 } | 742 } |
| 1161 | 743 |
| 1162 Element lineBreakpointElement(ScriptLine line) { | 744 Element lineBreakpointElement(S.ScriptLine line) { |
| 1163 var e = new DivElement(); | 745 var e = new DivElement(); |
| 1164 if (line == null || !_possibleBreakpointLines.contains(line.line)) { | 746 if (line == null || !_possibleBreakpointLines.contains(line.line)) { |
| 1165 e.classes.add('noCopy'); | 747 e.classes.add('noCopy'); |
| 1166 e.classes.add("emptyBreakpoint"); | 748 e.classes.add("emptyBreakpoint"); |
| 1167 e.text = nbsp; | 749 e.text = nbsp; |
| 1168 return e; | 750 return e; |
| 1169 } | 751 } |
| 1170 | 752 |
| 1171 e.text = 'B'; | 753 e.text = 'B'; |
| 1172 var busy = false; | 754 var busy = false; |
| (...skipping 23 matching lines...) Expand all Loading... |
| 1196 line.changes.listen((_) => update()); | 778 line.changes.listen((_) => update()); |
| 1197 e.onClick.listen((event) { | 779 e.onClick.listen((event) { |
| 1198 if (busy) { | 780 if (busy) { |
| 1199 return; | 781 return; |
| 1200 } | 782 } |
| 1201 busy = true; | 783 busy = true; |
| 1202 if (line.breakpoints == null) { | 784 if (line.breakpoints == null) { |
| 1203 // No breakpoint. Add it. | 785 // No breakpoint. Add it. |
| 1204 line.script.isolate.addBreakpoint(line.script, line.line) | 786 line.script.isolate.addBreakpoint(line.script, line.line) |
| 1205 .catchError((e, st) { | 787 .catchError((e, st) { |
| 1206 if (e is! ServerRpcException || | 788 if (e is! S.ServerRpcException || |
| 1207 (e as ServerRpcException).code != | 789 (e as S.ServerRpcException).code != |
| 1208 ServerRpcException.kCannotAddBreakpoint) { | 790 S.ServerRpcException.kCannotAddBreakpoint) { |
| 1209 app.handleException(e, st); | 791 ObservatoryApplication.app.handleException(e, st); |
| 1210 }}) | 792 }}) |
| 1211 .whenComplete(() { | 793 .whenComplete(() { |
| 1212 busy = false; | 794 busy = false; |
| 1213 update(); | 795 update(); |
| 1214 }); | 796 }); |
| 1215 } else { | 797 } else { |
| 1216 // Existing breakpoint. Remove it. | 798 // Existing breakpoint. Remove it. |
| 1217 List pending = []; | 799 List pending = []; |
| 1218 for (var bpt in line.breakpoints) { | 800 for (var bpt in line.breakpoints) { |
| 1219 pending.add(line.script.isolate.removeBreakpoint(bpt)); | 801 pending.add(line.script.isolate.removeBreakpoint(bpt)); |
| 1220 } | 802 } |
| 1221 Future.wait(pending).then((_) { | 803 Future.wait(pending).then((_) { |
| 1222 busy = false; | 804 busy = false; |
| 1223 update(); | 805 update(); |
| 1224 }); | 806 }); |
| 1225 } | 807 } |
| 1226 update(); | 808 update(); |
| 1227 }); | 809 }); |
| 1228 update(); | 810 update(); |
| 1229 return e; | 811 return e; |
| 1230 } | 812 } |
| 1231 | 813 |
| 1232 Element lineNumberElement(ScriptLine line, int lineNumPad) { | 814 Element lineNumberElement(S.ScriptLine line, int lineNumPad) { |
| 1233 var lineNumber = line == null ? "..." : line.line; | 815 var lineNumber = line == null ? "..." : line.line; |
| 1234 var e = span("$nbsp${lineNumber.toString().padLeft(lineNumPad,nbsp)}$nbsp"); | 816 var e = span("$nbsp${lineNumber.toString().padLeft(lineNumPad,nbsp)}$nbsp"); |
| 1235 e.classes.add('noCopy'); | 817 e.classes.add('noCopy'); |
| 1236 if (lineNumber == _currentLine) { | 818 if (lineNumber == _currentLine) { |
| 1237 hitsCurrent(e); | 819 hitsCurrent(e); |
| 1238 return e; | 820 return e; |
| 1239 } | 821 } |
| 1240 var ranges = _rangeMap[lineNumber]; | 822 var ranges = _rangeMap[lineNumber]; |
| 1241 if ((ranges == null) || ranges.isEmpty) { | 823 if ((ranges == null) || ranges.isEmpty) { |
| 1242 // This line is not code. | 824 // This line is not code. |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1274 } else if (hasCallInfo) { | 856 } else if (hasCallInfo) { |
| 1275 hitsNotExecuted(e); | 857 hitsNotExecuted(e); |
| 1276 } else if (compiled) { | 858 } else if (compiled) { |
| 1277 hitsCompiled(e); | 859 hitsCompiled(e); |
| 1278 } else { | 860 } else { |
| 1279 hitsNotCompiled(e); | 861 hitsNotCompiled(e); |
| 1280 } | 862 } |
| 1281 return e; | 863 return e; |
| 1282 } | 864 } |
| 1283 | 865 |
| 1284 Element lineSourceElement(ScriptLine line) { | 866 Element lineSourceElement(S.ScriptLine line) { |
| 1285 var e = new DivElement(); | 867 var e = new DivElement(); |
| 1286 e.classes.add("sourceItem"); | 868 e.classes.add("sourceItem"); |
| 1287 | 869 |
| 1288 if (line != null) { | 870 if (line != null) { |
| 871 e.classes.add(makeLineClass(line.line)); |
| 1289 if (line.line == _currentLine) { | 872 if (line.line == _currentLine) { |
| 1290 e.classes.add("currentLine"); | 873 e.classes.add("currentLine"); |
| 1291 } | 874 } |
| 1292 | 875 |
| 1293 e.id = makeLineId(line.line); | |
| 1294 | |
| 1295 var position = 0; | 876 var position = 0; |
| 1296 consumeUntil(var stop) { | 877 consumeUntil(var stop) { |
| 1297 if (stop <= position) { | 878 if (stop <= position) { |
| 1298 return null; // Empty gap between annotations/boundries. | 879 return null; // Empty gap between annotations/boundries. |
| 1299 } | 880 } |
| 1300 if (stop > line.text.length) { | 881 if (stop > line.text.length) { |
| 1301 // Approximated token length can run past the end of the line. | 882 // Approximated token length can run past the end of the line. |
| 1302 stop = line.text.length; | 883 stop = line.text.length; |
| 1303 } | 884 } |
| 1304 | 885 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 1317 } | 898 } |
| 1318 consumeUntil(line.text.length); | 899 consumeUntil(line.text.length); |
| 1319 } | 900 } |
| 1320 | 901 |
| 1321 // So blank lines are included when copying script to the clipboard. | 902 // So blank lines are included when copying script to the clipboard. |
| 1322 e.append(span('\n')); | 903 e.append(span('\n')); |
| 1323 | 904 |
| 1324 return e; | 905 return e; |
| 1325 } | 906 } |
| 1326 | 907 |
| 1327 ScriptInsetElement.created() | 908 /// Exclude nodes from being copied, for example the line numbers and |
| 1328 : super.created() { | 909 /// breakpoint toggles in script insets. Must be called after [root]'s |
| 1329 _updateTask = new Task(update); | 910 /// children have been added, and only supports one node at a time. |
| 1330 } | 911 static void _makeCssClassUncopyable(Element root, String className) { |
| 1331 } | 912 var noCopyNodes = root.getElementsByClassName(className); |
| 1332 | 913 for (var node in noCopyNodes) { |
| 1333 @CustomTag('refresh-button') | 914 node.style.setProperty('-moz-user-select', 'none'); |
| 1334 class RefreshButtonElement extends PolymerElement { | 915 node.style.setProperty('-khtml-user-select', 'none'); |
| 1335 RefreshButtonElement.created() : super.created(); | 916 node.style.setProperty('-webkit-user-select', 'none'); |
| 1336 | 917 node.style.setProperty('-ms-user-select', 'none'); |
| 1337 @published var callback = null; | 918 node.style.setProperty('user-select', 'none'); |
| 1338 bool busy = false; | 919 } |
| 1339 | 920 root.onCopy.listen((event) { |
| 1340 Future buttonClick(var event, var b, var c) async { | 921 // Mark the nodes as hidden before the copy happens, then mark them as |
| 1341 if (busy) { | 922 // visible on the next event loop turn. |
| 923 for (var node in noCopyNodes) { |
| 924 node.style.visibility = 'hidden'; |
| 925 } |
| 926 Timer.run(() { |
| 927 for (var node in noCopyNodes) { |
| 928 node.style.visibility = 'visible'; |
| 929 } |
| 930 }); |
| 931 }); |
| 932 } |
| 933 } |
| 934 |
| 935 const nbsp = "\u00A0"; |
| 936 |
| 937 void addInfoBox(Element content, Function infoBoxGenerator) { |
| 938 var infoBox; |
| 939 var show = false; |
| 940 var originalBackground = content.style.backgroundColor; |
| 941 buildInfoBox() { |
| 942 infoBox = infoBoxGenerator(); |
| 943 infoBox.style.position = 'absolute'; |
| 944 infoBox.style.padding = '1em'; |
| 945 infoBox.style.border = 'solid black 2px'; |
| 946 infoBox.style.zIndex = '10'; |
| 947 infoBox.style.backgroundColor = 'white'; |
| 948 infoBox.style.cursor = 'auto'; |
| 949 // Don't inherit pre formating from the script lines. |
| 950 infoBox.style.whiteSpace = 'normal'; |
| 951 content.append(infoBox); |
| 952 } |
| 953 content.onClick.listen((event) { |
| 954 show = !show; |
| 955 if (infoBox == null) buildInfoBox(); // Created lazily on the first click. |
| 956 infoBox.style.display = show ? 'block' : 'none'; |
| 957 content.style.backgroundColor = show ? 'white' : originalBackground; |
| 958 }); |
| 959 |
| 960 // Causes infoBox to be positioned relative to the bottom-left of content. |
| 961 content.style.display = 'inline-block'; |
| 962 content.style.cursor = 'pointer'; |
| 963 } |
| 964 |
| 965 |
| 966 void addLink(Element content, String target) { |
| 967 // Ick, destructive but still compatible with also adding an info box. |
| 968 var a = new AnchorElement(href: target); |
| 969 a.text = content.text; |
| 970 content.text = ''; |
| 971 content.append(a); |
| 972 } |
| 973 |
| 974 |
| 975 abstract class Annotation implements Comparable<Annotation> { |
| 976 M.IsolateRef _isolate; |
| 977 M.InstanceRepository _instances; |
| 978 RenderingQueue queue; |
| 979 int line; |
| 980 int columnStart; |
| 981 int columnStop; |
| 982 int get priority; |
| 983 |
| 984 Annotation(this._isolate, this._instances, this.queue); |
| 985 |
| 986 void applyStyleTo(element); |
| 987 |
| 988 int compareTo(Annotation other) { |
| 989 if (line == other.line) { |
| 990 if (columnStart == other.columnStart) { |
| 991 return priority.compareTo(other.priority); |
| 992 } |
| 993 return columnStart.compareTo(other.columnStart); |
| 994 } |
| 995 return line.compareTo(other.line); |
| 996 } |
| 997 |
| 998 Element table() { |
| 999 var e = new DivElement(); |
| 1000 e.style.display = "table"; |
| 1001 e.style.color = "#333"; |
| 1002 e.style.font = "400 14px 'Montserrat', sans-serif"; |
| 1003 return e; |
| 1004 } |
| 1005 |
| 1006 Element row([content]) { |
| 1007 var e = new DivElement(); |
| 1008 e.style.display = "table-row"; |
| 1009 if (content is String) e.text = content; |
| 1010 if (content is Element) e.children.add(content); |
| 1011 return e; |
| 1012 } |
| 1013 |
| 1014 Element cell(content) { |
| 1015 var e = new DivElement(); |
| 1016 e.style.display = "table-cell"; |
| 1017 e.style.padding = "3px"; |
| 1018 if (content is String) e.text = content; |
| 1019 if (content is Element) e.children.add(content); |
| 1020 return e; |
| 1021 } |
| 1022 |
| 1023 Element serviceRef(object) { |
| 1024 return anyRef(_isolate, object, _instances, queue: queue); |
| 1025 } |
| 1026 } |
| 1027 |
| 1028 class CurrentExecutionAnnotation extends Annotation { |
| 1029 int priority = 0; // highest priority. |
| 1030 |
| 1031 CurrentExecutionAnnotation(M.IsolateRef isolate, |
| 1032 M.InstanceRepository instances, |
| 1033 RenderingQueue queue) |
| 1034 : super(isolate, instances, queue); |
| 1035 |
| 1036 void applyStyleTo(element) { |
| 1037 if (element == null) { |
| 1038 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1039 } |
| 1040 element.classes.add("currentCol"); |
| 1041 element.title = "Current execution"; |
| 1042 } |
| 1043 } |
| 1044 |
| 1045 class BreakpointAnnotation extends Annotation { |
| 1046 M.Breakpoint bpt; |
| 1047 int priority = 1; |
| 1048 |
| 1049 BreakpointAnnotation(M.IsolateRef isolate, M.InstanceRepository instances, |
| 1050 RenderingQueue queue, this.bpt) |
| 1051 : super(isolate, instances, queue) { |
| 1052 var script = bpt.location.script; |
| 1053 var location = bpt.location; |
| 1054 if (location.tokenPos != null) { |
| 1055 var pos = location.tokenPos; |
| 1056 line = script.tokenToLine(pos); |
| 1057 columnStart = script.tokenToCol(pos) - 1; // tokenToCol is 1-origin. |
| 1058 } else if (location is M.UnresolvedSourceLocation) { |
| 1059 line = location.line; |
| 1060 columnStart = location.column; |
| 1061 if (columnStart == null) { |
| 1062 columnStart = 0; |
| 1063 } |
| 1064 } |
| 1065 var length = script.guessTokenLength(line, columnStart); |
| 1066 if (length == null) { |
| 1067 length = 1; |
| 1068 } |
| 1069 columnStop = columnStart + length; |
| 1070 } |
| 1071 |
| 1072 void applyStyleTo(element) { |
| 1073 if (element == null) { |
| 1074 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1075 } |
| 1076 var script = bpt.location.script; |
| 1077 var pos = bpt.location.tokenPos; |
| 1078 int line = script.tokenToLine(pos); |
| 1079 int column = script.tokenToCol(pos); |
| 1080 if (bpt.resolved) { |
| 1081 element.classes.add("resolvedBreakAnnotation"); |
| 1082 } else { |
| 1083 element.classes.add("unresolvedBreakAnnotation"); |
| 1084 } |
| 1085 element.title = "Breakpoint ${bpt.number} at ${line}:${column}"; |
| 1086 } |
| 1087 } |
| 1088 |
| 1089 class LibraryAnnotation extends Annotation { |
| 1090 S.Library target; |
| 1091 String url; |
| 1092 int priority = 2; |
| 1093 |
| 1094 LibraryAnnotation(M.IsolateRef isolate, M.InstanceRepository instances, |
| 1095 RenderingQueue queue, this.target, this.url) |
| 1096 : super(isolate, instances, queue); |
| 1097 |
| 1098 void applyStyleTo(element) { |
| 1099 if (element == null) { |
| 1100 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1101 } |
| 1102 element.title = "library ${target.uri}"; |
| 1103 addLink(element, url); |
| 1104 } |
| 1105 } |
| 1106 |
| 1107 class PartAnnotation extends Annotation { |
| 1108 S.Script part; |
| 1109 String url; |
| 1110 int priority = 2; |
| 1111 |
| 1112 PartAnnotation(M.IsolateRef isolate, M.InstanceRepository instances, |
| 1113 RenderingQueue queue, this.part, this.url) |
| 1114 : super(isolate, instances, queue); |
| 1115 |
| 1116 void applyStyleTo(element) { |
| 1117 if (element == null) { |
| 1118 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1119 } |
| 1120 element.title = "script ${part.uri}"; |
| 1121 addLink(element, url); |
| 1122 } |
| 1123 } |
| 1124 |
| 1125 class LocalVariableAnnotation extends Annotation { |
| 1126 final value; |
| 1127 int priority = 2; |
| 1128 |
| 1129 LocalVariableAnnotation(M.IsolateRef isolate, M.InstanceRepository instances, |
| 1130 RenderingQueue queue, S.LocalVarLocation location, |
| 1131 this.value): super(isolate, instances, queue) { |
| 1132 line = location.line; |
| 1133 columnStart = location.column; |
| 1134 columnStop = location.endColumn; |
| 1135 } |
| 1136 |
| 1137 void applyStyleTo(element) { |
| 1138 if (element == null) { |
| 1139 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1140 } |
| 1141 element.style.fontWeight = "bold"; |
| 1142 element.title = "${value.shortName}"; |
| 1143 } |
| 1144 } |
| 1145 |
| 1146 class CallSiteAnnotation extends Annotation { |
| 1147 S.CallSite callSite; |
| 1148 int priority = 2; |
| 1149 |
| 1150 CallSiteAnnotation(M.IsolateRef isolate, M.InstanceRepository instances, |
| 1151 RenderingQueue queue, this.callSite) |
| 1152 : super(isolate, instances, queue) { |
| 1153 line = callSite.line; |
| 1154 columnStart = callSite.column - 1; // Call site is 1-origin. |
| 1155 var tokenLength = callSite.script.guessTokenLength(line, columnStart); |
| 1156 if (tokenLength == null) { |
| 1157 tokenLength = callSite.name.length; // Approximate. |
| 1158 if (callSite.name.startsWith("get:") || |
| 1159 callSite.name.startsWith("set:")) tokenLength -= 4; |
| 1160 } |
| 1161 columnStop = columnStart + tokenLength; |
| 1162 } |
| 1163 |
| 1164 void applyStyleTo(element) { |
| 1165 if (element == null) { |
| 1166 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1167 } |
| 1168 element.style.fontWeight = "bold"; |
| 1169 element.title = "Call site: ${callSite.name}"; |
| 1170 |
| 1171 addInfoBox(element, () { |
| 1172 var details = table(); |
| 1173 if (callSite.entries.isEmpty) { |
| 1174 details.append(row('Call of "${callSite.name}" did not execute')); |
| 1175 } else { |
| 1176 var r = row(); |
| 1177 r.append(cell("Container")); |
| 1178 r.append(cell("Count")); |
| 1179 r.append(cell("Target")); |
| 1180 details.append(r); |
| 1181 |
| 1182 for (var entry in callSite.entries) { |
| 1183 var r = row(); |
| 1184 r.append(cell(serviceRef(entry.receiver))); |
| 1185 r.append(cell(entry.count.toString())); |
| 1186 r.append(cell(serviceRef(entry.target))); |
| 1187 details.append(r); |
| 1188 } |
| 1189 } |
| 1190 return details; |
| 1191 }); |
| 1192 } |
| 1193 } |
| 1194 |
| 1195 abstract class DeclarationAnnotation extends Annotation { |
| 1196 String url; |
| 1197 int priority = 2; |
| 1198 |
| 1199 DeclarationAnnotation(M.IsolateRef isolate, M.InstanceRepository instances, |
| 1200 RenderingQueue queue, decl, this.url) |
| 1201 : super(isolate, instances, queue) { |
| 1202 assert(decl.loaded); |
| 1203 S.SourceLocation location = decl.location; |
| 1204 if (location == null) { |
| 1205 line = 0; |
| 1206 columnStart = 0; |
| 1207 columnStop = 0; |
| 1342 return; | 1208 return; |
| 1343 } | 1209 } |
| 1344 busy = true; | 1210 |
| 1345 if (callback != null) { | 1211 S.Script script = location.script; |
| 1346 await callback(); | 1212 line = script.tokenToLine(location.tokenPos); |
| 1347 } | 1213 columnStart = script.tokenToCol(location.tokenPos); |
| 1348 busy = false; | 1214 if ((line == null) || (columnStart == null)) { |
| 1349 } | 1215 line = 0; |
| 1350 } | 1216 columnStart = 0; |
| 1351 | 1217 columnStop = 0; |
| 1352 | 1218 } else { |
| 1353 @CustomTag('toggle-button') | 1219 columnStart--; // 1-origin -> 0-origin. |
| 1354 class ToggleButtonElement extends PolymerElement { | 1220 |
| 1355 ToggleButtonElement.created() : super.created(); | 1221 // The method's token position is at the beginning of the method |
| 1356 | 1222 // declaration, which may be a return type annotation, metadata, static |
| 1357 @published var callback = null; | 1223 // modifier, etc. Try to scan forward to position this annotation on the |
| 1358 @observable bool enabled = false; | 1224 // function's name instead. |
| 1359 | 1225 var lineSource = script.getLine(line).text; |
| 1360 Future buttonClick(var event, var b, var c) async { | 1226 var betterStart = lineSource.indexOf(decl.name, columnStart); |
| 1361 enabled = !enabled; | 1227 if (betterStart != -1) { |
| 1362 if (callback != null) { | 1228 columnStart = betterStart; |
| 1363 await callback(enabled); | 1229 } |
| 1364 } | 1230 columnStop = columnStart + decl.name.length; |
| 1365 } | 1231 } |
| 1366 } | 1232 } |
| 1367 | 1233 } |
| 1368 | 1234 |
| 1369 @CustomTag('source-inset') | 1235 class ClassDeclarationAnnotation extends DeclarationAnnotation { |
| 1370 class SourceInsetElement extends PolymerElement { | 1236 S.Class klass; |
| 1371 SourceInsetElement.created() : super.created(); | 1237 |
| 1372 | 1238 ClassDeclarationAnnotation(M.IsolateRef isolate, |
| 1373 @published SourceLocation location; | 1239 M.InstanceRepository instances, |
| 1374 @published String height = null; | 1240 RenderingQueue queue, S.Class cls, String url) |
| 1375 @published int currentPos; | 1241 : klass = cls, |
| 1376 @published bool inDebuggerContext = false; | 1242 super(isolate, instances, queue, cls, url); |
| 1377 @published ObservableList variables; | 1243 |
| 1378 @published Element scroller; | 1244 void applyStyleTo(element) { |
| 1379 } | 1245 if (element == null) { |
| 1246 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1247 } |
| 1248 element.title = "class ${klass.name}"; |
| 1249 addLink(element, url); |
| 1250 } |
| 1251 } |
| 1252 |
| 1253 class FieldDeclarationAnnotation extends DeclarationAnnotation { |
| 1254 S.Field field; |
| 1255 |
| 1256 FieldDeclarationAnnotation(M.IsolateRef isolate, |
| 1257 M.InstanceRepository instances, |
| 1258 RenderingQueue queue, S.Field fld, String url) |
| 1259 : field = fld, |
| 1260 super(isolate, instances, queue, fld, url); |
| 1261 |
| 1262 void applyStyleTo(element) { |
| 1263 if (element == null) { |
| 1264 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1265 } |
| 1266 var tooltip = "field ${field.name}"; |
| 1267 element.title = tooltip; |
| 1268 addLink(element, url); |
| 1269 } |
| 1270 } |
| 1271 |
| 1272 class FunctionDeclarationAnnotation extends DeclarationAnnotation { |
| 1273 S.ServiceFunction function; |
| 1274 |
| 1275 FunctionDeclarationAnnotation(M.IsolateRef isolate, |
| 1276 M.InstanceRepository instances, |
| 1277 RenderingQueue queue, S.ServiceFunction func, |
| 1278 String url) |
| 1279 : function = func, |
| 1280 super(isolate, instances, queue, func, url); |
| 1281 |
| 1282 void applyStyleTo(element) { |
| 1283 if (element == null) { |
| 1284 return; // TODO(rmacnak): Handling overlapping annotations. |
| 1285 } |
| 1286 var tooltip = "method ${function.name}"; |
| 1287 if (function.isOptimizable == false) { |
| 1288 tooltip += "\nUnoptimizable!"; |
| 1289 } |
| 1290 if (function.isInlinable == false) { |
| 1291 tooltip += "\nNot inlinable!"; |
| 1292 } |
| 1293 if (function.deoptimizations > 0) { |
| 1294 tooltip += "\nDeoptimized ${function.deoptimizations} times!"; |
| 1295 } |
| 1296 element.title = tooltip; |
| 1297 |
| 1298 if (function.isOptimizable == false || |
| 1299 function.isInlinable == false || |
| 1300 function.deoptimizations >0) { |
| 1301 element.style.backgroundColor = "#EEA7A7"; // Low-saturation red. |
| 1302 } |
| 1303 |
| 1304 addLink(element, url); |
| 1305 } |
| 1306 } |
| 1307 |
| 1308 class ScriptLineProfile { |
| 1309 ScriptLineProfile(this.line, this.sampleCount); |
| 1310 |
| 1311 static const kHotThreshold = 0.05; // 5%. |
| 1312 static const kMediumThreshold = 0.02; // 2%. |
| 1313 |
| 1314 final int line; |
| 1315 final int sampleCount; |
| 1316 |
| 1317 int selfTicks = 0; |
| 1318 int totalTicks = 0; |
| 1319 |
| 1320 void process(int exclusive, int inclusive) { |
| 1321 selfTicks += exclusive; |
| 1322 totalTicks += inclusive; |
| 1323 } |
| 1324 |
| 1325 String get formattedSelfTicks { |
| 1326 return Utils.formatPercent(selfTicks, sampleCount); |
| 1327 } |
| 1328 |
| 1329 String get formattedTotalTicks { |
| 1330 return Utils.formatPercent(totalTicks, sampleCount); |
| 1331 } |
| 1332 |
| 1333 double _percent(bool self) { |
| 1334 if (sampleCount == 0) { |
| 1335 return 0.0; |
| 1336 } |
| 1337 if (self) { |
| 1338 return selfTicks / sampleCount; |
| 1339 } else { |
| 1340 return totalTicks / sampleCount; |
| 1341 } |
| 1342 } |
| 1343 |
| 1344 bool isHot(bool self) => _percent(self) > kHotThreshold; |
| 1345 bool isMedium(bool self) => _percent(self) > kMediumThreshold; |
| 1346 } |
| OLD | NEW |