Skip to content

Commit

Permalink
Merge branch 'master' of github.com:dart-lang/webdev
Browse files Browse the repository at this point in the history
  • Loading branch information
Anna Gringauze committed Feb 13, 2023
2 parents 5427e42 + 35fa34b commit bdb83ab
Show file tree
Hide file tree
Showing 30 changed files with 933 additions and 141 deletions.
7 changes: 7 additions & 0 deletions dwds/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

- Cleanup `getObject` code for lists and maps.
- Now works with offset `0` and `null` count.
- Fix failures on edge cases.
- Support records:
- Update SDK constraint to `>=3.0.0-188.0.dev <4.0.0`.
- Update `package:vm_service` constraint to `>=10.1.2 <12.0.0`.
- Update `package:dds` constraint to `^2.7.1`.
- Fill `BoundField.name` for records.
- Display records as a container of fields.

## 17.0.0

Expand Down
2 changes: 1 addition & 1 deletion dwds/debug_extension/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: >-
A chrome extension for Dart debugging.
environment:
sdk: ">=3.0.0-134.0.dev <4.0.0"
sdk: ">=3.0.0-188.0.dev <4.0.0"

dependencies:
async: ^2.3.0
Expand Down
2 changes: 1 addition & 1 deletion dwds/debug_extension_mv3/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: >-
A Chrome extension for Dart debugging.
environment:
sdk: ">=3.0.0-134.0.dev <4.0.0"
sdk: ">=3.0.0-188.0.dev <4.0.0"

dependencies:
built_value: ^8.3.0
Expand Down
21 changes: 5 additions & 16 deletions dwds/debug_extension_mv3/web/background.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
library background;

import 'dart:async';
import 'dart:html';

