blob: 19a04f847f7a030b8c685be96207ea8d624669ab [file] [log] [blame]
// Copyright (c) 2023, 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 cross_extension_communication;
import 'package:js/js.dart';
import 'chrome_api.dart';
import 'debug_session.dart';
import 'logger.dart';
import 'storage.dart';
import 'web_api.dart';
// The only extension allowed to communicate with this extension is the
// AngularDart DevTools extension.
//
// This ID is used to send messages to AngularDart DevTools, while the
// externally_connectable field in the manifest.json allows AngularDart DevTools
// to send messages to this extension.
const _angularDartDevToolsId = 'nbkbficgbembimioedhceniahniffgpl';
// A set of events to forward to the AngularDart DevTools extension.
final _eventsForAngularDartDevTools = {
'Overlay.inspectNodeRequested',
'dwds.encodedUri',
};
Future<void> handleMessagesFromAngularDartDevTools(
dynamic jsRequest, MessageSender sender, Function sendResponse) async {
if (jsRequest == null) return;
final message = jsRequest as ExternalExtensionMessage;
if (message.name == 'chrome.debugger.sendCommand') {
_forwardCommandToChromeDebugger(message, sendResponse);
} else if (message.name == 'dwds.encodedUri') {
await _respondWithEncodedUri(message.tabId, sendResponse);
} else if (message.name == 'dwds.startDebugging') {
await attachDebugger(message.tabId, trigger: Trigger.angularDartDevTools);
sendResponse(true);
} else {
sendResponse(
ErrorResponse()..error = 'Unknown message name: ${message.name}');
}
}
void maybeForwardMessageToAngularDartDevTools(
{required String method, required dynamic params, required int tabId}) {
if (!_eventsForAngularDartDevTools.contains(method)) return;
final message = method.startsWith('dwds')
? _dwdsEventMessage(method: method, params: params, tabId: tabId)
: _debugEventMessage(method: method, params: params, tabId: tabId);
_forwardMessageToAngularDartDevTools(message);
}
void _forwardCommandToChromeDebugger(
ExternalExtensionMessage message, Function sendResponse) {
try {
final options = message.options as SendCommandOptions;
chrome.debugger.sendCommand(
Debuggee(tabId: message.tabId),
options.method,
options.commandParams,
allowInterop(
([result]) => _respondWithChromeResult(result, sendResponse)),
);
} catch (e) {
sendResponse(ErrorResponse()..error = '$e');
}
}
void _respondWithChromeResult(Object? chromeResult, Function sendResponse) {
// No result indicates that an error occurred.
if (chromeResult == null) {
sendResponse(ErrorResponse()
..error = JSON.stringify(
chrome.runtime.lastError ?? 'Unknown error.',
));
} else {
sendResponse(chromeResult);
}
}
Future<void> _respondWithEncodedUri(int tabId, Function sendResponse) async {
final encodedUri = await fetchStorageObject<String>(
type: StorageObject.encodedUri, tabId: tabId);
sendResponse(encodedUri ?? '');
}
void _forwardMessageToAngularDartDevTools(ExternalExtensionMessage message) {
chrome.runtime.sendMessage(
_angularDartDevToolsId,
message,
/* options */ null,
allowInterop(([result]) => _checkForErrors(result, message.name)),
);
}
void _checkForErrors(Object? chromeResult, String messageName) {
// No result indicates that an error occurred.
if (chromeResult == null) {
final errorMessage = chrome.runtime.lastError?.message ?? 'Unknown error.';
debugWarn('Error forwarding $messageName: $errorMessage');
}
}
ExternalExtensionMessage _debugEventMessage({
required String method,
required dynamic params,
required int tabId,
}) =>
ExternalExtensionMessage(
name: 'chrome.debugger.event',
tabId: tabId,
options: DebugEvent(method: method, params: params),
);
ExternalExtensionMessage _dwdsEventMessage({
required String method,
required dynamic params,
required int tabId,
}) =>
ExternalExtensionMessage(
name: method,
tabId: tabId,
options: params,
);
// This message is used for cross-extension communication between this extension
// and the AngularDart DevTools extension.
@JS()
@anonymous
class ExternalExtensionMessage {
external int get tabId;
external String get name;
external dynamic get options;
external factory ExternalExtensionMessage(
{required int tabId, required String name, required dynamic options});
}
@JS()
@anonymous
class DebugEvent {
external factory DebugEvent({String method, Object? params});
}
@JS()
@anonymous
class SendCommandOptions {
external String get method;
external Object get commandParams;
}
@JS()
@anonymous
class ErrorResponse {
external set error(String error);
}