Skip to content

Commit b3d6ef1

Browse files
authored
Expose a keyboard shortcut to copy the Dart app ID (#2271)
1 parent c26b4e5 commit b3d6ef1

File tree

7 files changed

+197
-25
lines changed

7 files changed

+197
-25
lines changed

dwds/debug_extension_mv3/web/background.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ void _registerListeners() {
5353
chrome.webNavigation.onCommitted
5454
.addListener(allowInterop(_detectNavigationAwayFromDartApp));
5555

56+
chrome.commands.onCommand
57+
.addListener(allowInterop(_maybeSendCopyAppIdRequest));
58+
5659
// Detect clicks on the Dart Debug Extension icon.
5760
onExtensionIconClicked(
5861
allowInterop(
@@ -67,7 +70,6 @@ void _registerListeners() {
6770
Future<void> _handleRuntimeMessages(
6871
dynamic jsRequest,
6972
MessageSender sender,
70-
// ignore: avoid-unused-parameters
7173
Function sendResponse,
7274
) async {
7375
if (jsRequest is! String) return;
@@ -155,6 +157,8 @@ Future<void> _handleRuntimeMessages(
155157
_setWarningIcon();
156158
},
157159
);
160+
161+
sendResponse(defaultResponse);
158162
}
159163

160164
Future<void> _detectNavigationAwayFromDartApp(
@@ -206,6 +210,23 @@ DebugInfo _addTabInfo(DebugInfo debugInfo, {required Tab tab}) {
206210
);
207211
}
208212

213+
Future<bool> _maybeSendCopyAppIdRequest(String command, [Tab? tab]) async {
214+
if (command != 'copyAppId') return false;
215+
final tabId = (tab ?? await activeTab)?.id;
216+
if (tabId == null) return false;
217+
final debugInfo = await _fetchDebugInfo(tabId);
218+
final workspaceName = debugInfo?.workspaceName;
219+
if (workspaceName == null) return false;
220+
final appId = '$workspaceName-$tabId';
221+
return sendTabsMessage(
222+
tabId: tabId,
223+
type: MessageType.appId,
224+
body: appId,
225+
sender: Script.background,
226+
recipient: Script.copier,
227+
);
228+
}
229+
209230
Future<void> _updateIcon(int activeTabId) async {
210231
final debugInfo = await _fetchDebugInfo(activeTabId);
211232
if (debugInfo == null) {

dwds/debug_extension_mv3/web/chrome_api.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ external Chrome get chrome;
1212
@JS()
1313
@anonymous
1414
class Chrome {
15+
external Commands get commands;
1516
external Debugger get debugger;
1617
external Devtools get devtools;
1718
external Notifications get notifications;
@@ -25,6 +26,20 @@ class Chrome {
2526
/// chrome.debugger APIs:
2627
/// https://developer.chrome.com/docs/extensions/reference/debugger
2728
29+
@JS()
30+
@anonymous
31+
class Commands {
32+
external OnCommandHandler get onCommand;
33+
}
34+
35+
@JS()
36+
@anonymous
37+
class OnCommandHandler {
38+
external void addListener(
39+
void Function(String commandName, [Tab? tab]) callback,
40+
);
41+
}
42+
2843
@JS()
2944
@anonymous
3045
class Debugger {
@@ -228,7 +243,7 @@ class ConnectionHandler {
228243
@anonymous
229244
class OnMessageHandler {
230245
external void addListener(
231-
void Function(dynamic, MessageSender, Function) callback,
246+
dynamic Function(dynamic, MessageSender, Function) callback,
232247
);
233248
}
234249

@@ -299,6 +314,13 @@ class Tabs {
299314

300315
external dynamic remove(int tabId, void Function()? callback);
301316

317+
external Object sendMessage(
318+
int tabId,
319+
Object? message,
320+
Object? options,
321+
void Function() callback,
322+
);
323+
302324
external OnActivatedHandler get onActivated;
303325

304326
external OnRemovedHandler get onRemoved;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@JS()
6+
library copier;
7+
8+
import 'dart:html';
9+
10+
import 'package:js/js.dart';
11+
12+
import 'chrome_api.dart';
13+
import 'messaging.dart';
14+
15+
void main() {
16+
_registerListeners();
17+
}
18+
19+
void _registerListeners() {
20+
chrome.runtime.onMessage.addListener(
21+
allowInterop(_handleRuntimeMessages),
22+
);
23+
}
24+
25+
void _handleRuntimeMessages(
26+
dynamic jsRequest,
27+
MessageSender sender,
28+
Function sendResponse,
29+
) {
30+
interceptMessage<String>(
31+
message: jsRequest,
32+
expectedType: MessageType.appId,
33+
expectedSender: Script.background,
34+
expectedRecipient: Script.copier,
35+
messageHandler: _copyAppId,
36+
);
37+
38+
sendResponse(defaultResponse);
39+
}
40+
41+
void _copyAppId(String appId) {
42+
final clipboard = window.navigator.clipboard;
43+
if (clipboard == null) return;
44+
clipboard.writeText(appId);
45+
_showCopiedMessage(appId);
46+
}
47+
48+
Future<void> _showCopiedMessage(String appId) async {
49+
final snackbar = document.createElement('div');
50+
snackbar.setInnerHtml('Copied app ID: <i>$appId</i>');
51+
snackbar.classes.addAll(['snackbar', 'snackbar--info', 'show']);
52+
document.body?.append(snackbar);
53+
await Future.delayed(Duration(seconds: 4));
54+
snackbar.remove();
55+
}

dwds/debug_extension_mv3/web/manifest_mv2.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,20 @@
2424
"content_scripts": [
2525
{
2626
"matches": ["<all_urls>"],
27-
"js": ["detector.dart.js"],
27+
"js": ["detector.dart.js", "copier.dart.js"],
28+
"css": ["static_assets/styles.css"],
2829
"run_at": "document_end"
2930
}
3031
],
32+
"commands": {
33+
"copyAppId": {
34+
"suggestedKey": {
35+
"default": "Ctrl+Shift+7",
36+
"mac": "Command+Shift+7"
37+
},
38+
"description": "Copy the app ID"
39+
}
40+
},
3141
"web_accessible_resources": ["debug_info.dart.js"],
3242
"options_page": "static_assets/settings.html"
3343
}

dwds/debug_extension_mv3/web/manifest_mv3.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,20 @@
3131
"content_scripts": [
3232
{
3333
"matches": ["<all_urls>"],
34-
"js": ["detector.dart.js"],
34+
"js": ["detector.dart.js", "copier.dart.js"],
35+
"css": ["static_assets/styles.css"],
3536
"run_at": "document_end"
3637
}
3738
],
39+
"commands": {
40+
"copyAppId": {
41+
"suggestedKey": {
42+
"default": "Ctrl+Shift+7",
43+
"mac": "Command+Shift+7"
44+
},
45+
"description": "Copy the app ID"
46+
}
47+
},
3848
"web_accessible_resources": [
3949
{
4050
"matches": ["<all_urls>"],

dwds/debug_extension_mv3/web/messaging.dart

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,23 @@ library messaging;
77

88
import 'dart:async';
99
import 'dart:convert';
10+
import 'dart:js_util';
1011

1112
import 'package:js/js.dart';
1213

1314
import 'chrome_api.dart';
1415
import 'data_serializers.dart';
1516
import 'logger.dart';
1617

18+
// A default response for the sendResponse callback.
19+
//
20+
// Prevents the message port from closing. See:
21+
// https://developer.chrome.com/docs/extensions/mv3/messaging/#simple
22+
final defaultResponse = jsify({'response': 'received'});
23+
1724
enum Script {
1825
background,
26+
copier,
1927
debuggerPanel,
2028
detector;
2129

@@ -27,6 +35,7 @@ enum Script {
2735
enum MessageType {
2836
isAuthenticated,
2937
connectFailure,
38+
appId,
3039
debugInfo,
3140
debugStateChange,
3241
devToolsUrl,
@@ -104,34 +113,77 @@ void interceptMessage<T>({
104113
}
105114
}
106115

116+
/// Send a message using the chrome.runtime.sendMessage API.
107117
Future<bool> sendRuntimeMessage({
108118
required MessageType type,
109119
required String body,
110120
required Script sender,
111121
required Script recipient,
122+
}) =>
123+
_sendMessage(
124+
type: type,
125+
body: body,
126+
sender: sender,
127+
recipient: recipient,
128+
);
129+
130+
/// Send a message using the chrome.tabs.sendMessage API.
131+
Future<bool> sendTabsMessage({
132+
required int tabId,
133+
required MessageType type,
134+
required String body,
135+
required Script sender,
136+
required Script recipient,
137+
}) =>
138+
_sendMessage(
139+
tabId: tabId,
140+
type: type,
141+
body: body,
142+
sender: sender,
143+
recipient: recipient,
144+
);
145+
146+
Future<bool> _sendMessage({
147+
required MessageType type,
148+
required String body,
149+
required Script sender,
150+
required Script recipient,
151+
int? tabId,
112152
}) {
113153
final message = Message(
114154
to: recipient,
115155
from: sender,
116156
type: type,
117157
body: body,
118-
);
158+
).toJSON();
119159
final completer = Completer<bool>();
120-
chrome.runtime.sendMessage(
121-
// id
122-
null,
123-
message.toJSON(),
124-
// options
125-
null,
126-
allowInterop(() {
127-
final error = chrome.runtime.lastError;
128-
if (error != null) {
129-
debugError(
130-
'Error sending $type to $recipient from $sender: ${error.message}',
131-
);
132-
}
133-
completer.complete(error != null);
134-
}),
135-
);
160+
void responseHandler([dynamic _]) {
161+
final error = chrome.runtime.lastError;
162+
if (error != null) {
163+
debugError(
164+
'Error sending $type to $recipient from $sender: ${error.message}',
165+
);
166+
}
167+
completer.complete(error != null);
168+
}
169+
170+
if (tabId != null) {
171+
chrome.tabs.sendMessage(
172+
tabId,
173+
message,
174+
// options
175+
null,
176+
allowInterop(responseHandler),
177+
);
178+
} else {
179+
chrome.runtime.sendMessage(
180+
// id
181+
null,
182+
message,
183+
// options
184+
null,
185+
allowInterop(responseHandler),
186+
);
187+
}
136188
return completer.future;
137189
}

dwds/debug_extension_mv3/web/static_assets/styles.css

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,16 @@ iframe {
5757
}
5858

5959
.debugger-card > .mdl-card__title {
60+
background: url("debugger_settings.png");
6061
background-position: center;
6162
background-repeat: no-repeat;
62-
background: url("debugger_settings.png");
6363
height: 200px;
6464
}
6565

6666
.inspector-card > .mdl-card__title {
67+
background: url("inspect_widget.png");
6768
background-position: center;
6869
background-repeat: no-repeat;
69-
background: url("inspect_widget.png");
7070
height: 300px;
7171
}
7272

@@ -75,20 +75,22 @@ h6 {
7575
}
7676

7777
.snackbar {
78-
border-radius: 2px;
7978
bottom: 0px;
8079
color: #eeeeee;
80+
font-family: Roboto, 'Helvetica Neue', sans-serif;
81+
left: 0px;
8182
padding: 16px;
8283
position: fixed;
84+
right: 0px;
8385
text-align: center;
8486
visibility: hidden;
8587
width: 100%;
8688
z-index: 1;
8789
}
8890

8991
.snackbar > a {
90-
font-weight: bold;
9192
color: #eeeeee;
93+
font-weight: bold;
9294
}
9395

9496
.snackbar--info {

0 commit comments

Comments
 (0)