blob: 23f57ed628084d5585723429273802d933dc6f0a [file] [log] [blame]
// 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.
// Keeps the background service worker alive for the duration of a Dart debug
// session by using the workaround described in:
// https://bugs.chromium.org/p/chromium/issues/detail?id=1152255#c21
@JS()
library lifeline_ports;
import 'dart:async';
import 'package:js/js.dart';
import 'chrome_api.dart';
import 'logger.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;
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');
// Don't create a lifeline port if we already have one (meaning another Dart
// app is currently being debugged):
if (lifelinePort != null) {
debugWarn('Port already exists.');
return;
}
// Start the keep-alive logic when the port connects:
chrome.runtime.onConnect.addListener(allowInterop(_keepLifelinePortAlive));
// 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,
);
}
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;
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;
// 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), () {
debugLog('5 minutes have elapsed, therefore reconnecting.');
_reconnectToLifelinePort();
});
}
void _reconnectToLifelinePort() {
debugLog('Reconnecting...');
if (lifelinePort == null) {
debugWarn('Could not find a lifeline port.');
return;
}
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!);
debugLog('Reconnection complete.');
}
void _disconnectFromLifelinePort() {
debugLog('Disconnecting...');
if (lifelinePort != null) {
lifelinePort!.disconnect();
lifelinePort = null;
debugLog('Disconnection complete.');
}
}