Reland "[ Service / DDS ] Add method that can return local paths"

This is a reland of commit a091ff7b27a322e502c5d22f1150da3a76808d07

TEST=Check that a supplied URL conversion function is correctly applied when the `local` param is true.

Original change's description:
> [ Service / DDS ] Add method that can return local paths
>
> TEST=Check that a supplied URL conversion function is correctly applied when the `local` param is true.
>
> Change-Id: Ibe80b6229c574c976379a519baca5d1904b684b2
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245040
> Reviewed-by: Ben Konyi <bkonyi@google.com>
> Reviewed-by: Kenzie Davisson <kenzieschmoll@google.com>
> Commit-Queue: Ben Konyi <bkonyi@google.com>

Change-Id: I87433a410715393f853a6538dbfe67391e0c773b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246621
Commit-Queue: Helin Shiah <helinx@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index 7d833ab..177a926 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -11,6 +11,8 @@
 
 import 'src/dds_impl.dart';
 
+typedef UriConverter = String? Function(String uri);
+
 /// An intermediary between a Dart VM service and its clients that offers
 /// additional functionality on top of the standard VM service protocol.
 ///
@@ -49,6 +51,7 @@
     List<String> cachedUserTags = const [],
     DevToolsConfiguration? devToolsConfiguration,
     bool logRequests = false,
+    UriConverter? uriConverter,
   }) async {
     if (!remoteVmServiceUri.isScheme('http')) {
       throw ArgumentError(
@@ -89,6 +92,7 @@
       devToolsConfiguration,
       logRequests,
       enableServicePortFallback,
+      uriConverter,
     );
     await service.startService();
     return service;
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index 5b3abdc..a5e47bc 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.dart
@@ -248,6 +248,11 @@
       dds.expressionEvaluator.execute,
     );
 
+    _clientPeer.registerMethod(
+      'lookupResolvedPackageUris',
+      dds.packageUriConverter.convert,
+    );
+
     // When invoked within a fallback, the next fallback will start executing.
     // The final fallback forwards the request to the VM service directly.
     @alwaysThrows
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index a52fd13..33be98f 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -26,6 +26,7 @@
 import 'devtools/handler.dart';
 import 'expression_evaluator.dart';
 import 'isolate_manager.dart';
+import 'package_uri_converter.dart';
 import 'stream_manager.dart';
 
 @visibleForTesting
@@ -59,11 +60,13 @@
     this._devToolsConfiguration,
     this.shouldLogRequests,
     this._enableServicePortFallback,
+    this.uriConverter,
   ) {
     _clientManager = ClientManager(this);
     _expressionEvaluator = ExpressionEvaluator(this);
     _isolateManager = IsolateManager(this);
     _streamManager = StreamManager(this);
+    _packageUriConverter = PackageUriConverter(this);
     _authCode = _authCodesEnabled ? _makeAuthToken() : '';
   }
 
@@ -433,6 +436,10 @@
   bool _initializationComplete = false;
   bool _shuttingDown = false;
 
+  UriConverter? uriConverter;
+  PackageUriConverter get packageUriConverter => _packageUriConverter;
+  late PackageUriConverter _packageUriConverter;
+
   ClientManager get clientManager => _clientManager;
   late ClientManager _clientManager;
 
