| // 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 'package:js/js.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:source_maps/source_maps.dart'; |
| import 'package:source_span/source_span.dart'; |
| import 'package:stack_trace/stack_trace.dart'; |
| |
| import 'source_map_stack_trace.dart'; |
| |
| typedef ReadyCallback = void Function(); |
| |
| /// Global object DDC uses to see if a stack trace utility has been registered. |
| @JS(r'$dartStackTraceUtility') |
| external set dartStackTraceUtility(DartStackTraceUtility value); |
| |
| @JS(r'$dartLoader.rootDirectories') |
| external List get rootDirectories; |
| |
| typedef StackTraceMapper = String Function(String stackTrace); |
| typedef SourceMapProvider = dynamic Function(String modulePath); |
| typedef SetSourceMapProvider = void Function(SourceMapProvider); |
| |
| @JS() |
| @anonymous |
| class DartStackTraceUtility { |
| external factory DartStackTraceUtility( |
| {StackTraceMapper? mapper, SetSourceMapProvider? setSourceMapProvider}); |
| } |
| |
| @JS('JSON.stringify') |
| external String _stringify(dynamic json); |
| |
| /// 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 { |
| final MappingBundle _bundle = MappingBundle(); |
| final SourceMapProvider _provider; |
| |
| LazyMapping(this._provider); |
| |
| List toJson() => _bundle.toJson(); |
| |
| @override |
| SourceMapSpan? spanFor(int line, int column, |
| {Map<String, SourceFile>? files, String? uri}) { |
| if (uri == null) { |
| throw ArgumentError.notNull('uri'); |
| } |
| |
| if (!_bundle.containsMapping(uri)) { |
| var rawMap = _provider(uri); |
| if (rawMap != null) { |
| var strMap = rawMap is String ? rawMap : _stringify(rawMap); |
| var mapping = parse(strMap) as SingleMapping; |
| mapping |
| ..targetUrl = uri |
| ..sourceRoot = '${p.dirname(uri)}/'; |
| _bundle.addMapping(mapping); |
| } |
| } |
| var span = _bundle.spanFor(line, column, files: files, uri: uri); |
| // TODO(jacobr): we shouldn't have to filter out invalid sourceUrl entries |
| // here. |
| if (span == null || span.start.sourceUrl == null) return null; |
| var pathSegments = span.start.sourceUrl!.pathSegments; |
| if (pathSegments.isNotEmpty && pathSegments.last == 'null') return null; |
| return span; |
| } |
| } |
| |
| LazyMapping? _mapping; |
| |
| List<String> roots = rootDirectories.map((s) => '$s').toList(); |
| |
| String mapper(String rawStackTrace) { |
| var mapping = _mapping; |
| if (mapping == null) { |
| // This should not happen if the user has waited for the ReadyCallback |
| // to start the application. |
| throw StateError('Source maps are not done loading.'); |
| } |
| var trace = Trace.parse(rawStackTrace); |
| return mapStackTrace(mapping, trace, roots: roots).toString(); |
| } |
| |
| void setSourceMapProvider(SourceMapProvider provider) { |
| _mapping = LazyMapping(provider); |
| } |
| |
| void main() { |
| // Register with DDC. |
| dartStackTraceUtility = DartStackTraceUtility( |
| mapper: allowInterop(mapper), |
| setSourceMapProvider: allowInterop(setSourceMapProvider)); |
| } |