blob: 878b79e7fc83ccddeafda9e3c00effc79e25b89e [file]
// 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 background;
import 'package:dwds/data/debug_info.dart';
import 'package:js/js.dart';
import 'chrome_api.dart';
import 'cross_extension_communication.dart';
import 'data_types.dart';
import 'debug_session.dart';
import 'logger.dart';
import 'messaging.dart';
import 'storage.dart';
import 'utils.dart';
void main() {
_registerListeners();
}
void _registerListeners() {
chrome.runtime.onMessage.addListener(
allowInterop(_handleRuntimeMessages),
);
// The only extension allowed to send messages to this extension is the
// AngularDart DevTools extension. Its permission is set in the manifest.json
// externally_connectable field.
chrome.runtime.onMessageExternal.addListener(
allowInterop(handleMessagesFromAngularDartDevTools),
);
// Update the extension icon on tab navigation:
chrome.tabs.onActivated.addListener(allowInterop((ActiveInfo info) async {
await _updateIcon(info.tabId);
}));
chrome.windows.onFocusChanged.addListener(allowInterop((_) async {
final currentTab = await activeTab;
if (currentTab?.id != null) {
await _updateIcon(currentTab!.id);
}
}));
chrome.webNavigation.onCommitted
.addListener(allowInterop(_detectNavigationAwayFromDartApp));
// Detect clicks on the Dart Debug Extension icon.
onExtensionIconClicked(allowInterop(
(Tab tab) => attachDebugger(
tab.id,
trigger: Trigger.extensionIcon,
),
));
}
Future<void> _handleRuntimeMessages(
dynamic jsRequest, MessageSender sender, Function sendResponse) async {
if (jsRequest is! String) return;
interceptMessage<String>(
message: jsRequest,
expectedType: MessageType.isAuthenticated,
expectedSender: Script.detector,
expectedRecipient: Script.background,
messageHandler: (String isAuthenticated) async {
final dartTab = sender.tab;
if (dartTab == null) {
debugWarn('Received auth info but tab is missing.');
return;
}
// Save the authentication info in storage:
await setStorageObject<String>(
type: StorageObject.isAuthenticated,
value: isAuthenticated,
tabId: dartTab.id,
);
});
interceptMessage<DebugInfo>(
message: jsRequest,
expectedType: MessageType.debugInfo,
expectedSender: Script.detector,
expectedRecipient: Script.background,
messageHandler: (DebugInfo debugInfo) async {
final dartTab = sender.tab;
if (dartTab == null) {
debugWarn('Received debug info but tab is missing.');
return;
}
// If this is a new Dart app, we need to clear old debug session data:
if (!await _matchesAppInStorage(debugInfo.appId, tabId: dartTab.id)) {
await clearStaleDebugSession(dartTab.id);
}
// Save the debug info for the Dart app in storage:
await setStorageObject<DebugInfo>(
type: StorageObject.debugInfo,
value: _addTabUrl(debugInfo, tabUrl: dartTab.url),
tabId: dartTab.id);
// Update the icon to show that a Dart app has been detected:
final currentTab = await activeTab;
if (currentTab?.id == dartTab.id) {
await _updateIcon(dartTab.id);
}
});
interceptMessage<DebugStateChange>(
message: jsRequest,
expectedType: MessageType.debugStateChange,
expectedSender: Script.debuggerPanel,
expectedRecipient: Script.background,
messageHandler: (DebugStateChange debugStateChange) {
final newState = debugStateChange.newState;
final tabId = debugStateChange.tabId;
if (newState == DebugStateChange.startDebugging) {
attachDebugger(tabId, trigger: Trigger.extensionPanel);
}
});
interceptMessage<String>(
message: jsRequest,
expectedType: MessageType.multipleAppsDetected,
expectedSender: Script.detector,
expectedRecipient: Script.background,
messageHandler: (String multipleAppsDetected) async {
final dartTab = sender.tab;
if (dartTab == null) {
debugWarn('Received multiple apps detected but tab is missing.');
return;
}
// Save the multiple apps info in storage:
await setStorageObject<String>(
type: StorageObject.multipleAppsDetected,
value: multipleAppsDetected,
tabId: dartTab.id,
);
_setWarningIcon();
});
}
Future<void> _detectNavigationAwayFromDartApp(
NavigationInfo navigationInfo) async {
// Ignore any navigation events within the page itself (e.g., opening a link,
// reloading the page, reloading an IFRAME, etc):
if (_isInternalNavigation(navigationInfo)) return;
final tabId = navigationInfo.tabId;
final debugInfo = await _fetchDebugInfo(navigationInfo.tabId);
if (debugInfo == null) return;
if (debugInfo.tabUrl != navigationInfo.url) {
_setDefaultIcon();
await clearStaleDebugSession(tabId);
await removeStorageObject(type: StorageObject.debugInfo, tabId: tabId);
await detachDebugger(
tabId,
type: TabType.dartApp,
reason: DetachReason.navigatedAwayFromApp,
);
}
}
bool _isInternalNavigation(NavigationInfo navigationInfo) {
return [
'auto_subframe',
'form_submit',
'link',
'manual_subframe',
'reload',
].contains(navigationInfo.transitionType);
}
DebugInfo _addTabUrl(DebugInfo debugInfo, {required String tabUrl}) {
return DebugInfo((b) => b
..appEntrypointPath = debugInfo.appEntrypointPath
..appId = debugInfo.appId
..appInstanceId = debugInfo.appInstanceId
..appOrigin = debugInfo.appOrigin
..appUrl = debugInfo.appUrl
..authUrl = debugInfo.authUrl
..extensionUrl = debugInfo.extensionUrl
..isInternalBuild = debugInfo.isInternalBuild
..isFlutterApp = debugInfo.isFlutterApp
..tabUrl = tabUrl);
}
Future<void> _updateIcon(int activeTabId) async {
final debugInfo = await _fetchDebugInfo(activeTabId);
if (debugInfo == null) {
_setDefaultIcon();
return;
}
final multipleApps = await fetchStorageObject<String>(
type: StorageObject.multipleAppsDetected,
tabId: activeTabId,
);
multipleApps == null ? _setDebuggableIcon() : _setWarningIcon();
}
void _setDebuggableIcon() {
setExtensionIcon(IconInfo(path: 'static_assets/dart.png'));
}
void _setWarningIcon() {
setExtensionIcon(IconInfo(path: 'static_assets/dart_warning.png'));
}
void _setDefaultIcon() {
final iconPath =
isDevMode ? 'static_assets/dart_dev.png' : 'static_assets/dart_grey.png';
setExtensionIcon(IconInfo(path: iconPath));
}
Future<DebugInfo?> _fetchDebugInfo(int tabId) {
return fetchStorageObject<DebugInfo>(
type: StorageObject.debugInfo,
tabId: tabId,
);
}
Future<bool> _matchesAppInStorage(String? appId, {required int tabId}) async {
final debugInfo = await _fetchDebugInfo(tabId);
return appId != null && appId == debugInfo?.appId;
}