| 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 'dart:math'; |
| 10 import 'observatory_element.dart'; | 10 import 'observatory_element.dart'; |
| (...skipping 343 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 354 if (function.isOptimizable == false || | 354 if (function.isOptimizable == false || |
| 355 function.isInlinable == false || | 355 function.isInlinable == false || |
| 356 function.deoptimizations >0) { | 356 function.deoptimizations >0) { |
| 357 element.style.backgroundColor = "#EEA7A7"; // Low-saturation red. | 357 element.style.backgroundColor = "#EEA7A7"; // Low-saturation red. |
| 358 } | 358 } |
| 359 | 359 |
| 360 addLink(element, url); | 360 addLink(element, url); |
| 361 } | 361 } |
| 362 } | 362 } |
| 363 | 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() { |
| 390 if (sampleCount == 0) { |
| 391 return 0.0; |
| 392 } |
| 393 return totalTicks / sampleCount; |
| 394 } |
| 395 |
| 396 bool get isHot => _percent() > kHotThreshold; |
| 397 bool get isMedium => _percent() > kMediumThreshold; |
| 398 } |
| 399 |
| 364 /// Box with script source code in it. | 400 /// Box with script source code in it. |
| 365 @CustomTag('script-inset') | 401 @CustomTag('script-inset') |
| 366 class ScriptInsetElement extends ObservatoryElement { | 402 class ScriptInsetElement extends ObservatoryElement { |
| 367 @published Script script; | 403 @published Script script; |
| 368 @published int startPos; | 404 @published int startPos; |
| 369 @published int endPos; | 405 @published int endPos; |
| 370 | 406 |
| 371 /// Set the height to make the script inset scroll. Otherwise it | 407 /// Set the height to make the script inset scroll. Otherwise it |
| 372 /// will show from startPos to endPos. | 408 /// will show from startPos to endPos. |
| 373 @published String height = null; | 409 @published String height = null; |
| 374 | 410 |
| 375 @published int currentPos; | 411 @published int currentPos; |
| 376 @published bool inDebuggerContext = false; | 412 @published bool inDebuggerContext = false; |
| 377 @published ObservableList variables; | 413 @published ObservableList variables; |
| 378 | 414 |
| 379 @published Element scroller; | 415 @published Element scroller; |
| 380 RefreshButtonElement _refreshButton; | 416 RefreshButtonElement _refreshButton; |
| 417 ToggleButtonElement _toggleProfileButton; |
| 381 | 418 |
| 382 int _currentLine; | 419 int _currentLine; |
| 383 int _currentCol; | 420 int _currentCol; |
| 384 int _startLine; | 421 int _startLine; |
| 385 int _endLine; | 422 int _endLine; |
| 386 | 423 |
| 387 Map<int, List<ServiceMap>> _rangeMap = {}; | 424 Map<int, List<ServiceMap>> _rangeMap = {}; |
| 388 Set _callSites = new Set<CallSite>(); | 425 Set _callSites = new Set<CallSite>(); |
| 389 Set _possibleBreakpointLines = new Set<int>(); | 426 Set _possibleBreakpointLines = new Set<int>(); |
| 427 Map<int, ScriptLineProfile> _profileMap = {}; |
| 390 | 428 |
| 391 var annotations = []; | 429 var annotations = []; |
| 392 var annotationsCursor; | 430 var annotationsCursor; |
| 393 | 431 |
| 394 StreamSubscription _scriptChangeSubscription; | 432 StreamSubscription _scriptChangeSubscription; |
| 395 Future<StreamSubscription> _debugSubscriptionFuture; | 433 Future<StreamSubscription> _debugSubscriptionFuture; |
| 396 StreamSubscription _scrollSubscription; | 434 StreamSubscription _scrollSubscription; |
| 397 | 435 |
| 398 bool hasLoadedLibraryDeclarations = false; | 436 bool hasLoadedLibraryDeclarations = false; |
| 437 bool _includeProfile = false; |
| 399 | 438 |
| 400 String makeLineId(int line) { | 439 String makeLineId(int line) { |
| 401 return 'line-$line'; | 440 return 'line-$line'; |
| 402 } | 441 } |
| 403 | 442 |
| 404 void _scrollToCurrentPos() { | 443 void _scrollToCurrentPos() { |
| 405 var line = shadowRoot.getElementById(makeLineId(_currentLine)); | 444 var line = shadowRoot.getElementById(makeLineId(_currentLine)); |
| 406 if (line != null) { | 445 if (line != null) { |
| 407 line.scrollIntoView(); | 446 line.scrollIntoView(); |
| 408 } | 447 } |
| (...skipping 19 matching lines...) Expand all Loading... |
| 428 } | 467 } |
| 429 if (_scriptChangeSubscription != null) { | 468 if (_scriptChangeSubscription != null) { |
| 430 // Don't leak. If only Dart and Javascript exposed weak references... | 469 // Don't leak. If only Dart and Javascript exposed weak references... |
| 431 _scriptChangeSubscription.cancel(); | 470 _scriptChangeSubscription.cancel(); |
| 432 _scriptChangeSubscription = null; | 471 _scriptChangeSubscription = null; |
| 433 } | 472 } |
| 434 super.detached(); | 473 super.detached(); |
| 435 } | 474 } |
| 436 | 475 |
| 437 void _onScroll(event) { | 476 void _onScroll(event) { |
| 438 if (_refreshButton == null) { | 477 if (_refreshButton != null) { |
| 439 return; | 478 var newTop = _buttonTop(_refreshButton); |
| 479 if (_refreshButton.style.top != newTop) { |
| 480 _refreshButton.style.top = '${newTop}px'; |
| 481 } |
| 440 } | 482 } |
| 441 var currentTop = _refreshButton.style.top; | 483 if (_toggleProfileButton != null) { |
| 442 var newTop = _refreshButtonTop(); | 484 var newTop = _buttonTop(_toggleProfileButton); |
| 443 if (currentTop != newTop) { | 485 if (_toggleProfileButton.style.top != newTop) { |
| 444 _refreshButton.style.top = '${newTop}px'; | 486 _toggleProfileButton.style.top = '${newTop}px'; |
| 487 } |
| 445 } | 488 } |
| 446 } | 489 } |
| 447 | 490 |
| 448 void _onDebugEvent(event) { | 491 void _onDebugEvent(event) { |
| 449 if (script == null) { | 492 if (script == null) { |
| 450 return; | 493 return; |
| 451 } | 494 } |
| 452 switch (event.kind) { | 495 switch (event.kind) { |
| 453 case ServiceEvent.kBreakpointAdded: | 496 case ServiceEvent.kBreakpointAdded: |
| 454 case ServiceEvent.kBreakpointResolved: | 497 case ServiceEvent.kBreakpointResolved: |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 528 } | 571 } |
| 529 | 572 |
| 530 Element container; | 573 Element container; |
| 531 | 574 |
| 532 Future _refresh() async { | 575 Future _refresh() async { |
| 533 await update(); | 576 await update(); |
| 534 } | 577 } |
| 535 | 578 |
| 536 // Build _rangeMap and _callSites from a source report. | 579 // Build _rangeMap and _callSites from a source report. |
| 537 Future _refreshSourceReport() async { | 580 Future _refreshSourceReport() async { |
| 581 var reports = [Isolate.kCallSitesReport, |
| 582 Isolate.kPossibleBreakpointsReport]; |
| 583 if (_includeProfile) { |
| 584 reports.add(Isolate.kProfileReport); |
| 585 } |
| 538 var sourceReport = await script.isolate.getSourceReport( | 586 var sourceReport = await script.isolate.getSourceReport( |
| 539 [Isolate.kCallSitesReport, Isolate.kPossibleBreakpointsReport], | 587 reports, |
| 540 script, startPos, endPos); | 588 script, startPos, endPos); |
| 541 _possibleBreakpointLines = getPossibleBreakpointLines(sourceReport, script); | 589 _possibleBreakpointLines = getPossibleBreakpointLines(sourceReport, script); |
| 542 _rangeMap.clear(); | 590 _rangeMap.clear(); |
| 543 _callSites.clear(); | 591 _callSites.clear(); |
| 592 _profileMap.clear(); |
| 544 for (var range in sourceReport['ranges']) { | 593 for (var range in sourceReport['ranges']) { |
| 545 int startLine = script.tokenToLine(range['startPos']); | 594 int startLine = script.tokenToLine(range['startPos']); |
| 546 int endLine = script.tokenToLine(range['endPos']); | 595 int endLine = script.tokenToLine(range['endPos']); |
| 547 for (var line = startLine; line <= endLine; line++) { | 596 for (var line = startLine; line <= endLine; line++) { |
| 548 var rangeList = _rangeMap[line]; | 597 var rangeList = _rangeMap[line]; |
| 549 if (rangeList == null) { | 598 if (rangeList == null) { |
| 550 _rangeMap[line] = [range]; | 599 _rangeMap[line] = [range]; |
| 551 } else { | 600 } else { |
| 552 rangeList.add(range); | 601 rangeList.add(range); |
| 553 } | 602 } |
| 554 } | 603 } |
| 604 if (_includeProfile && range['profile'] != null) { |
| 605 List positions = range['profile']['positions']; |
| 606 List exclusiveTicks = range['profile']['exclusiveTicks']; |
| 607 List inclusiveTicks = range['profile']['inclusiveTicks']; |
| 608 int sampleCount = range['profile']['metadata']['sampleCount']; |
| 609 assert(positions.length == exclusiveTicks.length); |
| 610 assert(positions.length == inclusiveTicks.length); |
| 611 for (int i = 0; i < positions.length; i++) { |
| 612 if (positions[i] is String) { |
| 613 // String positions are classifying token positions. |
| 614 // TODO(johnmccutchan): Add classifier data to UI. |
| 615 continue; |
| 616 } |
| 617 int line = script.tokenToLine(positions[i]); |
| 618 ScriptLineProfile lineProfile = _profileMap[line]; |
| 619 if (lineProfile == null) { |
| 620 lineProfile = new ScriptLineProfile(line, sampleCount); |
| 621 _profileMap[line] = lineProfile; |
| 622 } |
| 623 lineProfile.process(exclusiveTicks[i], inclusiveTicks[i]); |
| 624 } |
| 625 } |
| 555 if (range['compiled']) { | 626 if (range['compiled']) { |
| 556 var rangeCallSites = range['callSites']; | 627 var rangeCallSites = range['callSites']; |
| 557 if (rangeCallSites != null) { | 628 if (rangeCallSites != null) { |
| 558 for (var callSiteMap in rangeCallSites) { | 629 for (var callSiteMap in rangeCallSites) { |
| 559 _callSites.add(new CallSite.fromMap(callSiteMap, script)); | 630 _callSites.add(new CallSite.fromMap(callSiteMap, script)); |
| 560 } | 631 } |
| 561 } | 632 } |
| 562 } | 633 } |
| 563 } | 634 } |
| 564 } | 635 } |
| (...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 894 | 965 |
| 895 // Annotate locations. | 966 // Annotate locations. |
| 896 for (var location in locations) { | 967 for (var location in locations) { |
| 897 annotations.add(new LocalVariableAnnotation(location, | 968 annotations.add(new LocalVariableAnnotation(location, |
| 898 variable['value'])); | 969 variable['value'])); |
| 899 } | 970 } |
| 900 } | 971 } |
| 901 } | 972 } |
| 902 } | 973 } |
| 903 | 974 |
| 904 int _refreshButtonTop() { | 975 int _buttonTop(Element element) { |
| 905 if (_refreshButton == null) { | 976 if (element == null) { |
| 906 return 5; | 977 return 5; |
| 907 } | 978 } |
| 908 const padding = 5; | 979 const padding = 5; |
| 909 const navbarHeight = NavBarElement.height; | 980 const navbarHeight = NavBarElement.height; |
| 910 var rect = getBoundingClientRect(); | 981 var rect = getBoundingClientRect(); |
| 911 var buttonHeight = _refreshButton.clientHeight; | 982 var buttonHeight = element.clientHeight; |
| 912 return min(max(0, navbarHeight - rect.top) + padding, | 983 return min(max(0, navbarHeight - rect.top) + padding, |
| 913 rect.height - (buttonHeight + padding)); | 984 rect.height - (buttonHeight + padding)); |
| 914 } | 985 } |
| 915 | 986 |
| 916 RefreshButtonElement _newRefreshButton() { | 987 RefreshButtonElement _newRefreshButton() { |
| 917 var button = new Element.tag('refresh-button'); | 988 var button = new Element.tag('refresh-button'); |
| 918 button.style.position = 'absolute'; | 989 button.style.position = 'absolute'; |
| 919 button.style.display = 'inline-block'; | 990 button.style.display = 'inline-block'; |
| 920 button.style.top = '${_refreshButtonTop()}px'; | 991 button.style.top = '${_buttonTop(null)}px'; |
| 921 button.style.right = '5px'; | 992 button.style.right = '5px'; |
| 922 button.callback = _refresh; | 993 button.callback = _refresh; |
| 994 button.title = 'Refresh coverage'; |
| 923 return button; | 995 return button; |
| 924 } | 996 } |
| 925 | 997 |
| 998 ToggleButtonElement _newToggleProfileButton() { |
| 999 ToggleButtonElement button = new Element.tag('toggle-button'); |
| 1000 button.style.position = 'absolute'; |
| 1001 button.style.display = 'inline-block'; |
| 1002 button.style.top = '${_buttonTop(null)}px'; |
| 1003 button.style.right = '30px'; |
| 1004 button.title = 'Toggle CPU profile information'; |
| 1005 button.callback = (enabled) async { |
| 1006 _includeProfile = enabled; |
| 1007 if (button.children.length > 0) { |
| 1008 var content = button.children[0]; |
| 1009 if (enabled) { |
| 1010 content.style.color = 'black'; |
| 1011 } else { |
| 1012 content.style.color = 'rgba(0, 0, 0 ,.3)'; |
| 1013 } |
| 1014 } |
| 1015 await update(); |
| 1016 }; |
| 1017 button.children.add(new Element.tag('icon-whatshot')); |
| 1018 button.children[0].style.color = 'rgba(0, 0, 0 ,.3)'; |
| 1019 button.enabled = _includeProfile; |
| 1020 return button; |
| 1021 } |
| 1022 |
| 926 Element linesTable() { | 1023 Element linesTable() { |
| 927 var table = new DivElement(); | 1024 var table = new DivElement(); |
| 928 table.classes.add("sourceTable"); | 1025 table.classes.add("sourceTable"); |
| 929 | 1026 |
| 930 _refreshButton = _newRefreshButton(); | 1027 _refreshButton = _newRefreshButton(); |
| 1028 _toggleProfileButton = _newToggleProfileButton(); |
| 931 table.append(_refreshButton); | 1029 table.append(_refreshButton); |
| 1030 table.append(_toggleProfileButton); |
| 932 | 1031 |
| 933 if (_startLine == null || _endLine == null) { | 1032 if (_startLine == null || _endLine == null) { |
| 934 return table; | 1033 return table; |
| 935 } | 1034 } |
| 936 | 1035 |
| 937 var endLine = (endPos != null | 1036 var endLine = (endPos != null |
| 938 ? script.tokenToLine(endPos) | 1037 ? script.tokenToLine(endPos) |
| 939 : script.lines.length + script.lineOffset); | 1038 : script.lines.length + script.lineOffset); |
| 940 var lineNumPad = endLine.toString().length; | 1039 var lineNumPad = endLine.toString().length; |
| 941 | 1040 |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 989 if (annotation.line != line) return null; | 1088 if (annotation.line != line) return null; |
| 990 annotationsCursor++; | 1089 annotationsCursor++; |
| 991 return annotation; | 1090 return annotation; |
| 992 } | 1091 } |
| 993 | 1092 |
| 994 Element lineElement(ScriptLine line, int lineNumPad) { | 1093 Element lineElement(ScriptLine line, int lineNumPad) { |
| 995 var e = new DivElement(); | 1094 var e = new DivElement(); |
| 996 e.classes.add("sourceRow"); | 1095 e.classes.add("sourceRow"); |
| 997 e.append(lineBreakpointElement(line)); | 1096 e.append(lineBreakpointElement(line)); |
| 998 e.append(lineNumberElement(line, lineNumPad)); | 1097 e.append(lineNumberElement(line, lineNumPad)); |
| 1098 if (_includeProfile) { |
| 1099 e.append(lineProfileElement(line, false)); |
| 1100 e.append(lineProfileElement(line, true)); |
| 1101 } |
| 999 e.append(lineSourceElement(line)); | 1102 e.append(lineSourceElement(line)); |
| 1000 return e; | 1103 return e; |
| 1001 } | 1104 } |
| 1002 | 1105 |
| 1106 Element lineProfileElement(ScriptLine line, bool self) { |
| 1107 var e = span(''); |
| 1108 e.classes.add('noCopy'); |
| 1109 if (self) { |
| 1110 e.title = 'Self %'; |
| 1111 } else { |
| 1112 e.title = 'Total %'; |
| 1113 } |
| 1114 |
| 1115 if (line == null) { |
| 1116 e.classes.add('notSourceProfile'); |
| 1117 e.text = nbsp; |
| 1118 return e; |
| 1119 } |
| 1120 |
| 1121 var ranges = _rangeMap[line.line]; |
| 1122 if ((ranges == null) || ranges.isEmpty) { |
| 1123 e.classes.add('notSourceProfile'); |
| 1124 e.text = nbsp; |
| 1125 return e; |
| 1126 } |
| 1127 |
| 1128 ScriptLineProfile lineProfile = _profileMap[line.line]; |
| 1129 if (lineProfile == null) { |
| 1130 e.classes.add('noProfile'); |
| 1131 e.text = nbsp; |
| 1132 return e; |
| 1133 } |
| 1134 |
| 1135 if (self) { |
| 1136 e.text = lineProfile.formattedSelfTicks; |
| 1137 } else { |
| 1138 e.text = lineProfile.formattedTotalTicks; |
| 1139 } |
| 1140 |
| 1141 if (lineProfile.isHot) { |
| 1142 e.classes.add('hotProfile'); |
| 1143 } else if (lineProfile.isMedium) { |
| 1144 e.classes.add('mediumProfile'); |
| 1145 } else { |
| 1146 e.classes.add('coldProfile'); |
| 1147 } |
| 1148 |
| 1149 return e; |
| 1150 } |
| 1151 |
| 1003 Element lineBreakpointElement(ScriptLine line) { | 1152 Element lineBreakpointElement(ScriptLine line) { |
| 1004 var e = new DivElement(); | 1153 var e = new DivElement(); |
| 1005 if (line == null || !_possibleBreakpointLines.contains(line.line)) { | 1154 if (line == null || !_possibleBreakpointLines.contains(line.line)) { |
| 1006 e.classes.add('noCopy'); | 1155 e.classes.add('noCopy'); |
| 1007 e.classes.add("emptyBreakpoint"); | 1156 e.classes.add("emptyBreakpoint"); |
| 1008 e.text = nbsp; | 1157 e.text = nbsp; |
| 1009 return e; | 1158 return e; |
| 1010 } | 1159 } |
| 1011 | 1160 |
| 1012 e.text = 'B'; | 1161 e.text = 'B'; |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1184 } | 1333 } |
| 1185 busy = true; | 1334 busy = true; |
| 1186 if (callback != null) { | 1335 if (callback != null) { |
| 1187 await callback(); | 1336 await callback(); |
| 1188 } | 1337 } |
| 1189 busy = false; | 1338 busy = false; |
| 1190 } | 1339 } |
| 1191 } | 1340 } |
| 1192 | 1341 |
| 1193 | 1342 |
| 1343 @CustomTag('toggle-button') |
| 1344 class ToggleButtonElement extends PolymerElement { |
| 1345 ToggleButtonElement.created() : super.created(); |
| 1346 |
| 1347 @published var callback = null; |
| 1348 @observable bool enabled = false; |
| 1349 |
| 1350 Future buttonClick(var event, var b, var c) async { |
| 1351 enabled = !enabled; |
| 1352 if (callback != null) { |
| 1353 await callback(enabled); |
| 1354 } |
| 1355 } |
| 1356 } |
| 1357 |
| 1358 |
| 1194 @CustomTag('source-inset') | 1359 @CustomTag('source-inset') |
| 1195 class SourceInsetElement extends PolymerElement { | 1360 class SourceInsetElement extends PolymerElement { |
| 1196 SourceInsetElement.created() : super.created(); | 1361 SourceInsetElement.created() : super.created(); |
| 1197 | 1362 |
| 1198 @published SourceLocation location; | 1363 @published SourceLocation location; |
| 1199 @published String height = null; | 1364 @published String height = null; |
| 1200 @published int currentPos; | 1365 @published int currentPos; |
| 1201 @published bool inDebuggerContext = false; | 1366 @published bool inDebuggerContext = false; |
| 1202 @published ObservableList variables; | 1367 @published ObservableList variables; |
| 1203 @published Element scroller; | 1368 @published Element scroller; |
| 1204 } | 1369 } |
| OLD | NEW |