From b2cc0e31c53e0610f70d71a23aa41f97d75af086 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Fri, 15 Sep 2023 17:26:43 -0700 Subject: [PATCH 1/6] Good stopping point --- dwds/debug_extension_mv3/web/background.dart | 12 ++- dwds/debug_extension_mv3/web/chrome_api.dart | 17 +++- .../web/cider_connection.dart | 94 +++++++++++++++++++ dwds/debug_extension_mv3/web/storage.dart | 30 ++++++ dwds/lib/data/debug_info.dart | 2 + dwds/lib/data/debug_info.g.dart | 54 ++++++++++- dwds/lib/src/injected/client.js | 38 ++++++-- 7 files changed, 232 insertions(+), 15 deletions(-) create mode 100644 dwds/debug_extension_mv3/web/cider_connection.dart diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart index 1ccec7d2e..23d51ad3a 100644 --- a/dwds/debug_extension_mv3/web/background.dart +++ b/dwds/debug_extension_mv3/web/background.dart @@ -9,6 +9,7 @@ import 'package:dwds/data/debug_info.dart'; import 'package:js/js.dart'; import 'chrome_api.dart'; +import 'cider_connection.dart'; import 'cross_extension_communication.dart'; import 'data_types.dart'; import 'debug_session.dart'; @@ -31,6 +32,10 @@ void _registerListeners() { chrome.runtime.onMessageExternal.addListener( allowInterop(handleMessagesFromAngularDartDevTools), ); + // The only external service that sends messages to the Dart Debug Extension + // is Cider. + chrome.runtime.onConnectExternal + .addListener(allowInterop(handleCiderConnectRequest)); // Update the extension icon on tab navigation: chrome.tabs.onActivated.addListener( allowInterop((ActiveInfo info) async { @@ -105,7 +110,7 @@ Future _handleRuntimeMessages( // Save the debug info for the Dart app in storage: await setStorageObject( type: StorageObject.debugInfo, - value: _addTabUrl(debugInfo, tabUrl: dartTab.url), + value: _addTabInfo(debugInfo, tab: dartTab), tabId: dartTab.id, ); // Update the icon to show that a Dart app has been detected: @@ -183,7 +188,7 @@ bool _isInternalNavigation(NavigationInfo navigationInfo) { ].contains(navigationInfo.transitionType); } -DebugInfo _addTabUrl(DebugInfo debugInfo, {required String tabUrl}) { +DebugInfo _addTabInfo(DebugInfo debugInfo, {required Tab tab}) { return DebugInfo( (b) => b ..appEntrypointPath = debugInfo.appEntrypointPath @@ -195,7 +200,8 @@ DebugInfo _addTabUrl(DebugInfo debugInfo, {required String tabUrl}) { ..extensionUrl = debugInfo.extensionUrl ..isInternalBuild = debugInfo.isInternalBuild ..isFlutterApp = debugInfo.isFlutterApp - ..tabUrl = tabUrl, + ..tabUrl = tab.url + ..tabId = tab.id, ); } diff --git a/dwds/debug_extension_mv3/web/chrome_api.dart b/dwds/debug_extension_mv3/web/chrome_api.dart index 20bb95df6..937d1d9f7 100644 --- a/dwds/debug_extension_mv3/web/chrome_api.dart +++ b/dwds/debug_extension_mv3/web/chrome_api.dart @@ -181,6 +181,8 @@ class Runtime { external ConnectionHandler get onConnect; + external ConnectionHandler get onConnectExternal; + external OnMessageHandler get onMessage; external OnMessageHandler get onMessageExternal; @@ -203,9 +205,19 @@ class ConnectInfo { class Port { external String? get name; external void disconnect(); + external void postMessage(Object message); + external OnPortMessageHandler get onMessage; external ConnectionHandler get onDisconnect; } +@JS() +@anonymous +class OnPortMessageHandler { + external void addListener( + void Function(dynamic, Port) callback, + ); +} + @JS() @anonymous class ConnectionHandler { @@ -252,7 +264,10 @@ class Storage { @JS() @anonymous class StorageArea { - external Object get(List keys, void Function(Object result) callback); + external Object get( + List? keys, + void Function(Object result) callback, + ); external Object set(Object items, void Function()? callback); diff --git a/dwds/debug_extension_mv3/web/cider_connection.dart b/dwds/debug_extension_mv3/web/cider_connection.dart new file mode 100644 index 000000000..411a39ae6 --- /dev/null +++ b/dwds/debug_extension_mv3/web/cider_connection.dart @@ -0,0 +1,94 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// 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. + +@JS() +library cider_connection; + +import 'dart:convert'; + +import 'package:dwds/data/debug_info.dart'; +import 'package:js/js.dart'; + +import 'chrome_api.dart'; +import 'storage.dart'; + +const ciderPortName = 'cider'; + +enum CiderMessageType { + startDebug, + stopDebug, + error, +} + +Port? _ciderPort; + +// The only site allowed to connect with this extension is Cider. +// +// The allowed URIs for Cider are set in the externally_connectable field in the +//manifest.json. +void handleCiderConnectRequest(Port port) { + if (port.name == ciderPortName) { + _ciderPort = port; + + port.onMessage.addListener( + allowInterop(_handleMessageFromCider), + ); + } +} + +Future _handleMessageFromCider(dynamic message, Port _) async { + if (message! is String) { + // TODO: send error to Cider. + return; + } + + final decoded = jsonDecode(message as String) as Map; + final messageType = decoded['messageType'] as String?; + final messageBody = decoded['messageBody'] as String?; + + if (messageType == CiderMessageType.startDebug.name) { + await _startDebugging(workspaceName: messageBody); + } else if (messageType == CiderMessageType.stopDebug.name) { + // TODO: stop debugging. + } +} + +Future _startDebugging({String? workspaceName}) async { + if (workspaceName == null) { + // TODO: send error to Cider. + return; + } + + final tabs = await _findDartTabsForWorkspace(workspaceName); + + if (tabs.isEmpty) { + // TODO: send error to Cider. + return; + } + if (tabs.length > 1) { + // TODO: send error to Cider. + return; + } + if (tabs.first == null) { + // TODO: send error to Cider. + return; + } + + // TODO: call attachDebugger with Cider V as trigger. +} + +Future> _findDartTabsForWorkspace(String workspaceName) async { + final allTabsInfo = await fetchAllStorageObjectsOfType( + type: StorageObject.debugInfo, + ); + final tabIds = allTabsInfo + .where( + (debugInfo) => debugInfo.workspaceName == workspaceName, + ) + .map( + (info) => info.tabId, + ) + .toList(); + return tabIds; +} diff --git a/dwds/debug_extension_mv3/web/storage.dart b/dwds/debug_extension_mv3/web/storage.dart index 3281783cc..0b46cd4a9 100644 --- a/dwds/debug_extension_mv3/web/storage.dart +++ b/dwds/debug_extension_mv3/web/storage.dart @@ -102,6 +102,36 @@ Future fetchStorageObject({required StorageObject type, int? tabId}) { return completer.future; } +Future> fetchAllStorageObjectsOfType({required StorageObject type}) { + final completer = Completer>(); + final storageArea = _getStorageArea(type.persistence); + storageArea.get( + null, + allowInterop((Object? storageContents) { + if (storageContents == null) { + debugWarn('No storage objects of type exist.', prefix: type.name); + completer.complete([]); + return; + } + final allKeys = List.from(objectKeys(storageContents)); + final storageKeys = allKeys.where((key) => key.contains(type.name)); + final result = []; + for (final key in storageKeys) { + final json = getProperty(storageContents, key) as String?; + if (json != null) { + if (T == String) { + result.add(json as T); + } else { + result.add(serializers.deserialize(jsonDecode(json)) as T); + } + } + } + completer.complete(result); + }), + ); + return completer.future; +} + Future removeStorageObject({required StorageObject type, int? tabId}) { final storageKey = _createStorageKey(type, tabId); final completer = Completer(); diff --git a/dwds/lib/data/debug_info.dart b/dwds/lib/data/debug_info.dart index d1f9eb5e4..fa0132bff 100644 --- a/dwds/lib/data/debug_info.dart +++ b/dwds/lib/data/debug_info.dart @@ -24,5 +24,7 @@ abstract class DebugInfo implements Built { String? get extensionUrl; bool? get isInternalBuild; bool? get isFlutterApp; + String? get workspaceName; String? get tabUrl; + int? get tabId; } diff --git a/dwds/lib/data/debug_info.g.dart b/dwds/lib/data/debug_info.g.dart index 85a444ff6..e4ccd3ee5 100644 --- a/dwds/lib/data/debug_info.g.dart +++ b/dwds/lib/data/debug_info.g.dart @@ -89,6 +89,13 @@ class _$DebugInfoSerializer implements StructuredSerializer { ..add( serializers.serialize(value, specifiedType: const FullType(bool))); } + value = object.workspaceName; + if (value != null) { + result + ..add('workspaceName') + ..add(serializers.serialize(value, + specifiedType: const FullType(String))); + } value = object.tabUrl; if (value != null) { result @@ -96,6 +103,12 @@ class _$DebugInfoSerializer implements StructuredSerializer { ..add(serializers.serialize(value, specifiedType: const FullType(String))); } + value = object.tabId; + if (value != null) { + result + ..add('tabId') + ..add(serializers.serialize(value, specifiedType: const FullType(int))); + } return result; } @@ -150,10 +163,18 @@ class _$DebugInfoSerializer implements StructuredSerializer { result.isFlutterApp = serializers.deserialize(value, specifiedType: const FullType(bool)) as bool?; break; + case 'workspaceName': + result.workspaceName = serializers.deserialize(value, + specifiedType: const FullType(String)) as String?; + break; case 'tabUrl': result.tabUrl = serializers.deserialize(value, specifiedType: const FullType(String)) as String?; break; + case 'tabId': + result.tabId = serializers.deserialize(value, + specifiedType: const FullType(int)) as int?; + break; } } @@ -183,7 +204,11 @@ class _$DebugInfo extends DebugInfo { @override final bool? isFlutterApp; @override + final String? workspaceName; + @override final String? tabUrl; + @override + final int? tabId; factory _$DebugInfo([void Function(DebugInfoBuilder)? updates]) => (new DebugInfoBuilder()..update(updates))._build(); @@ -199,7 +224,9 @@ class _$DebugInfo extends DebugInfo { this.extensionUrl, this.isInternalBuild, this.isFlutterApp, - this.tabUrl}) + this.workspaceName, + this.tabUrl, + this.tabId}) : super._(); @override @@ -223,7 +250,9 @@ class _$DebugInfo extends DebugInfo { extensionUrl == other.extensionUrl && isInternalBuild == other.isInternalBuild && isFlutterApp == other.isFlutterApp && - tabUrl == other.tabUrl; + workspaceName == other.workspaceName && + tabUrl == other.tabUrl && + tabId == other.tabId; } @override @@ -239,7 +268,9 @@ class _$DebugInfo extends DebugInfo { _$hash = $jc(_$hash, extensionUrl.hashCode); _$hash = $jc(_$hash, isInternalBuild.hashCode); _$hash = $jc(_$hash, isFlutterApp.hashCode); + _$hash = $jc(_$hash, workspaceName.hashCode); _$hash = $jc(_$hash, tabUrl.hashCode); + _$hash = $jc(_$hash, tabId.hashCode); _$hash = $jf(_$hash); return _$hash; } @@ -257,7 +288,9 @@ class _$DebugInfo extends DebugInfo { ..add('extensionUrl', extensionUrl) ..add('isInternalBuild', isInternalBuild) ..add('isFlutterApp', isFlutterApp) - ..add('tabUrl', tabUrl)) + ..add('workspaceName', workspaceName) + ..add('tabUrl', tabUrl) + ..add('tabId', tabId)) .toString(); } } @@ -308,10 +341,19 @@ class DebugInfoBuilder implements Builder { bool? get isFlutterApp => _$this._isFlutterApp; set isFlutterApp(bool? isFlutterApp) => _$this._isFlutterApp = isFlutterApp; + String? _workspaceName; + String? get workspaceName => _$this._workspaceName; + set workspaceName(String? workspaceName) => + _$this._workspaceName = workspaceName; + String? _tabUrl; String? get tabUrl => _$this._tabUrl; set tabUrl(String? tabUrl) => _$this._tabUrl = tabUrl; + int? _tabId; + int? get tabId => _$this._tabId; + set tabId(int? tabId) => _$this._tabId = tabId; + DebugInfoBuilder(); DebugInfoBuilder get _$this { @@ -327,7 +369,9 @@ class DebugInfoBuilder implements Builder { _extensionUrl = $v.extensionUrl; _isInternalBuild = $v.isInternalBuild; _isFlutterApp = $v.isFlutterApp; + _workspaceName = $v.workspaceName; _tabUrl = $v.tabUrl; + _tabId = $v.tabId; _$v = null; } return this; @@ -360,7 +404,9 @@ class DebugInfoBuilder implements Builder { extensionUrl: extensionUrl, isInternalBuild: isInternalBuild, isFlutterApp: isFlutterApp, - tabUrl: tabUrl); + workspaceName: workspaceName, + tabUrl: tabUrl, + tabId: tabId); replace(_$result); return _$result; } diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js index eaf5751cf..cf995b3be 100644 --- a/dwds/lib/src/injected/client.js +++ b/dwds/lib/src/injected/client.js @@ -1,4 +1,4 @@ -// Generated by dart2js (NullSafetyMode.sound, csp, intern-composite-values), the Dart to JavaScript compiler version: 3.2.0-edge.01653dc009582892c9ad334502b0c9f262272544. +// Generated by dart2js (NullSafetyMode.sound, csp, intern-composite-values), the Dart to JavaScript compiler version: 3.2.0-edge.a8a9cc0337efe1c4855901f78adc877cb28dd1c7. // The code supports the following hooks: // dartPrint(message): // if this function is defined it is called instead of the Dart [print] @@ -9031,7 +9031,7 @@ }, _$DebugInfoSerializer: function _$DebugInfoSerializer() { }, - _$DebugInfo: function _$DebugInfo(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) { + _$DebugInfo: function _$DebugInfo(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) { var _ = this; _.appEntrypointPath = t0; _.appId = t1; @@ -9043,11 +9043,13 @@ _.extensionUrl = t7; _.isInternalBuild = t8; _.isFlutterApp = t9; - _.tabUrl = t10; + _.workspaceName = t10; + _.tabUrl = t11; + _.tabId = t12; }, DebugInfoBuilder: function DebugInfoBuilder() { var _ = this; - _._tabUrl = _._isFlutterApp = _._isInternalBuild = _._extensionUrl = _._dwdsVersion = _._authUrl = _._appUrl = _._appOrigin = _._appInstanceId = _._debug_info$_appId = _._appEntrypointPath = _._debug_info$_$v = null; + _._tabId = _._tabUrl = _._workspaceName = _._isFlutterApp = _._isInternalBuild = _._extensionUrl = _._dwdsVersion = _._authUrl = _._appUrl = _._appOrigin = _._appInstanceId = _._debug_info$_appId = _._appEntrypointPath = _._debug_info$_$v = null; }, DevToolsRequest: function DevToolsRequest() { }, @@ -23117,11 +23119,21 @@ result.push("isFlutterApp"); result.push(serializers.serialize$2$specifiedType(value, B.FullType_MtR)); } + value = object.workspaceName; + if (value != null) { + result.push("workspaceName"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_h8g)); + } value = object.tabUrl; if (value != null) { result.push("tabUrl"); result.push(serializers.serialize$2$specifiedType(value, B.FullType_h8g)); } + value = object.tabId; + if (value != null) { + result.push("tabId"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_kjq)); + } return result; }, serialize$2(serializers, object) { @@ -23178,10 +23190,18 @@ t1 = A._asBoolQ(serializers.deserialize$2$specifiedType(value, B.FullType_MtR)); result.get$_$this()._isFlutterApp = t1; break; + case "workspaceName": + t1 = A._asStringQ(serializers.deserialize$2$specifiedType(value, B.FullType_h8g)); + result.get$_$this()._workspaceName = t1; + break; case "tabUrl": t1 = A._asStringQ(serializers.deserialize$2$specifiedType(value, B.FullType_h8g)); result.get$_$this()._tabUrl = t1; break; + case "tabId": + t1 = A._asIntQ(serializers.deserialize$2$specifiedType(value, B.FullType_kjq)); + result.get$_$this()._tabId = t1; + break; } } return result._debug_info$_build$0(); @@ -23205,11 +23225,11 @@ return false; if (other === _this) return true; - return other instanceof A._$DebugInfo && _this.appEntrypointPath == other.appEntrypointPath && _this.appId == other.appId && _this.appInstanceId == other.appInstanceId && _this.appOrigin == other.appOrigin && _this.appUrl == other.appUrl && _this.authUrl == other.authUrl && _this.dwdsVersion == other.dwdsVersion && _this.extensionUrl == other.extensionUrl && _this.isInternalBuild == other.isInternalBuild && _this.isFlutterApp == other.isFlutterApp && _this.tabUrl == other.tabUrl; + return other instanceof A._$DebugInfo && _this.appEntrypointPath == other.appEntrypointPath && _this.appId == other.appId && _this.appInstanceId == other.appInstanceId && _this.appOrigin == other.appOrigin && _this.appUrl == other.appUrl && _this.authUrl == other.authUrl && _this.dwdsVersion == other.dwdsVersion && _this.extensionUrl == other.extensionUrl && _this.isInternalBuild == other.isInternalBuild && _this.isFlutterApp == other.isFlutterApp && _this.workspaceName == other.workspaceName && _this.tabUrl == other.tabUrl && _this.tabId == other.tabId; }, get$hashCode(_) { var _this = this; - return A.$jf(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(0, J.get$hashCode$(_this.appEntrypointPath)), J.get$hashCode$(_this.appId)), J.get$hashCode$(_this.appInstanceId)), J.get$hashCode$(_this.appOrigin)), J.get$hashCode$(_this.appUrl)), J.get$hashCode$(_this.authUrl)), J.get$hashCode$(_this.dwdsVersion)), J.get$hashCode$(_this.extensionUrl)), J.get$hashCode$(_this.isInternalBuild)), J.get$hashCode$(_this.isFlutterApp)), J.get$hashCode$(_this.tabUrl))); + return A.$jf(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(0, J.get$hashCode$(_this.appEntrypointPath)), J.get$hashCode$(_this.appId)), J.get$hashCode$(_this.appInstanceId)), J.get$hashCode$(_this.appOrigin)), J.get$hashCode$(_this.appUrl)), J.get$hashCode$(_this.authUrl)), J.get$hashCode$(_this.dwdsVersion)), J.get$hashCode$(_this.extensionUrl)), J.get$hashCode$(_this.isInternalBuild)), J.get$hashCode$(_this.isFlutterApp)), J.get$hashCode$(_this.workspaceName)), J.get$hashCode$(_this.tabUrl)), J.get$hashCode$(_this.tabId))); }, toString$0(_) { var _this = this, @@ -23225,7 +23245,9 @@ t2.add$2(t1, "extensionUrl", _this.extensionUrl); t2.add$2(t1, "isInternalBuild", _this.isInternalBuild); t2.add$2(t1, "isFlutterApp", _this.isFlutterApp); + t2.add$2(t1, "workspaceName", _this.workspaceName); t2.add$2(t1, "tabUrl", _this.tabUrl); + t2.add$2(t1, "tabId", _this.tabId); return t2.toString$0(t1); } }; @@ -23244,7 +23266,9 @@ _this._extensionUrl = $$v.extensionUrl; _this._isInternalBuild = $$v.isInternalBuild; _this._isFlutterApp = $$v.isFlutterApp; + _this._workspaceName = $$v.workspaceName; _this._tabUrl = $$v.tabUrl; + _this._tabId = $$v.tabId; _this._debug_info$_$v = null; } return _this; @@ -23253,7 +23277,7 @@ var _this = this, _$result = _this._debug_info$_$v; if (_$result == null) - _$result = new A._$DebugInfo(_this.get$_$this()._appEntrypointPath, _this.get$_$this()._debug_info$_appId, _this.get$_$this()._appInstanceId, _this.get$_$this()._appOrigin, _this.get$_$this()._appUrl, _this.get$_$this()._authUrl, _this.get$_$this()._dwdsVersion, _this.get$_$this()._extensionUrl, _this.get$_$this()._isInternalBuild, _this.get$_$this()._isFlutterApp, _this.get$_$this()._tabUrl); + _$result = new A._$DebugInfo(_this.get$_$this()._appEntrypointPath, _this.get$_$this()._debug_info$_appId, _this.get$_$this()._appInstanceId, _this.get$_$this()._appOrigin, _this.get$_$this()._appUrl, _this.get$_$this()._authUrl, _this.get$_$this()._dwdsVersion, _this.get$_$this()._extensionUrl, _this.get$_$this()._isInternalBuild, _this.get$_$this()._isFlutterApp, _this.get$_$this()._workspaceName, _this.get$_$this()._tabUrl, _this.get$_$this()._tabId); A.ArgumentError_checkNotNull(_$result, "other", type$.DebugInfo); return _this._debug_info$_$v = _$result; } From b9d264b897f7bd2958c4340ecb0933ad9bf80f34 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:30:35 -0700 Subject: [PATCH 2/6] Implement TODOs --- .../web/cider_connection.dart | 112 +++++++++++++++--- .../web/debug_session.dart | 14 ++- 2 files changed, 107 insertions(+), 19 deletions(-) diff --git a/dwds/debug_extension_mv3/web/cider_connection.dart b/dwds/debug_extension_mv3/web/cider_connection.dart index 411a39ae6..1c0380cbe 100644 --- a/dwds/debug_extension_mv3/web/cider_connection.dart +++ b/dwds/debug_extension_mv3/web/cider_connection.dart @@ -11,6 +11,8 @@ import 'package:dwds/data/debug_info.dart'; import 'package:js/js.dart'; import 'chrome_api.dart'; +import 'debug_session.dart'; +import 'logger.dart'; import 'storage.dart'; const ciderPortName = 'cider'; @@ -21,12 +23,20 @@ enum CiderMessageType { error, } +enum CiderErrorType { + invalidRequest, + noWorkspace, + noDartTab, + multipleDartTabs, + chromeError, +} + Port? _ciderPort; // The only site allowed to connect with this extension is Cider. // // The allowed URIs for Cider are set in the externally_connectable field in the -//manifest.json. +// manifest.json. void handleCiderConnectRequest(Port port) { if (port.name == ciderPortName) { _ciderPort = port; @@ -37,9 +47,40 @@ void handleCiderConnectRequest(Port port) { } } +// Sends a message to the Cider-connected port. +void sendMessageToCider({ + required CiderMessageType messageType, + String? messageBody, +}) { + if (_ciderPort == null) return; + final message = jsonEncode({ + 'messageType': messageType.name, + 'messageBody': messageBody, + }); + _ciderPort!.postMessage(message); +} + +// Sends an error message to the Cider-connected port. +void sendErrorMessageToCider({ + required CiderErrorType errorType, + String? errorDetails, +}) { + debugWarn('CiderError.${errorType.name} $errorDetails'); + if (_ciderPort == null) return; + final message = jsonEncode({ + 'messageType': CiderMessageType.error.name, + 'errorType': errorType.name, + 'messageBody': errorDetails, + }); + _ciderPort!.postMessage(message); +} + Future _handleMessageFromCider(dynamic message, Port _) async { if (message! is String) { - // TODO: send error to Cider. + sendErrorMessageToCider( + errorType: CiderErrorType.invalidRequest, + errorDetails: 'Expected request to be a string: $message', + ); return; } @@ -50,39 +91,50 @@ Future _handleMessageFromCider(dynamic message, Port _) async { if (messageType == CiderMessageType.startDebug.name) { await _startDebugging(workspaceName: messageBody); } else if (messageType == CiderMessageType.stopDebug.name) { - // TODO: stop debugging. + await _stopDebugging(workspaceName: messageBody); } } Future _startDebugging({String? workspaceName}) async { if (workspaceName == null) { - // TODO: send error to Cider. + _sendNoWorkspaceError(); return; } - final tabs = await _findDartTabsForWorkspace(workspaceName); - - if (tabs.isEmpty) { - // TODO: send error to Cider. - return; + final dartTab = await _findDartTabIdForWorkspace(workspaceName); + if (dartTab != null) { + await attachDebugger(dartTab, trigger: Trigger.cider); } - if (tabs.length > 1) { - // TODO: send error to Cider. +} + +Future _stopDebugging({String? workspaceName}) async { + if (workspaceName == null) { + _sendNoWorkspaceError(); return; } - if (tabs.first == null) { - // TODO: send error to Cider. - return; + + final dartTab = await _findDartTabIdForWorkspace(workspaceName); + if (dartTab != null) { + await detachDebugger( + dartTab, + type: TabType.dartApp, + reason: DetachReason.canceledByUser, + ); } +} - // TODO: call attachDebugger with Cider V as trigger. +void _sendNoWorkspaceError() { + sendErrorMessageToCider( + errorType: CiderErrorType.noWorkspace, + errorDetails: 'Cannot find a debuggable Dart tab without a workspace', + ); } -Future> _findDartTabsForWorkspace(String workspaceName) async { +Future _findDartTabIdForWorkspace(String workspaceName) async { final allTabsInfo = await fetchAllStorageObjectsOfType( type: StorageObject.debugInfo, ); - final tabIds = allTabsInfo + final dartTabIds = allTabsInfo .where( (debugInfo) => debugInfo.workspaceName == workspaceName, ) @@ -90,5 +142,29 @@ Future> _findDartTabsForWorkspace(String workspaceName) async { (info) => info.tabId, ) .toList(); - return tabIds; + + if (dartTabIds.isEmpty) { + sendErrorMessageToCider( + errorType: CiderErrorType.noDartTab, + errorDetails: 'No debuggable Dart tabs found.', + ); + return null; + } + if (dartTabIds.length > 1) { + sendErrorMessageToCider( + errorType: CiderErrorType.noDartTab, + errorDetails: 'Too many debuggable Dart tabs found.', + ); + return null; + } + final tabId = dartTabIds.first; + if (tabId == null) { + sendErrorMessageToCider( + errorType: CiderErrorType.chromeError, + errorDetails: 'Debuggable Dart tab is null.', + ); + return null; + } + + return tabId; } diff --git a/dwds/debug_extension_mv3/web/debug_session.dart b/dwds/debug_extension_mv3/web/debug_session.dart index 310f5b684..1bfc5d392 100644 --- a/dwds/debug_extension_mv3/web/debug_session.dart +++ b/dwds/debug_extension_mv3/web/debug_session.dart @@ -22,6 +22,7 @@ import 'package:sse/client/sse_client.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'chrome_api.dart'; +import 'cider_connection.dart'; import 'cross_extension_communication.dart'; import 'data_serializers.dart'; import 'data_types.dart'; @@ -79,6 +80,7 @@ enum TabType { enum Trigger { angularDartDevTools, + cider, extensionPanel, extensionIcon, } @@ -408,7 +410,15 @@ void _routeDwdsEvent(String eventData, SocketClient client, int tabId) { tabId: tabId, ); if (message.method == 'dwds.devtoolsUri') { - _openDevTools(message.params, dartAppTabId: tabId); + if (_tabIdToTrigger[tabId] != Trigger.cider) { + _openDevTools(message.params, dartAppTabId: tabId); + } + } + if (message.method == 'dwds.plainUri') { + sendMessageToCider( + messageType: CiderMessageType.startDebug, + messageBody: message.params, + ); } if (message.method == 'dwds.encodedUri') { setStorageObject( @@ -774,6 +784,8 @@ DebuggerLocation? _debuggerLocation(int dartAppTabId) { return DebuggerLocation.angularDartDevTools; case Trigger.extensionPanel: return DebuggerLocation.chromeDevTools; + case Trigger.cider: + return DebuggerLocation.ide; } } From 47dc8fc66ec63311361498841ec8d11283c925e5 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:34:35 -0700 Subject: [PATCH 3/6] Update TODOs --- dwds/debug_extension_mv3/web/manifest_mv3.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dwds/debug_extension_mv3/web/manifest_mv3.json b/dwds/debug_extension_mv3/web/manifest_mv3.json index f73e0e29a..1b0475353 100644 --- a/dwds/debug_extension_mv3/web/manifest_mv3.json +++ b/dwds/debug_extension_mv3/web/manifest_mv3.json @@ -7,7 +7,15 @@ "default_icon": "static_assets/dart_grey.png" }, "externally_connectable": { - "ids": ["nbkbficgbembimioedhceniahniffgpl"] + "ids": ["nbkbficgbembimioedhceniahniffgpl"], + "matches": [ + "https://cider.corp.google.com/*", + "https://cider-staging.corp.google.com/*", + "https://cider-test.corp.google.com/*", + "https://cider-v.corp.google.com/*", + "https://cider-v-staging.corp.google.com/*", + "https://cider-v-test.corp.google.com/*" + ] }, "permissions": [ "debugger", From d0bab1cf4da675add01f79453b643bbca94c9f5d Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:45:09 -0700 Subject: [PATCH 4/6] Clean up manifest.json --- dwds/debug_extension_mv3/web/manifest_mv3.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/dwds/debug_extension_mv3/web/manifest_mv3.json b/dwds/debug_extension_mv3/web/manifest_mv3.json index 1b0475353..7b809dcbe 100644 --- a/dwds/debug_extension_mv3/web/manifest_mv3.json +++ b/dwds/debug_extension_mv3/web/manifest_mv3.json @@ -9,9 +9,6 @@ "externally_connectable": { "ids": ["nbkbficgbembimioedhceniahniffgpl"], "matches": [ - "https://cider.corp.google.com/*", - "https://cider-staging.corp.google.com/*", - "https://cider-test.corp.google.com/*", "https://cider-v.corp.google.com/*", "https://cider-v-staging.corp.google.com/*", "https://cider-v-test.corp.google.com/*" From 8cfe040b323a47f066bc9773c1c516136f374675 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:23:25 -0700 Subject: [PATCH 5/6] Web debugging from cider works --- dwds/debug_extension_mv3/web/background.dart | 1 + .../web/cider_connection.dart | 35 +++++++++++++------ .../web/debug_session.dart | 2 +- .../debug_extension_mv3/web/manifest_mv2.json | 10 +++++- .../debug_extension_mv3/web/manifest_mv3.json | 3 ++ 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart index 23d51ad3a..b24d44629 100644 --- a/dwds/debug_extension_mv3/web/background.dart +++ b/dwds/debug_extension_mv3/web/background.dart @@ -200,6 +200,7 @@ DebugInfo _addTabInfo(DebugInfo debugInfo, {required Tab tab}) { ..extensionUrl = debugInfo.extensionUrl ..isInternalBuild = debugInfo.isInternalBuild ..isFlutterApp = debugInfo.isFlutterApp + ..workspaceName = debugInfo.workspaceName ..tabUrl = tab.url ..tabId = tab.id, ); diff --git a/dwds/debug_extension_mv3/web/cider_connection.dart b/dwds/debug_extension_mv3/web/cider_connection.dart index 1c0380cbe..b4a9a0795 100644 --- a/dwds/debug_extension_mv3/web/cider_connection.dart +++ b/dwds/debug_extension_mv3/web/cider_connection.dart @@ -18,8 +18,10 @@ import 'storage.dart'; const ciderPortName = 'cider'; enum CiderMessageType { - startDebug, - stopDebug, + startDebugRequest, + startDebugResponse, + stopDebugRequest, + stopDebugResponse, error, } @@ -29,6 +31,7 @@ enum CiderErrorType { noDartTab, multipleDartTabs, chromeError, + internalError, } Port? _ciderPort; @@ -76,7 +79,7 @@ void sendErrorMessageToCider({ } Future _handleMessageFromCider(dynamic message, Port _) async { - if (message! is String) { + if (message is! String) { sendErrorMessageToCider( errorType: CiderErrorType.invalidRequest, errorDetails: 'Expected request to be a string: $message', @@ -84,13 +87,13 @@ Future _handleMessageFromCider(dynamic message, Port _) async { return; } - final decoded = jsonDecode(message as String) as Map; + final decoded = jsonDecode(message) as Map; final messageType = decoded['messageType'] as String?; final messageBody = decoded['messageBody'] as String?; - if (messageType == CiderMessageType.startDebug.name) { + if (messageType == CiderMessageType.startDebugRequest.name) { await _startDebugging(workspaceName: messageBody); - } else if (messageType == CiderMessageType.stopDebug.name) { + } else if (messageType == CiderMessageType.stopDebugRequest.name) { await _stopDebugging(workspaceName: messageBody); } } @@ -103,6 +106,8 @@ Future _startDebugging({String? workspaceName}) async { final dartTab = await _findDartTabIdForWorkspace(workspaceName); if (dartTab != null) { + // TODO(https://github.com/dart-lang/webdev/issues/2198): When debugging + // with Cider, disable debugging with DevTools. await attachDebugger(dartTab, trigger: Trigger.cider); } } @@ -114,11 +119,19 @@ Future _stopDebugging({String? workspaceName}) async { } final dartTab = await _findDartTabIdForWorkspace(workspaceName); - if (dartTab != null) { - await detachDebugger( - dartTab, - type: TabType.dartApp, - reason: DetachReason.canceledByUser, + if (dartTab == null) return; + final successfullyDetached = await detachDebugger( + dartTab, + type: TabType.dartApp, + reason: DetachReason.canceledByUser, + ); + + if (successfullyDetached) { + sendMessageToCider(messageType: CiderMessageType.stopDebugResponse); + } else { + sendErrorMessageToCider( + errorType: CiderErrorType.internalError, + errorDetails: 'Unable to detach debugger.', ); } } diff --git a/dwds/debug_extension_mv3/web/debug_session.dart b/dwds/debug_extension_mv3/web/debug_session.dart index 1bfc5d392..2008408d2 100644 --- a/dwds/debug_extension_mv3/web/debug_session.dart +++ b/dwds/debug_extension_mv3/web/debug_session.dart @@ -416,7 +416,7 @@ void _routeDwdsEvent(String eventData, SocketClient client, int tabId) { } if (message.method == 'dwds.plainUri') { sendMessageToCider( - messageType: CiderMessageType.startDebug, + messageType: CiderMessageType.startDebugResponse, messageBody: message.params, ); } diff --git a/dwds/debug_extension_mv3/web/manifest_mv2.json b/dwds/debug_extension_mv3/web/manifest_mv2.json index 49f78ad4f..1c700f1ea 100644 --- a/dwds/debug_extension_mv3/web/manifest_mv2.json +++ b/dwds/debug_extension_mv3/web/manifest_mv2.json @@ -7,7 +7,15 @@ "default_icon": "static_assets/dart_grey.png" }, "externally_connectable": { - "ids": ["nbkbficgbembimioedhceniahniffgpl"] + "ids": ["nbkbficgbembimioedhceniahniffgpl"], + "matches": [ + "https://cider.corp.google.com/*", + "https://cider-staging.corp.google.com/*", + "https://cider-test.corp.google.com/*", + "https://cider-v.corp.google.com/*", + "https://cider-v-staging.corp.google.com/*", + "https://cider-v-test.corp.google.com/*" + ] }, "permissions": ["debugger", "notifications", "storage", "webNavigation"], "background": { diff --git a/dwds/debug_extension_mv3/web/manifest_mv3.json b/dwds/debug_extension_mv3/web/manifest_mv3.json index 7b809dcbe..1b0475353 100644 --- a/dwds/debug_extension_mv3/web/manifest_mv3.json +++ b/dwds/debug_extension_mv3/web/manifest_mv3.json @@ -9,6 +9,9 @@ "externally_connectable": { "ids": ["nbkbficgbembimioedhceniahniffgpl"], "matches": [ + "https://cider.corp.google.com/*", + "https://cider-staging.corp.google.com/*", + "https://cider-test.corp.google.com/*", "https://cider-v.corp.google.com/*", "https://cider-v-staging.corp.google.com/*", "https://cider-v-test.corp.google.com/*" From 427f0201fcbdbf0a65728068550f59b9b285c16d Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:36:37 -0700 Subject: [PATCH 6/6] Clean up --- .../web/cider_connection.dart | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/dwds/debug_extension_mv3/web/cider_connection.dart b/dwds/debug_extension_mv3/web/cider_connection.dart index b4a9a0795..71b4b30be 100644 --- a/dwds/debug_extension_mv3/web/cider_connection.dart +++ b/dwds/debug_extension_mv3/web/cider_connection.dart @@ -15,33 +15,40 @@ import 'debug_session.dart'; import 'logger.dart'; import 'storage.dart'; -const ciderPortName = 'cider'; - +/// Defines the message types that can be passed to/from Cider. +/// +/// The types must match those defined by ChromeExtensionMessageType in the +/// Cider extension. enum CiderMessageType { - startDebugRequest, + error, startDebugResponse, - stopDebugRequest, + startDebugRequest, stopDebugResponse, - error, + stopDebugRequest, } +/// Defines the error types that can be sent to Cider. +/// +/// The types must match those defined by ChromeExtensionErrorType in the +/// Cider extension. enum CiderErrorType { - invalidRequest, - noWorkspace, - noDartTab, - multipleDartTabs, chromeError, internalError, + invalidRequest, + multipleDartTabs, + noDartTab, + noWorkspace, } +const _ciderPortName = 'cider'; Port? _ciderPort; -// The only site allowed to connect with this extension is Cider. -// -// The allowed URIs for Cider are set in the externally_connectable field in the -// manifest.json. +/// Handles a connect request from Cider. +/// +/// The only site allowed to connect with this extension is Cider. The allowed +/// URIs for Cider are set in the externally_connectable field in the manifest. void handleCiderConnectRequest(Port port) { - if (port.name == ciderPortName) { + if (port.name == _ciderPortName) { _ciderPort = port; port.onMessage.addListener( @@ -50,7 +57,7 @@ void handleCiderConnectRequest(Port port) { } } -// Sends a message to the Cider-connected port. +/// Sends a message to the Cider-connected port. void sendMessageToCider({ required CiderMessageType messageType, String? messageBody, @@ -63,7 +70,7 @@ void sendMessageToCider({ _ciderPort!.postMessage(message); } -// Sends an error message to the Cider-connected port. +/// Sends an error message to the Cider-connected port. void sendErrorMessageToCider({ required CiderErrorType errorType, String? errorDetails,