Skip to content

Commit 3e2364e

Browse files
authored
Add the Dart Debugger / Flutter Inspector panels in Chrome DevTools (#1812)
1 parent c164231 commit 3e2364e

File tree

10 files changed

+339
-40
lines changed

10 files changed

+339
-40
lines changed

dwds/debug_extension_mv3/web/chrome_api.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
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

5+
import 'dart:html';
6+
57
import 'package:js/js.dart';
68

79
@JS()
@@ -12,6 +14,7 @@ external Chrome get chrome;
1214
class Chrome {
1315
external Action get action;
1416
external Debugger get debugger;
17+
external Devtools get devtools;
1518
external Notifications get notifications;
1619
external Runtime get runtime;
1720
external Scripting get scripting;
@@ -87,6 +90,50 @@ class Debuggee {
8790
external factory Debuggee({int tabId, String? extensionId, String? targetId});
8891
}
8992

93+
/// chrome.devtools APIs:
94+
95+
@JS()
96+
@anonymous
97+
class Devtools {
98+
// https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow
99+
external InspectedWindow get inspectedWindow;
100+
101+
// https://developer.chrome.com/docs/extensions/reference/devtools_panels/
102+
external Panels get panels;
103+
}
104+
105+
@JS()
106+
@anonymous
107+
class InspectedWindow {
108+
external int get tabId;
109+
}
110+
111+
@JS()
112+
@anonymous
113+
class Panels {
114+
external void create(String title, String iconPath, String pagePath,
115+
void Function(ExtensionPanel)? callback);
116+
}
117+
118+
@JS()
119+
@anonymous
120+
class ExtensionPanel {
121+
external OnHiddenHandler get onHidden;
122+
external OnShownHandler get onShown;
123+
}
124+
125+
@JS()
126+
@anonymous
127+
class OnHiddenHandler {
128+
external void addListener(void Function() callback);
129+
}
130+
131+
@JS()
132+
@anonymous
133+
class OnShownHandler {
134+
external void addListener(void Function(Window window) callback);
135+
}
136+
90137
/// chrome.notification APIs:
91138
/// https://developer.chrome.com/docs/extensions/reference/notifications
92139
@@ -211,6 +258,8 @@ class Storage {
211258
external StorageArea get local;
212259

213260
external StorageArea get session;
261+
262+
external OnChangedHandler get onChanged;
214263
}
215264

216265
@JS()
@@ -223,6 +272,14 @@ class StorageArea {
223272
external Object remove(List<String> keys, void Function()? callback);
224273
}
225274

275+
@JS()
276+
@anonymous
277+
class OnChangedHandler {
278+
external void addListener(
279+
void Function(Object changes, String areaName) callback,
280+
);
281+
}
282+
226283
/// chrome.tabs APIs
227284
/// https://developer.chrome.com/docs/extensions/reference/tabs
228285
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 devtools;
7+
8+
import 'dart:html';
9+
import 'package:js/js.dart';
10+
import 'package:dwds/data/debug_info.dart';
11+
12+
import 'chrome_api.dart';
13+
import 'logger.dart';
14+
import 'storage.dart';
15+
import 'utils.dart';
16+
17+
bool panelsExist = false;
18+
19+
void main() async {
20+
_registerListeners();
21+
_maybeCreatePanels();
22+
}
23+
24+
void _registerListeners() {
25+
chrome.storage.onChanged.addListener(allowInterop((
26+
Object _,
27+
String storageArea,
28+
) {
29+
if (storageArea != 'session') return;
30+
_maybeCreatePanels();
31+
}));
32+
}
33+
34+
void _maybeCreatePanels() async {
35+
if (panelsExist) return;
36+
final tabId = chrome.devtools.inspectedWindow.tabId;
37+
final debugInfo = await fetchStorageObject<DebugInfo>(
38+
type: StorageObject.debugInfo,
39+
tabId: tabId,
40+
);
41+
if (debugInfo == null) return;
42+
final isInternalBuild = debugInfo.isInternalBuild ?? false;
43+
if (!isInternalBuild) return;
44+
// Create a Debugger panel for all internal apps:
45+
chrome.devtools.panels.create(
46+
isDevMode() ? '[DEV] Dart Debugger' : 'Dart Debugger',
47+
'',
48+
'panel.html',
49+
allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
50+
);
51+
// Create an inspector panel for internal Flutter apps:
52+
final isFlutterApp = debugInfo.isFlutterApp ?? false;
53+
if (isFlutterApp) {
54+
chrome.devtools.panels.create(
55+
isDevMode() ? '[DEV] Flutter Inspector' : 'Flutter Inspector',
56+
'',
57+
'panel.html',
58+
allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
59+
);
60+
}
61+
panelsExist = true;
62+
}
63+
64+
void _onPanelAdded(ExtensionPanel panel, DebugInfo debugInfo) {
65+
panel.onShown.addListener(allowInterop((Window window) {
66+
if (window.origin != debugInfo.appOrigin) {
67+
debugWarn('Page at ${window.origin} is no longer a Dart app.');
68+
// TODO(elliette): Display banner that panel is not applicable. See:
69+
// https://stackoverflow.com/questions/18927147/how-to-close-destroy-chrome-devtools-extensionpanel-programmatically
70+
}
71+
}));
72+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
</head>
6+
7+
<body>
8+
<script src="devtools.dart.js"></script>
9+
</body>
10+
11+
</html>

dwds/debug_extension_mv3/web/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "MV3 Dart Debug Extension",
33
"version": "1.0",
44
"manifest_version": 3,
5+
"devtools_page": "devtools.html",
56
"action": {
67
"default_icon": "dart_dev.png"
78
},
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
</head>
6+
7+
<body>
8+
<h1>Panel</h1>
9+
</body>
10+
11+
</html>

dwds/test/fixtures/context.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ class TestContext {
162162
SdkConfigurationProvider? sdkConfigurationProvider,
163163
bool useDebuggerModuleNames = false,
164164
bool launchChrome = true,
165+
bool isFlutterApp = false,
166+
bool isInternalBuild = false,
165167
}) async {
166168
sdkConfigurationProvider ??= DefaultSdkConfigurationProvider();
167169

@@ -370,6 +372,8 @@ class TestContext {
370372
expressionCompiler,
371373
spawnDds,
372374
ddcService,
375+
isFlutterApp,
376+
isInternalBuild,
373377
);
374378

375379
_appUrl = basePath.isEmpty

dwds/test/fixtures/server.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ class TestServer {
8080
ExpressionCompiler? expressionCompiler,
8181
bool spawnDds,
8282
ExpressionCompilerService? ddcService,
83+
bool isFlutterApp,
84+
bool isInternalBuild,
8385
) async {
8486
var pipeline = const Pipeline();
8587

@@ -113,6 +115,8 @@ class TestServer {
113115
hostname: hostname,
114116
urlEncoder: urlEncoder,
115117
expressionCompiler: expressionCompiler,
118+
isInternalBuild: isInternalBuild,
119+
isFlutterApp: isFlutterApp,
116120
devtoolsLauncher: serveDevTools
117121
? (hostname) async {
118122
final server = await DevToolsServer().serveDevTools(

0 commit comments

Comments
 (0)