20faaa5d94
Change-Id: Ib18f554c1076f27a3f7c0df5a36410da41a1f467 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/148784 Reviewed-by: Paul Berry <paulberry@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
652 lines
20 KiB
Dart
652 lines
20 KiB
Dart
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// @dart = 2.7
|
|
|
|
/// Helper for creating HTML visualization of the source map information
|
|
/// generated by a [SourceMapProcessor].
|
|
|
|
library sourcemap.html.helper;
|
|
|
|
import 'dart:convert';
|
|
import 'dart:math' as Math;
|
|
|
|
import 'package:compiler/src/io/source_file.dart';
|
|
import 'package:compiler/src/io/source_information.dart';
|
|
import 'package:compiler/src/js/js.dart' as js;
|
|
|
|
import 'colors.dart';
|
|
import 'sourcemap_helper.dart';
|
|
import 'sourcemap_html_templates.dart';
|
|
import 'html_parts.dart';
|
|
|
|
/// Truncate [input] to [length], adding '...' if truncated.
|
|
String truncate(String input, int length) {
|
|
if (input.length > length) {
|
|
return '${input.substring(0, length - 3)}...';
|
|
}
|
|
return input;
|
|
}
|
|
|
|
const int HUE_COUNT = 24;
|
|
|
|
/// Returns the [index]th color for visualization.
|
|
HSV toColor(int index) {
|
|
double h = 360.0 * (index % HUE_COUNT) / HUE_COUNT;
|
|
double v = 1.0;
|
|
double s = 0.5;
|
|
return new HSV(h, s, v);
|
|
}
|
|
|
|
/// Return the CSS color value for the [index]th color.
|
|
String toColorCss(int index) {
|
|
return toColor(index).toCss;
|
|
}
|
|
|
|
/// Return the CSS color value for the [index]th span.
|
|
String toPattern(int index) {
|
|
/// Use gradient on spans to visually identify consecutive spans mapped to the
|
|
/// same source location.
|
|
HSV startColor = toColor(index);
|
|
HSV endColor = new HSV(startColor.h, startColor.s + 0.4, startColor.v - 0.2);
|
|
return 'linear-gradient(to right, ${startColor.toCss}, ${endColor.toCss})';
|
|
}
|
|
|
|
/// Return the html for the [index] line number. If [width] is provided, shorter
|
|
/// line numbers will be prefixed with spaces to match the width.
|
|
String lineNumber(int index,
|
|
{int width, bool useNbsp: false, String className}) {
|
|
if (className == null) {
|
|
className = 'lineNumber';
|
|
}
|
|
String text = '${index + 1}';
|
|
String padding = useNbsp ? ' ' : ' ';
|
|
if (width != null && text.length < width) {
|
|
text = (padding * (width - text.length)) + text;
|
|
}
|
|
return '<span class="$className">$text$padding</span>';
|
|
}
|
|
|
|
/// Return the html escaped [text].
|
|
String escape(String text) {
|
|
return const HtmlEscape().convert(text);
|
|
}
|
|
|
|
/// Information needed to generate HTML for a single [SourceMapInfo].
|
|
class SourceMapHtmlInfo {
|
|
final SourceMapInfo sourceMapInfo;
|
|
final CodeProcessor codeProcessor;
|
|
final SourceLocationCollection sourceLocationCollection;
|
|
|
|
SourceMapHtmlInfo(
|
|
this.sourceMapInfo, this.codeProcessor, this.sourceLocationCollection);
|
|
|
|
@override
|
|
String toString() {
|
|
return sourceMapInfo.toString();
|
|
}
|
|
}
|
|
|
|
/// A collection of source locations.
|
|
///
|
|
/// Used to index source locations for visualization and linking.
|
|
class SourceLocationCollection {
|
|
List<SourceLocation> sourceLocations = [];
|
|
Map<SourceLocation, int> sourceLocationIndexMap;
|
|
|
|
SourceLocationCollection([SourceLocationCollection parent])
|
|
: sourceLocationIndexMap =
|
|
parent == null ? {} : parent.sourceLocationIndexMap;
|
|
|
|
int registerSourceLocation(SourceLocation sourceLocation) {
|
|
return sourceLocationIndexMap.putIfAbsent(sourceLocation, () {
|
|
sourceLocations.add(sourceLocation);
|
|
return sourceLocationIndexMap.length;
|
|
});
|
|
}
|
|
|
|
int getIndex(SourceLocation sourceLocation) {
|
|
return sourceLocationIndexMap[sourceLocation];
|
|
}
|
|
}
|
|
|
|
abstract class CssColorScheme {
|
|
String singleLocationToCssColor(int id);
|
|
|
|
String multiLocationToCssColor(List<int> ids);
|
|
|
|
bool get showLocationAsSpan;
|
|
}
|
|
|
|
class CustomColorScheme implements CssColorScheme {
|
|
@override
|
|
final bool showLocationAsSpan;
|
|
final Function single;
|
|
final Function multi;
|
|
|
|
CustomColorScheme(
|
|
{this.showLocationAsSpan: false,
|
|
String this.single(int id),
|
|
String this.multi(List<int> ids)});
|
|
|
|
@override
|
|
String singleLocationToCssColor(int id) => single != null ? single(id) : null;
|
|
|
|
@override
|
|
String multiLocationToCssColor(List<int> ids) =>
|
|
multi != null ? multi(ids) : null;
|
|
}
|
|
|
|
class PatternCssColorScheme implements CssColorScheme {
|
|
const PatternCssColorScheme();
|
|
|
|
@override
|
|
bool get showLocationAsSpan => true;
|
|
|
|
@override
|
|
String singleLocationToCssColor(int index) {
|
|
return "background:${toPattern(index)};";
|
|
}
|
|
|
|
@override
|
|
String multiLocationToCssColor(List<int> indices) {
|
|
StringBuffer sb = new StringBuffer();
|
|
double delta = 100.0 / (indices.length);
|
|
double position = 0.0;
|
|
|
|
void addColor(String color) {
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
position += delta;
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
}
|
|
|
|
for (int index in indices) {
|
|
addColor('${toColorCss(index)}');
|
|
}
|
|
return 'background: linear-gradient(to right${sb}); '
|
|
'background-size: 10px 10px;';
|
|
}
|
|
}
|
|
|
|
class SingleColorScheme implements CssColorScheme {
|
|
const SingleColorScheme();
|
|
|
|
@override
|
|
bool get showLocationAsSpan => false;
|
|
|
|
@override
|
|
String singleLocationToCssColor(int index) {
|
|
return "background:${toColorCss(index)};";
|
|
}
|
|
|
|
@override
|
|
String multiLocationToCssColor(List<int> indices) {
|
|
StringBuffer sb = new StringBuffer();
|
|
double delta = 100.0 / (indices.length);
|
|
double position = 0.0;
|
|
|
|
void addColor(String color) {
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
position += delta;
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
}
|
|
|
|
for (int index in indices) {
|
|
addColor('${toColorCss(index)}');
|
|
}
|
|
return 'background: linear-gradient(to bottom${sb}); '
|
|
'background-size: 10px 3px;';
|
|
}
|
|
}
|
|
|
|
/// Processor that computes the HTML representation of a block of JavaScript
|
|
/// code and collects the source locations mapped in the code.
|
|
class CodeProcessor {
|
|
int lineIndex = 0;
|
|
final String name;
|
|
int currentJsSourceOffset = 0;
|
|
final SourceLocationCollection collection;
|
|
final Map<int, List<SourceLocation>> codeLocations = {};
|
|
final CssColorScheme colorScheme;
|
|
|
|
CodeProcessor(this.name, this.collection,
|
|
{this.colorScheme: const PatternCssColorScheme()});
|
|
|
|
void addSourceLocation(int targetOffset, SourceLocation sourceLocation) {
|
|
codeLocations.putIfAbsent(targetOffset, () => []).add(sourceLocation);
|
|
collection.registerSourceLocation(sourceLocation);
|
|
}
|
|
|
|
String convertToHtml(String text) {
|
|
List<Annotation> annotations = <Annotation>[];
|
|
codeLocations.forEach((int codeOffset, List<SourceLocation> locations) {
|
|
for (SourceLocation location in locations) {
|
|
if (location != null) {
|
|
annotations.add(new Annotation(
|
|
collection.getIndex(location), codeOffset, location.shortText));
|
|
}
|
|
}
|
|
});
|
|
return convertAnnotatedCodeToHtml(text, annotations,
|
|
colorScheme: colorScheme,
|
|
elementScheme: new HighlightLinkScheme(name),
|
|
windowSize: 3);
|
|
}
|
|
}
|
|
|
|
class ElementScheme {
|
|
const ElementScheme();
|
|
|
|
String getName(int id, Set<int> ids) => null;
|
|
String getHref(int id, Set<int> ids) => null;
|
|
String onClick(int id, Set<int> ids) => null;
|
|
String onMouseOver(int id, Set<int> ids) => null;
|
|
String onMouseOut(int id, Set<int> ids) => null;
|
|
}
|
|
|
|
class HighlightLinkScheme implements ElementScheme {
|
|
final String name;
|
|
|
|
HighlightLinkScheme(this.name);
|
|
|
|
@override
|
|
String getName(int id, Set<int> indices) {
|
|
return 'js$id';
|
|
}
|
|
|
|
@override
|
|
String getHref(int id, Set<int> indices) {
|
|
return "#${id}";
|
|
}
|
|
|
|
@override
|
|
String onClick(int id, Set<int> indices) {
|
|
return "show(\'$name\');";
|
|
}
|
|
|
|
@override
|
|
String onMouseOut(int id, Set<int> indices) {
|
|
return "highlight([]);";
|
|
}
|
|
|
|
@override
|
|
String onMouseOver(int id, Set<int> indices) {
|
|
String onmouseover = indices.map((i) => '\'$i\'').join(',');
|
|
return "highlight([${onmouseover}]);";
|
|
}
|
|
}
|
|
|
|
String convertAnnotatedCodeToHtml(String code, Iterable<Annotation> annotations,
|
|
{CssColorScheme colorScheme: const SingleColorScheme(),
|
|
ElementScheme elementScheme: const ElementScheme(),
|
|
int windowSize}) {
|
|
StringBuffer htmlBuffer = new StringBuffer();
|
|
List<CodeLine> lines = convertAnnotatedCodeToCodeLines(code, annotations,
|
|
windowSize: windowSize);
|
|
int lineNoWidth;
|
|
if (lines.isNotEmpty) {
|
|
lineNoWidth = '${lines.last.lineNo + 1}'.length;
|
|
}
|
|
HtmlPrintContext context = new HtmlPrintContext(
|
|
lineNoWidth: lineNoWidth,
|
|
getAnnotationData: createAnnotationDataFunction(
|
|
colorScheme: colorScheme, elementScheme: elementScheme));
|
|
for (CodeLine line in lines) {
|
|
line.printHtmlOn(htmlBuffer, context);
|
|
}
|
|
return htmlBuffer.toString();
|
|
}
|
|
|
|
List<CodeLine> convertAnnotatedCodeToCodeLines(
|
|
String code, Iterable<Annotation> annotations,
|
|
{int startLine, int endLine, int windowSize, Uri uri}) {
|
|
List<CodeLine> lines = <CodeLine>[];
|
|
CodeLine currentLine;
|
|
final List<Annotation> currentAnnotations = <Annotation>[];
|
|
int offset = 0;
|
|
int lineIndex = 0;
|
|
int firstLine;
|
|
int lastLine;
|
|
|
|
void addCode(String code) {
|
|
if (currentLine != null) {
|
|
currentLine.codeBuffer.write(code);
|
|
currentLine.codeParts
|
|
.add(new CodePart(currentAnnotations.toList(), code));
|
|
currentAnnotations.clear();
|
|
}
|
|
}
|
|
|
|
void addAnnotations(List<Annotation> annotations) {
|
|
currentAnnotations.addAll(annotations);
|
|
if (currentLine != null) {
|
|
currentLine.annotations.addAll(annotations);
|
|
}
|
|
}
|
|
|
|
void beginLine(int currentOffset) {
|
|
lines
|
|
.add(currentLine = new CodeLine(lines.length, currentOffset, uri: uri));
|
|
}
|
|
|
|
void endCurrentLocation() {
|
|
if (currentAnnotations.isNotEmpty) {
|
|
addCode('');
|
|
}
|
|
}
|
|
|
|
void addSubstring(int until, {bool isFirst: false, bool isLast: false}) {
|
|
if (until <= offset) return;
|
|
if (offset >= code.length) return;
|
|
|
|
String substring = code.substring(offset, until);
|
|
bool first = true;
|
|
|
|
if (isLast) {
|
|
lastLine = lineIndex;
|
|
}
|
|
int localOffset = 0;
|
|
if (isFirst) {
|
|
beginLine(offset + localOffset);
|
|
}
|
|
for (String line in substring.split('\n')) {
|
|
if (!first) {
|
|
endCurrentLocation();
|
|
lineIndex++;
|
|
beginLine(offset + localOffset);
|
|
}
|
|
addCode(line);
|
|
first = false;
|
|
localOffset += line.length + 1;
|
|
}
|
|
if (isFirst) {
|
|
firstLine = lineIndex;
|
|
}
|
|
offset = until;
|
|
}
|
|
|
|
void insertAnnotations(List<Annotation> annotations) {
|
|
endCurrentLocation();
|
|
addAnnotations(annotations);
|
|
if (annotations.last == null) {
|
|
endCurrentLocation();
|
|
}
|
|
}
|
|
|
|
Map<int, List<Annotation>> annotationMap = <int, List<Annotation>>{};
|
|
for (Annotation annotation in annotations) {
|
|
annotationMap
|
|
.putIfAbsent(annotation.codeOffset, () => <Annotation>[])
|
|
.add(annotation);
|
|
}
|
|
|
|
bool first = true;
|
|
for (int codeOffset in annotationMap.keys.toList()..sort()) {
|
|
List<Annotation> annotationList = annotationMap[codeOffset];
|
|
addSubstring(codeOffset, isFirst: first);
|
|
insertAnnotations(annotationList);
|
|
first = false;
|
|
}
|
|
|
|
addSubstring(code.length, isFirst: first, isLast: true);
|
|
endCurrentLocation();
|
|
|
|
int start = startLine ?? 0;
|
|
int end = endLine ?? lines.length - 1;
|
|
if (lastLine == 0) lastLine = firstLine;
|
|
if (windowSize != null) {
|
|
start = Math.max(firstLine - windowSize, start);
|
|
end = Math.min(lastLine + windowSize, end);
|
|
}
|
|
return lines.sublist(start, end);
|
|
}
|
|
|
|
/// Computes the HTML representation for a collection of JavaScript code blocks.
|
|
String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) {
|
|
StringBuffer jsCodeBuffer = new StringBuffer();
|
|
for (SourceMapHtmlInfo info in infoList) {
|
|
String name = info.sourceMapInfo.name;
|
|
String html = info.codeProcessor.convertToHtml(info.sourceMapInfo.code);
|
|
String onclick = 'show(\'$name\');';
|
|
jsCodeBuffer
|
|
.write('<h3 onclick="$onclick">JS code for: ${escape(name)}</h3>\n');
|
|
jsCodeBuffer.write('''
|
|
<pre>
|
|
$html
|
|
</pre>
|
|
''');
|
|
}
|
|
return jsCodeBuffer.toString();
|
|
}
|
|
|
|
/// Computes the HTML representation of the source mapping information for a
|
|
/// collection of JavaScript code blocks.
|
|
String computeJsTraceHtml(Iterable<SourceMapHtmlInfo> infoList) {
|
|
StringBuffer jsTraceBuffer = new StringBuffer();
|
|
for (SourceMapHtmlInfo info in infoList) {
|
|
String name = info.sourceMapInfo.name;
|
|
String jsTrace = computeJsTraceHtmlPart(
|
|
info.sourceMapInfo.codePoints, info.sourceLocationCollection);
|
|
jsTraceBuffer.write('''
|
|
<div name="$name" class="js-trace-buffer" style="display:none;">
|
|
<h3>Trace for: ${escape(name)}</h3>
|
|
$jsTrace
|
|
</div>
|
|
''');
|
|
}
|
|
return jsTraceBuffer.toString();
|
|
}
|
|
|
|
/// Computes the HTML information for the [info].
|
|
SourceMapHtmlInfo createHtmlInfo(
|
|
SourceLocationCollection collection, SourceMapInfo info) {
|
|
String name = info.name;
|
|
SourceLocationCollection subcollection =
|
|
new SourceLocationCollection(collection);
|
|
CodeProcessor codeProcessor = new CodeProcessor(name, subcollection);
|
|
for (js.Node node in info.nodeMap.nodes) {
|
|
info.nodeMap[node]
|
|
.forEach((int targetOffset, List<SourceLocation> sourceLocations) {
|
|
for (SourceLocation sourceLocation in sourceLocations) {
|
|
codeProcessor.addSourceLocation(targetOffset, sourceLocation);
|
|
}
|
|
});
|
|
}
|
|
return new SourceMapHtmlInfo(info, codeProcessor, subcollection);
|
|
}
|
|
|
|
/// Outputs a HTML file in [jsMapHtmlUri] containing an interactive
|
|
/// visualization of the source mapping information in [infoList] computed
|
|
/// with the [sourceMapProcessor].
|
|
void createTraceSourceMapHtml(Uri jsMapHtmlUri,
|
|
SourceMapProcessor sourceMapProcessor, Iterable<SourceMapInfo> infoList) {
|
|
SourceFileManager sourceFileManager = sourceMapProcessor.sourceFileManager;
|
|
SourceLocationCollection collection = new SourceLocationCollection();
|
|
List<SourceMapHtmlInfo> htmlInfoList = <SourceMapHtmlInfo>[];
|
|
for (SourceMapInfo info in infoList) {
|
|
htmlInfoList.add(createHtmlInfo(collection, info));
|
|
}
|
|
|
|
String jsCode = computeJsHtml(htmlInfoList);
|
|
String dartCode = computeDartHtml(sourceFileManager, htmlInfoList);
|
|
|
|
String jsTraceHtml = computeJsTraceHtml(htmlInfoList);
|
|
outputJsDartTrace(jsMapHtmlUri, jsCode, dartCode, jsTraceHtml);
|
|
print('Trace source map html generated: $jsMapHtmlUri');
|
|
}
|
|
|
|
/// Computes the HTML representation for the Dart code snippets referenced in
|
|
/// [infoList].
|
|
String computeDartHtml(
|
|
SourceFileManager sourceFileManager, Iterable<SourceMapHtmlInfo> infoList) {
|
|
StringBuffer dartCodeBuffer = new StringBuffer();
|
|
for (SourceMapHtmlInfo info in infoList) {
|
|
dartCodeBuffer.write(computeDartHtmlPart(info.sourceMapInfo.name,
|
|
sourceFileManager, info.sourceLocationCollection));
|
|
}
|
|
return dartCodeBuffer.toString();
|
|
}
|
|
|
|
/// Computes the HTML representation for the Dart code snippets in [collection].
|
|
String computeDartHtmlPart(String name, SourceFileManager sourceFileManager,
|
|
SourceLocationCollection collection,
|
|
{bool showAsBlock: false}) {
|
|
const int windowSize = 3;
|
|
StringBuffer dartCodeBuffer = new StringBuffer();
|
|
Map<Uri, Map<int, List<SourceLocation>>> sourceLocationMap = {};
|
|
collection.sourceLocations.forEach((SourceLocation sourceLocation) {
|
|
if (sourceLocation.sourceUri == null || sourceLocation.line == null) return;
|
|
Map<int, List<SourceLocation>> uriMap =
|
|
sourceLocationMap.putIfAbsent(sourceLocation.sourceUri, () => {});
|
|
List<SourceLocation> lineList =
|
|
uriMap.putIfAbsent(sourceLocation.line - 1, () => []);
|
|
lineList.add(sourceLocation);
|
|
});
|
|
sourceLocationMap.forEach((Uri uri, Map<int, List<SourceLocation>> uriMap) {
|
|
SourceFile sourceFile = sourceFileManager.getSourceFile(uri);
|
|
if (sourceFile == null) {
|
|
print('No source file for $uri');
|
|
return;
|
|
}
|
|
StringBuffer codeBuffer = new StringBuffer();
|
|
|
|
int firstLineIndex;
|
|
int lastLineIndex;
|
|
List<int> lineIndices = uriMap.keys.toList()..sort();
|
|
int lineNoWidth;
|
|
if (lineIndices.isNotEmpty) {
|
|
lineNoWidth = '${lineIndices.last + windowSize + 1}'.length;
|
|
}
|
|
|
|
void flush() {
|
|
if (firstLineIndex != null && lastLineIndex != null) {
|
|
dartCodeBuffer.write('<h4>${uri.pathSegments.last}, '
|
|
'${firstLineIndex - windowSize + 1}-'
|
|
'${lastLineIndex + windowSize + 1}'
|
|
'</h4>\n');
|
|
dartCodeBuffer.write('<pre>\n');
|
|
dartCodeBuffer.write('<p class="line">');
|
|
for (int line = firstLineIndex - windowSize;
|
|
line < firstLineIndex;
|
|
line++) {
|
|
if (line >= 0) {
|
|
dartCodeBuffer.write('</p><p class="line">');
|
|
dartCodeBuffer.write(lineNumber(line, width: lineNoWidth));
|
|
dartCodeBuffer.write(sourceFile.kernelSource.getTextLine(line + 1));
|
|
}
|
|
}
|
|
dartCodeBuffer.write(codeBuffer);
|
|
for (int line = lastLineIndex + 1;
|
|
line <= lastLineIndex + windowSize;
|
|
line++) {
|
|
if (line < sourceFile.lines) {
|
|
dartCodeBuffer.write('</p><p class="line">');
|
|
dartCodeBuffer.write(lineNumber(line, width: lineNoWidth));
|
|
dartCodeBuffer.write(sourceFile.kernelSource.getTextLine(line + 1));
|
|
}
|
|
}
|
|
dartCodeBuffer.write('</p>');
|
|
dartCodeBuffer.write('</pre>\n');
|
|
firstLineIndex = null;
|
|
lastLineIndex = null;
|
|
}
|
|
codeBuffer.clear();
|
|
}
|
|
|
|
lineIndices.forEach((int lineIndex) {
|
|
List<SourceLocation> locations = uriMap[lineIndex];
|
|
if (lastLineIndex != null && lastLineIndex + windowSize * 4 < lineIndex) {
|
|
flush();
|
|
}
|
|
if (firstLineIndex == null) {
|
|
firstLineIndex = lineIndex;
|
|
} else {
|
|
for (int line = lastLineIndex + 1; line < lineIndex; line++) {
|
|
codeBuffer.write('</p><p class="line">');
|
|
codeBuffer.write(lineNumber(line, width: lineNoWidth));
|
|
codeBuffer.write(sourceFile.kernelSource.getTextLine(line + 1));
|
|
}
|
|
}
|
|
String line = sourceFile.kernelSource.getTextLine(lineIndex + 1);
|
|
locations.sort((a, b) => a.offset.compareTo(b.offset));
|
|
for (int i = 0; i < locations.length; i++) {
|
|
SourceLocation sourceLocation = locations[i];
|
|
int index = collection.getIndex(sourceLocation);
|
|
int start = sourceLocation.column - 1;
|
|
int end = line.length;
|
|
if (i + 1 < locations.length) {
|
|
end = locations[i + 1].column - 1;
|
|
}
|
|
if (i == 0) {
|
|
codeBuffer.write('</p><p class="line">');
|
|
codeBuffer.write(lineNumber(lineIndex, width: lineNoWidth));
|
|
codeBuffer.write(line.substring(0, start));
|
|
}
|
|
codeBuffer
|
|
.write('<a name="${index}" style="background:${toPattern(index)};" '
|
|
'title="[${lineIndex + 1},${start + 1}]" '
|
|
'onmouseover="highlight(\'$index\');" '
|
|
'onmouseout="highlight();">');
|
|
codeBuffer.write(line.substring(start, end));
|
|
codeBuffer.write('</a>');
|
|
}
|
|
lastLineIndex = lineIndex;
|
|
});
|
|
|
|
flush();
|
|
});
|
|
String display = showAsBlock ? 'block' : 'none';
|
|
return '''
|
|
<div name="$name" class="dart-buffer" style="display:$display;">
|
|
<h3>Dart code for: ${escape(name)}</h3>
|
|
${dartCodeBuffer}
|
|
</div>''';
|
|
}
|
|
|
|
/// Computes a HTML visualization of the [codePoints].
|
|
String computeJsTraceHtmlPart(
|
|
List<CodePoint> codePoints, SourceLocationCollection collection) {
|
|
StringBuffer buffer = new StringBuffer();
|
|
buffer.write('<table style="width:100%;">');
|
|
buffer.write('<tr><th>Node kind</th><th>JS code @ offset</th>'
|
|
'<th>Dart code @ mapped location</th><th>file:position:name</th></tr>');
|
|
codePoints.forEach((CodePoint codePoint) {
|
|
String jsCode = truncate(codePoint.jsCode, 50);
|
|
if (codePoint.sourceLocation != null) {
|
|
int index = collection.getIndex(codePoint.sourceLocation);
|
|
if (index != null) {
|
|
String style = '';
|
|
if (!codePoint.isMissing) {
|
|
style = 'style="background:${toColorCss(index)};" ';
|
|
}
|
|
buffer.write('<tr $style'
|
|
'name="trace$index" '
|
|
'onmouseover="highlight([${index}]);"'
|
|
'onmouseout="highlight([]);">');
|
|
} else {
|
|
buffer.write('<tr>');
|
|
print('${codePoint.sourceLocation} not found in ');
|
|
collection.sourceLocationIndexMap.keys
|
|
.where((l) => l.sourceUri == codePoint.sourceLocation.sourceUri)
|
|
.forEach((l) => print(' $l'));
|
|
}
|
|
} else {
|
|
buffer.write('<tr>');
|
|
}
|
|
buffer.write('<td>${codePoint.kind}</td>');
|
|
buffer.write('<td class="code">${codePoint.targetOffset}:${jsCode}</td>');
|
|
if (codePoint.sourceLocation == null) {
|
|
//buffer.write('<td></td>');
|
|
} else {
|
|
String dartCode = truncate(codePoint.dartCode, 50);
|
|
buffer.write('<td class="code">${dartCode}</td>');
|
|
buffer.write('<td>${escape(codePoint.sourceLocation.shortText)}</td>');
|
|
}
|
|
buffer.write('</tr>');
|
|
});
|
|
buffer.write('</table>');
|
|
|
|
return buffer.toString();
|
|
}
|