support basic scope in evaluate calls (#344)
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index 1af0fed..e277380 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.2
+
+- Add support for `scope` in `evaluate` calls.
+
## 0.3.1
- Improve error reporting for evals, give the full JS eval in the error message
diff --git a/dwds/example/hello_world/main.dart b/dwds/example/hello_world/main.dart
index 6be5aee..70b6e6c 100644
--- a/dwds/example/hello_world/main.dart
+++ b/dwds/example/hello_world/main.dart
@@ -51,7 +51,14 @@
String helloString(String response) => response;
bool helloBool(bool response) => response;
num helloNum(num response) => response;
+MyTestClass createObject(String message) => MyTestClass(message: message);
+String messageFor(MyTestClass instance) => instance.message;
+String messagesCombined(MyTestClass a, MyTestClass b) => a.message + b.message;
class MyTestClass {
- String hello() => 'world';
+ final String message;
+
+ MyTestClass({this.message = 'world'});
+
+ String hello() => message;
}
diff --git a/dwds/lib/src/chrome_proxy_service.dart b/dwds/lib/src/chrome_proxy_service.dart
index 3277a15..72605ba 100644
--- a/dwds/lib/src/chrome_proxy_service.dart
+++ b/dwds/lib/src/chrome_proxy_service.dart
@@ -246,51 +246,80 @@
@override
Future evaluate(String isolateId, String targetId, String expression,
{Map<String, String> scope, bool disableBreakpoints}) async {
+ scope ??= {};
+ disableBreakpoints ??= false;
var library = await _getLibrary(isolateId, targetId);
if (library == null) {
throw UnsupportedError(
'Evaluate is only supported when `targetId` is a library.');
}
- var evalExpression = '''
+ WipResponse result;
+
+ // If there is scope, we use `callFunctionOn` because that accepts the
+ // `arguments` option.
+ //
+ // If there is no scope we run a normal evaluate call.
+ if (scope.isEmpty) {
+ var evalExpression = '''
(function() {
${_getLibrarySnippet(library.uri)};
return library.$expression;
})();
''';
- var result = await tabConnection.runtime.sendCommand('Runtime.evaluate',
- params: {'expression': evalExpression, 'returnByValue': true});
- _handleErrorIfPresent(result,
- evalContents: evalExpression,
- additionalDetails: {
- 'Dart expression': expression,
- 'scope': scope,
- });
+ result = await tabConnection.runtime.sendCommand('Runtime.evaluate',
+ params: {'expression': evalExpression});
+ _handleErrorIfPresent(result,
+ evalContents: evalExpression,
+ additionalDetails: {
+ 'Dart expression': expression,
+ });
+ } else {
+ var argsString = scope.keys.join(', ');
+ var arguments = scope.values.map((id) => {'objectId': id}).toList();
+ var evalExpression = '''
+function($argsString) {
+ ${_getLibrarySnippet(library.uri)};
+ return library.$expression;
+}
+ ''';
+ result = await tabConnection.runtime
+ .sendCommand('Runtime.callFunctionOn', params: {
+ 'functionDeclaration': evalExpression,
+ 'arguments': arguments,
+ // TODO(jakemac): Use the executionContext instead, or possibly the
+ // library object. This will get weird if people try to use `this` in
+ // their expression.
+ 'objectId': scope.values.first,
+ });
+ _handleErrorIfPresent(result,
+ evalContents: evalExpression,
+ additionalDetails: {
+ 'Dart expression': expression,
+ 'scope': scope,
+ });
+ }
var remoteObject =
RemoteObject(result.result['result'] as Map<String, dynamic>);
- String kind;
- var classRef = ClassRef()
- ..id = 'dart:core:${remoteObject.type}'
- ..name = remoteObject.type;
switch (remoteObject.type) {
case 'string':
- kind = InstanceKind.kString;
- break;
+ return _primitiveInstance(InstanceKind.kString, remoteObject);
case 'number':
- kind = InstanceKind.kDouble;
- break;
+ return _primitiveInstance(InstanceKind.kDouble, remoteObject);
case 'boolean':
- kind = InstanceKind.kBool;
- break;
+ return _primitiveInstance(InstanceKind.kBool, remoteObject);
+ case 'object':
+ return InstanceRef()
+ ..kind = InstanceKind.kPlainInstance
+ ..id = remoteObject.objectId
+ // TODO(jakemac): Create a real ClassRef, we need a way of looking
+ // up the library for a given instance to create it though.
+ // https://github.com/dart-lang/sdk/issues/36771.
+ ..classRef = ClassRef();
default:
throw UnsupportedError(
'Unsupported response type ${remoteObject.type}');
}
-
- return InstanceRef()
- ..valueAsString = '${remoteObject.value}'
- ..classRef = classRef
- ..kind = kind;
}
@override
@@ -765,8 +794,9 @@
var inspectee = InstanceRef()
..kind = InstanceKind.kPlainInstance
..id = event.args[1].objectId
- // TODO: A real classref? we need something here so it can properly
- // serialize, but it isn't used by the widget inspector.
+ // TODO(jakemac): Create a real ClassRef, we need a way of looking
+ // up the library for a given instance to create it though.
+ // https://github.com/dart-lang/sdk/issues/36771.
..classRef = ClassRef();
_streamNotify(
'Debug',
@@ -870,3 +900,14 @@
'all': PauseState.all,
'unhandled': PauseState.uncaught,
};
+
+/// Creates an [InstanceRef] for a primitive [RemoteObject].
+InstanceRef _primitiveInstance(String kind, RemoteObject remoteObject) {
+ var classRef = ClassRef()
+ ..id = 'dart:core:${remoteObject.type}'
+ ..name = kind;
+ return InstanceRef()
+ ..valueAsString = '${remoteObject.value}'
+ ..classRef = classRef
+ ..kind = kind;
+}
diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml
index 110ec77..82a3c2a 100644
--- a/dwds/pubspec.yaml
+++ b/dwds/pubspec.yaml
@@ -1,5 +1,5 @@
name: dwds
-version: 0.3.1
+version: 0.3.2
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/dart-lang/webdev/tree/master/dwds
description: >-
diff --git a/dwds/test/chrome_proxy_service_test.dart b/dwds/test/chrome_proxy_service_test.dart
index df63e1d..8ab28bd 100644
--- a/dwds/test/chrome_proxy_service_test.dart
+++ b/dwds/test/chrome_proxy_service_test.dart
@@ -150,6 +150,47 @@
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'valueAsString', '42.2'));
});
+
+ test('can return objects with ids', () async {
+ expect(
+ await service.evaluate(
+ isolate.id, isolate.rootLib.id, 'createObject("cool")'),
+ const TypeMatcher<InstanceRef>()
+ .having((instance) => instance.id, 'id', isNotNull));
+ // TODO(jakemac): Add tests for the ClassRef once we create one,
+ // https://github.com/dart-lang/sdk/issues/36771.
+ });
+
+ group('with provided scope', () {
+ Future<InstanceRef> createRemoteObject(String message) async {
+ return await service.evaluate(
+ isolate.id, isolate.rootLib.id, 'createObject("$message")')
+ as InstanceRef;
+ }
+
+ test('single scope object', () async {
+ var instance = await createRemoteObject('A');
+ var result = await service.evaluate(
+ isolate.id, isolate.rootLib.id, 'messageFor(arg1)',
+ scope: {'arg1': instance.id});
+ expect(
+ result,
+ const TypeMatcher<InstanceRef>().having(
+ (instance) => instance.valueAsString, 'valueAsString', 'A'));
+ });
+
+ test('multiple scope objects', () async {
+ var instance1 = await createRemoteObject('A');
+ var instance2 = await createRemoteObject('B');
+ var result = await service.evaluate(
+ isolate.id, isolate.rootLib.id, 'messagesCombined(arg1, arg2)',
+ scope: {'arg1': instance1.id, 'arg2': instance2.id});
+ expect(
+ result,
+ const TypeMatcher<InstanceRef>().having(
+ (instance) => instance.valueAsString, 'valueAsString', 'AB'));
+ });
+ });
});
});