diff --git a/pkg/dds/lib/src/package_uri_converter.dart b/pkg/dds/lib/src/package_uri_converter.dart
new file mode 100644
index 0000000..0f9b64a
--- /dev/null
+++ b/pkg/dds/lib/src/package_uri_converter.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, 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 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+
+import 'dds_impl.dart';
+
+/// Converts from `package:` URIs to resolved file paths.
+class PackageUriConverter {
+  PackageUriConverter(this.dds);
+
+  Future<Map<String, dynamic>> convert(json_rpc.Parameters parameters) async {
+    final isolateId = parameters['isolateId'].asString;
+    final uris = parameters['uris'].asList;
+    final useLocalResolver = parameters['local'].asBoolOr(false);
+
+    final params = <String, dynamic>{
+      'isolateId': isolateId,
+      'uris': uris,
+    };
+    final result = await dds.vmServiceClient.sendRequest(
+      'lookupResolvedPackageUris',
+      params,
+    );
+
+    final converter = dds.uriConverter;
+    if (converter != null && useLocalResolver) {
+      final vmUris = result['uris'];
+
+      final localUris = uris.map((x) => converter(x as String)).toList();
+
+      final resultUris = <String?>[
+        for (var i = 0; i < vmUris.length; i++) localUris[i] ?? vmUris[i],
+      ];
+
+      return <String, dynamic>{
+        'type': 'UriList',
+        'uris': resultUris,
+      };
+    }
+    return result;
+  }
+
+  final DartDevelopmentServiceImpl dds;
+}
diff --git a/pkg/dds/test/uri_converter_test.dart b/pkg/dds/test/uri_converter_test.dart
new file mode 100644
index 0000000..2d82ea6
--- /dev/null
+++ b/pkg/dds/test/uri_converter_test.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2021, 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:async';
+import 'dart:io';
+
+import 'package:dds/dds.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+import 'package:vm_service/vm_service_io.dart';
+import 'common/test_helper.dart';
+
+void main() {
+  late Process process;
+  DartDevelopmentService? dds;
+
+  setUp(() async {
+    process = await spawnDartProcess(
+      'get_cached_cpu_samples_script.dart',
+      disableServiceAuthCodes: true,
+    );
+  });
+
+  tearDown(() async {
+    await dds?.shutdown();
+    process.kill();
+  });
+
+  test(
+    'DDS returns local paths with a converter',
+    () async {
+      Uri serviceUri = remoteVmServiceUri;
+      dds = await DartDevelopmentService.startDartDevelopmentService(
+        remoteVmServiceUri,
+        uriConverter: (uri) => uri == 'package:test/has_local.dart'
+            ? 'file:///has_local.dart'
+            : null,
+      );
+      serviceUri = dds!.wsUri!;
+      expect(dds!.isRunning, true);
+      final service = await vmServiceConnectUri(serviceUri.toString());
+
+      IsolateRef isolate;
+      while (true) {
+        final vm = await service.getVM();
+        if (vm.isolates!.isNotEmpty) {
+          isolate = vm.isolates!.first;
+          try {
+            isolate = await service.getIsolate(isolate.id!);
+            if ((isolate as Isolate).runnable!) {
+              break;
+            }
+          } on SentinelException {
+            // ignore
+          }
+        }
+        await Future.delayed(const Duration(seconds: 1));
+      }
+      expect(isolate, isNotNull);
+
+      final unresolvedUris = <String>[
+        'dart:io', // dart:io -> org-dartlang-sdk:///sdk/lib/io/io.dart
+        'package:test/has_local.dart', // package:test/test.dart -> file:///has_local.dart
+        'package:does_not_exist/does_not_exist.dart', // invalid URI -> null
+      ];
+      var result = await service
+          .lookupResolvedPackageUris(isolate.id!, unresolvedUris, local: true);
+
+      expect(result.uris?[0], 'org-dartlang-sdk:///sdk/lib/io/io.dart');
+      expect(result.uris?[1], 'file:///has_local.dart');
+      expect(result.uris?[2], null);
+    },
+    timeout: Timeout.none,
+  );
+}
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index 3e17349..8aee343 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.57
+version=3.58
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index b859add..de2a2ea 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -26,7 +26,7 @@
         HeapSnapshotObjectNoData,
         HeapSnapshotObjectNullData;
 
-const String vmServiceVersion = '3.57.0';
+const String vmServiceVersion = '3.58.0';
 
 /// @optional
 const String optional = 'optional';
@@ -945,9 +945,12 @@
   /// If a URI is not known, the corresponding entry in the [UriList] response
   /// will be `null`.
   ///
+  /// If `local` is true, the VM will attempt to return local file paths instead
+  /// of relative paths, but this is not guaranteed.
+  ///
   /// See [UriList].
-  Future<UriList> lookupResolvedPackageUris(
-      String isolateId, List<String> uris);
+  Future<UriList> lookupResolvedPackageUris(String isolateId, List<String> uris,
+      {bool? local});
 
   /// The `lookupPackageUris` RPC is used to convert a list of URIs to their
   /// unresolved paths. For example, URIs passed to this RPC are mapped in the
@@ -1552,6 +1555,7 @@
           response = await _serviceImplementation.lookupResolvedPackageUris(
             params!['isolateId'],
             List<String>.from(params['uris'] ?? []),
+            local: params['local'],
           );
           break;
         case 'lookupPackageUris':
@@ -2086,10 +2090,13 @@
       _call('kill', {'isolateId': isolateId});
 
   @override
-  Future<UriList> lookupResolvedPackageUris(
-          String isolateId, List<String> uris) =>
-      _call(
-          'lookupResolvedPackageUris', {'isolateId': isolateId, 'uris': uris});
+  Future<UriList> lookupResolvedPackageUris(String isolateId, List<String> uris,
+          {bool? local}) =>
+      _call('lookupResolvedPackageUris', {
+        'isolateId': isolateId,
+        'uris': uris,
+        if (local != null) 'local': local,
+      });
 
   @override
   Future<UriList> lookupPackageUris(String isolateId, List<String> uris) =>
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index fc2f766..8c07540 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.57
+# Dart VM Service Protocol 3.58
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.57_ of the Dart VM Service Protocol. This
+This document describes of _version 3.58_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -1220,7 +1220,7 @@
 ### lookupResolvedPackageUris
 
 ```
-UriList lookupResolvedPackageUris(string isolateId, string[] uris)
+UriList lookupResolvedPackageUris(string isolateId, string[] uris, bool local [optional])
 ```
 
 The _lookupResolvedPackageUris_ RPC is used to convert a list of URIs to their
@@ -1234,6 +1234,8 @@
 If a URI is not known, the corresponding entry in the [UriList] response will be
 `null`.
 
+If `local` is true, the VM will attempt to return local file paths instead of relative paths, but this is not guaranteed.
+
 See [UriList](#urilist).
 
 ### lookupPackageUris
@@ -4369,5 +4371,6 @@
 3.55 | Added `streamCpuSamplesWithUserTag` RPC.
 3.56 | Added optional `line` and `column` properties to `SourceLocation`. Added a new `SourceReportKind`, `BranchCoverage`, which reports branch level coverage information.
 3.57 | Added optional `libraryFilters` parameter to `getSourceReport` RPC.
+3.58 | Added optional `local` parameter to `lookupResolvedPackageUris` RPC.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss