Skip to content

Handle page refreshes and opening in new tabs #222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 20, 2019
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