Skip to content
Merged
1 change: 1 addition & 0 deletions dwds/example/hello_world/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<script defer src="main.dart.js"></script>
<script>
window.$dartAppId = 'id-for-testing';
window.$dartAppInstanceId = 'instance-id-for-testing';
</script>
</head>

Expand Down
10 changes: 5 additions & 5 deletions dwds/lib/service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ class DebugService {

String get wsUri => 'ws://$hostname:$port';

/// [appId] is a unique String embedded in the application available through
/// `window.$dartAppId`.
/// [appInstanceId] is a unique String embedded in the instance of the
/// application available through `window.$dartAppInstanceId`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment that an instance in this case is one to one with a tab?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It changes if there is a refresh in the same tab though - it unique per instance of the app.

static Future<DebugService> start(
String hostname,
ChromeConnection chromeConnection,
Future<String> Function(String) assetHandler,
String appId) async {
var chromeProxyService =
await ChromeProxyService.create(chromeConnection, assetHandler, appId);
String appInstanceId) async {
var chromeProxyService = await ChromeProxyService.create(
chromeConnection, assetHandler, appInstanceId);
var serviceExtensionRegistry = ServiceExtensionRegistry();
var cascade = Cascade().add(webSocketHandler(_createNewConnectionHandler(
chromeProxyService, serviceExtensionRegistry)));
Expand Down
48 changes: 37 additions & 11 deletions dwds/lib/src/chrome_proxy_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:pedantic/pedantic.dart';
import 'package:pub_semver/pub_semver.dart' as semver;
import 'package:vm_service_lib/vm_service_lib.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
Expand Down Expand Up @@ -49,21 +50,25 @@ class ChromeProxyService implements VmServiceInterface {
ChromeProxyService._(
this._vm, this._tab, this.tabConnection, this._assetHandler);

static Future<ChromeProxyService> create(ChromeConnection chromeConnection,
Future<String> Function(String) assetHandler, String appId) async {
static Future<ChromeProxyService> create(
ChromeConnection chromeConnection,
Future<String> Function(String) assetHandler,
String appInstanceId) async {
ChromeTab appTab;
for (var tab in await chromeConnection.getTabs()) {
if (tab.url.startsWith('chrome-extensions:')) continue;
var tabConnection = await tab.connect();
var result = await tabConnection.runtime.sendCommand('Runtime.evaluate',
params: {'expression': r'window.$dartAppId;', 'awaitPromise': true});
if (result.result['result']['value'] == appId) {
var result = await tabConnection.runtime
.evaluate(r'window["$dartAppInstanceId"];');
if (result.value == appInstanceId) {
appTab = tab;
break;
}
unawaited(tabConnection.close());
}
if (appTab == null) {
throw StateError('Could not connect to application with appId: $appId');
throw StateError('Could not connect to application with appInstanceId: '
'$appInstanceId');
}
var tabConnection = await appTab.connect();
await tabConnection.debugger.enable();
Expand Down Expand Up @@ -142,6 +147,7 @@ class ChromeProxyService implements VmServiceInterface {
// Listen for `registerExtension` and `postEvent` calls.
_consoleSubscription = tabConnection.runtime.onConsoleAPICalled
.listen((ConsoleAPIEvent event) {
if (_isolate == null) return;
if (event.type != 'debug') return;
var firstArgValue = event.args[0].value as String;
switch (firstArgValue) {
Expand All @@ -152,7 +158,8 @@ class ChromeProxyService implements VmServiceInterface {
'Isolate',
Event()
..kind = EventKind.kServiceExtensionAdded
..extensionRPC = service);
..extensionRPC = service
..isolate = isolateRef);
break;
case 'dart.developer.postEvent':
_streamNotify(
Expand All @@ -161,7 +168,8 @@ class ChromeProxyService implements VmServiceInterface {
..kind = EventKind.kExtension
..extensionKind = event.args[1].value as String
..extensionData = ExtensionData.parse(
jsonDecode(event.args[2].value as String) as Map));
jsonDecode(event.args[2].value as String) as Map)
..isolate = isolateRef);
break;
case 'dart.developer.inspect':
// All inspected objects should be real objects.
Expand All @@ -178,7 +186,8 @@ class ChromeProxyService implements VmServiceInterface {
Event()
..kind = EventKind.kInspect
..inspectee = inspectee
..timestamp = event.timestamp.toInt());
..timestamp = event.timestamp.toInt()
..isolate = isolateRef);
break;
default:
break;
Expand All @@ -198,12 +207,25 @@ class ChromeProxyService implements VmServiceInterface {
Event()
..kind = EventKind.kIsolateRunnable
..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 isolate.extensionRPCs) {
_streamNotify(
'Isolate',
Event()
..kind = EventKind.kServiceExtensionAdded
..extensionRPC = extensionRpc
..isolate = isolateRef);
}
}

/// Should be called when there is a hot restart or full page refresh.
///
/// Clears out [_isolate] and all related cached information.
void destroyIsolate() {
if (_isolate == null) return;
_streamNotify(
'Isolate',
Event()
Expand Down Expand Up @@ -354,7 +376,7 @@ require("dart_sdk").developer.invokeExtension(
/// Sync version of [getIsolate] for internal use, also has stronger typing
/// than the public one which has to be dynamic.
Isolate _getIsolate(String isolateId) {
if (_isolate.id == isolateId) return _isolate;
if (_isolate?.id == isolateId) return _isolate;
throw ArgumentError.value(
isolateId, 'isolateId', 'Unrecognized isolate id');
}
Expand Down Expand Up @@ -439,7 +461,7 @@ require("dart_sdk").developer.invokeExtension(
}

Future<Library> _getLibrary(String isolateId, String objectId) async {
if (isolateId != _isolate.id) return null;
if (isolateId != _isolate?.id) return null;
var libraryRef = _libraryRefs[objectId];
if (libraryRef == null) return null;
var library = _libraries[objectId];
Expand Down Expand Up @@ -670,6 +692,7 @@ require("dart_sdk").developer.invokeExtension(
}, onListen: () {
chromeConsoleSubscription =
tabConnection.runtime.onConsoleAPICalled.listen((e) {
if (_isolate == null) return;
if (!filter(e)) return;
var args = e.params['args'] as List;
var item = args[0] as Map;
Expand All @@ -683,6 +706,7 @@ require("dart_sdk").developer.invokeExtension(
if (includeExceptions) {
exceptionsSubscription =
tabConnection.runtime.onExceptionThrown.listen((e) {
if (_isolate == null) return;
controller.add(Event()
..kind = EventKind.kWriteEvent
..isolate = toIsolateRef(_isolate)
Expand All @@ -703,6 +727,7 @@ require("dart_sdk").developer.invokeExtension(
StreamSubscription resumeSubscription;
return StreamController<Event>.broadcast(onListen: () {
pauseSubscription = tabConnection.debugger.onPaused.listen((e) {
if (_isolate == null) return;
var event = Event()..isolate = toIsolateRef(_isolate);
var params = e.params;
var breakpoints = params['hitBreakpoints'] as List;
Expand All @@ -717,6 +742,7 @@ require("dart_sdk").developer.invokeExtension(
_streamNotify('Debug', event);
});
resumeSubscription = tabConnection.debugger.onResumed.listen((e) {
if (_isolate == null) return;
_streamNotify(
'Debug',
Event()
Expand Down
2 changes: 1 addition & 1 deletion dwds/test/chrome_proxy_service_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void main() {
connection,
assetHandler,
// Provided in the example index.html.
'id-for-testing',
'instance-id-for-testing',
);
});

Expand Down
7 changes: 4 additions & 3 deletions webdev/lib/src/daemon/app_domain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ class AppDomain extends Domain {
void _initialize(ServerManager serverManager) async {
var devHandler = serverManager.servers.first.devHandler;
// The connection is established right before `main()` is called.
_appId = await devHandler.connectedApps.first;
var request = await devHandler.connectedApps.first;
_appId = request.appId;
sendEvent('app.start', {
'appId': _appId,
'directory': Directory.current.path,
'deviceId': 'chrome',
'launchMode': 'run'
});
var chrome = await Chrome.connectedInstance;
_debugService =
await devHandler.startDebugService(chrome.chromeConnection, _appId);
_debugService = await devHandler.startDebugService(
chrome.chromeConnection, request.instanceId);
_webdevVmClient = await WebdevVmClient.create(_debugService);
_vmService = _webdevVmClient.client;
sendEvent('app.started', {
Expand Down
4 changes: 4 additions & 0 deletions webdev/lib/src/serve/data/connect_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ abstract class ConnectRequest

ConnectRequest._();

/// Identifies a given application, across tabs/windows.
String get appId;

/// Identifies a given instance of an application, unique per tab/window.
String get instanceId;
}
32 changes: 27 additions & 5 deletions webdev/lib/src/serve/data/connect_request.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions webdev/lib/src/serve/data/devtools_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,26 @@ abstract class DevToolsRequest

DevToolsRequest._();

/// Identifies a given application, across tabs/windows.
String get appId;

/// Identifies a given instance of an application, unique per tab/window.
String get instanceId;
}

/// A response to a [DevToolsRequest].
abstract class DevToolsResponse
implements Built<DevToolsResponse, DevToolsResponseBuilder> {
static Serializer<DevToolsResponse> get serializer =>
_$devToolsResponseSerializer;

factory DevToolsResponse([updates(DevToolsResponseBuilder b)]) =
_$DevToolsResponse;

DevToolsResponse._();

bool get success;

@nullable
String get error;
}
Loading