From f938434556317b119fb4be0496dec25cb0866d99 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:17:15 -0700 Subject: [PATCH] Reset to nice starting point --- dwds/debug_extension_mv3/web/background.dart | 48 +++---- dwds/debug_extension_mv3/web/chrome_api.dart | 86 ++----------- dwds/debug_extension_mv3/web/dart.png | Bin 0 -> 3062 bytes dwds/debug_extension_mv3/web/debug_tab.dart | 59 --------- dwds/debug_extension_mv3/web/debug_tab.html | 10 -- dwds/debug_extension_mv3/web/detector.dart | 45 +++++++ dwds/debug_extension_mv3/web/iframe.dart | 75 ----------- dwds/debug_extension_mv3/web/iframe.html | 9 -- .../web/iframe_injector.dart | 72 ----------- dwds/debug_extension_mv3/web/manifest.json | 13 +- dwds/debug_extension_mv3/web/messaging.dart | 121 ++---------------- 11 files changed, 96 insertions(+), 442 deletions(-) create mode 100644 dwds/debug_extension_mv3/web/dart.png delete mode 100644 dwds/debug_extension_mv3/web/debug_tab.dart delete mode 100644 dwds/debug_extension_mv3/web/debug_tab.html create mode 100644 dwds/debug_extension_mv3/web/detector.dart delete mode 100644 dwds/debug_extension_mv3/web/iframe.dart delete mode 100644 dwds/debug_extension_mv3/web/iframe.html delete mode 100644 dwds/debug_extension_mv3/web/iframe_injector.dart diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart index 43e490f44..a971fb4ee 100644 --- a/dwds/debug_extension_mv3/web/background.dart +++ b/dwds/debug_extension_mv3/web/background.dart @@ -5,44 +5,30 @@ @JS() library background; -import 'dart:html'; - import 'package:js/js.dart'; import 'chrome_api.dart'; +import 'messaging.dart'; void main() { - // Detect clicks on the Dart Debug Extension icon. - chrome.action.onClicked.addListener(allowInterop((_) async { - await _createDebugTab(); - await _executeInjectorScript(); - })); -} - -Future _createDebugTab() async { - final url = chrome.runtime.getURL('debug_tab.html'); - final tabPromise = chrome.tabs.create(TabInfo( - active: false, - pinned: true, - url: url, - )); - return promiseToFuture(tabPromise); + _registerListeners(); } -Future _executeInjectorScript() async { - final tabId = await _getTabId(); - if (tabId != null) { - chrome.scripting.executeScript( - InjectDetails( - target: Target(tabId: tabId), files: ['iframe_injector.dart.js']), - /*callback*/ null, - ); - } +void _registerListeners() { + chrome.runtime.onMessage.addListener(allowInterop(_handleRuntimeMessages)); } -Future _getTabId() async { - final query = QueryInfo(active: true, currentWindow: true); - final tabs = List.from(await promiseToFuture(chrome.tabs.query(query))); - final tab = tabs.isNotEmpty ? tabs.first : null; - return tab?.id; +void _handleRuntimeMessages( + dynamic jsRequest, MessageSender sender, Function sendResponse) async { + if (jsRequest is! String) return; + + interceptMessage( + message: jsRequest, + expectedSender: Script.detector, + expectedRecipient: Script.background, + expectedType: MessageType.dartAppReady, + messageHandler: (_) { + // Update the icon to show that a Dart app has been detected: + chrome.action.setIcon(IconInfo(path: 'dart.png'), /*callback*/ null); + }); } diff --git a/dwds/debug_extension_mv3/web/chrome_api.dart b/dwds/debug_extension_mv3/web/chrome_api.dart index 505b2b3a6..82f33ddcb 100644 --- a/dwds/debug_extension_mv3/web/chrome_api.dart +++ b/dwds/debug_extension_mv3/web/chrome_api.dart @@ -11,16 +11,17 @@ external Chrome get chrome; @anonymous class Chrome { external Action get action; - external Debugger get debugger; external Runtime get runtime; - external Scripting get scripting; - external Tabs get tabs; } +/// chrome.action APIs +/// https://developer.chrome.com/docs/extensions/reference/action + @JS() @anonymous class Action { - // https://developer.chrome.com/docs/extensions/reference/action/#event-onClicked + external void setIcon(IconInfo iconInfo, Function? callback); + external OnClickedHandler get onClicked; } @@ -32,27 +33,20 @@ class OnClickedHandler { @JS() @anonymous -class Debugger { - // https://developer.chrome.com/docs/extensions/reference/debugger/#method-attach - external void attach( - Debuggee target, String requiredVersion, Function? callback); - - // https://developer.chrome.com/docs/extensions/reference/debugger/#method-sendCommand - external void sendCommand(Debuggee target, String method, - Object? commandParams, Function? callback); +class IconInfo { + external String get path; + external factory IconInfo({String path}); } +/// chrome.runtime APIs: +/// https://developer.chrome.com/docs/extensions/reference/runtime + @JS() @anonymous class Runtime { - // https://developer.chrome.com/docs/extensions/reference/runtime/#method-sendMessage external void sendMessage( String? id, Object? message, Object? options, Function? callback); - // https://developer.chrome.com/docs/extensions/reference/runtime/#method-getURL - external String getURL(String path); - - // https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessage external OnMessageHandler get onMessage; } @@ -63,32 +57,6 @@ class OnMessageHandler { void Function(dynamic, MessageSender, Function) callback); } -@JS() -@anonymous -class Scripting { - // https://developer.chrome.com/docs/extensions/reference/scripting/#method-executeScript - external executeScript(InjectDetails details, Function? callback); -} - -@JS() -@anonymous -class Tabs { - // https://developer.chrome.com/docs/extensions/reference/tabs/#method-query - external Object query(QueryInfo queryInfo); - - // https://developer.chrome.com/docs/extensions/reference/tabs/#method-create - external Object create(TabInfo tabInfo); -} - -@JS() -@anonymous -class Debuggee { - external int get tabId; - external String get extensionId; - external String get targetId; - external factory Debuggee({int tabId, String? extensionId, String? targetId}); -} - @JS() @anonymous class MessageSender { @@ -98,41 +66,9 @@ class MessageSender { external factory MessageSender({String? id, String? url, Tab? tab}); } -@JS() -@anonymous -class TabInfo { - external bool? get active; - external bool? get pinned; - external String? get url; - external factory TabInfo({bool? active, bool? pinned, String? url}); -} - -@JS() -@anonymous -class QueryInfo { - external bool get active; - external bool get currentWindow; - external factory QueryInfo({bool? active, bool? currentWindow}); -} - @JS() @anonymous class Tab { external int get id; external String get url; } - -@JS() -@anonymous -class InjectDetails { - external Target get target; - external List? get files; - external factory InjectDetails({Target target, List files}); -} - -@JS() -@anonymous -class Target { - external int get tabId; - external factory Target({int tabId}); -} diff --git a/dwds/debug_extension_mv3/web/dart.png b/dwds/debug_extension_mv3/web/dart.png new file mode 100644 index 0000000000000000000000000000000000000000..47e4b7b7d07ff239ac5f597a730ec6dd2c5f278e GIT binary patch literal 3062 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 - message: messageData, - expectedType: MessageType.debugInfo, - expectedSender: Script.iframe, - expectedRecipient: Script.debugTab, - messageHandler: _debugInfoMessageHandler, - ); -} - -void _debugInfoMessageHandler(DebugInfo message) { - final tabId = message.tabId; - _startDebugging(tabId); -} - -void _startDebugging(int tabId) { - final debuggee = Debuggee(tabId: tabId); - chrome.debugger.attach(debuggee, '1.3', allowInterop(() async { - chrome.debugger.sendCommand( - debuggee, 'Runtime.enable', EmptyParam(), allowInterop((e) {})); - })); -} - -@JS() -@anonymous -class EmptyParam { - external factory EmptyParam(); -} diff --git a/dwds/debug_extension_mv3/web/debug_tab.html b/dwds/debug_extension_mv3/web/debug_tab.html deleted file mode 100644 index 063f933e7..000000000 --- a/dwds/debug_extension_mv3/web/debug_tab.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - -

Dart Debug Tab

-

Closing will disconnect the debug connection.

- - - - \ No newline at end of file diff --git a/dwds/debug_extension_mv3/web/detector.dart b/dwds/debug_extension_mv3/web/detector.dart new file mode 100644 index 000000000..d30964e79 --- /dev/null +++ b/dwds/debug_extension_mv3/web/detector.dart @@ -0,0 +1,45 @@ +// 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 detector; + +import 'dart:html'; +import 'package:js/js.dart'; + +import 'chrome_api.dart'; +import 'messaging.dart'; + +void main() { + _registerListeners(); +} + +void _registerListeners() { + document.addEventListener('dart-app-ready', _onDartAppReadyEvent); +} + +void _onDartAppReadyEvent(Event event) { + _sendMessageToBackgroundScript( + type: MessageType.dartAppReady, + body: 'Dart app ready!', + ); +} + +void _sendMessageToBackgroundScript({ + required MessageType type, + required String body, +}) { + final message = Message( + to: Script.background, + from: Script.detector, + type: type, + body: body, + ); + chrome.runtime.sendMessage( + /*id*/ null, + message.toJSON(), + /*options*/ null, + /*callback*/ null, + ); +} diff --git a/dwds/debug_extension_mv3/web/iframe.dart b/dwds/debug_extension_mv3/web/iframe.dart deleted file mode 100644 index 9da414451..000000000 --- a/dwds/debug_extension_mv3/web/iframe.dart +++ /dev/null @@ -1,75 +0,0 @@ -// 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 iframe; - -import 'dart:html'; - -import 'package:js/js.dart'; - -import 'chrome_api.dart'; -import 'messaging.dart'; - -final _channel = BroadcastChannel(debugTabChannelName); - -void main() { - _registerListeners(); - - // Send a message to the injector script that the IFRAME has loaded. - _sendMessageToIframeInjector( - type: MessageType.iframeReady, - encodedBody: IframeReady(isReady: true).toJSON(), - ); -} - -void _registerListeners() { - chrome.runtime.onMessage.addListener(allowInterop(_handleRuntimeMessages)); -} - -void _sendMessageToIframeInjector({ - required MessageType type, - required String encodedBody, -}) { - final message = Message( - to: Script.iframeInjector, - from: Script.iframe, - type: type, - encodedBody: encodedBody, - ); - window.parent?.postMessage(message.toJSON(), '*'); -} - -void _handleRuntimeMessages( - dynamic jsRequest, MessageSender sender, Function sendResponse) { - if (jsRequest is! String) return; - - interceptMessage( - message: jsRequest, - expectedType: MessageType.debugState, - expectedSender: Script.iframeInjector, - expectedRecipient: Script.iframe, - messageHandler: (DebugState message) { - final tabId = sender.tab?.id; - if (tabId == null) return; - - _sendMessageToDebugTab( - type: MessageType.debugInfo, - encodedBody: DebugInfo(tabId: tabId).toJSON(), - ); - }); -} - -void _sendMessageToDebugTab({ - required MessageType type, - required String encodedBody, -}) { - final message = Message( - to: Script.debugTab, - from: Script.iframe, - type: type, - encodedBody: encodedBody, - ); - _channel.postMessage(message.toJSON()); -} diff --git a/dwds/debug_extension_mv3/web/iframe.html b/dwds/debug_extension_mv3/web/iframe.html deleted file mode 100644 index 1b4ba17bc..000000000 --- a/dwds/debug_extension_mv3/web/iframe.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -

Dart Debug IFRAME

- - - - \ No newline at end of file diff --git a/dwds/debug_extension_mv3/web/iframe_injector.dart b/dwds/debug_extension_mv3/web/iframe_injector.dart deleted file mode 100644 index a83f14520..000000000 --- a/dwds/debug_extension_mv3/web/iframe_injector.dart +++ /dev/null @@ -1,72 +0,0 @@ -// 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. - -import 'dart:html'; -import 'dart:js'; - -import 'chrome_api.dart'; -import 'messaging.dart'; - -void main() { - _registerListeners(); - - // Inject the IFRAME into the current tab. - _injectIframe(); -} - -void _registerListeners() { - window.addEventListener( - 'message', - allowInterop(_handleWindowMessageEvents), - ); -} - -void _injectIframe() { - final iframe = document.createElement('iframe'); - final iframeSrc = chrome.runtime.getURL('iframe.html'); - iframe.setAttribute('src', iframeSrc); - document.body?.append(iframe); -} - -void _handleWindowMessageEvents(Event event) { - final messageData = - jsEventToMessageData(event, expectedOrigin: chrome.runtime.getURL('')); - if (messageData == null) return; - - interceptMessage( - message: messageData, - expectedType: MessageType.iframeReady, - expectedSender: Script.iframe, - expectedRecipient: Script.iframeInjector, - messageHandler: _iframeReadyMessageHandler, - ); -} - -void _iframeReadyMessageHandler(IframeReady message) { - if (message.isReady != true) return; - // TODO(elliette): Inject a script to fetch debug info global variables. - - // Send a message back to IFRAME so that it has access to the tab ID. - _sendMessageToIframe( - type: MessageType.debugState, - encodedBody: DebugState(shouldDebug: true).toJSON()); -} - -void _sendMessageToIframe({ - required MessageType type, - required String encodedBody, -}) { - final message = Message( - to: Script.iframe, - from: Script.iframeInjector, - type: type, - encodedBody: encodedBody, - ); - chrome.runtime.sendMessage( - /*id*/ null, - message.toJSON(), - /*options*/ null, - /*callback*/ null, - ); -} diff --git a/dwds/debug_extension_mv3/web/manifest.json b/dwds/debug_extension_mv3/web/manifest.json index 06fc0cabf..2492e4604 100644 --- a/dwds/debug_extension_mv3/web/manifest.json +++ b/dwds/debug_extension_mv3/web/manifest.json @@ -26,5 +26,16 @@ ], "background": { "service_worker": "background.dart.js" - } + }, + "content_scripts": [ + { + "matches": [ + "" + ], + "js": [ + "detector.dart.js" + ], + "run_at": "document_end" + } + ] } \ No newline at end of file diff --git a/dwds/debug_extension_mv3/web/messaging.dart b/dwds/debug_extension_mv3/web/messaging.dart index 1593a3b6c..08128a13a 100644 --- a/dwds/debug_extension_mv3/web/messaging.dart +++ b/dwds/debug_extension_mv3/web/messaging.dart @@ -6,19 +6,14 @@ library messaging; import 'dart:convert'; -import 'dart:html'; import 'package:js/js.dart'; import 'web_api.dart'; -const debugTabChannelName = 'DEBUG_TAB_CHANNEL'; - enum Script { background, - debugTab, - iframe, - iframeInjector; + detector; factory Script.fromString(String value) { return Script.values.byName(value); @@ -26,9 +21,7 @@ enum Script { } enum MessageType { - debugInfo, - debugState, - iframeReady; + dartAppReady; factory MessageType.fromString(String value) { return MessageType.values.byName(value); @@ -39,14 +32,14 @@ class Message { final Script to; final Script from; final MessageType type; - final String encodedBody; + final String body; final String? error; Message({ required this.to, required this.from, required this.type, - required this.encodedBody, + required this.body, this.error, }); @@ -57,7 +50,7 @@ class Message { to: Script.fromString(decoded['to'] as String), from: Script.fromString(decoded['from'] as String), type: MessageType.fromString(decoded['type'] as String), - encodedBody: decoded['encodedBody'] as String, + body: decoded['body'] as String, error: decoded['error'] as String?, ); } @@ -67,18 +60,18 @@ class Message { 'type': type.name, 'to': to.name, 'from': from.name, - 'encodedBody': encodedBody, + 'encodedBody': body, if (error != null) 'error': error, }); } } -void interceptMessage({ +void interceptMessage({ required String? message, required MessageType expectedType, required Script expectedSender, required Script expectedRecipient, - required void Function(T message) messageHandler, + required void Function(String message) messageHandler, }) { try { if (message == null) return; @@ -88,102 +81,10 @@ void interceptMessage({ decodedMessage.from != expectedSender) { return; } - final messageType = decodedMessage.type; - final messageBody = decodedMessage.encodedBody; - switch (messageType) { - case MessageType.debugInfo: - messageHandler(DebugInfo.fromJSON(messageBody) as T); - break; - case MessageType.debugState: - messageHandler(DebugState.fromJSON(messageBody) as T); - break; - case MessageType.iframeReady: - messageHandler(IframeReady.fromJSON(messageBody) as T); - break; - } + messageHandler(decodedMessage.body); } catch (error) { - console.warn('Error intercepting expected message: $error'); + console.warn( + 'Error intercepting $expectedType message from $expectedSender to $expectedRecipient: $error'); return; } } - -String? jsEventToMessageData( - Event event, { - required String expectedOrigin, -}) { - try { - final messageEvent = event as MessageEvent; - final origin = messageEvent.origin; - if (origin.removeTrailingSlash() != expectedOrigin.removeTrailingSlash()) { - return null; - } - return messageEvent.data as String; - } catch (error) { - console.warn('Error converting event to message data: $error'); - return null; - } -} - -class IframeReady { - final bool isReady; - - IframeReady({required this.isReady}); - - factory IframeReady.fromJSON(String json) { - final decoded = jsonDecode(json) as Map; - final isReady = decoded['isReady'] as bool; - return IframeReady(isReady: isReady); - } - - String toJSON() { - return jsonEncode({ - 'isReady': isReady, - }); - } -} - -class DebugState { - final bool shouldDebug; - - DebugState({required this.shouldDebug}); - - factory DebugState.fromJSON(String json) { - final decoded = jsonDecode(json) as Map; - final shouldDebug = decoded['shouldDebug'] as bool; - return DebugState(shouldDebug: shouldDebug); - } - - String toJSON() { - return jsonEncode({ - 'shouldDebug': shouldDebug, - }); - } -} - -class DebugInfo { - final int tabId; - - DebugInfo({required this.tabId}); - - factory DebugInfo.fromJSON(String json) { - final decoded = jsonDecode(json) as Map; - final tabId = decoded['tabId'] as int; - return DebugInfo(tabId: tabId); - } - - String toJSON() { - return jsonEncode({ - 'tabId': tabId, - }); - } -} - -extension RemoveTrailingSlash on String { - String removeTrailingSlash() { - final trailingSlash = '/'; - if (endsWith(trailingSlash)) { - return substring(0, length - 1); - } - return this; - } -}