Skip to content

Add the Dart Debugger / Flutter Inspector panels in Chrome DevTools #1812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Dec 7, 2022
Merged
57 changes: 57 additions & 0 deletions dwds/debug_extension_mv3/web/chrome_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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.

import 'dart:html';

import 'package:js/js.dart';

@JS()
Expand All @@ -12,6 +14,7 @@ external Chrome get chrome;
class Chrome {
external Action get action;
external Debugger get debugger;
external Devtools get devtools;
external Notifications get notifications;
external Runtime get runtime;
external Scripting get scripting;
Expand Down Expand Up @@ -87,6 +90,50 @@ class Debuggee {
external factory Debuggee({int tabId, String? extensionId, String? targetId});
}

/// chrome.devtools APIs:

@JS()
@anonymous
class Devtools {
// https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow
external InspectedWindow get inspectedWindow;

// https://developer.chrome.com/docs/extensions/reference/devtools_panels/
external Panels get panels;
}

@JS()
@anonymous
class InspectedWindow {
external int get tabId;
}

@JS()
@anonymous
class Panels {
external void create(String title, String iconPath, String pagePath,
void Function(ExtensionPanel)? callback);
}

@JS()
@anonymous
class ExtensionPanel {
external OnHiddenHandler get onHidden;
external OnShownHandler get onShown;
}

@JS()
@anonymous
class OnHiddenHandler {
external void addListener(void Function() callback);
}

@JS()
@anonymous
class OnShownHandler {
external void addListener(void Function(Window window) callback);
}

/// chrome.notification APIs:
/// https://developer.chrome.com/docs/extensions/reference/notifications

Expand Down Expand Up @@ -211,6 +258,8 @@ class Storage {
external StorageArea get local;

external StorageArea get session;

external OnChangedHandler get onChanged;
}

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

@JS()
@anonymous
class OnChangedHandler {
external void addListener(
void Function(Object changes, String areaName) callback,
);
}

/// chrome.tabs APIs
/// https://developer.chrome.com/docs/extensions/reference/tabs

Expand Down
72 changes: 72 additions & 0 deletions dwds/debug_extension_mv3/web/devtools.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) 2022, 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 devtools;

import 'dart:html';
import 'package:js/js.dart';
import 'package:dwds/data/debug_info.dart';

import 'chrome_api.dart';
import 'logger.dart';
import 'storage.dart';
import 'utils.dart';

bool panelsExist = false;

void main() async {
_registerListeners();
_maybeCreatePanels();
}

void _registerListeners() {
chrome.storage.onChanged.addListener(allowInterop((
Object _,
String storageArea,
) {
if (storageArea != 'session') return;
_maybeCreatePanels();
}));
}

void _maybeCreatePanels() async {
if (panelsExist) return;
final tabId = chrome.devtools.inspectedWindow.tabId;
final debugInfo = await fetchStorageObject<DebugInfo>(
type: StorageObject.debugInfo,
tabId: tabId,
);
if (debugInfo == null) return;
final isInternalBuild = debugInfo.isInternalBuild ?? false;
if (!isInternalBuild) return;
// Create a Debugger panel for all internal apps:
chrome.devtools.panels.create(
isDevMode() ? '[DEV] Dart Debugger' : 'Dart Debugger',
'',
'panel.html',
allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
);
// Create an inspector panel for internal Flutter apps:
final isFlutterApp = debugInfo.isFlutterApp ?? false;
if (isFlutterApp) {
chrome.devtools.panels.create(
isDevMode() ? '[DEV] Flutter Inspector' : 'Flutter Inspector',
'',
'panel.html',
allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
);
}
panelsExist = true;
}

void _onPanelAdded(ExtensionPanel panel, DebugInfo debugInfo) {
panel.onShown.addListener(allowInterop((Window window) {
if (window.origin != debugInfo.appOrigin) {
debugWarn('Page at ${window.origin} is no longer a Dart app.');
// TODO(elliette): Display banner that panel is not applicable. See:
// https://stackoverflow.com/questions/18927147/how-to-close-destroy-chrome-devtools-extensionpanel-programmatically
}
}));
}
11 changes: 11 additions & 0 deletions dwds/debug_extension_mv3/web/devtools.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>

<head>
</head>

<body>
<script src="devtools.dart.js"></script>
</body>

</html>
1 change: 1 addition & 0 deletions dwds/debug_extension_mv3/web/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "MV3 Dart Debug Extension",
"version": "1.0",
"manifest_version": 3,
"devtools_page": "devtools.html",
"action": {
"default_icon": "dart_dev.png"
},
Expand Down
11 changes: 11 additions & 0 deletions dwds/debug_extension_mv3/web/panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>

<head>
</head>

<body>
<h1>Panel</h1>
</body>

</html>
4 changes: 4 additions & 0 deletions dwds/test/fixtures/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ class TestContext {
SdkConfigurationProvider? sdkConfigurationProvider,
bool useDebuggerModuleNames = false,
bool launchChrome = true,
bool isFlutterApp = false,
bool isInternalBuild = false,
}) async {
sdkConfigurationProvider ??= DefaultSdkConfigurationProvider();

Expand Down Expand Up @@ -370,6 +372,8 @@ class TestContext {
expressionCompiler,
spawnDds,
ddcService,
isFlutterApp,
isInternalBuild,
);

_appUrl = basePath.isEmpty
Expand Down
4 changes: 4 additions & 0 deletions dwds/test/fixtures/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class TestServer {
ExpressionCompiler? expressionCompiler,
bool spawnDds,
ExpressionCompilerService? ddcService,
bool isFlutterApp,
bool isInternalBuild,
) async {
var pipeline = const Pipeline();

Expand Down Expand Up @@ -113,6 +115,8 @@ class TestServer {
hostname: hostname,
urlEncoder: urlEncoder,
expressionCompiler: expressionCompiler,
isInternalBuild: isInternalBuild,
isFlutterApp: isFlutterApp,
devtoolsLauncher: serveDevTools
? (hostname) async {
final server = await DevToolsServer().serveDevTools(
Expand Down
Loading