import 'package:dwds/data/debug_info.dart';
import 'package:js/js.dart';
Expand All @@ -15,7 +14,6 @@ import 'chrome_api.dart';
import 'cross_extension_communication.dart';
import 'data_types.dart';
import 'debug_session.dart';
import 'lifeline_ports.dart';
import 'logger.dart';
import 'messaging.dart';
import 'storage.dart';
Expand All @@ -35,14 +33,12 @@ void _registerListeners() {
chrome.runtime.onMessageExternal.addListener(
allowInterop(handleMessagesFromAngularDartDevTools),
);
chrome.tabs.onRemoved
.addListener(allowInterop((tabId, _) => maybeRemoveLifelinePort(tabId)));
// Update the extension icon on tab navigation:
chrome.tabs.onActivated.addListener(allowInterop((ActiveInfo info) {
_updateIcon(info.tabId);
}));
chrome.windows.onFocusChanged.addListener(allowInterop((_) async {
final currentTab = await _getTab();
final currentTab = await activeTab;
if (currentTab?.id != null) {
_updateIcon(currentTab!.id);
}
Expand All @@ -51,7 +47,7 @@ void _registerListeners() {
.addListener(allowInterop(_detectNavigationAwayFromDartApp));

// Detect clicks on the Dart Debug Extension icon.
chrome.action.onClicked.addListener(allowInterop(
onExtensionIconClicked(allowInterop(
(Tab tab) => attachDebugger(
tab.id,
trigger: Trigger.extensionIcon,
Expand Down Expand Up @@ -97,7 +93,7 @@ void _handleRuntimeMessages(
await setStorageObject<DebugInfo>(
type: StorageObject.debugInfo, value: debugInfo, tabId: dartTab.id);
// Update the icon to show that a Dart app has been detected:
final currentTab = await _getTab();
final currentTab = await activeTab;
if (currentTab?.id == dartTab.id) {
_setDebuggableIcon();
}
Expand Down Expand Up @@ -142,15 +138,14 @@ void _updateIcon(int activeTabId) async {
}

void _setDebuggableIcon() {
chrome.action
.setIcon(IconInfo(path: 'static_assets/dart.png'), /*callback*/ null);
setExtensionIcon(IconInfo(path: 'static_assets/dart.png'));
}

void _setDefaultIcon() {
final iconPath = isDevMode()
? 'static_assets/dart_dev.png'
: 'static_assets/dart_grey.png';
chrome.action.setIcon(IconInfo(path: iconPath), /*callback*/ null);
setExtensionIcon(IconInfo(path: iconPath));
}

Future<DebugInfo?> _fetchDebugInfo(int tabId) {
Expand All @@ -159,9 +154,3 @@ Future<DebugInfo?> _fetchDebugInfo(int tabId) {
tabId: tabId,
);
}

Future<Tab?> _getTab() async {
final query = QueryInfo(active: true, currentWindow: true);
final tabs = List<Tab>.from(await promiseToFuture(chrome.tabs.query(query)));
return tabs.isNotEmpty ? tabs.first : null;
}
11 changes: 6 additions & 5 deletions dwds/debug_extension_mv3/web/chrome_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,14 @@ class OnChangedHandler {
@JS()
@anonymous
class Tabs {
external Object query(QueryInfo queryInfo);
external dynamic query(
QueryInfo queryInfo, void Function(List<Tab>) callback);

external Object create(TabInfo tabInfo);
external dynamic create(TabInfo tabInfo, void Function(Tab) callback);

external Object get(int tabId);
external dynamic get(int tabId, void Function(Tab?) callback);

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

external OnActivatedHandler get onActivated;

Expand Down Expand Up @@ -378,7 +379,7 @@ class NavigationInfo {
@JS()
@anonymous
class Windows {
external Object create(WindowInfo? createData);
external dynamic create(WindowInfo? createData, Function(WindowObj) callback);

external OnFocusChangedHandler get onFocusChanged;
}
Expand Down
17 changes: 14 additions & 3 deletions dwds/debug_extension_mv3/web/debug_session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import 'chrome_api.dart';
import 'cross_extension_communication.dart';
import 'data_serializers.dart';
import 'data_types.dart';
import 'lifeline_ports.dart';
import 'logger.dart';
import 'messaging.dart';
import 'storage.dart';
Expand Down Expand Up @@ -98,6 +99,11 @@ enum DebuggerLocation {
}
}

bool get existsActiveDebugSession => _debugSessions.isNotEmpty;

int? get latestAppBeingDebugged =>
existsActiveDebugSession ? _debugSessions.last.appTabId : null;

void attachDebugger(int dartAppTabId, {required Trigger trigger}) async {
// Check if a debugger is already attached:
final existingDebuggerLocation = _debuggerLocation(dartAppTabId);
Expand Down Expand Up @@ -260,8 +266,10 @@ Future<bool> _connectToDwds({
cancelOnError: true,
);
_debugSessions.add(debugSession);
final tabUrl = await _getTabUrl(dartAppTabId);
// Create a connection with the lifeline port to keep the debug session alive:
await maybeCreateLifelinePort(dartAppTabId);
// Send a DevtoolsRequest to the event stream:
final tabUrl = await _getTabUrl(dartAppTabId);
debugSession.sendEvent(DevToolsRequest((b) => b
..appId = debugInfo.appId
..instanceId = debugInfo.appInstanceId
Expand Down Expand Up @@ -393,7 +401,7 @@ void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
final devToolsTab = await getTab(devToolsTabId);
if (devToolsTab != null) {
debugLog('Closing DevTools tab...');
chrome.tabs.remove(devToolsTabId);
await removeTab(devToolsTabId);
}
}

Expand All @@ -408,7 +416,10 @@ void _removeDebugSession(_DebugSession debugSession) {
debugSession.sendEvent(event);
debugSession.close();
final removed = _debugSessions.remove(debugSession);
if (!removed) {
if (removed) {
// Maybe remove the corresponding lifeline connection:
maybeRemoveLifelinePort(debugSession.appTabId);
} else {
debugWarn('Could not remove debug session.');
}
}
Expand Down
61 changes: 22 additions & 39 deletions dwds/debug_extension_mv3/web/lifeline_ports.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,17 @@ import 'dart:async';
import 'package:js/js.dart';

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

// Switch to true to enable debug logs.
// TODO(elliette): Enable / disable with flag while building the extension.
final enableDebugLogging = true;
Port? _lifelinePort;
int? _lifelineTab;

Port? lifelinePort;
int? lifelineTab;
final dartTabs = <int>{};

void maybeCreateLifelinePort(int tabId) {
// Keep track of current Dart tabs that are being debugged. This way if one of
// them is closed, we can reconnect the lifeline port to another one:
dartTabs.add(tabId);
debugLog('Dart tabs are: $dartTabs');
Future<void> maybeCreateLifelinePort(int tabId) async {
// Don't create a lifeline port if we already have one (meaning another Dart
// app is currently being debugged):
if (lifelinePort != null) {
if (_lifelinePort != null) {
debugWarn('Port already exists.');
return;
}
Expand All @@ -38,40 +31,30 @@ void maybeCreateLifelinePort(int tabId) {
// Inject the connection script into the current Dart tab, that way the tab
// will connect to the port:
debugLog('Creating lifeline port.');
lifelineTab = tabId;
chrome.scripting.executeScript(
InjectDetails(
target: Target(tabId: tabId),
files: ['lifeline_connection.dart.js'],
),
/*callback*/ null,
);
_lifelineTab = tabId;
await injectScript('lifeline_connection.dart.js', tabId: tabId);
}

void maybeRemoveLifelinePort(int removedTabId) {
final removedDartTab = dartTabs.remove(removedTabId);
// If the removed tab was not a Dart tab, return early.
if (!removedDartTab) return;
debugLog('Removed tab $removedTabId, Dart tabs are now $dartTabs.');
// If the removed Dart tab hosted the lifeline port connection, see if there
// are any other Dart tabs to connect to. Otherwise disconnect the port.
if (lifelineTab == removedTabId) {
if (dartTabs.isEmpty) {
lifelineTab = null;
if (_lifelineTab == removedTabId) {
if (existsActiveDebugSession) {
_lifelineTab = latestAppBeingDebugged;
debugLog('Reconnecting lifeline port to a new Dart tab: $_lifelineTab.');
_reconnectToLifelinePort();
} else {
_lifelineTab = null;
debugLog('No more Dart tabs, disconnecting from lifeline port.');
_disconnectFromLifelinePort();
} else {
lifelineTab = dartTabs.last;
debugLog('Reconnecting lifeline port to a new Dart tab: $lifelineTab.');
_reconnectToLifelinePort();
}
}
}

void _keepLifelinePortAlive(Port port) {
final portName = port.name ?? '';
if (portName != 'keepAlive') return;
lifelinePort = port;
_lifelinePort = port;
// Reconnect to the lifeline port every 5 minutes, as per:
// https://bugs.chromium.org/p/chromium/issues/detail?id=1146434#c6
Timer(Duration(minutes: 5), () {
Expand All @@ -82,26 +65,26 @@ void _keepLifelinePortAlive(Port port) {

void _reconnectToLifelinePort() {
debugLog('Reconnecting...');
if (lifelinePort == null) {
if (_lifelinePort == null) {
debugWarn('Could not find a lifeline port.');
return;
}
if (lifelineTab == null) {
if (_lifelineTab == null) {
debugWarn('Could not find a lifeline tab.');
return;
}
// Disconnect from the port, and then recreate the connection with the current
// Dart tab:
_disconnectFromLifelinePort();
maybeCreateLifelinePort(lifelineTab!);
maybeCreateLifelinePort(_lifelineTab!);
debugLog('Reconnection complete.');
}

void _disconnectFromLifelinePort() {
debugLog('Disconnecting...');
if (lifelinePort != null) {
lifelinePort!.disconnect();
lifelinePort = null;
if (_lifelinePort != null) {
_lifelinePort!.disconnect();
_lifelinePort = null;
debugLog('Disconnection complete.');
}
}
77 changes: 64 additions & 13 deletions dwds/debug_extension_mv3/web/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,80 @@ import 'package:js/js.dart';

import 'chrome_api.dart';

Future<Tab> createTab(String url, {bool inNewWindow = false}) async {
Future<Tab> createTab(String url, {bool inNewWindow = false}) {
final completer = Completer<Tab>();
if (inNewWindow) {
final windowPromise = chrome.windows.create(
chrome.windows.create(
WindowInfo(focused: true, url: url),
allowInterop(
(WindowObj windowObj) {
completer.complete(windowObj.tabs.first);
},
),
);
} else {
chrome.tabs.create(
TabInfo(
active: true,
url: url,
),
allowInterop(
(Tab tab) {
completer.complete(tab);
},
),
);
final windowObj = await promiseToFuture<WindowObj>(windowPromise);
return windowObj.tabs.first;
}
final tabPromise = chrome.tabs.create(TabInfo(
active: true,
url: url,
));
return promiseToFuture<Tab>(tabPromise);
return completer.future;
}

Future<Tab?> getTab(int tabId) {
return promiseToFuture<Tab?>(chrome.tabs.get(tabId));
final completer = Completer<Tab?>();
chrome.tabs.get(tabId, allowInterop((tab) {
completer.complete(tab);
}));
return completer.future;
}

Future<Tab?> getActiveTab() async {
Future<Tab?> get activeTab async {
final completer = Completer<Tab?>();
final query = QueryInfo(active: true, currentWindow: true);
final tabs = List<Tab>.from(await promiseToFuture(chrome.tabs.query(query)));
return tabs.isNotEmpty ? tabs.first : null;
chrome.tabs.query(query, allowInterop((List tabs) {
if (tabs.isNotEmpty) {
completer.complete(tabs.first as Tab);
} else {
completer.complete(null);
}
}));
return completer.future;
}

Future<bool> removeTab(int tabId) {
final completer = Completer<bool>();
chrome.tabs.remove(tabId, allowInterop(() {
completer.complete(true);
}));
return completer.future;
}

Future<bool> injectScript(String scriptName, {required int tabId}) {
final completer = Completer<bool>();
chrome.scripting.executeScript(
InjectDetails(
target: Target(tabId: tabId),
files: [scriptName],
), allowInterop(() {
completer.complete(true);
}));
return completer.future;
}

void onExtensionIconClicked(void Function(Tab) callback) {
chrome.action.onClicked.addListener(callback);
}

void setExtensionIcon(IconInfo info) {
chrome.action.setIcon(info, /*callback*/ null);
}

bool? _isDevMode;
Expand Down
Loading

0 comments on commit bdb83ab

Please sign in to comment.