diff --git a/example/web/main.dart b/example/web/main.dart index 9da786cbb..1c039fee1 100644 --- a/example/web/main.dart +++ b/example/web/main.dart @@ -2,6 +2,12 @@ // 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:convert'; +import 'dart:developer'; + void main() { - print('Hello World!'); + registerExtension('ext.print', (_, __) async { + print('Hello World'); + return ServiceExtensionResponse.result(json.encode({'success': true})); + }); } diff --git a/webdev/lib/src/daemon/app_domain.dart b/webdev/lib/src/daemon/app_domain.dart index 5d7b754c9..ef48c31e4 100644 --- a/webdev/lib/src/daemon/app_domain.dart +++ b/webdev/lib/src/daemon/app_domain.dart @@ -3,9 +3,11 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:dwds/service.dart'; +import 'package:vm_service_lib/vm_service_lib.dart'; import '../serve/chrome.dart'; import '../serve/debugger/webdev_vm_client.dart'; @@ -17,6 +19,7 @@ import 'utilites.dart'; /// A collection of method and events relevant to the running application. class AppDomain extends Domain { String _appId; + VmService _vmService; WebdevVmClient _webdevVmClient; DebugService _debugService; bool _isShutdown = false; @@ -35,14 +38,23 @@ class AppDomain extends Domain { _debugService = await devHandler.startDebugService(chrome.chromeConnection, _appId); _webdevVmClient = await WebdevVmClient.create(_debugService); + _vmService = _webdevVmClient.client; + sendEvent('app.started', { + 'appId': _appId, + }); + await _vmService.streamListen('Stdout'); + _vmService.onStdoutEvent.listen((log) { + sendEvent('app.log', { + 'appId': _appId, + 'log': utf8.decode(base64.decode(log.bytes)), + }); + }); sendEvent('app.debugPort', { 'appId': _appId, 'port': _debugService.port, 'wsUri': _debugService.wsUri, }); - sendEvent('app.started', { - 'appId': _appId, - }); + // Shutdown could have been triggered while awaiting above. // ignore: invariant_booleans if (_isShutdown) dispose(); @@ -56,17 +68,39 @@ class AppDomain extends Domain { _initialize(serverManager); } - Future _callServiceExtension(Map args) { - throw UnimplementedError(); + Future> _callServiceExtension( + Map args) async { + var appId = getStringArg(args, 'appId', required: true); + if (_appId != appId) throw ArgumentError.value(appId, 'appId', 'Not found'); + var methodName = getStringArg(args, 'methodName', required: true); + var params = args['params'] != null + ? (args['params'] as Map) + : {}; + var response = + await _vmService.callServiceExtension(methodName, args: params); + return response.json; } - Future _restart(Map args) async { - throw UnimplementedError(); + Future> _restart(Map args) async { + var appId = getStringArg(args, 'appId', required: true); + if (_appId != appId) throw ArgumentError.value(appId, 'appId', 'Not found'); + var fullRestart = getBoolArg(args, 'fullRestart') ?? false; + if (!fullRestart) { + throw ArgumentError.value( + fullRestart, 'fullRestart', 'We do not support hot reload yet.'); + } + // TODO(grouma) - Support pauseAfterRestart. + // var pauseAfterRestart = getBoolArg(args, 'pause') ?? false; + var response = await _vmService.callServiceExtension('hotRestart'); + return { + 'code': response.type == 'Success' ? 0 : 1, + 'message': response.toString() + }; } Future _stop(Map args) async { var appId = getStringArg(args, 'appId', required: true); - if (_appId != appId) throw ArgumentError("app '$appId' not found"); + if (_appId != appId) throw ArgumentError.value(appId, 'appId', 'Not found'); var chrome = await Chrome.connectedInstance; await chrome.close(); return true; diff --git a/webdev/lib/src/serve/debugger/webdev_vm_client.dart b/webdev/lib/src/serve/debugger/webdev_vm_client.dart index 58c186aef..9ee9f87f4 100644 --- a/webdev/lib/src/serve/debugger/webdev_vm_client.dart +++ b/webdev/lib/src/serve/debugger/webdev_vm_client.dart @@ -12,17 +12,17 @@ import 'package:vm_service_lib/vm_service_lib.dart'; // A client of the vm service that registers some custom extensions like // hotRestart. class WebdevVmClient { - final VmService _client; + final VmService client; final StreamController> _requestController; final StreamController> _responseController; WebdevVmClient( - this._client, this._requestController, this._responseController); + this.client, this._requestController, this._responseController); Future close() async { await _requestController.close(); await _responseController.close(); - _client.dispose(); + client.dispose(); } static Future create(DebugService debugService) async { diff --git a/webdev/test/daemon/app_domain_test.dart b/webdev/test/daemon/app_domain_test.dart index 9806362ac..56bcd2a0a 100644 --- a/webdev/test/daemon/app_domain_test.dart +++ b/webdev/test/daemon/app_domain_test.dart @@ -5,11 +5,31 @@ @Timeout(Duration(minutes: 2)) @Tags(['requires-edge-sdk']) +import 'dart:async'; +import 'dart:convert'; + +@Tags(['requires-edge-sdk']) import 'package:test/test.dart'; +import 'package:test_process/test_process.dart'; import '../test_utils.dart'; import 'utils.dart'; +Future _getAppId(TestProcess webdev) async { + var appId = ''; + while (await webdev.stdout.hasNext) { + var line = await webdev.stdout.next; + if (line.startsWith('[{"event":"app.started"')) { + line = line.substring(1, line.length - 1); + var message = json.decode(line) as Map; + appId = message['params']['appId'] as String; + break; + } + } + assert(appId.isNotEmpty); + return appId; +} + void main() { String exampleDirectory; @@ -43,5 +63,35 @@ void main() { await exitWebdev(webdev); }); }); + + group('Methods', () { + test('.callServiceExtension', () async { + var webdev = + await runWebDev(['daemon'], workingDirectory: exampleDirectory); + var appId = await _getAppId(webdev); + var extensionCall = '[{"method":"app.callServiceExtension","id":0,' + '"params" : { "appId" : "$appId", "methodName" : "ext.print"}}]'; + webdev.stdin.add(utf8.encode('$extensionCall\n')); + // The example app sets up a service extension for printing. + await expectLater( + webdev.stdout, + emitsThrough( + startsWith('[{"event":"app.log","params":{"appId":"$appId",' + '"log":"Hello World\\n"}}'))); + await exitWebdev(webdev); + }); + + test('.restart', () async { + var webdev = + await runWebDev(['daemon'], workingDirectory: exampleDirectory); + var appId = await _getAppId(webdev); + var extensionCall = '[{"method":"app.restart","id":0,' + '"params" : { "appId" : "$appId", "fullRestart" : true}}]'; + webdev.stdin.add(utf8.encode('$extensionCall\n')); + await expectLater(webdev.stdout, + emitsThrough(startsWith('[{"id":0,"result":{"code":0'))); + await exitWebdev(webdev); + }); + }); }, tags: ['webdriver']); } diff --git a/webdev/test/daemon/daemon_domain_test.dart b/webdev/test/daemon/daemon_domain_test.dart index c1ca4b325..1f134fc53 100644 --- a/webdev/test/daemon/daemon_domain_test.dart +++ b/webdev/test/daemon/daemon_domain_test.dart @@ -30,21 +30,24 @@ void main() { }); }); - test('.version', () async { - var webdev = - await runWebDev(['daemon'], workingDirectory: exampleDirectory); - webdev.stdin.add(utf8.encode('[{"method":"daemon.version","id":0}]\n')); - await expectLater( - webdev.stdout, emitsThrough(equals('[{"id":0,"result":"0.4.2"}]'))); - await exitWebdev(webdev); - }); + group('Methods', () { + test('.version', () async { + var webdev = + await runWebDev(['daemon'], workingDirectory: exampleDirectory); + webdev.stdin.add(utf8.encode('[{"method":"daemon.version","id":0}]\n')); + await expectLater( + webdev.stdout, emitsThrough(equals('[{"id":0,"result":"0.4.2"}]'))); + await exitWebdev(webdev); + }); - test('.shutdown', () async { - var webdev = - await runWebDev(['daemon'], workingDirectory: exampleDirectory); - webdev.stdin.add(utf8.encode('[{"method":"daemon.shutdown","id":0}]\n')); - await expectLater(webdev.stdout, emitsThrough(equals('[{"id":0}]'))); - expect(await webdev.exitCode, equals(0)); + test('.shutdown', () async { + var webdev = + await runWebDev(['daemon'], workingDirectory: exampleDirectory); + webdev.stdin + .add(utf8.encode('[{"method":"daemon.shutdown","id":0}]\n')); + await expectLater(webdev.stdout, emitsThrough(equals('[{"id":0}]'))); + expect(await webdev.exitCode, equals(0)); + }); }); }, tags: ['webdriver']); }