Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: dart-lang/webdev
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: cfe9753
Choose a base ref
..
head repository: dart-lang/webdev
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a15fde0
Choose a head ref
2 changes: 2 additions & 0 deletions dwds/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

- Fix failure to map JS exceptions to dart. - [#2004](https://github.com/dart-lang/webdev/pull/2004)
- Fix for listening to custom streams. - [#2011](https://github.com/dart-lang/webdev/pull/2011)
- Handle unexpected extension debugger disconnect events without crashing the app - [#2021](https://github.com/dart-lang/webdev/pull/2021)
- Support `Set` inspection. - [#2024](https://github.com/dart-lang/webdev/pull/2024)

## 18.0.0

15 changes: 14 additions & 1 deletion dwds/debug_extension_mv3/web/debug_session.dart
Original file line number Diff line number Diff line change
@@ -563,7 +563,7 @@ Future<bool> _authenticateUser(int tabId) async {
type: StorageObject.debugInfo,
tabId: tabId,
);
final authUrl = debugInfo?.authUrl;
final authUrl = debugInfo?.authUrl ?? _authUrl(debugInfo?.extensionUrl);
if (authUrl == null) {
await _showWarningNotification('Cannot authenticate user.');
return false;
@@ -742,6 +742,19 @@ class _DebugSession {
}
}

String? _authUrl(String? extensionUrl) {
if (extensionUrl == null) return null;
final authUrl = Uri.parse(extensionUrl).replace(path: authenticationPath);
switch (authUrl.scheme) {
case 'ws':
return authUrl.replace(scheme: 'http').toString();
case 'wss':
return authUrl.replace(scheme: 'https').toString();
default:
return authUrl.toString();
}
}

@JS()
@anonymous
class _EvalResponse {
65 changes: 63 additions & 2 deletions dwds/lib/src/debugging/instance.dart
Original file line number Diff line number Diff line change
@@ -128,6 +128,14 @@ class InstanceHelper extends Domain {
} else if (metaData.isRecord) {
return await _recordInstanceFor(classRef, remoteObject,
offset: offset, count: count, length: metaData.length);
} else if (metaData.isSet) {
return await _setInstanceFor(
classRef,
remoteObject,
offset: offset,
count: count,
length: metaData.length,
);
} else if (metaData.isNativeError) {
return await _plainInstanceFor(classRefForNativeJsError, remoteObject,
offset: offset, count: count, length: metaData.length);
@@ -376,7 +384,7 @@ class InstanceHelper extends Domain {
///
/// If [offset] is `null`, assumes 0 offset.
/// If [count] is `null`, return all fields starting from the offset.
Future<List<BoundField>> _recordFields(RemoteObject map,
Future<List<BoundField>> _recordFields(RemoteObject record,
{int? offset, int? count}) async {
// We do this in in awkward way because we want the keys and values, but we
// can't return things by value or some Dart objects will come back as
@@ -398,7 +406,7 @@ class InstanceHelper extends Domain {
};
}
''';
final result = await inspector.jsCallFunctionOn(map, expression, []);
final result = await inspector.jsCallFunctionOn(record, expression, []);
final positionalCountObject =
await inspector.loadField(result, 'positionalCount');
if (positionalCountObject == null || positionalCountObject.value is! int) {
@@ -494,6 +502,51 @@ class InstanceHelper extends Domain {
..fields = fields;
}

Future<Instance?> _setInstanceFor(
ClassRef classRef,
RemoteObject remoteObject, {
int? offset,
int? count,
int? length,
}) async {
final objectId = remoteObject.objectId;
if (objectId == null) return null;

final expression = '''
function() {
const sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart;
const jsSet = sdkUtils.dloadRepl(this, "_map");
const entries = [...jsSet.values()];
return {
entries: entries
};
}
''';

final result =
await inspector.jsCallFunctionOn(remoteObject, expression, []);
final entriesObject = await inspector.loadField(result, 'entries');
final entriesInstance =
await instanceFor(entriesObject, offset: offset, count: count);
final elements = entriesInstance?.elements ?? [];

final setInstance = Instance(
identityHashCode: remoteObject.objectId.hashCode,
kind: InstanceKind.kSet,
id: objectId,
classRef: classRef)
..length = length
..elements = elements;
if (offset != null && offset > 0) {
setInstance.offset = offset;
}
if (length != null && elements.length < length) {
setInstance.count = elements.length;
}

return setInstance;
}

/// Return the available count of elements in the requested range.
/// Return `null` if the range includes the whole object.
/// [count] is the range length requested by the `getObject` call.
@@ -633,6 +686,14 @@ class InstanceHelper extends Domain {
classRef: metaData.classRef)
..length = metaData.length;
}
if (metaData.isSet) {
return InstanceRef(
kind: InstanceKind.kSet,
id: objectId,
identityHashCode: remoteObject.objectId.hashCode,
classRef: metaData.classRef)
..length = metaData.length;
}
if (metaData.isNativeError) {
return InstanceRef(
kind: InstanceKind.kPlainInstance,
2 changes: 2 additions & 0 deletions dwds/lib/src/debugging/metadata/class.dart
Original file line number Diff line number Diff line change
@@ -196,6 +196,8 @@ class ClassMetaData {
/// True if this class refers to system Lists, which are treated specially.
bool get isSystemList => jsName == 'JSArray';

bool get isSet => jsName == '_HashSet';

/// True if this class refers to a function type.
bool isFunction;

197 changes: 106 additions & 91 deletions dwds/lib/src/handlers/dev_handler.dart
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ import 'package:dwds/src/loaders/strategy.dart';
import 'package:dwds/src/readers/asset_reader.dart';
import 'package:dwds/src/servers/devtools.dart';
import 'package:dwds/src/servers/extension_backend.dart';
import 'package:dwds/src/servers/extension_debugger.dart';
import 'package:dwds/src/services/app_debug_services.dart';
import 'package:dwds/src/services/debug_service.dart';
import 'package:dwds/src/services/expression_compiler.dart';
@@ -473,109 +474,123 @@ class DevHandler {
}

Future<void> _listenForDebugExtension() async {
while (await _extensionBackend!.connections.hasNext) {
await _startExtensionDebugService();
final extensionBackend = _extensionBackend;
if (extensionBackend == null) {
_logger.severe('No debug extension backend. Debugging will not work.');
return;
}

while (await extensionBackend.connections.hasNext) {
final extensionDebugger = await extensionBackend.extensionDebugger;
await _startExtensionDebugService(extensionDebugger);
}
}

/// Starts a [DebugService] for Dart Debug Extension.
Future<void> _startExtensionDebugService() async {
final extensionDebugger = await _extensionBackend!.extensionDebugger;
Future<void> _startExtensionDebugService(
ExtensionDebugger extensionDebugger) async {
// Waits for a `DevToolsRequest` to be sent from the extension background
// when the extension is clicked.
extensionDebugger.devToolsRequestStream.listen((devToolsRequest) async {
// TODO(grouma) - Ideally we surface those warnings to the extension so
// that it can be displayed to the user through an alert.
final tabUrl = devToolsRequest.tabUrl;
final appId = devToolsRequest.appId;
if (tabUrl == null) {
_logger.warning('Failed to start extension debug service. '
'Missing tab url in DevTools request for app with id: $appId');
return;
}
final connection = _appConnectionByAppId[appId];
if (connection == null) {
_logger.warning('Failed to start extension debug service. '
'Not connected to an app with id: $appId');
return;
}
final executionContext = extensionDebugger.executionContext;
if (executionContext == null) {
_logger.warning('Failed to start extension debug service. '
'No execution context for app with id: $appId');
return;
try {
await _handleDevToolsRequest(extensionDebugger, devToolsRequest);
} catch (error) {
_logger.severe('Encountered error handling DevTools request.');
extensionDebugger.closeWithError(error);
}
});
}

final debuggerStart = DateTime.now();
var appServices = _servicesByAppId[appId];
if (appServices == null) {
final debugService = await DebugService.start(
_hostname,
extensionDebugger,
executionContext,
basePathForServerUri(tabUrl),
_assetReader,
_loadStrategy,
connection,
_urlEncoder,
onResponse: (response) {
if (response['error'] == null) return;
_logger
.finest('VmService proxy responded with an error:\n$response');
},
useSse: _useSseForDebugProxy,
expressionCompiler: _expressionCompiler,
spawnDds: _spawnDds,
);
appServices = await _createAppDebugServices(
devToolsRequest.appId,
debugService,
);
final encodedUri = await debugService.encodedUri;
extensionDebugger.sendEvent('dwds.encodedUri', encodedUri);
safeUnawaited(appServices
.chromeProxyService.remoteDebugger.onClose.first
.whenComplete(() async {
appServices?.chromeProxyService.destroyIsolate();
await appServices?.close();
_servicesByAppId.remove(devToolsRequest.appId);
_logger.info('Stopped debug service on '
'${await appServices?.debugService.encodedUri}\n');
}));
extensionDebugConnections.add(DebugConnection(appServices));
_servicesByAppId[appId] = appServices;
}
// If we don't have a DevTools instance, then are connecting to an IDE.
// Therefore return early instead of opening DevTools:
if (_devTools == null) return;

final encodedUri = await appServices.debugService.encodedUri;

appServices.dwdsStats.updateLoadTime(
debuggerStart: debuggerStart, devToolsStart: DateTime.now());

// TODO(elliette): Remove handling requests from the MV2 extension after
// MV3 release.
// If we only want the URI, this means the Dart Debug Extension should
// handle how to open it. Therefore return early before opening a new
// tab or window:
if (devToolsRequest.uriOnly ?? false) {
final devToolsUri = _constructDevToolsUri(
encodedUri,
ideQueryParam: 'ChromeDevTools',
);
return extensionDebugger.sendEvent('dwds.devtoolsUri', devToolsUri);
}
Future<void> _handleDevToolsRequest(
ExtensionDebugger extensionDebugger,
DevToolsRequest devToolsRequest,
) async {
// TODO(grouma) - Ideally we surface those warnings to the extension so
// that it can be displayed to the user through an alert.
final tabUrl = devToolsRequest.tabUrl;
final appId = devToolsRequest.appId;
if (tabUrl == null) {
throw StateError('Failed to start extension debug service. '
'Missing tab url in DevTools request for app with id: $appId');
}
final connection = _appConnectionByAppId[appId];
if (connection == null) {
throw StateError('Failed to start extension debug service. '
'Not connected to an app with id: $appId');
}
final executionContext = extensionDebugger.executionContext;
if (executionContext == null) {
throw StateError('Failed to start extension debug service. '
'No execution context for app with id: $appId');
}

// Otherwise, launch DevTools in a new tab / window:
await _launchDevTools(
final debuggerStart = DateTime.now();
var appServices = _servicesByAppId[appId];
if (appServices == null) {
final debugService = await DebugService.start(
_hostname,
extensionDebugger,
_constructDevToolsUri(
encodedUri,
ideQueryParam: 'DebugExtension',
),
executionContext,
basePathForServerUri(tabUrl),
_assetReader,
_loadStrategy,
connection,
_urlEncoder,
onResponse: (response) {
if (response['error'] == null) return;
_logger.finest('VmService proxy responded with an error:\n$response');
},
useSse: _useSseForDebugProxy,
expressionCompiler: _expressionCompiler,
spawnDds: _spawnDds,
);
});
appServices = await _createAppDebugServices(
devToolsRequest.appId,
debugService,
);
final encodedUri = await debugService.encodedUri;
extensionDebugger.sendEvent('dwds.encodedUri', encodedUri);
safeUnawaited(appServices.chromeProxyService.remoteDebugger.onClose.first
.whenComplete(() async {
appServices?.chromeProxyService.destroyIsolate();
await appServices?.close();
_servicesByAppId.remove(devToolsRequest.appId);
_logger.info('Stopped debug service on '
'${await appServices?.debugService.encodedUri}\n');
}));
extensionDebugConnections.add(DebugConnection(appServices));
_servicesByAppId[appId] = appServices;
}
// If we don't have a DevTools instance, then are connecting to an IDE.
// Therefore return early instead of opening DevTools:
if (_devTools == null) return;

final encodedUri = await appServices.debugService.encodedUri;

appServices.dwdsStats.updateLoadTime(
debuggerStart: debuggerStart, devToolsStart: DateTime.now());

// TODO(elliette): Remove handling requests from the MV2 extension after
// MV3 release.
// If we only want the URI, this means the Dart Debug Extension should
// handle how to open it. Therefore return early before opening a new
// tab or window:
if (devToolsRequest.uriOnly ?? false) {
final devToolsUri = _constructDevToolsUri(
encodedUri,
ideQueryParam: 'ChromeDevTools',
);
return extensionDebugger.sendEvent('dwds.devtoolsUri', devToolsUri);
}

// Otherwise, launch DevTools in a new tab / window:
await _launchDevTools(
extensionDebugger,
_constructDevToolsUri(
encodedUri,
ideQueryParam: 'DebugExtension',
),
);
}

DevTools _ensureDevTools() {
Loading