Save debug information in `chrome.storage` after a Dart app loads (#1791)
diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart
index 60d2477..b749136 100644
--- a/dwds/debug_extension_mv3/web/background.dart
+++ b/dwds/debug_extension_mv3/web/background.dart
@@ -53,11 +53,19 @@
final currentTab = await _getTab();
final currentUrl = currentTab?.url ?? '';
final appUrl = debugInfo.appUrl ?? '';
- if (currentUrl.isEmpty || appUrl.isEmpty || currentUrl != appUrl) {
+ if (currentTab == null ||
+ currentUrl.isEmpty ||
+ appUrl.isEmpty ||
+ currentUrl != appUrl) {
console.warn(
'Dart app detected at $appUrl but current tab is $currentUrl.');
return;
}
+ // Save the debug info for the Dart app in storage:
+ await setStorageObject<DebugInfo>(
+ type: StorageObject.debugInfo,
+ value: debugInfo,
+ tabId: currentTab.id);
// 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/data_serializers.dart b/dwds/debug_extension_mv3/web/data_serializers.dart
index 3692027..20552e6 100644
--- a/dwds/debug_extension_mv3/web/data_serializers.dart
+++ b/dwds/debug_extension_mv3/web/data_serializers.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:built_value/serializer.dart';
+import 'package:dwds/data/debug_info.dart';
import 'data_types.dart';
@@ -10,6 +11,7 @@
/// Serializers for all the data types used in the Dart Debug Extension.
@SerializersFor([
+ DebugInfo,
DevToolsOpener,
])
final Serializers serializers = _$serializers;
diff --git a/dwds/debug_extension_mv3/web/data_serializers.g.dart b/dwds/debug_extension_mv3/web/data_serializers.g.dart
index 0b4f692..2d11b95 100644
--- a/dwds/debug_extension_mv3/web/data_serializers.g.dart
+++ b/dwds/debug_extension_mv3/web/data_serializers.g.dart
@@ -6,7 +6,9 @@
// BuiltValueGenerator
// **************************************************************************
-Serializers _$serializers =
- (new Serializers().toBuilder()..add(DevToolsOpener.serializer)).build();
+Serializers _$serializers = (new Serializers().toBuilder()
+ ..add(DebugInfo.serializer)
+ ..add(DevToolsOpener.serializer))
+ .build();
// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
diff --git a/dwds/debug_extension_mv3/web/storage.dart b/dwds/debug_extension_mv3/web/storage.dart
index 7e8aa5f..a989e63 100644
--- a/dwds/debug_extension_mv3/web/storage.dart
+++ b/dwds/debug_extension_mv3/web/storage.dart
@@ -13,12 +13,19 @@
import 'chrome_api.dart';
import 'data_serializers.dart';
+import 'web_api.dart';
+
+/// Switch to true for debug logging.
+bool enableDebugLogging = true;
enum StorageObject {
+ debugInfo,
devToolsOpener;
String get keyName {
switch (this) {
+ case StorageObject.debugInfo:
+ return 'debugInfo';
case StorageObject.devToolsOpener:
return 'devToolsOpener';
}
@@ -28,9 +35,10 @@
Future<bool> setStorageObject<T>({
required StorageObject type,
required T value,
+ int? tabId,
void Function()? callback,
}) {
- final storageKey = type.keyName;
+ final storageKey = _createStorageKey(type, tabId);
final json = jsonEncode(serializers.serialize(value));
final storageObj = <String, String>{storageKey: json};
final completer = Completer<bool>();
@@ -38,22 +46,42 @@
if (callback != null) {
callback();
}
+ _debugLog(storageKey, 'Set: $json');
completer.complete(true);
}));
return completer.future;
}
-Future<T?> fetchStorageObject<T>({required StorageObject type}) {
- final storageKey = type.keyName;
+Future<T?> fetchStorageObject<T>({required StorageObject type, int? tabId}) {
+ final storageKey = _createStorageKey(type, tabId);
final completer = Completer<T?>();
chrome.storage.local.get([storageKey], allowInterop((Object storageObj) {
final json = getProperty(storageObj, storageKey) as String?;
if (json == null) {
+ _debugWarn(storageKey, 'Does not exist.');
completer.complete(null);
} else {
final value = serializers.deserialize(jsonDecode(json)) as T;
+ _debugLog(storageKey, 'Fetched: $json');
completer.complete(value);
}
}));
return completer.future;
}
+
+String _createStorageKey(StorageObject type, int? tabId) {
+ if (tabId == null) return type.keyName;
+ return '$tabId-${type.keyName}';
+}
+
+void _debugLog(String storageKey, String msg) {
+ if (enableDebugLogging) {
+ console.log('[$storageKey] $msg');
+ }
+}
+
+void _debugWarn(String storageKey, String msg) {
+ if (enableDebugLogging) {
+ console.warn('[$storageKey] $msg');
+ }
+}
diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js
index 794e091..777c1d6 100644
--- a/dwds/lib/src/injected/client.js
+++ b/dwds/lib/src/injected/client.js
@@ -25172,11 +25172,10 @@
A.main__closure8.prototype = {
call$1(b) {
var t2, t3,
- _s17_ = "$dartExtensionUri",
t1 = A._asStringQ(self.$dartEntrypointPath);
b.get$_$this()._appEntrypointPath = t1;
t1 = this.windowContext;
- t2 = A._asStringQ(t1.$index(0, _s17_));
+ t2 = A._asStringQ(t1.$index(0, "$dartAppId"));
b.get$_$this()._appId = t2;
t2 = A._asStringQ(self.$dartAppInstanceId);
b.get$_$this()._appInstanceId = t2;
@@ -25186,7 +25185,7 @@
t2 = t2._as(window.location).href;
t2.toString;
b.get$_$this()._appUrl = t2;
- t2 = A._asStringQ(t1.$index(0, _s17_));
+ t2 = A._asStringQ(t1.$index(0, "$dartExtensionUri"));
b.get$_$this()._extensionUrl = t2;
t1 = A._asBoolQ(t1.$index(0, "$isInternalDartBuild"));
b.get$_$this()._isInternalBuild = t1;
diff --git a/dwds/test/puppeteer/extension_test.dart b/dwds/test/puppeteer/extension_test.dart
index 016f8dd..78ec470 100644
--- a/dwds/test/puppeteer/extension_test.dart
+++ b/dwds/test/puppeteer/extension_test.dart
@@ -10,7 +10,10 @@
})
@Timeout(Duration(seconds: 60))
import 'dart:async';
+import 'dart:convert';
+import 'package:dwds/data/debug_info.dart';
+import 'package:dwds/data/serializers.dart';
import 'package:puppeteer/puppeteer.dart';
import 'package:test/test.dart';
@@ -19,6 +22,10 @@
final context = TestContext();
+// Note: The following delay is required to reduce flakiness. It makes
+// sure the service worker execution context is ready.
+const executionContextDelay = 1;
+
void main() async {
late Target serviceWorkerTarget;
late Browser browser;
@@ -53,6 +60,31 @@
await browser.close();
});
+ test('the debug info for a Dart app is saved in the extension storage',
+ () async {
+ final appUrl = context.appUrl;
+ // Navigate to the Dart app:
+ final appTab =
+ await navigateToPage(browser, url: appUrl, isNew: true);
+ final worker = (await serviceWorkerTarget.worker)!;
+ await Future.delayed(Duration(seconds: executionContextDelay));
+ // Verify that we have debug info for the Dart app:
+ final tabIdForAppJs = _tabIdForTabJs(appUrl);
+ final appTabId = (await worker.evaluate(tabIdForAppJs)) as int;
+ final debugInfoKey = '$appTabId-debugInfo';
+ final storageObj =
+ await worker.evaluate(_fetchStorageObjJs(debugInfoKey));
+ final json = storageObj[debugInfoKey];
+ final debugInfo =
+ serializers.deserialize(jsonDecode(json)) as DebugInfo;
+ expect(debugInfo.appId, isNotNull);
+ expect(debugInfo.appEntrypointPath, isNotNull);
+ expect(debugInfo.appInstanceId, isNotNull);
+ expect(debugInfo.appOrigin, isNotNull);
+ expect(debugInfo.appUrl, isNotNull);
+ await appTab.close();
+ });
+
test(
'can configure opening DevTools in a tab/window with extension settings',
() async {
@@ -62,12 +94,11 @@
final windowIdForAppJs = _windowIdForTabJs(appUrl);
final windowIdForDevToolsJs = _windowIdForTabJs(devToolsUrl);
// Navigate to the Dart app:
- await navigateToPage(browser, url: appUrl, isNew: true);
+ final appTab =
+ await navigateToPage(browser, url: appUrl, isNew: true);
// Click on the Dart Debug Extension icon:
final worker = (await serviceWorkerTarget.worker)!;
- // Note: The following delay is required to reduce flakiness (it makes
- // sure the execution context is ready):
- await Future.delayed(Duration(seconds: 1));
+ await Future.delayed(Duration(seconds: executionContextDelay));
await worker.evaluate(clickIconJs);
// Verify the extension opened the Dart docs in the same window:
var devToolsTabTarget = await browser
@@ -107,12 +138,23 @@
// Close the DevTools tab:
devToolsTab = await devToolsTabTarget.page;
await devToolsTab.close();
+ await appTab.close();
});
});
}
});
}
+String _tabIdForTabJs(String tabUrl) {
+ return '''
+ async () => {
+ const matchingTabs = await chrome.tabs.query({ url: "$tabUrl" });
+ const tab = matchingTabs[0];
+ return tab.id;
+ }
+''';
+}
+
String _windowIdForTabJs(String tabUrl) {
return '''
async () => {
@@ -122,3 +164,20 @@
}
''';
}
+
+String _fetchStorageObjJs(String storageKey) {
+ return '''
+ async () => {
+ const storageKey = "$storageKey";
+ return new Promise((resolve, reject) => {
+ chrome.storage.local.get(storageKey, (storageObj) => {
+ if (storageObj != null) {
+ resolve(storageObj);
+ } else {
+ resolve(null);
+ }
+ });
+ });
+ }
+''';
+}
diff --git a/dwds/web/client.dart b/dwds/web/client.dart
index 79da73e..ac430dc 100644
--- a/dwds/web/client.dart
+++ b/dwds/web/client.dart
@@ -176,7 +176,7 @@
final windowContext = JsObject.fromBrowserObject(window);
final debugInfoJson = jsonEncode(serializers.serialize(DebugInfo((b) => b
..appEntrypointPath = dartEntrypointPath
- ..appId = windowContext['\$dartExtensionUri']
+ ..appId = windowContext['\$dartAppId']
..appInstanceId = dartAppInstanceId
..appOrigin = window.location.origin
..appUrl = window.location.href