// 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; 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? 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 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)); }