Index: tools/dart2js/sourceMapViewer/web/display.dart |
diff --git a/tools/dart2js/sourceMapViewer/web/display.dart b/tools/dart2js/sourceMapViewer/web/display.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a6d02bec24852d68244abe8d3efb98fb3444cabb |
--- /dev/null |
+++ b/tools/dart2js/sourceMapViewer/web/display.dart |
@@ -0,0 +1,323 @@ |
+// Copyright (c) 2014, 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. |
+ |
+import 'dart:html'; |
+import 'dart:convert'; |
+import 'dart:async'; |
+import 'package:source_maps/source_maps.dart'; |
+ |
+Element targetFileName = querySelector("#target_filename"); |
+Element sourceFileName = querySelector("#source_filename"); |
+DivElement generatedOutput = querySelector("#generated_output"); |
+DivElement selectedSource = querySelector("#selected_source"); |
+DivElement selectedOutputSpan = querySelector("#current_span"); |
+DivElement decodedMap = querySelector("#decoded_map"); |
+DivElement originalMap = querySelector("#original_map"); |
+ |
+Map<TargetEntry, List<SpanElement>> targetEntryMap = {}; |
+List<SpanElement> highlightedMapEntry = null; |
+List<String> target; |
+SingleMapping sourceMap; |
+ |
+void adjustDivHeightsToWindow() { |
+ generatedOutput.style.height = "${window.innerHeight / 3 - 50}px"; |
+ selectedSource.style.height = "${window.innerHeight / 3 - 50}px"; |
+ decodedMap.style.height = "${window.innerHeight / 3 - 50}px"; |
+ originalMap.style.height = "${window.innerHeight / 3 - 50}px"; |
+} |
+ |
+Future getMap() { |
+ Completer c = new Completer(); |
+ HttpRequest httpRequest = new HttpRequest(); |
+ httpRequest |
+ ..open('GET', '/map') |
+ ..onLoadEnd.listen((_) => c.complete(httpRequest.responseText)) |
+ ..send(''); |
+ return c.future; |
+} |
+ |
+Future fetchFile(String path) { |
+ Completer c = new Completer(); |
+ HttpRequest httpRequest = new HttpRequest(); |
+ sourceFileName.text = path; |
+ httpRequest |
+ ..open('GET', path) |
+ ..onLoadEnd.listen((_) => c.complete(httpRequest.responseText)) |
+ ..send(''); |
+ return c.future; |
+} |
+ |
+displaySource(String filename, List<String> source, TargetEntry entry) { |
+ int line = entry.sourceLine; |
+ int column = entry.sourceColumn; |
+ int nameId = entry.sourceNameId; |
+ String id = nameId == null ? null : sourceMap.names[nameId]; |
+ selectedSource.children.clear(); |
+ SpanElement marker = new SpanElement() |
+ ..className = "marker" |
+ ..appendText("*"); |
+ for (int pos = 0; pos < source.length; pos++) { |
+ String l = source[pos]; |
+ if (pos != line) { |
+ selectedSource.children.add(l.isEmpty ? new BRElement() : new DivElement() |
+ ..appendText(l)); |
+ } else { |
+ selectedSource.children.add(new DivElement() |
+ ..appendText(l.substring(0, column)) |
+ ..children.add(marker) |
+ ..appendText(l.substring(column))); |
+ } |
+ } |
+ sourceFileName.text = filename; |
+ marker.scrollIntoView(); |
+} |
+ |
+void highlightSelectedSpan(TargetEntry entry, TargetLineEntry lineEntry) { |
+ selectedOutputSpan.children.clear(); |
+ String spanEndCol; |
+ TargetEntry spanEnd; |
+ bool nextEntryIsSpanEnd = false; |
+ for (TargetEntry e in lineEntry.entries) { |
+ if (nextEntryIsSpanEnd) { |
+ spanEnd = e; |
+ break; |
+ } |
+ if (e == entry) { |
+ nextEntryIsSpanEnd = true; |
+ } |
+ } |
+ if (spanEnd == null) { |
+ spanEndCol = '${target[lineEntry.line].length} (EOL).'; |
+ } else { |
+ spanEndCol = '${spanEnd.column}.'; |
+ } |
+ |
+ String targetSpan = |
+ 'Target: Line ${lineEntry.line} Col. ${entry.column} - $spanEndCol'; |
+ |
+ if (entry.sourceUrlId == null) { |
+ targetSpan += ' Source: unknown'; |
+ selectedOutputSpan.children.add(getTextElement(targetSpan)); |
+ return; |
+ } |
+ |
+ String source = sourceMap.urls[entry.sourceUrlId]; |
+ String sourceName = source.substring(source.lastIndexOf('/') + 1); |
+ String sourcePoint = |
+ 'Source: Line ${entry.sourceLine} Col. ${entry.sourceColumn}'; |
+ sourcePoint += |
+ entry.sourceNameId == null ? '' |
+ : ' (${sourceMap.names[entry.sourceNameId]})'; |
+ sourcePoint += ' in $sourceName'; |
+ selectedOutputSpan.children.add(getTextElement(targetSpan)); |
+ selectedOutputSpan.children.add(new BRElement()); |
+ selectedOutputSpan.children.add(getTextElement(sourcePoint)); |
+ |
+ if (highlightedMapEntry != null) { |
+ highlightedMapEntry[0].style.background = 'white'; |
+ highlightedMapEntry[1].style.background = 'white'; |
+ } |
+ |
+ String highlightColor = "#99ff99"; |
+ highlightedMapEntry = targetEntryMap[entry]; |
+ highlightedMapEntry[0] |
+ ..scrollIntoView() |
+ ..style.backgroundColor = highlightColor; |
+ highlightedMapEntry[1] |
+ ..scrollIntoView() |
+ ..style.backgroundColor = highlightColor; |
+ highlightedMapEntry[1].onMouseOver.listen((e) { |
+ selectedOutputSpan.style.zIndex = "2"; |
+ selectedOutputSpan.style.visibility = "visible"; |
+ selectedOutputSpan.style.top = "${decodedMap.offsetTo(document.body).y + |
+ decodedMap.clientHeight - 20}px"; |
+ selectedOutputSpan.style.left = "${decodedMap.offsetTo(document.body).x}px"; |
+ selectedOutputSpan.style.width= "${decodedMap.clientWidth}px"; |
+ }); |
+ |
+ highlightedMapEntry[1].onMouseOut.listen( (e) { |
+ selectedOutputSpan.style.visibility = "hidden"; |
+ }); |
+ |
+ adjustDivHeightsToWindow(); |
+} |
+ |
+void loadSource(TargetEntry entry) { |
+ if (entry.sourceUrlId == null) { |
+ return; |
+ } |
+ |
+ String source = sourceMap.urls[entry.sourceUrlId]; |
+ fetchFile(new Uri(path: "/file", |
+ queryParameters: {"path": source}).toString()).then((text) |
+ => displaySource(source, text.split("\n"), entry)); |
+ selectedSource.text = "loading"; |
+} |
+ |
+SpanElement createSpan(String content, TargetEntry entry, |
+ TargetLineEntry lineEntry) { |
+ return new SpanElement() |
+ ..addEventListener('click', (e) { |
+ loadSource(entry); |
+ highlightSelectedSpan(entry, lineEntry); |
+ }, false) |
+ ..className = "range${entry.sourceUrlId % 4}" |
+ ..appendText(content); |
+} |
+ |
+Element getLineNumberElement(int line) { |
+ SpanElement result = new SpanElement(); |
+ result.style.fontFamily = "Courier"; |
+ result.style.fontSize = "10pt"; |
+ result.appendText("${line} "); |
+ return result; |
+} |
+ |
+Element getTextElement(String text) { |
+ SpanElement result = new SpanElement(); |
+ result.text = text; |
+ return result; |
+} |
+ |
+addTargetLine(int lineNumber, String content, TargetLineEntry lineEntry) { |
+ if (content.isEmpty) { |
+ generatedOutput.children.add(new DivElement() |
+ ..children.add(getLineNumberElement(lineNumber))); |
+ return; |
+ } |
+ if (lineEntry == null) { |
+ generatedOutput.children.add(new DivElement() |
+ ..children.add(getLineNumberElement(lineNumber)) |
+ ..children.add(getTextElement(content))); |
+ return; |
+ } |
+ DivElement div = new DivElement(); |
+ div.children.add(getLineNumberElement(lineNumber)); |
+ |
+ int pos = 0; |
+ TargetEntry previous = null; |
+ for (TargetEntry next in lineEntry.entries) { |
+ if (previous == null) { |
+ if (pos < next.column) { |
+ div.appendText(content.substring(pos, next.column)); |
+ } |
+ if (content.length == next.column) { |
+ div.children.add(createSpan(" ", next, lineEntry)); |
+ } |
+ } else { |
+ if (next.column <= content.length) { |
+ String token = content.substring(pos, next.column); |
+ div.children.add(createSpan(token, previous, lineEntry)); |
+ } |
+ if (content.length == next.column) { |
+ div.children.add(createSpan(" ", next, lineEntry)); |
+ } |
+ } |
+ pos = next.column; |
+ previous = next; |
+ } |
+ String token = content.substring(pos); |
+ if (previous == null) { |
+ div.appendText(token); |
+ } else { |
+ div..children.add(createSpan(token, previous, lineEntry)); |
+ } |
+ generatedOutput.children.add(div); |
+} |
+ |
+// Display the target source in the HTML. |
+void displayTargetSource() { |
+ List<TargetLineEntry> targetLines = sourceMap.lines; |
+ int linesIndex = 0; |
+ for (int line = 0; line < target.length; line++) { |
+ TargetLineEntry entry = null; |
+ if (linesIndex < targetLines.length |
+ && targetLines[linesIndex].line == line) { |
+ entry = targetLines[linesIndex]; |
+ linesIndex++; |
+ } |
+ if (entry != null) { |
+ addTargetLine(line, target[line], entry); |
+ } else { |
+ addTargetLine(line, target[line], null); |
+ } |
+ } |
+} |
+ |
+String getMappedData(String mapFileContent) { |
+ // Source map contains mapping information in this format: |
+ // "mappings": "A;A,yC;" |
+ List<String> mapEntry = mapFileContent.split('mappings'); |
+ return mapEntry[mapEntry.length-1].split('"')[2]; |
+} |
+ |
+SpanElement createMapSpan(String segment) { |
+ return new SpanElement()..text = segment; |
+} |
+ |
+SpanElement createDecodedMapSpan(TargetEntry entry) { |
+ return new SpanElement()..text = '(${entry.column}, ${entry.sourceUrlId},' |
+ ' ${entry.sourceLine},' |
+ ' ${entry.sourceColumn})'; |
+} |
+ |
+displayMap(String mapFileContent) { |
+ String mappedData = getMappedData(mapFileContent); |
+ int sourceMapLine = 0; |
+ for (String group in mappedData.split(';')) { |
+ if (group.length == 0) continue; |
+ |
+ List<String> segments = []; |
+ if (!group.contains(',')) { |
+ segments.add(group); |
+ } else { |
+ segments = group.split(','); |
+ } |
+ |
+ TargetLineEntry targetLineEntry = sourceMap.lines[sourceMapLine]; |
+ decodedMap.children.add(getLineNumberElement(targetLineEntry.line)); |
+ originalMap.children.add(getLineNumberElement(targetLineEntry.line)); |
+ bool first = true; |
+ int entryNumber = 0; |
+ for (String segment in segments) { |
+ TargetEntry entry = targetLineEntry.entries[entryNumber]; |
+ SpanElement orignalMapSpan = createMapSpan(segment); |
+ SpanElement decodedMapSpan = createDecodedMapSpan(entry); |
+ if (first) { |
+ first = false; |
+ } else { |
+ originalMap.children.add(getTextElement(', ')); |
+ decodedMap.children.add(getTextElement(', ')); |
+ } |
+ originalMap.children.add(orignalMapSpan); |
+ decodedMap.children.add(decodedMapSpan); |
+ ++entryNumber; |
+ targetEntryMap.putIfAbsent(entry, () => [orignalMapSpan, decodedMapSpan]); |
+ } |
+ originalMap.children.add(new BRElement()); |
+ decodedMap.children.add(new BRElement()); |
+ ++sourceMapLine; |
+ } |
+} |
+ |
+void main() { |
+ Future load(String q) => fetchFile(new Uri(path: "/file", queryParameters: { |
+ "path": q |
+ }).toString()); |
+ |
+ getMap().then((mapFileName) { |
+ load(mapFileName).then((mapFileContent) { |
+ sourceMap = new SingleMapping.fromJson(JSON.decode(mapFileContent)); |
+ displayMap(mapFileContent); |
+ targetFileName.text = sourceMap.targetUrl; |
+ load(targetFileName.text).then((targetFileContent) { |
+ target = targetFileContent.split('\n'); |
+ displayTargetSource(); |
+ adjustDivHeightsToWindow(); |
+ }); |
+ }); |
+ }); |
+ |
+ sourceFileName.text = "<source not selected>"; |
+} |