| // Copyright (c) 2019, 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:convert'; |
| import 'dart:io'; |
| |
| import 'package:dwds/data/debug_event.dart'; |
| import 'package:dwds/data/register_event.dart'; |
| import 'package:dwds/src/config/tool_configuration.dart'; |
| import 'package:dwds/src/connections/app_connection.dart'; |
| import 'package:dwds/src/debugging/debugger.dart'; |
| import 'package:dwds/src/debugging/execution_context.dart'; |
| import 'package:dwds/src/debugging/inspector.dart'; |
| import 'package:dwds/src/debugging/instance.dart'; |
| import 'package:dwds/src/debugging/location.dart'; |
| import 'package:dwds/src/debugging/modules.dart'; |
| import 'package:dwds/src/debugging/remote_debugger.dart'; |
| import 'package:dwds/src/debugging/skip_list.dart'; |
| import 'package:dwds/src/events.dart'; |
| import 'package:dwds/src/readers/asset_reader.dart'; |
| import 'package:dwds/src/services/batched_expression_evaluator.dart'; |
| import 'package:dwds/src/services/expression_compiler.dart'; |
| import 'package:dwds/src/services/expression_evaluator.dart'; |
| import 'package:dwds/src/utilities/dart_uri.dart'; |
| import 'package:dwds/src/utilities/shared.dart'; |
| import 'package:logging/logging.dart' hide LogRecord; |
| import 'package:pub_semver/pub_semver.dart' as semver; |
| import 'package:vm_service/vm_service.dart' hide vmServiceVersion; |
| import 'package:vm_service_interface/vm_service_interface.dart'; |
| import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; |
| |
| /// A proxy from the chrome debug protocol to the dart vm service protocol. |
| class ChromeProxyService implements VmServiceInterface { |
| /// Cache of all existing StreamControllers. |
| /// |
| /// These are all created through [onEvent]. |
| final _streamControllers = <String, StreamController<Event>>{}; |
| |
| /// The root `VM` instance. There can only be one of these, but its isolates |
| /// are dynamic and roughly map to chrome tabs. |
| final VM _vm; |
| |
| /// Signals when isolate is initialized. |
| Future<void> get isInitialized => _initializedCompleter.future; |
| Completer<void> _initializedCompleter = Completer<void>(); |
| |
| /// Signals when isolate starts. |
| Future<void> get isStarted => _startedCompleter.future; |
| Completer<void> _startedCompleter = Completer<void>(); |
| |
| /// Signals when expression compiler is ready to evaluate. |
| Future<void> get isCompilerInitialized => _compilerCompleter.future; |
| Completer<void> _compilerCompleter = Completer<void>(); |
| |
| /// The root at which we're serving. |
| final String root; |
| |
| final RemoteDebugger remoteDebugger; |
| final ExecutionContext executionContext; |
| |
| final AssetReader _assetReader; |
| |
| final Locations _locations; |
| |
| final SkipLists _skipLists; |
| |
| final Modules _modules; |
| |
| /// Provides debugger-related functionality. |
| Future<Debugger> get debuggerFuture => _debuggerCompleter.future; |
| final _debuggerCompleter = Completer<Debugger>(); |
| |
| /// Provides variable inspection functionality. |
| AppInspector get inspector { |
| if (_inspector == null) { |
| throw StateError('No running isolate (inspector is not set).'); |
| } |
| return _inspector!; |
| } |
| |
| AppInspector? _inspector; |
| |
| /// Determines if there an isolate running currently. |
| /// |
| /// [_inspector] is `null` iff the isolate is not running, |
| /// for example, before the first isolate starts or during |
| /// a hot restart. |
| bool get _isIsolateRunning => _inspector != null; |
| |
| StreamSubscription<ConsoleAPIEvent>? _consoleSubscription; |
| |
| final _disabledBreakpoints = <Breakpoint>{}; |
| final _previousBreakpoints = <Breakpoint>{}; |
| |
| final _logger = Logger('ChromeProxyService'); |
| |
| final ExpressionCompiler? _compiler; |
| ExpressionEvaluator? _expressionEvaluator; |
| |
| bool terminatingIsolates = false; |
| |
| ChromeProxyService._( |
| this._vm, |
| this.root, |
| this._assetReader, |
| this.remoteDebugger, |
| this._modules, |
| this._locations, |
| this._skipLists, |
| this.executionContext, |
| this._compiler, |
| ) { |
| final debugger = Debugger.create( |
| remoteDebugger, |
| _streamNotify, |
| _locations, |
| _skipLists, |
| root, |
| ); |
| debugger.then(_debuggerCompleter.complete); |
| } |
| |
| static Future<ChromeProxyService> create( |
| RemoteDebugger remoteDebugger, |
| String root, |
| AssetReader assetReader, |
| AppConnection appConnection, |
| ExecutionContext executionContext, |
| ExpressionCompiler? expressionCompiler, |
| ) async { |
| final vm = VM( |
| name: 'ChromeDebugProxy', |
| operatingSystem: Platform.operatingSystem, |
| startTime: DateTime.now().millisecondsSinceEpoch, |
| version: Platform.version, |
| isolates: [], |
| isolateGroups: [], |
| systemIsolates: [], |
| systemIsolateGroups: [], |
| targetCPU: 'Web', |
| hostCPU: 'DWDS', |
| architectureBits: -1, |
| pid: -1, |
| ); |
| |
| final modules = Modules(root); |
| final locations = Locations(assetReader, modules, root); |
| final skipLists = SkipLists(); |
| final service = ChromeProxyService._( |
| vm, |
| root, |
| assetReader, |
| remoteDebugger, |
| modules, |
| locations, |
| skipLists, |
| executionContext, |
| expressionCompiler, |
| ); |
| safeUnawaited(service.createIsolate(appConnection)); |
| return service; |
| } |
| |
| /// Initializes metadata in [Locations], [Modules], and [ExpressionCompiler]. |
| void _initializeEntrypoint(String entrypoint) { |
| _locations.initialize(entrypoint); |
| _modules.initialize(entrypoint); |
| _skipLists.initialize(); |
| // We do not need to wait for compiler dependencies to be updated as the |
| // [ExpressionEvaluator] is robust to evaluation requests during updates. |
| safeUnawaited(_updateCompilerDependencies(entrypoint)); |
| } |
| |
| Future<void> _updateCompilerDependencies(String entrypoint) async { |
| final loadStrategy = globalToolConfiguration.loadStrategy; |
| final moduleFormat = loadStrategy.moduleFormat; |
| final canaryFeatures = loadStrategy.buildSettings.canaryFeatures; |
| final experiments = loadStrategy.buildSettings.experiments; |
| |
| // TODO(annagrin): Read null safety setting from the build settings. |
| final metadataProvider = loadStrategy.metadataProviderFor(entrypoint); |
| final soundNullSafety = await metadataProvider.soundNullSafety; |
| |
| _logger.info('Initializing expression compiler for $entrypoint ' |
| 'with sound null safety: $soundNullSafety'); |
| |
| final compilerOptions = CompilerOptions( |
| moduleFormat: moduleFormat, |
| soundNullSafety: soundNullSafety, |
| canaryFeatures: canaryFeatures, |
| experiments: experiments, |
| ); |
| |
| final compiler = _compiler; |
| if (compiler != null) { |
| await compiler.initialize(compilerOptions); |
| final dependencies = |
| await loadStrategy.moduleInfoForEntrypoint(entrypoint); |
| await captureElapsedTime( |
| () async { |
| final result = await compiler.updateDependencies(dependencies); |
| // Expression evaluation is ready after dependencies are updated. |
| if (!_compilerCompleter.isCompleted) _compilerCompleter.complete(); |
| return result; |
| }, |
| (result) => DwdsEvent.compilerUpdateDependencies(entrypoint), |
| ); |
| } |
| } |
| |
| Future<void> _prewarmExpressionCompilerCache() async { |
| // Exit early if the expression evaluation is not enabled. |
| if (_compiler == null || _expressionEvaluator == null) { |
| return; |
| } |
| // Wait until the inspector is ready. |
| await isInitialized; |
| |
| // Pre-warm the flutter framework module cache in the compiler. |
| // |
| // Flutter inspector relies on evaluations in widget_inspector |
| // library, which is a part of the flutter framework module, to |
| // produce widget trees, draw the layout explorer, show hover |
| // cards etc. |
| // Pre-warming the cache while DevTools is still loading helps |
| // Flutter Inspector start faster. |
| final libraryToCache = await inspector.flutterWidgetInspectorLibrary; |
| if (libraryToCache != null) { |
| final isolateId = inspector.isolateRef.id; |
| final libraryId = libraryToCache.id; |
| if (isolateId != null && libraryId != null) { |
| _logger.finest( |
| 'Caching ${libraryToCache.uri} in expression compiler worker', |
| ); |
| await evaluate(isolateId, libraryId, 'true'); |
| } |
| } |
| } |
| |
| /// Creates a new isolate. |
| /// |
| /// Only one isolate at a time is supported, but they should be cleaned up |
| /// with [destroyIsolate] and recreated with this method there is a hot |
| /// restart or full page refresh. |
| Future<void> createIsolate(AppConnection appConnection) async { |
| // Inspector is null if the previous isolate is destroyed. |
| if (_isIsolateRunning) { |
| throw UnsupportedError( |
| 'Cannot create multiple isolates for the same app', |
| ); |
| } |
| // Waiting for the debugger to be ready before initializing the entrypoint. |
| // |
| // Note: moving `await debugger` after the `_initializeEntryPoint` call |
| // causes `getcwd` system calls to fail. Since that system call is used |
| // in first `Uri.base` call in the expression compiler service isolate, |
| // the expression compiler service will fail to start. |
| // Issue: https://github.com/dart-lang/webdev/issues/1282 |
| final debugger = await debuggerFuture; |
| final entrypoint = appConnection.request.entrypointPath; |
| _initializeEntrypoint(entrypoint); |
| |
| debugger.notifyPausedAtStart(); |
| _inspector = await AppInspector.create( |
| appConnection, |
| remoteDebugger, |
| _assetReader, |
| _locations, |
| root, |
| debugger, |
| executionContext, |
| ); |
| |
| final compiler = _compiler; |
| _expressionEvaluator = compiler == null |
| ? null |
| : BatchedExpressionEvaluator( |
| entrypoint, |
| inspector, |
| debugger, |
| _locations, |
| _modules, |
| compiler, |
| ); |
| |
| safeUnawaited(_prewarmExpressionCompilerCache()); |
| |
| await debugger.reestablishBreakpoints( |
| _previousBreakpoints, |
| _disabledBreakpoints, |
| ); |
| _disabledBreakpoints.clear(); |
| |
| safeUnawaited( |
| appConnection.onStart.then((_) { |
| debugger.resumeFromStart(); |
| _startedCompleter.complete(); |
| }), |
| ); |
| |
| safeUnawaited(appConnection.onDone.then((_) => destroyIsolate())); |
| |
| final isolateRef = inspector.isolateRef; |
| final timestamp = DateTime.now().millisecondsSinceEpoch; |
| |
| // Listen for `registerExtension` and `postEvent` calls. |
| _setUpChromeConsoleListeners(isolateRef); |
| |
| _vm.isolates?.add(isolateRef); |
| |
| _streamNotify( |
| 'Isolate', |
| Event( |
| kind: EventKind.kIsolateStart, |
| timestamp: timestamp, |
| isolate: isolateRef, |
| ), |
| ); |
| _streamNotify( |
| 'Isolate', |
| Event( |
| kind: EventKind.kIsolateRunnable, |
| timestamp: timestamp, |
| isolate: isolateRef, |
| ), |
| ); |
| |
| // TODO: We shouldn't need to fire these events since they exist on the |
| // isolate, but devtools doesn't recognize extensions after a page refresh |
| // otherwise. |
| for (var extensionRpc in inspector.isolate.extensionRPCs ?? []) { |
| _streamNotify( |
| 'Isolate', |
| Event( |
| kind: EventKind.kServiceExtensionAdded, |
| timestamp: timestamp, |
| isolate: isolateRef, |
| )..extensionRPC = extensionRpc, |
| ); |
| } |
| |
| // The service is considered initialized when the first isolate is created. |
| if (!_initializedCompleter.isCompleted) _initializedCompleter.complete(); |
| } |
| |
| /// Should be called when there is a hot restart or full page refresh. |
| /// |
| /// Clears out the [_inspector] and all related cached information. |
| void destroyIsolate() { |
| _logger.fine('Destroying isolate'); |
| if (!_isIsolateRunning) return; |
| |
| final isolate = inspector.isolate; |
| final isolateRef = inspector.isolateRef; |
| |
| _initializedCompleter = Completer<void>(); |
| _startedCompleter = Completer<void>(); |
| _compilerCompleter = Completer<void>(); |
| _streamNotify( |
| 'Isolate', |
| Event( |
| kind: EventKind.kIsolateExit, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| isolate: isolateRef, |
| ), |
| ); |
| _vm.isolates?.removeWhere((ref) => ref.id == isolate.id); |
| _inspector = null; |
| _previousBreakpoints.clear(); |
| _previousBreakpoints.addAll(isolate.breakpoints ?? []); |
| _expressionEvaluator?.close(); |
| _consoleSubscription?.cancel(); |
| _consoleSubscription = null; |
| } |
| |
| Future<void> disableBreakpoints() async { |
| _disabledBreakpoints.clear(); |
| if (!_isIsolateRunning) return; |
| final isolate = inspector.isolate; |
| |
| _disabledBreakpoints.addAll(isolate.breakpoints ?? []); |
| for (var breakpoint in isolate.breakpoints?.toList() ?? []) { |
| await (await debuggerFuture).removeBreakpoint(breakpoint.id); |
| } |
| } |
| |
| @override |
| Future<Breakpoint> addBreakpoint( |
| String isolateId, |
| String scriptId, |
| int line, { |
| int? column, |
| }) { |
| return wrapInErrorHandlerAsync( |
| 'addBreakpoint', |
| () => _addBreakpoint(isolateId, scriptId, line), |
| ); |
| } |
| |
| Future<Breakpoint> _addBreakpoint( |
| String isolateId, |
| String scriptId, |
| int line, { |
| int? column, |
| }) async { |
| await isInitialized; |
| _checkIsolate('addBreakpoint', isolateId); |
| return (await debuggerFuture).addBreakpoint(scriptId, line, column: column); |
| } |
| |
| @override |
| Future<Breakpoint> addBreakpointAtEntry(String isolateId, String functionId) { |
| return _rpcNotSupportedFuture('addBreakpointAtEntry'); |
| } |
| |
| @override |
| Future<Breakpoint> addBreakpointWithScriptUri( |
| String isolateId, |
| String scriptUri, |
| int line, { |
| int? column, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'addBreakpointWithScriptUri', |
| () => _addBreakpointWithScriptUri( |
| isolateId, |
| scriptUri, |
| line, |
| column: column, |
| ), |
| ); |
| |
| Future<Breakpoint> _addBreakpointWithScriptUri( |
| String isolateId, |
| String scriptUri, |
| int line, { |
| int? column, |
| }) async { |
| await isInitialized; |
| _checkIsolate('addBreakpointWithScriptUri', isolateId); |
| if (Uri.parse(scriptUri).scheme == 'dart') { |
| // TODO(annagrin): Support setting breakpoints in dart SDK locations. |
| // Issue: https://github.com/dart-lang/webdev/issues/1584 |
| throw RPCError( |
| 'addBreakpoint', |
| 102, |
| 'The VM is unable to add a breakpoint ' |
| 'at the specified line or function: $scriptUri:$line:$column: ' |
| 'breakpoints in dart SDK locations are not supported yet.'); |
| } |
| final dartUri = DartUri(scriptUri, root); |
| final scriptRef = await inspector.scriptRefFor(dartUri.serverPath); |
| final scriptId = scriptRef?.id; |
| if (scriptId == null) { |
| throw RPCError( |
| 'addBreakpoint', |
| 102, |
| 'The VM is unable to add a breakpoint ' |
| 'at the specified line or function: $scriptUri:$line:$column: ' |
| 'cannot find script ID for ${dartUri.serverPath}'); |
| } |
| return (await debuggerFuture).addBreakpoint(scriptId, line, column: column); |
| } |
| |
| @override |
| Future<Response> callServiceExtension( |
| String method, { |
| String? isolateId, |
| Map? args, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'callServiceExtension', |
| () => _callServiceExtension( |
| method, |
| isolateId: isolateId, |
| args: args, |
| ), |
| ); |
| |
| Future<Response> _callServiceExtension( |
| String method, { |
| String? isolateId, |
| Map? args, |
| }) async { |
| await isInitialized; |
| isolateId ??= _inspector?.isolate.id; |
| _checkIsolate('callServiceExtension', isolateId); |
| args ??= <String, String>{}; |
| final stringArgs = args.map( |
| (k, v) => MapEntry( |
| k is String ? k : jsonEncode(k), |
| v is String ? v : jsonEncode(v), |
| ), |
| ); |
| final expression = ''' |
| ${globalToolConfiguration.loadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension( |
| "$method", JSON.stringify(${jsonEncode(stringArgs)})); |
| '''; |
| final result = await inspector.jsEvaluate(expression, awaitPromise: true); |
| final decodedResponse = |
| jsonDecode(result.value as String) as Map<String, dynamic>; |
| if (decodedResponse.containsKey('code') && |
| decodedResponse.containsKey('message') && |
| decodedResponse.containsKey('data')) { |
| // ignore: only_throw_errors |
| throw RPCError( |
| method, |
| decodedResponse['code'] as int, |
| decodedResponse['message'] as String, |
| decodedResponse['data'] as Map, |
| ); |
| } else { |
| return Response()..json = decodedResponse; |
| } |
| } |
| |
| @override |
| Future<Success> clearVMTimeline() { |
| return _rpcNotSupportedFuture('clearVMTimeline'); |
| } |
| |
| Future<Response> _getEvaluationResult( |
| String isolateId, |
| Future<RemoteObject> Function() evaluation, |
| String expression, |
| ) async { |
| try { |
| final result = await evaluation(); |
| if (!_isIsolateRunning || isolateId != inspector.isolate.id) { |
| _logger.fine('Cannot get evaluation result for isolate $isolateId: ' |
| ' isolate exited.'); |
| return ErrorRef( |
| kind: 'error', |
| message: 'Isolate exited', |
| id: createId(), |
| ); |
| } |
| |
| // Handle compilation errors, internal errors, |
| // and reference errors from JavaScript evaluation in chrome. |
| if (_hasEvaluationError(result.type)) { |
| if (_hasReportableEvaluationError(result.type)) { |
| _logger.warning('Failed to evaluate expression \'$expression\': ' |
| '${result.type}: ${result.value}.'); |
| |
| _logger.info('Please follow instructions at ' |
| 'https://github.com/dart-lang/webdev/issues/956 ' |
| 'to file a bug.'); |
| } |
| return ErrorRef( |
| kind: 'error', |
| message: '${result.type}: ${result.value}', |
| id: createId(), |
| ); |
| } |
| return (await _instanceRef(result)); |
| } on RPCError catch (_) { |
| rethrow; |
| } catch (e, s) { |
| // Handle errors that throw exceptions, such as invalid JavaScript |
| // generated by the expression evaluator. |
| _logger.warning('Failed to evaluate expression \'$expression\'. '); |
| _logger.info('Please follow instructions at ' |
| 'https://github.com/dart-lang/webdev/issues/956 ' |
| 'to file a bug.'); |
| _logger.info('$e:$s'); |
| return ErrorRef(kind: 'error', message: '<unknown>', id: createId()); |
| } |
| } |
| |
| bool _hasEvaluationError(String type) => type.contains('Error'); |
| |
| // Decides if the error is serious enough to be shown to the user |
| // to encourage bug reporting. |
| bool _hasReportableEvaluationError(String type) { |
| if (!_hasEvaluationError(type)) return false; |
| |
| if (type == EvaluationErrorKind.compilation || |
| type == EvaluationErrorKind.asyncFrame) { |
| return false; |
| } |
| return true; |
| } |
| |
| @override |
| Future<Response> evaluate( |
| String isolateId, |
| String targetId, |
| String expression, { |
| Map<String, String>? scope, |
| // TODO(798) - respect disableBreakpoints. |
| bool? disableBreakpoints, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'evaluate', |
| () => _evaluate( |
| isolateId, |
| targetId, |
| expression, |
| scope: scope, |
| ), |
| ); |
| |
| Future<Response> _evaluate( |
| String isolateId, |
| String targetId, |
| String expression, { |
| Map<String, String>? scope, |
| }) { |
| // TODO(798) - respect disableBreakpoints. |
| return captureElapsedTime( |
| () async { |
| await isInitialized; |
| final evaluator = _expressionEvaluator; |
| if (evaluator != null) { |
| await isCompilerInitialized; |
| _checkIsolate('evaluate', isolateId); |
| |
| late Obj object; |
| try { |
| object = await inspector.getObject(targetId); |
| } catch (_) { |
| return ErrorRef( |
| kind: 'error', |
| message: 'Evaluate is called on an unsupported target:' |
| '$targetId', |
| id: createId(), |
| ); |
| } |
| |
| final library = |
| object is Library ? object : inspector.isolate.rootLib; |
| |
| if (object is Instance) { |
| // Evaluate is called on a target - convert this to a dart |
| // expression and scope by adding a target variable to the |
| // expression and the scope, for example: |
| // |
| // Library: 'package:hello_world/main.dart' |
| // Expression: 'hashCode' => 'x.hashCode' |
| // Scope: {} => { 'x' : targetId } |
| |
| final target = _newVariableForScope(scope); |
| expression = '$target.$expression'; |
| scope = (scope ?? {})..addAll({target: targetId}); |
| } |
| |
| return await _getEvaluationResult( |
| isolateId, |
| () => evaluator.evaluateExpression( |
| isolateId, |
| library?.uri, |
| expression, |
| scope, |
| ), |
| expression, |
| ); |
| } |
| throw RPCError( |
| 'evaluate', |
| RPCErrorKind.kInvalidRequest.code, |
| 'Expression evaluation is not supported for this configuration.', |
| ); |
| }, |
| (result) => DwdsEvent.evaluate(expression, result), |
| ); |
| } |
| |
| String _newVariableForScope(Map<String, String>? scope) { |
| // Find a new variable not in scope. |
| var candidate = 'x'; |
| while (scope?.containsKey(candidate) ?? false) { |
| candidate += '\$1'; |
| } |
| return candidate; |
| } |
| |
| @override |
| Future<Response> evaluateInFrame( |
| String isolateId, |
| int frameIndex, |
| String expression, { |
| Map<String, String>? scope, |
| // TODO(798) - respect disableBreakpoints. |
| bool? disableBreakpoints, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'evaluateInFrame', |
| () => _evaluateInFrame( |
| isolateId, |
| frameIndex, |
| expression, |
| scope: scope, |
| ), |
| ); |
| |
| Future<Response> _evaluateInFrame( |
| String isolateId, |
| int frameIndex, |
| String expression, { |
| Map<String, String>? scope, |
| }) { |
| // TODO(798) - respect disableBreakpoints. |
| |
| return captureElapsedTime( |
| () async { |
| await isInitialized; |
| final evaluator = _expressionEvaluator; |
| if (evaluator != null) { |
| await isCompilerInitialized; |
| _checkIsolate('evaluateInFrame', isolateId); |
| |
| return await _getEvaluationResult( |
| isolateId, |
| () => evaluator.evaluateExpressionInFrame( |
| isolateId, |
| frameIndex, |
| expression, |
| scope, |
| ), |
| expression, |
| ); |
| } |
| throw RPCError( |
| 'evaluateInFrame', |
| RPCErrorKind.kInvalidRequest.code, |
| 'Expression evaluation is not supported for this configuration.', |
| ); |
| }, |
| (result) => DwdsEvent.evaluateInFrame(expression, result), |
| ); |
| } |
| |
| @override |
| Future<AllocationProfile> getAllocationProfile( |
| String isolateId, { |
| bool? gc, |
| bool? reset, |
| }) { |
| return _rpcNotSupportedFuture('getAllocationProfile'); |
| } |
| |
| @override |
| Future<ClassList> getClassList(String isolateId) { |
| // See dart-lang/webdev/issues/971. |
| return _rpcNotSupportedFuture('getClassList'); |
| } |
| |
| @override |
| Future<FlagList> getFlagList() async { |
| // VM flags do not apply to web apps. |
| return FlagList(flags: []); |
| } |
| |
| @override |
| Future<InstanceSet> getInstances( |
| String isolateId, |
| String classId, |
| int limit, { |
| bool? includeImplementers, |
| bool? includeSubclasses, |
| }) { |
| return _rpcNotSupportedFuture('getInstances'); |
| } |
| |
| @override |
| Future<Isolate> getIsolate(String isolateId) => wrapInErrorHandlerAsync( |
| 'getIsolate', |
| () => _getIsolate(isolateId), |
| ); |
| |
| Future<Isolate> _getIsolate(String isolateId) { |
| return captureElapsedTime( |
| () async { |
| await isInitialized; |
| _checkIsolate('getIsolate', isolateId); |
| return inspector.isolate; |
| }, |
| (result) => DwdsEvent.getIsolate(), |
| ); |
| } |
| |
| @override |
| Future<MemoryUsage> getMemoryUsage(String isolateId) => |
| wrapInErrorHandlerAsync( |
| 'getMemoryUsage', |
| () => _getMemoryUsage(isolateId), |
| ); |
| |
| Future<MemoryUsage> _getMemoryUsage(String isolateId) async { |
| await isInitialized; |
| _checkIsolate('getMemoryUsage', isolateId); |
| return inspector.getMemoryUsage(); |
| } |
| |
| @override |
| Future<Obj> getObject( |
| String isolateId, |
| String objectId, { |
| int? offset, |
| int? count, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'getObject', |
| () => _getObject( |
| isolateId, |
| objectId, |
| offset: offset, |
| count: count, |
| ), |
| ); |
| |
| Future<Obj> _getObject( |
| String isolateId, |
| String objectId, { |
| int? offset, |
| int? count, |
| }) async { |
| await isInitialized; |
| _checkIsolate('getObject', isolateId); |
| return inspector.getObject(objectId, offset: offset, count: count); |
| } |
| |
| @override |
| Future<ScriptList> getScripts(String isolateId) => wrapInErrorHandlerAsync( |
| 'getScripts', |
| () => _getScripts(isolateId), |
| ); |
| |
| Future<ScriptList> _getScripts(String isolateId) { |
| return captureElapsedTime( |
| () async { |
| await isInitialized; |
| _checkIsolate('getScripts', isolateId); |
| return inspector.getScripts(); |
| }, |
| (result) => DwdsEvent.getScripts(), |
| ); |
| } |
| |
| @override |
| Future<SourceReport> getSourceReport( |
| String isolateId, |
| List<String> reports, { |
| String? scriptId, |
| int? tokenPos, |
| int? endTokenPos, |
| bool? forceCompile, |
| bool? reportLines, |
| List<String>? libraryFilters, |
| // Note: Ignore the optional librariesAlreadyCompiled parameter. It is here |
| // to match the VM service interface. |
| List<String>? librariesAlreadyCompiled, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'getSourceReport', |
| () => _getSourceReport( |
| isolateId, |
| reports, |
| scriptId: scriptId, |
| tokenPos: tokenPos, |
| endTokenPos: endTokenPos, |
| forceCompile: forceCompile, |
| reportLines: reportLines, |
| libraryFilters: libraryFilters, |
| ), |
| ); |
| |
| Future<SourceReport> _getSourceReport( |
| String isolateId, |
| List<String> reports, { |
| String? scriptId, |
| int? tokenPos, |
| int? endTokenPos, |
| bool? forceCompile, |
| bool? reportLines, |
| List<String>? libraryFilters, |
| }) { |
| return captureElapsedTime( |
| () async { |
| await isInitialized; |
| _checkIsolate('getSourceReport', isolateId); |
| return await inspector.getSourceReport( |
| reports, |
| scriptId: scriptId, |
| tokenPos: tokenPos, |
| endTokenPos: endTokenPos, |
| forceCompile: forceCompile, |
| reportLines: reportLines, |
| libraryFilters: libraryFilters, |
| ); |
| }, |
| (result) => DwdsEvent.getSourceReport(), |
| ); |
| } |
| |
| /// Returns the current stack. |
| /// |
| /// Throws RPCError the corresponding isolate is not paused. |
| /// |
| /// The returned stack will contain up to [limit] frames if provided. |
| @override |
| Future<Stack> getStack(String isolateId, {int? limit}) => |
| wrapInErrorHandlerAsync( |
| 'getStack', |
| () => _getStack(isolateId, limit: limit), |
| ); |
| |
| Future<Stack> _getStack(String isolateId, {int? limit}) async { |
| await isInitialized; |
| await isStarted; |
| _checkIsolate('getStack', isolateId); |
| return (await debuggerFuture).getStack(limit: limit); |
| } |
| |
| @override |
| Future<VM> getVM() => wrapInErrorHandlerAsync('getVM', _getVM); |
| |
| Future<VM> _getVM() { |
| return captureElapsedTime( |
| () async { |
| await isInitialized; |
| return _vm; |
| }, |
| (result) => DwdsEvent.getVM(), |
| ); |
| } |
| |
| @override |
| Future<Timeline> getVMTimeline({ |
| int? timeOriginMicros, |
| int? timeExtentMicros, |
| }) { |
| return _rpcNotSupportedFuture('getVMTimeline'); |
| } |
| |
| @override |
| Future<TimelineFlags> getVMTimelineFlags() { |
| return _rpcNotSupportedFuture('getVMTimelineFlags'); |
| } |
| |
| @override |
| Future<Version> getVersion() => |
| wrapInErrorHandlerAsync('getVersion', _getVersion); |
| |
| Future<Version> _getVersion() async { |
| final version = semver.Version.parse(vmServiceVersion); |
| return Version(major: version.major, minor: version.minor); |
| } |
| |
| @override |
| Future<Response> invoke( |
| String isolateId, |
| String targetId, |
| String selector, |
| List argumentIds, { |
| // TODO(798) - respect disableBreakpoints. |
| bool? disableBreakpoints, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'invoke', |
| () => _invoke( |
| isolateId, |
| targetId, |
| selector, |
| argumentIds, |
| ), |
| ); |
| |
| Future<Response> _invoke( |
| String isolateId, |
| String targetId, |
| String selector, |
| List argumentIds, |
| ) async { |
| await isInitialized; |
| _checkIsolate('invoke', isolateId); |
| final remote = await inspector.invoke(targetId, selector, argumentIds); |
| return _instanceRef(remote); |
| } |
| |
| @override |
| Future<Success> kill(String isolateId) { |
| return _rpcNotSupportedFuture('kill'); |
| } |
| |
| @override |
| Stream<Event> onEvent(String streamId) { |
| return _streamControllers.putIfAbsent(streamId, () { |
| switch (streamId) { |
| case EventStreams.kExtension: |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kIsolate: |
| // TODO: right now we only support the `ServiceExtensionAdded` event |
| // for the Isolate stream. |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kVM: |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kGC: |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kTimeline: |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kService: |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kDebug: |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kLogging: |
| return StreamController<Event>.broadcast(); |
| case EventStreams.kStdout: |
| return _chromeConsoleStreamController( |
| (e) => _stdoutTypes.contains(e.type), |
| ); |
| case EventStreams.kStderr: |
| return _chromeConsoleStreamController( |
| (e) => _stderrTypes.contains(e.type), |
| includeExceptions: true, |
| ); |
| default: |
| throw RPCError( |
| 'streamListen', |
| RPCErrorKind.kInvalidParams.code, |
| 'The stream `$streamId` is not supported on web devices', |
| ); |
| } |
| }).stream; |
| } |
| |
| @override |
| Future<Success> pause(String isolateId) => |
| wrapInErrorHandlerAsync('pause', () => _pause(isolateId)); |
| |
| Future<Success> _pause(String isolateId) async { |
| await isInitialized; |
| _checkIsolate('pause', isolateId); |
| return (await debuggerFuture).pause(); |
| } |
| |
| // Note: Ignore the optional local parameter, when it is set to `true` the |
| // request is intercepted and handled by DDS. |
| @override |
| Future<UriList> lookupResolvedPackageUris( |
| String isolateId, |
| List<String> uris, { |
| bool? local, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'lookupResolvedPackageUris', |
| () => _lookupResolvedPackageUris(isolateId, uris), |
| ); |
| |
| Future<UriList> _lookupResolvedPackageUris( |
| String isolateId, |
| List<String> uris, |
| ) async { |
| await isInitialized; |
| _checkIsolate('lookupResolvedPackageUris', isolateId); |
| return UriList(uris: uris.map(DartUri.toResolvedUri).toList()); |
| } |
| |
| @override |
| Future<UriList> lookupPackageUris(String isolateId, List<String> uris) => |
| wrapInErrorHandlerAsync( |
| 'lookupPackageUris', |
| () => _lookupPackageUris(isolateId, uris), |
| ); |
| |
| Future<UriList> _lookupPackageUris( |
| String isolateId, |
| List<String> uris, |
| ) async { |
| await isInitialized; |
| _checkIsolate('lookupPackageUris', isolateId); |
| return UriList(uris: uris.map(DartUri.toPackageUri).toList()); |
| } |
| |
| @override |
| Future<Success> registerService(String service, String alias) { |
| return _rpcNotSupportedFuture('registerService'); |
| } |
| |
| @override |
| Future<ReloadReport> reloadSources( |
| String isolateId, { |
| bool? force, |
| bool? pause, |
| String? rootLibUri, |
| String? packagesUri, |
| }) { |
| return Future.error( |
| RPCError( |
| 'reloadSources', |
| RPCErrorKind.kMethodNotFound.code, |
| 'Hot reload not supported on web devices', |
| ), |
| ); |
| } |
| |
| @override |
| Future<Success> removeBreakpoint( |
| String isolateId, |
| String breakpointId, |
| ) => |
| wrapInErrorHandlerAsync( |
| 'removeBreakpoint', |
| () => _removeBreakpoint(isolateId, breakpointId), |
| ); |
| |
| Future<Success> _removeBreakpoint( |
| String isolateId, |
| String breakpointId, |
| ) async { |
| await isInitialized; |
| _checkIsolate('removeBreakpoint', isolateId); |
| _disabledBreakpoints |
| .removeWhere((breakpoint) => breakpoint.id == breakpointId); |
| return (await debuggerFuture).removeBreakpoint(breakpointId); |
| } |
| |
| @override |
| Future<Success> resume( |
| String isolateId, { |
| String? step, |
| int? frameIndex, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'resume', |
| () => _resume( |
| isolateId, |
| step: step, |
| frameIndex: frameIndex, |
| ), |
| ); |
| |
| Future<Success> _resume( |
| String isolateId, { |
| String? step, |
| int? frameIndex, |
| }) async { |
| if (inspector.appConnection.isStarted) { |
| return captureElapsedTime( |
| () async { |
| await isInitialized; |
| await isStarted; |
| _checkIsolate('resume', isolateId); |
| return await (await debuggerFuture) |
| .resume(step: step, frameIndex: frameIndex); |
| }, |
| (result) => DwdsEvent.resume(step), |
| ); |
| } else { |
| inspector.appConnection.runMain(); |
| return Success(); |
| } |
| } |
| |
| /// This method is deprecated in vm_service package. |
| /// |
| /// TODO(annagrin): remove after dart-code and IntelliJ stop using this API. |
| /// Issue: https://github.com/dart-lang/webdev/issues/1868 |
| /// |
| // ignore: annotate_overrides |
| Future<Success> setExceptionPauseMode( |
| String isolateId, |
| /*ExceptionPauseMode*/ |
| String mode, |
| ) => |
| setIsolatePauseMode(isolateId, exceptionPauseMode: mode); |
| |
| @override |
| Future<Success> setIsolatePauseMode( |
| String isolateId, { |
| String? exceptionPauseMode, |
| // TODO(elliette): Is there a way to respect the shouldPauseOnExit parameter |
| // in Chrome? |
| bool? shouldPauseOnExit, |
| }) => |
| wrapInErrorHandlerAsync( |
| 'setIsolatePauseMode', |
| () => _setIsolatePauseMode( |
| isolateId, |
| exceptionPauseMode: exceptionPauseMode, |
| ), |
| ); |
| |
| Future<Success> _setIsolatePauseMode( |
| String isolateId, { |
| String? exceptionPauseMode, |
| }) async { |
| await isInitialized; |
| _checkIsolate('setIsolatePauseMode', isolateId); |
| return (await debuggerFuture) |
| .setExceptionPauseMode(exceptionPauseMode ?? ExceptionPauseMode.kNone); |
| } |
| |
| @override |
| Future<Success> setFlag(String name, String value) { |
| return _rpcNotSupportedFuture('setFlag'); |
| } |
| |
| @override |
| Future<Success> setLibraryDebuggable( |
| String isolateId, |
| String libraryId, |
| bool isDebuggable, |
| ) { |
| return _rpcNotSupportedFuture('setLibraryDebuggable'); |
| } |
| |
| @override |
| Future<Success> setName(String isolateId, String name) => |
| wrapInErrorHandlerAsync( |
| 'setName', |
| () => _setName(isolateId, name), |
| ); |
| |
| Future<Success> _setName(String isolateId, String name) async { |
| await isInitialized; |
| _checkIsolate('setName', isolateId); |
| inspector.isolate.name = name; |
| return Success(); |
| } |
| |
| @override |
| Future<Success> setVMName(String name) => |
| wrapInErrorHandlerAsync('setVMName', () => _setVMName(name)); |
| |
| Future<Success> _setVMName(String name) async { |
| _vm.name = name; |
| _streamNotify( |
| 'VM', |
| Event( |
| kind: EventKind.kVMUpdate, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| // We are not guaranteed to have an isolate at this point in time. |
| isolate: null, |
| )..vm = toVMRef(_vm), |
| ); |
| return Success(); |
| } |
| |
| @override |
| Future<Success> setVMTimelineFlags(List<String> recordedStreams) { |
| return _rpcNotSupportedFuture('setVMTimelineFlags'); |
| } |
| |
| @override |
| Future<Success> streamCancel(String streamId) { |
| // TODO: We should implement this (as we've already implemented |
| // streamListen). |
| return _rpcNotSupportedFuture('streamCancel'); |
| } |
| |
| @override |
| Future<Success> streamListen(String streamId) => |
| wrapInErrorHandlerAsync('streamListen', () => _streamListen(streamId)); |
| |
| Future<Success> _streamListen(String streamId) async { |
| // TODO: This should return an error if the stream is already being listened |
| // to. |
| onEvent(streamId); |
| return Success(); |
| } |
| |
| @override |
| Future<Success> clearCpuSamples(String isolateId) { |
| return _rpcNotSupportedFuture('clearCpuSamples'); |
| } |
| |
| @override |
| Future<CpuSamples> getCpuSamples( |
| String isolateId, |
| int timeOriginMicros, |
| int timeExtentMicros, |
| ) { |
| return _rpcNotSupportedFuture('getCpuSamples'); |
| } |
| |
| /// Returns a streamController that listens for console logs from chrome and |
| /// adds all events passing [filter] to the stream. |
| StreamController<Event> _chromeConsoleStreamController( |
| bool Function(ConsoleAPIEvent) filter, { |
| bool includeExceptions = false, |
| }) { |
| late StreamController<Event> controller; |
| StreamSubscription? chromeConsoleSubscription; |
| StreamSubscription? exceptionsSubscription; |
| |
| // This is an edge case for this lint apparently |
| // |
| // ignore: join_return_with_assignment |
| controller = StreamController<Event>.broadcast( |
| onCancel: () { |
| chromeConsoleSubscription?.cancel(); |
| exceptionsSubscription?.cancel(); |
| }, |
| onListen: () { |
| chromeConsoleSubscription = |
| remoteDebugger.onConsoleAPICalled.listen((e) { |
| if (!_isIsolateRunning) return; |
| final isolateRef = inspector.isolateRef; |
| if (!filter(e)) return; |
| final args = e.params?['args'] as List?; |
| final item = args?[0] as Map?; |
| final value = '${item?["value"]}\n'; |
| controller.add( |
| Event( |
| kind: EventKind.kWriteEvent, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| isolate: isolateRef, |
| ) |
| ..bytes = base64.encode(utf8.encode(value)) |
| ..timestamp = e.timestamp.toInt(), |
| ); |
| }); |
| if (includeExceptions) { |
| exceptionsSubscription = |
| remoteDebugger.onExceptionThrown.listen((e) async { |
| if (!_isIsolateRunning) return; |
| final isolateRef = inspector.isolateRef; |
| var description = e.exceptionDetails.exception?.description; |
| if (description != null) { |
| description = await inspector.mapExceptionStackTrace(description); |
| } |
| controller.add( |
| Event( |
| kind: EventKind.kWriteEvent, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| isolate: isolateRef, |
| )..bytes = base64.encode(utf8.encode(description ?? '')), |
| ); |
| }); |
| } |
| }, |
| ); |
| return controller; |
| } |
| |
| /// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service |
| /// protocol [Event]s. |
| void parseBatchedDebugEvents(BatchedDebugEvents debugEvents) { |
| for (var debugEvent in debugEvents.events) { |
| parseDebugEvent(debugEvent); |
| } |
| } |
| |
| /// Parses the [DebugEvent] and emits a corresponding Dart VM Service |
| /// protocol [Event]. |
| void parseDebugEvent(DebugEvent debugEvent) { |
| if (terminatingIsolates) return; |
| if (!_isIsolateRunning) return; |
| final isolateRef = inspector.isolateRef; |
| |
| _streamNotify( |
| EventStreams.kExtension, |
| Event( |
| kind: EventKind.kExtension, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| isolate: isolateRef, |
| ) |
| ..extensionKind = debugEvent.kind |
| ..extensionData = ExtensionData.parse( |
| jsonDecode(debugEvent.eventData) as Map<String, dynamic>, |
| ), |
| ); |
| } |
| |
| /// Parses the [RegisterEvent] and emits a corresponding Dart VM Service |
| /// protocol [Event]. |
| void parseRegisterEvent(RegisterEvent registerEvent) { |
| if (terminatingIsolates) return; |
| if (!_isIsolateRunning) return; |
| |
| final isolate = inspector.isolate; |
| final isolateRef = inspector.isolateRef; |
| final service = registerEvent.eventData; |
| isolate.extensionRPCs?.add(service); |
| |
| _streamNotify( |
| EventStreams.kIsolate, |
| Event( |
| kind: EventKind.kServiceExtensionAdded, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| isolate: isolateRef, |
| )..extensionRPC = service, |
| ); |
| } |
| |
| /// Listens for chrome console events and handles the ones we care about. |
| void _setUpChromeConsoleListeners(IsolateRef isolateRef) { |
| _consoleSubscription = |
| remoteDebugger.onConsoleAPICalled.listen((event) async { |
| if (terminatingIsolates) return; |
| if (event.type != 'debug') return; |
| if (!_isIsolateRunning) return; |
| |
| final isolate = inspector.isolate; |
| if (isolateRef.id != isolate.id) return; |
| |
| final args = event.args; |
| final firstArgValue = (args.isNotEmpty ? args[0].value : null) as String?; |
| // TODO(nshahan) - Migrate 'inspect' and 'log' events to the injected |
| // client communication approach as well? |
| switch (firstArgValue) { |
| case 'dart.developer.inspect': |
| // All inspected objects should be real objects. |
| if (event.args[1].type != 'object') break; |
| |
| final inspectee = await _instanceRef(event.args[1]); |
| _streamNotify( |
| EventStreams.kDebug, |
| Event( |
| kind: EventKind.kInspect, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| isolate: isolateRef, |
| ) |
| ..inspectee = inspectee |
| ..timestamp = event.timestamp.toInt(), |
| ); |
| break; |
| case 'dart.developer.log': |
| await _handleDeveloperLog(isolateRef, event); |
| break; |
| default: |
| break; |
| } |
| }); |
| } |
| |
| void _streamNotify(String streamId, Event event) { |
| final controller = _streamControllers[streamId]; |
| if (controller == null) return; |
| controller.add(event); |
| } |
| |
| Future<void> _handleDeveloperLog( |
| IsolateRef isolateRef, |
| ConsoleAPIEvent event, |
| ) async { |
| final logObject = event.params?['args'][1] as Map?; |
| final logParams = <String, RemoteObject>{}; |
| for (dynamic obj in logObject?['preview']?['properties'] ?? {}) { |
| if (obj['name'] != null && obj is Map<String, dynamic>) { |
| logParams[obj['name'] as String] = RemoteObject(obj); |
| } |
| } |
| |
| final logRecord = LogRecord( |
| message: await _instanceRef(logParams['message']), |
| loggerName: await _instanceRef(logParams['name']), |
| level: logParams['level'] != null |
| ? int.tryParse(logParams['level']!.value.toString()) |
| : 0, |
| error: await _instanceRef(logParams['error']), |
| time: event.timestamp.toInt(), |
| sequenceNumber: logParams['sequenceNumber'] != null |
| ? int.tryParse(logParams['sequenceNumber']!.value.toString()) |
| : 0, |
| stackTrace: await _instanceRef(logParams['stackTrace']), |
| zone: await _instanceRef(logParams['zone']), |
| ); |
| |
| _streamNotify( |
| EventStreams.kLogging, |
| Event( |
| kind: EventKind.kLogging, |
| timestamp: DateTime.now().millisecondsSinceEpoch, |
| isolate: isolateRef, |
| ) |
| ..logRecord = logRecord |
| ..timestamp = event.timestamp.toInt(), |
| ); |
| } |
| |
| @override |
| Future<Timestamp> getVMTimelineMicros() { |
| return _rpcNotSupportedFuture('getVMTimelineMicros'); |
| } |
| |
| @override |
| Future<InboundReferences> getInboundReferences( |
| String isolateId, |
| String targetId, |
| int limit, |
| ) { |
| return _rpcNotSupportedFuture('getInboundReferences'); |
| } |
| |
| @override |
| Future<RetainingPath> getRetainingPath( |
| String isolateId, |
| String targetId, |
| int limit, |
| ) { |
| return _rpcNotSupportedFuture('getRetainingPath'); |
| } |
| |
| @override |
| Future<Success> requestHeapSnapshot(String isolateId) { |
| return _rpcNotSupportedFuture('requestHeapSnapshot'); |
| } |
| |
| @override |
| Future<IsolateGroup> getIsolateGroup(String isolateGroupId) { |
| return _rpcNotSupportedFuture('getIsolateGroup'); |
| } |
| |
| @override |
| Future<MemoryUsage> getIsolateGroupMemoryUsage(String isolateGroupId) { |
| return _rpcNotSupportedFuture('getIsolateGroupMemoryUsage'); |
| } |
| |
| @override |
| Future<ProtocolList> getSupportedProtocols() => wrapInErrorHandlerAsync( |
| 'getSupportedProtocols', |
| _getSupportedProtocols, |
| ); |
| |
| Future<ProtocolList> _getSupportedProtocols() async { |
| final version = semver.Version.parse(vmServiceVersion); |
| return ProtocolList( |
| protocols: [ |
| Protocol( |
| protocolName: 'VM Service', |
| major: version.major, |
| minor: version.minor, |
| ), |
| ], |
| ); |
| } |
| |
| Future<InstanceRef> _instanceRef(RemoteObject? obj) async { |
| final instance = obj == null ? null : await inspector.instanceRefFor(obj); |
| return instance ?? InstanceHelper.kNullInstanceRef; |
| } |
| |
| static RPCError _rpcNotSupported(String method) { |
| return RPCError( |
| method, |
| RPCErrorKind.kMethodNotFound.code, |
| '$method: Not supported on web devices', |
| ); |
| } |
| |
| static Future<T> _rpcNotSupportedFuture<T>(String method) { |
| return Future.error(_rpcNotSupported(method)); |
| } |
| |
| @override |
| Future<ProcessMemoryUsage> getProcessMemoryUsage() => |
| _rpcNotSupportedFuture('getProcessMemoryUsage'); |
| |
| @override |
| Future<PortList> getPorts(String isolateId) => throw UnimplementedError(); |
| |
| @override |
| Future<CpuSamples> getAllocationTraces( |
| String isolateId, { |
| int? timeOriginMicros, |
| int? timeExtentMicros, |
| String? classId, |
| }) => |
| throw UnimplementedError(); |
| |
| @override |
| Future<Success> setTraceClassAllocation( |
| String isolateId, |
| String classId, |
| bool enable, |
| ) => |
| throw UnimplementedError(); |
| |
| @override |
| Future<Breakpoint> setBreakpointState( |
| String isolateId, |
| String breakpointId, |
| bool enable, |
| ) => |
| throw UnimplementedError(); |
| |
| @override |
| Future<Success> streamCpuSamplesWithUserTag(List<String> userTags) => |
| _rpcNotSupportedFuture('streamCpuSamplesWithUserTag'); |
| |
| /// Prevent DWDS from blocking Dart SDK rolls if changes in package:vm_service |
| /// are unimplemented in DWDS. |
| @override |
| dynamic noSuchMethod(Invocation invocation) { |
| return super.noSuchMethod(invocation); |
| } |
| |
| /// Validate that isolateId matches the current isolate we're connected to and |
| /// return that isolate. |
| /// |
| /// This is useful to call at the beginning of API methods that are passed an |
| /// isolate id. |
| Isolate _checkIsolate(String methodName, String? isolateId) { |
| final currentIsolateId = inspector.isolate.id; |
| if (currentIsolateId == null) { |
| throw StateError('No running isolate ID'); |
| } |
| if (isolateId != currentIsolateId) { |
| _throwSentinel( |
| methodName, |
| SentinelKind.kCollected, |
| 'Unrecognized isolateId: $isolateId', |
| ); |
| } |
| return inspector.isolate; |
| } |
| |
| static Never _throwSentinel(String method, String kind, String message) { |
| final data = <String, String>{'kind': kind, 'valueAsString': message}; |
| throw SentinelException.parse(method, data); |
| } |
| } |
| |
| /// The `type`s of [ConsoleAPIEvent]s that are treated as `stderr` logs. |
| const _stderrTypes = ['error']; |
| |
| /// The `type`s of [ConsoleAPIEvent]s that are treated as `stdout` logs. |
| const _stdoutTypes = ['log', 'info', 'warning']; |