Index: pkg/dev_compiler/web/stack_trace_mapper.dart |
diff --git a/pkg/dev_compiler/web/stack_trace_mapper.dart b/pkg/dev_compiler/web/stack_trace_mapper.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..557e6fd6ddf1e4db0aaf3e67a442de81140b8549 |
--- /dev/null |
+++ b/pkg/dev_compiler/web/stack_trace_mapper.dart |
@@ -0,0 +1,134 @@ |
+// Copyright (c) 2017, 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. |
+ |
+/// Standalone utility that manages loading source maps for all Dart scripts |
+/// on the page compiled with DDC. |
+/// |
+/// Example JavaScript usage: |
+/// $dartStackTraceUtility.addLoadedListener(function() { |
+/// // All Dart source maps are now loaded. It is now safe to start your |
+/// // Dart application compiled with DDC. |
+/// dart_library.start('your_dart_application'); |
+/// }) |
+/// |
+/// If $dartStackTraceUtility is set, the dart:core StackTrace class calls |
+/// $dartStackTraceUtility.mapper(someJSStackTrace) |
+/// to apply source maps. |
+/// |
+/// This utility can be compiled to JavaScript using Dart2JS while the rest |
+/// of the application is compiled with DDC or could be compiled with DDC. |
+ |
+@JS() |
+library stack_trace_mapper; |
+ |
+import 'dart:async'; |
+import 'dart:html'; |
+ |
+import 'package:js/js.dart'; |
+import 'package:path/path.dart' as path; |
+import 'package:source_map_stack_trace/source_map_stack_trace.dart'; |
+import 'package:source_maps/source_maps.dart'; |
+import 'package:source_span/source_span.dart'; |
+import 'package:stack_trace/stack_trace.dart'; |
+ |
+typedef void ReadyCallback(); |
+ |
+/// Global object DDC uses to see if a stack trace utility has been registered. |
+@JS(r'$dartStackTraceUtility') |
+external set dartStackTraceUtility(DartStackTraceUtility value); |
+ |
+typedef String StackTraceMapper(String stackTrace); |
+typedef dynamic LoadSourceMaps(List<String> scripts, ReadyCallback callback); |
+ |
+@JS() |
+@anonymous |
+class DartStackTraceUtility { |
+ external factory DartStackTraceUtility( |
+ {StackTraceMapper mapper, LoadSourceMaps loadSourceMaps}); |
+} |
+ |
+/// Source mapping that is waits to parse source maps until they match the uri |
+/// of a requested source map. |
+/// |
+/// This improves startup performance compared to using MappingBundle directly. |
+/// The unparsed data for the source maps must still be loaded before |
+/// LazyMapping is used. |
+class LazyMapping extends Mapping { |
+ MappingBundle _bundle = new MappingBundle(); |
+ |
+ /// Map from url to unparsed source map. |
+ Map<String, String> _sourceMaps; |
+ |
+ LazyMapping(this._sourceMaps) {} |
+ |
+ List toJson() => _bundle.toJson(); |
+ |
+ SourceMapSpan spanFor(int line, int column, |
+ {Map<String, SourceFile> files, String uri}) { |
+ if (uri == null) { |
+ throw new ArgumentError.notNull('uri'); |
+ } |
+ var rawMap = _sourceMaps[uri]; |
+ |
+ if (rawMap != null && rawMap.isNotEmpty && !_bundle.containsMapping(uri)) { |
+ SingleMapping mapping = parse(rawMap); |
+ mapping |
+ ..targetUrl = uri |
+ ..sourceRoot = '${path.dirname(uri)}/'; |
+ _bundle.addMapping(mapping); |
+ } |
+ |
+ return _bundle.spanFor(line, column, files: files, uri: uri); |
+ } |
+} |
+ |
+String _toSourceMapLocation(String url) { |
+ // The url may have cache busting query parameters which we need to maintain |
+ // in the source map url. |
+ // For example: |
+ // http://localhost/foo.js?cachebusting=23419 |
+ // Should get source map |
+ // http://localhost/foo.js.map?cachebusting=23419 |
+ var uri = Uri.parse(url); |
+ return uri.replace(path: '${uri.path}.map').toString(); |
+} |
+ |
+/// Load a source map for the specified url. |
+/// |
+/// Returns a null string rather than reporting an error if the file cannot be |
+/// found as we don't want to throw errors if a few source maps are missing. |
+Future<String> loadSourceMap(String url) async { |
+ try { |
+ return await HttpRequest.getString(_toSourceMapLocation(url)); |
+ } catch (e) { |
+ return null; |
+ } |
+} |
+ |
+LazyMapping _mapping; |
+ |
+String mapper(String rawStackTrace) { |
+ if (_mapping == null) { |
+ // This should not happen if the user has waited for the ReadyCallback |
+ // to start the application. |
+ throw new StateError('Source maps are not done loading.'); |
+ } |
+ return mapStackTrace(_mapping, new Trace.parse(rawStackTrace)).toString(); |
+} |
+ |
+Future<Null> loadSourceMaps( |
+ List<String> scripts, ReadyCallback callback) async { |
+ List<Future<String>> sourceMapFutures = |
+ scripts.map((script) => loadSourceMap(script)).toList(); |
+ List<String> sourceMaps = await Future.wait(sourceMapFutures); |
+ _mapping = new LazyMapping(new Map.fromIterables(scripts, sourceMaps)); |
+ callback(); |
+} |
+ |
+main() { |
+ // Register with DDC. |
+ dartStackTraceUtility = new DartStackTraceUtility( |
+ mapper: allowInterop(mapper), |
+ loadSourceMaps: allowInterop(loadSourceMaps)); |
+} |