Skip to content

Commit f480bfc

Browse files
authored
Attaches a debug connection to the tab when clicked (#1740)
1 parent 539dd2d commit f480bfc

File tree

13 files changed

+578
-7
lines changed

13 files changed

+578
-7
lines changed

dwds/debug_extension_mv3/build.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ builders:
2020
{
2121
"web/{{}}.dart.js": ["compiled/{{}}.dart.js"],
2222
"web/{{}}.png": ["compiled/{{}}.png"],
23+
"web/{{}}.html": ["compiled/{{}}.html"],
2324
"web/manifest.json": ["compiled/manifest.json"],
2425
}
2526
auto_apply: none

dwds/debug_extension_mv3/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: >-
66
A Chrome extension for Dart debugging.
77
88
environment:
9-
sdk: '>=2.12.0 <3.0.0'
9+
sdk: '>=2.18.0 <3.0.0'
1010

1111
dependencies:
1212
js: ^0.6.1+1

dwds/debug_extension_mv3/tool/copy_builder.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

@@ -12,6 +12,7 @@ class _CopyBuilder extends Builder {
1212
Map<String, List<String>> get buildExtensions => {
1313
"web/{{}}.dart.js": ["compiled/{{}}.dart.js"],
1414
"web/{{}}.png": ["compiled/{{}}.png"],
15+
"web/{{}}.html": ["compiled/{{}}.html"],
1516
"web/manifest.json": ["compiled/manifest.json"],
1617
};
1718

dwds/debug_extension_mv3/web/background.dart

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,44 @@
55
@JS()
66
library background;
77

8+
import 'dart:html';
9+
810
import 'package:js/js.dart';
911

1012
import 'chrome_api.dart';
11-
import 'web_api.dart';
1213

1314
void main() {
14-
console.log('Running Dart Debug Extension.');
1515
// Detect clicks on the Dart Debug Extension icon.
16-
chrome.action.onClicked.addListener(allowInterop((_) {
17-
console.log('Detected click on the Dart Debug Extension icon.');
16+
chrome.action.onClicked.addListener(allowInterop((_) async {
17+
await _createDebugTab();
18+
await _executeInjectorScript();
1819
}));
1920
}
21+
22+
Future<Tab> _createDebugTab() async {
23+
final url = chrome.runtime.getURL('debug_tab.html');
24+
final tabPromise = chrome.tabs.create(TabInfo(
25+
active: false,
26+
pinned: true,
27+
url: url,
28+
));
29+
return promiseToFuture<Tab>(tabPromise);
30+
}
31+
32+
Future<void> _executeInjectorScript() async {
33+
final tabId = await _getTabId();
34+
if (tabId != null) {
35+
chrome.scripting.executeScript(
36+
InjectDetails(
37+
target: Target(tabId: tabId), files: ['iframe_injector.dart.js']),
38+
/*callback*/ null,
39+
);
40+
}
41+
}
42+
43+
Future<int?> _getTabId() async {
44+
final query = QueryInfo(active: true, currentWindow: true);
45+
final tabs = List<Tab>.from(await promiseToFuture(chrome.tabs.query(query)));
46+
final tab = tabs.isNotEmpty ? tabs.first : null;
47+
return tab?.id;
48+
}

dwds/debug_extension_mv3/web/chrome_api.dart

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ external Chrome get chrome;
1111
@anonymous
1212
class Chrome {
1313
external Action get action;
14+
external Debugger get debugger;
15+
external Runtime get runtime;
16+
external Scripting get scripting;
17+
external Tabs get tabs;
1418
}
1519

1620
@JS()
@@ -26,9 +30,109 @@ class OnClickedHandler {
2630
external void addListener(void Function(Tab tab) callback);
2731
}
2832

33+
@JS()
34+
@anonymous
35+
class Debugger {
36+
// https://developer.chrome.com/docs/extensions/reference/debugger/#method-attach
37+
external void attach(
38+
Debuggee target, String requiredVersion, Function? callback);
39+
40+
// https://developer.chrome.com/docs/extensions/reference/debugger/#method-sendCommand
41+
external void sendCommand(Debuggee target, String method,
42+
Object? commandParams, Function? callback);
43+
}
44+
45+
@JS()
46+
@anonymous
47+
class Runtime {
48+
// https://developer.chrome.com/docs/extensions/reference/runtime/#method-sendMessage
49+
external void sendMessage(
50+
String? id, Object? message, Object? options, Function? callback);
51+
52+
// https://developer.chrome.com/docs/extensions/reference/runtime/#method-getURL
53+
external String getURL(String path);
54+
55+
// https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessage
56+
external OnMessageHandler get onMessage;
57+
}
58+
59+
@JS()
60+
@anonymous
61+
class OnMessageHandler {
62+
external void addListener(
63+
void Function(dynamic, MessageSender, Function) callback);
64+
}
65+
66+
@JS()
67+
@anonymous
68+
class Scripting {
69+
// https://developer.chrome.com/docs/extensions/reference/scripting/#method-executeScript
70+
external executeScript(InjectDetails details, Function? callback);
71+
}
72+
73+
@JS()
74+
@anonymous
75+
class Tabs {
76+
// https://developer.chrome.com/docs/extensions/reference/tabs/#method-query
77+
external Object query(QueryInfo queryInfo);
78+
79+
// https://developer.chrome.com/docs/extensions/reference/tabs/#method-create
80+
external Object create(TabInfo tabInfo);
81+
}
82+
83+
@JS()
84+
@anonymous
85+
class Debuggee {
86+
external int get tabId;
87+
external String get extensionId;
88+
external String get targetId;
89+
external factory Debuggee({int tabId, String? extensionId, String? targetId});
90+
}
91+
92+
@JS()
93+
@anonymous
94+
class MessageSender {
95+
external String? get id;
96+
external Tab? get tab;
97+
external String? get url;
98+
external factory MessageSender({String? id, String? url, Tab? tab});
99+
}
100+
101+
@JS()
102+
@anonymous
103+
class TabInfo {
104+
external bool? get active;
105+
external bool? get pinned;
106+
external String? get url;
107+
external factory TabInfo({bool? active, bool? pinned, String? url});
108+
}
109+
110+
@JS()
111+
@anonymous
112+
class QueryInfo {
113+
external bool get active;
114+
external bool get currentWindow;
115+
external factory QueryInfo({bool? active, bool? currentWindow});
116+
}
117+
29118
@JS()
30119
@anonymous
31120
class Tab {
32121
external int get id;
33122
external String get url;
34123
}
124+
125+
@JS()
126+
@anonymous
127+
class InjectDetails {
128+
external Target get target;
129+
external List<String>? get files;
130+
external factory InjectDetails({Target target, List<String> files});
131+
}
132+
133+
@JS()
134+
@anonymous
135+
class Target {
136+
external int get tabId;
137+
external factory Target({int tabId});
138+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) 2022, 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 debug_tab;
7+
8+
import 'dart:html';
9+
10+
import 'package:js/js.dart';
11+
12+
import 'chrome_api.dart';
13+
import 'messaging.dart';
14+
15+
final _channel = BroadcastChannel(debugTabChannelName);
16+
17+
void main() {
18+
_registerListeners();
19+
}
20+
21+
void _registerListeners() {
22+
_channel.addEventListener(
23+
'message',
24+
allowInterop(_handleChannelMessageEvents),
25+
);
26+
}
27+
28+
void _handleChannelMessageEvents(Event event) {
29+
final messageData =
30+
jsEventToMessageData(event, expectedOrigin: chrome.runtime.getURL(''));
31+
if (messageData == null) return;
32+
33+
interceptMessage<DebugInfo>(
34+
message: messageData,
35+
expectedType: MessageType.debugInfo,
36+
expectedSender: Script.iframe,
37+
expectedRecipient: Script.debugTab,
38+
messageHandler: _debugInfoMessageHandler,
39+
);
40+
}
41+
42+
void _debugInfoMessageHandler(DebugInfo message) {
43+
final tabId = message.tabId;
44+
_startDebugging(tabId);
45+
}
46+
47+
void _startDebugging(int tabId) {
48+
final debuggee = Debuggee(tabId: tabId);
49+
chrome.debugger.attach(debuggee, '1.3', allowInterop(() async {
50+
chrome.debugger.sendCommand(
51+
debuggee, 'Runtime.enable', EmptyParam(), allowInterop((e) {}));
52+
}));
53+
}
54+
55+
@JS()
56+
@anonymous
57+
class EmptyParam {
58+
external factory EmptyParam();
59+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<body>
5+
<h1>Dart Debug Tab</h1>
6+
<h2>Closing will disconnect the debug connection.</h2>
7+
<script type="module" src="debug_tab.dart.js"></script>
8+
</body>
9+
10+
</html>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) 2022, 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 iframe;
7+
8+
import 'dart:html';
9+
10+
import 'package:js/js.dart';
11+
12+
import 'chrome_api.dart';
13+
import 'messaging.dart';
14+
15+
final _channel = BroadcastChannel(debugTabChannelName);
16+
17+
void main() {
18+
_registerListeners();
19+
20+
// Send a message to the injector script that the IFRAME has loaded.
21+
_sendMessageToIframeInjector(
22+
type: MessageType.iframeReady,
23+
encodedBody: IframeReady(isReady: true).toJSON(),
24+
);
25+
}
26+
27+
void _registerListeners() {
28+
chrome.runtime.onMessage.addListener(allowInterop(_handleRuntimeMessages));
29+
}
30+
31+
void _sendMessageToIframeInjector({
32+
required MessageType type,
33+
required String encodedBody,
34+
}) {
35+
final message = Message(
36+
to: Script.iframeInjector,
37+
from: Script.iframe,
38+
type: type,
39+
encodedBody: encodedBody,
40+
);
41+
window.parent?.postMessage(message.toJSON(), '*');
42+
}
43+
44+
void _handleRuntimeMessages(
45+
dynamic jsRequest, MessageSender sender, Function sendResponse) {
46+
if (jsRequest is! String) return;
47+
48+
interceptMessage<DebugState>(
49+
message: jsRequest,
50+
expectedType: MessageType.debugState,
51+
expectedSender: Script.iframeInjector,
52+
expectedRecipient: Script.iframe,
53+
messageHandler: (DebugState message) {
54+
final tabId = sender.tab?.id;
55+
if (tabId == null) return;
56+
57+
_sendMessageToDebugTab(
58+
type: MessageType.debugInfo,
59+
encodedBody: DebugInfo(tabId: tabId).toJSON(),
60+
);
61+
});
62+
}
63+
64+
void _sendMessageToDebugTab({
65+
required MessageType type,
66+
required String encodedBody,
67+
}) {
68+
final message = Message(
69+
to: Script.debugTab,
70+
from: Script.iframe,
71+
type: type,
72+
encodedBody: encodedBody,
73+
);
74+
_channel.postMessage(message.toJSON());
75+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<body>
5+
<h1>Dart Debug IFRAME</h1>
6+
<script type="module" src="iframe.dart.js"></script>
7+
</body>
8+
9+
</html>

0 commit comments

Comments
 (0)