Chromium Code Reviews| 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..498ac3141628b1e5d12b4c55a6c54b409321dfc4 |
| --- /dev/null |
| +++ b/pkg/dev_compiler/web/stack_trace_mapper.dart |
| @@ -0,0 +1,145 @@ |
| +// 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 registed. |
|
vsm
2017/03/08 15:34:44
registed -> registered
Jacob
2017/03/09 02:28:42
Done.
|
| +@JS(r'$dartStackTraceUtility') |
| +external set dartStackTraceUtility(DartStackTraceUtility value); |
| + |
| +typedef String StackTraceMapper(String stackTrace); |
| +typedef dynamic AddLoadedListener(ReadyCallback callback); |
| + |
| +@JS() |
| +@anonymous |
| +class DartStackTraceUtility { |
| + external factory DartStackTraceUtility( |
| + {StackTraceMapper mapper, AddLoadedListener addLoadedListener}); |
| +} |
| + |
| +/// 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('Souce maps are not done loading.'); |
|
vsm
2017/03/08 15:34:44
Souce -> Source
Jacob
2017/03/09 02:28:42
Done.
Clealy my compute keyboad 'r' key needs to
|
| + } |
| + return mapStackTrace(_mapping, new Trace.parse(rawStackTrace)).toString(); |
| +} |
| + |
| +Future<Null> addLoadedListener(ReadyCallback callback) async { |
| + var scripts = document.querySelectorAll('script'); |
| + var sourceMapFutures = <Future<String>>[]; |
| + var urls = <String>[]; |
| + for (ScriptElement script in scripts) { |
|
vsm
2017/03/08 15:34:44
Would it make more sense to do this in the bootstr
Jacob
2017/03/09 02:28:42
Good point.
I've switched up the code so the API
|
| + // By convention only attempt to load source maps for scripts tagged with |
| + // the data attribute loadSourceMap avoiding triggering 404 errors for |
| + // regular JS scripts on the page and Dart scripts that we should not load |
| + // source maps for. |
| + if (!script.dataset.containsKey('loadSourceMap')) continue; |
| + var src = script.src; |
| + if (src.isEmpty) continue; |
| + urls.add(src); |
| + sourceMapFutures.add(loadSourceMap(src)); |
| + } |
| + List<String> sourceMaps = await Future.wait(sourceMapFutures); |
| + _mapping = new LazyMapping(new Map.fromIterables(urls, sourceMaps)); |
| + callback(); |
| +} |
| + |
| +main() { |
| + // Register with DDC. |
| + dartStackTraceUtility = new DartStackTraceUtility( |
| + mapper: allowInterop(mapper), |
| + addLoadedListener: allowInterop(addLoadedListener)); |
| +} |