[roll] Update third-party dart packages
Roller-URL: https://ci.chromium.org/b/8790173535669781329
Roller-Owners: flutter-on-fuchsia-team@google.com, godofredoc@google.com
CQ-Do-Not-Cancel-Tryjobs: true
Change-Id: I01035865da12449654d131d7c83f8db5dfbfa220
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/dart-pkg/+/796942
Commit-Queue: GI Roller <global-integration-roller@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/dwds/BUILD.gn b/dwds/BUILD.gn
index 80acd3c..0d85f99 100644
--- a/dwds/BUILD.gn
+++ b/dwds/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for dwds-16.0.3
+# This file is generated by package_importer.py for dwds-17.0.0
import("//build/dart/dart_library.gni")
dart_library("dwds") {
package_name = "dwds"
- language_version = "2.19"
+ language_version = "3.0"
disable_analysis = true
@@ -48,6 +48,8 @@
"data/connect_request.g.dart",
"data/debug_event.dart",
"data/debug_event.g.dart",
+ "data/debug_info.dart",
+ "data/debug_info.g.dart",
"data/devtools_request.dart",
"data/devtools_request.g.dart",
"data/error_response.dart",
@@ -116,6 +118,7 @@
"src/utilities/objects.dart",
"src/utilities/sdk_configuration.dart",
"src/utilities/shared.dart",
+ "src/utilities/synchronized.dart",
"src/version.dart",
"src/web_utilities/batched_stream.dart",
]
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index c6eb8c4..677abae 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -1,12 +1,38 @@
-## 16.0.3
+## 17.0.0
-- Update the `vm_service` constraint to `>=10.1.0 <12.0.0`. See
- https://github.com/dart-lang/webdev/issues/1912
+- Include debug information in the event sent from the injected client to the
+ Dart Debug Extension notifying that the Dart app is ready.
+- Fix null cast error on expression evaluations after dwds fails to find class
+ metadata.
+- Include the entire exception description up to the stacktrace in
+ `mapExceptionStackTrace`.
+- Allow enabling experiments in the expression compiler service.
+- Pre-warm expression compiler cache to speed up Flutter Inspector loading.
+- Display full error on failure to start DDS.
+- Fix crash on processing DevTools event when starting DevTools from DevTools
+ uri.
+- Prepare or Dart 3 alpha breaking changes:
+ - Move weak null safety tests to special branch of `build_web_compilers`.
+ - Do not pass `--(no)-sound-null-safety` flag to build daemon.
+- Add back `ChromeProxyService.setExceptionPauseMode()` without override.
+- Make hot restart atomic to prevent races on simultaneous execution.
+- Return error on expression evaluation if expression evaluator stopped.
+- Update SDK constraint to `>=3.0.0-134.0.dev <4.0.0`.
+- Update `package:vm_service` constraint to `>=10.1.0 <12.0.0`.
+- Fix expression compiler throwing when weak SDK summary is not found.
-## 16.0.2
-
-- Don't complete an already completed `Completer` in `ChromeProxyService` to fix
- Flutter tools crash: https://github.com/dart-lang/webdev/pull/1862
+**Breaking changes**
+- Include an optional param to `Dwds.start` to indicate whether it is running
+ internally or externally.
+- Include an optional param to `Dwds.start` to indicate whether it a Flutter
+ app or not.
+- Remove deprecated `ChromeProxyService.setExceptionPauseMode()`.
+- Support dart 3.0-alpha breaking changes:
+ - Generate missing SDK assets for tests.
+ - Enable frontend server null safe tests.
+ - Update `build_web_compilers` constraint to `^4.0.0`.
+ - Update `build_runner` constraint to `^2.4.0`.
+ - Support changes in the SDK layout for dart 3.0.
## 16.0.1
diff --git a/dwds/CONTRIBUTING.md b/dwds/CONTRIBUTING.md
index 4ffa555..ae3abaa 100644
--- a/dwds/CONTRIBUTING.md
+++ b/dwds/CONTRIBUTING.md
@@ -19,8 +19,8 @@
> - `/PATH_TO_YOUR_FLUTTER_REPO/packages/flutter_tools/bin/flutter_tools.dart`:
> This is the path to Flutter Tools itself
>
-> *More details can be found at the Flutter Tools
-> [README](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/README.md).*
+> _More details can be found at the Flutter Tools
+> [README](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/README.md)._
3. In your Flutter Tools
[`pubspec.yaml`](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/pubspec.yaml),
@@ -73,8 +73,8 @@
### Step 1: Roll DWDS into g3
-> *NOTE: You must be a Googler to do this step. If you are not, please ask
-> someone for help.*
+> _NOTE: You must be a Googler to do this step. If you are not, please ask
+> someone for help._
- See directions at: go/roll-dwds
- Wait a few days after rolling into g3 before continuing to step 2. We do so to
@@ -91,21 +91,21 @@
- From `/dwds` run `dart run build_runner build`, this will build and update the
version in `/dwds/lib/src/version.dart`
- Submit a PR with those changes (example PR:
- https://github.com/dart-lang/webdev/pull/1456). *Note: Ensure your PR doesn’t
- have any dependency overrides.*
+ https://github.com/dart-lang/webdev/pull/1456). _Note: Ensure your PR doesn’t
+ have any dependency overrides._
- Once the PR is submitted, pull from master and `run dart pub publish`
- Finally, go to https://github.com/dart-lang/webdev/releases and create a new
release, eg https://github.com/dart-lang/webdev/releases/tag/dwds-v12.0.0. You
might need to delete some of the content of the autogenerated notes.
-> *Note: To have the right permissions for publishing, you need to be invited to
+> _Note: To have the right permissions for publishing, you need to be invited to
> the tools.dart.dev. A member of the Dart team should be able to add you at
-> https://pub.dev/publishers/tools.dart.dev/admin.*
+> https://pub.dev/publishers/tools.dart.dev/admin._
## Step 3: Publish Webdev to pub
-> *Note: DWDS is a dependency of Webdev, which is why DWDS must be published
-> before Webdev can be published.*
+> _Note: DWDS is a dependency of Webdev, which is why DWDS must be published
+> before Webdev can be published._
Follow instructions in the `webdev/webdev`
[CONTRIBUTING](/webdev/CONTRIBUTING.md) to release Webdev.
@@ -138,3 +138,58 @@
> run tests for all earlier stable releases of the SDK that match the
> constraint, which would have differences in functionality and therefore need
> different tests.
+
+## Hotfixes
+
+Sometimes you might need to do a hotfix release of DWDS. An example of why this
+might be necessary is if you need to do a hotfix of DWDS into Flutter, but don't
+want to release a new version of DWDS with the current untested changes on the
+master branch. Instead you only want to apply a fix to the current version of
+DWDS in Flutter.
+
+### Instructions:
+
+1. Create a branch off the release that needs a hotfix:
+
+ a. In the Github UI's
+ [commit history view](https://github.com/dart-lang/webdev/commits/master),
+ find the commit that prepared the release of DWDS that you would like to
+ hotfix.
+
+ b. Click on `< >` ("Browse the repository at this point in history").
+
+ c. At the top-left, you should see the commit hash in a dropdown. Click the
+ dropdown, and type in a name for your hotfix branch (e.g.
+ `16.0.2-hotfix-release`). Then select "Create branch `16.0.2-hotfix-release`
+ from `commit_hash`".
+
+ d. From your local clone of DWDS, run `git fetch upstream`. (_Note: this
+ assumes you have already configured git to sync your fork with the `upstream`
+ repository. If you haven't, follow
+ [these instructions](https://docs.github.com/en/get-started/quickstart/fork-a-repo#configuring-git-to-sync-your-fork-with-the-upstream-repository).)_
+
+ e. Search for the branch that you just created, e.g.
+ `git branch -a | grep 16.0.2-hotfix-release` f. Track that branch with
+ `git checkout --track branch_name` (e.g.
+ `remotes/upstream/16.0.2-hotfix-release`)
+
+1. Update the CI tests so that the branch tests against the appropriate Dart
+ SDKs:
+
+ a. Make the appropriate changes to DWDS' `mono_pkg.yaml` then run
+ `mono_repo generate`. Submit this change to the branch you created in step
+ #3, **not** `master`.
+
+1. Make the fix:
+
+ a. You can now make the change you would like to hotfix. From the Github UI,
+ open a PR to merge your change into the branch you created in step #3,
+ **not** `master`. See https://github.com/dart-lang/webdev/pull/1867 as an
+ example.
+
+1. Once it's merged, you can follow the instructions to
+ [publish DWDS to pub](#step-2-publish-dwds-to-pub), except instead of pulling
+ from `master`, pull from the branch your created in step #3.
+
+1. If necessary, open a cherry-pick request in Flutter to update the version.
+ See https://github.com/flutter/flutter/issues/118122 for an example.
diff --git a/dwds/analysis_options.yaml b/dwds/analysis_options.yaml
index 84b1173..3106b5c 100644
--- a/dwds/analysis_options.yaml
+++ b/dwds/analysis_options.yaml
@@ -5,9 +5,15 @@
analyzer:
exclude:
- # Ignore generated files
+ # Ignore generated files
- "lib/data/*"
+ # Ignore debug extension builds
+ - "debug_extension/dev_build/*"
+ - "debug_extension/prod_build/*"
+ - "debug_extension_mv3/dev_build/*"
+ - "debug_extension_mv3/prod_build/*"
linter:
rules:
- prefer_final_locals
+ - unawaited_futures
diff --git a/dwds/debug_extension/pubspec.yaml b/dwds/debug_extension/pubspec.yaml
index 8bbc7e4..eec4a91 100644
--- a/dwds/debug_extension/pubspec.yaml
+++ b/dwds/debug_extension/pubspec.yaml
@@ -6,7 +6,7 @@
A chrome extension for Dart debugging.
environment:
- sdk: '>=2.12.0 <3.0.0'
+ sdk: ">=3.0.0-134.0.dev <4.0.0"
dependencies:
async: ^2.3.0
@@ -18,8 +18,8 @@
dev_dependencies:
build: ^2.0.0
- build_web_compilers: ^3.0.0
- build_runner: ^2.0.6
+ build_web_compilers: ^4.0.0
+ build_runner: ^2.4.0
built_collection: ^5.0.0
dwds: ^11.0.0
webdev: ^2.0.0
diff --git a/dwds/debug_extension/tool/update_dev_files.dart b/dwds/debug_extension/tool/update_dev_files.dart
index 24ec970..ece7413 100644
--- a/dwds/debug_extension/tool/update_dev_files.dart
+++ b/dwds/debug_extension/tool/update_dev_files.dart
@@ -2,11 +2,11 @@
// 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:async';
import 'dart:io';
void main() async {
- _updateManifestJson();
- _updateDevtoolsJs();
+ await Future.wait([_updateManifestJson(), _updateDevtoolsJs()]);
}
/// Adds the Googler extension key, updates the extension icon, and prefixes the
@@ -17,7 +17,7 @@
final extensionKey = await extensionKeyTxt.exists()
? await extensionKeyTxt.readAsString()
: null;
- _transformDevFile(manifestJson, (line) {
+ return _transformDevFile(manifestJson, (line) {
if (_matchesKey(line: line, key: 'name')) {
return [
_newKeyValue(
diff --git a/dwds/debug_extension/web/background.dart b/dwds/debug_extension/web/background.dart
index 350291c..08cb079 100644
--- a/dwds/debug_extension/web/background.dart
+++ b/dwds/debug_extension/web/background.dart
@@ -23,6 +23,7 @@
// NOTE(annagrin): using 'package:dwds/src/utilities/batched_stream.dart'
// makes dart2js skip creating background.js, so we use a copy instead.
// import 'package:dwds/src/utilities/batched_stream.dart';
+// Issue: https://github.com/dart-lang/sdk/issues/49973
import 'package:dwds/src/web_utilities/batched_stream.dart';
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
diff --git a/dwds/debug_extension_mv3/CONTRIBUTING.md b/dwds/debug_extension_mv3/CONTRIBUTING.md
new file mode 100644
index 0000000..18a6049
--- /dev/null
+++ b/dwds/debug_extension_mv3/CONTRIBUTING.md
@@ -0,0 +1,94 @@
+## Building
+
+> Note: First make the script executable: `chmod +x tool/build_extension.sh`
+
+- For development: `./tool/build_extension.sh`
+- For release: `./tool/build_extension.sh prod`
+
+The dart2js-compiled extension will be located in the `/compiled` directory.
+
+## Local Development
+
+### \[For Googlers\] Create an `extension_key.txt` file:
+
+- Create a `extension_key.txt` file at the root of `/debug_extension`. Paste in
+ the value of one of the whitelisted developer keys into this txt file.
+ IMPORTANT: DO NOT COMMIT THE KEY. It will be copied into the `manifest.json`
+ when you build the extension.
+
+### Build and upload your local extension
+
+- Build the extension following the instructions above
+- Visit chrome://extensions
+- Toggle "Developer mode" on
+- Click the "Load unpacked" button
+- Select the extension directory: `/compiled`
+
+### Debug your local extension
+
+- Click the Extensions puzzle piece, and pin the Dart Debug Extension with the
+ dev icon (unpin the published version so you don't confuse them)
+- You can now use the extension normally by clicking it when a local Dart web
+ application has loaded in a Chrome tab
+- To debug, visit chrome://extensions and click "Inspect view on background
+ page" to open Chrome DevTools for the extension
+- More debugging information can be found in the
+ [Chrome Developers documentation](https://developer.chrome.com/docs/extensions/mv3/devguide/)
+
+## Release process
+
+- Update the version in `web/manifest.json`, `pubspec.yaml`, and in the
+ `CHANGELOG`.
+- Follow the instructions above to build the release version of the extension.
+
+> \*At this point, you should manually verify that everything is working by
+> following the steps in [Local Development](#local-development), except load
+> the extension from the `compiled` directory. You will need to add an extension
+> key to the `manifest.json` file in `compiled` to test locally.
+
+- Open a PR to submit the version change.
+- Once submitted, pull the changes down to your local branch, and create a zip
+ of the `compiled` directory. **Remove the Googler extension key that was added
+ by the builder to the `manifest.json` file.**
+- Rename the zip `version_XX.XX.XX.zip` (eg, `version_1.24.0.zip`) and add it to
+ the go/dart-debug-extension-zips folder
+
+> *You must be a Googler to do this. Ask for help if not.*
+
+- Go to the
+ [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole).
+- At the top-right, under Publisher, select dart-bat.
+
+> *If you don’t see dart-bat as an option, you will need someone on the Dart
+> team to add you to the dart-bat Google group.*
+
+- Under Items, select the "Dart Debug Extension".
+- Go to “Package” then select “Upload new package”.
+
+> *The first time you do this, you will be asked to pay a $5 registration fee.
+> The registration fee can be expensed.*
+
+- Upload the zip file you created in step 4.
+- Save as draft, and verify that the new version is correct.
+- Publish. The extension will be published immediately after going through the
+ review process.
+
+## Rollback process
+
+> The Chrome Web Store Developer Dashboard does not support rollbacks. Instead
+> you must re-publish an earlier version. This means that the extension will
+> still have to go through the review process, which can take anywhere from a
+> few hours (most common) to a few days.
+
+- Find the previous version you want to rollback to in the
+ go/dart-debug-extension-zips folder.
+
+> *You must be a Googler to do this. Ask for help if not.*
+
+- Unzip the version you have chosen, and in `manifest.json` edit the version
+ number to be the next sequential version after the current "bad" version (eg,
+ the bad version is `1.28.0` and you are rolling back to version `1.27.0`.
+ Therefore you change `1.27.0` to `1.29.0`).
+- Re-zip the directory and rename it to the new version number. Add it to the
+ go/dart-debug-extension-zips folder.
+- Now, follow steps 6 - 11 in [Release process](#release-process).
diff --git a/dwds/debug_extension_mv3/build.yaml b/dwds/debug_extension_mv3/build.yaml
index b5aaf6d..a1cdac3 100644
--- a/dwds/debug_extension_mv3/build.yaml
+++ b/dwds/debug_extension_mv3/build.yaml
@@ -19,8 +19,9 @@
build_extensions:
{
"web/{{}}.dart.js": ["compiled/{{}}.dart.js"],
- "web/{{}}.png": ["compiled/{{}}.png"],
- "web/{{}}.html": ["compiled/{{}}.html"],
+ "web/static_assets/{{}}.png": ["compiled/static_assets/{{}}.png"],
+ "web/static_assets/{{}}.html": ["compiled/static_assets/{{}}.html"],
+ "web/static_assets/{{}}.css": ["compiled/static_assets/{{}}.css"],
"web/manifest.json": ["compiled/manifest.json"],
}
auto_apply: none
diff --git a/dwds/debug_extension_mv3/pubspec.yaml b/dwds/debug_extension_mv3/pubspec.yaml
index a1167d1..e043196 100644
--- a/dwds/debug_extension_mv3/pubspec.yaml
+++ b/dwds/debug_extension_mv3/pubspec.yaml
@@ -6,12 +6,23 @@
A Chrome extension for Dart debugging.
environment:
- sdk: '>=2.18.0 <3.0.0'
+ sdk: ">=3.0.0-134.0.dev <4.0.0"
dependencies:
+ built_value: ^8.3.0
+ collection: ^1.15.0
js: ^0.6.1+1
dev_dependencies:
build: ^2.0.0
- build_web_compilers: ^3.0.0
- build_runner: ^2.0.6
+ build_runner: ^2.4.0
+ built_collection: ^5.0.0
+ built_value_generator: ^8.3.0
+ build_web_compilers: ^4.0.0
+ dwds: ^16.0.0
+ sse: ^4.1.2
+ web_socket_channel: ^2.2.0
+
+dependency_overrides:
+ dwds:
+ path: ..
diff --git a/dwds/debug_extension_mv3/tool/build_extension.sh b/dwds/debug_extension_mv3/tool/build_extension.sh
index 4600bb4..9df8f4f 100755
--- a/dwds/debug_extension_mv3/tool/build_extension.sh
+++ b/dwds/debug_extension_mv3/tool/build_extension.sh
@@ -9,11 +9,24 @@
# Builds the unminifed dart2js app (see DDC issue: https://github.com/dart-lang/sdk/issues/49869):
# ./tool/build_extension.sh
+
+prod="false"
+
+case "$1" in
+ prod)
+ prod="true"
+ shift;;
+esac
+
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "Building dart2js-compiled extension to /compiled directory."
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
dart run build_runner build web --output build --release
+if [ $prod == true ]; then
+ exit 1
+fi
+
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "Updating manifest.json in /compiled directory."
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
diff --git a/dwds/debug_extension_mv3/tool/copy_builder.dart b/dwds/debug_extension_mv3/tool/copy_builder.dart
index 5f0760f..93875c9 100644
--- a/dwds/debug_extension_mv3/tool/copy_builder.dart
+++ b/dwds/debug_extension_mv3/tool/copy_builder.dart
@@ -11,8 +11,9 @@
@override
Map<String, List<String>> get buildExtensions => {
"web/{{}}.dart.js": ["compiled/{{}}.dart.js"],
- "web/{{}}.png": ["compiled/{{}}.png"],
- "web/{{}}.html": ["compiled/{{}}.html"],
+ "web/static_assets/{{}}.png": ["compiled/static_assets/{{}}.png"],
+ "web/static_assets/{{}}.html": ["compiled/static_assets/{{}}.html"],
+ "web/static_assets/{{}}.css": ["compiled/static_assets/{{}}.css"],
"web/manifest.json": ["compiled/manifest.json"],
};
diff --git a/dwds/debug_extension_mv3/tool/update_dev_files.dart b/dwds/debug_extension_mv3/tool/update_dev_files.dart
index 22bb4c4..7cc2f48 100644
--- a/dwds/debug_extension_mv3/tool/update_dev_files.dart
+++ b/dwds/debug_extension_mv3/tool/update_dev_files.dart
@@ -2,23 +2,28 @@
// 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:async';
import 'dart:io';
void main() async {
- _updateManifestJson();
+ await _updateManifestJson();
}
-/// Adds the Googler extension key.
+/// Adds the Googler extension key, and prefixes the extension name with "DEV".
Future<void> _updateManifestJson() async {
final manifestJson = File('compiled/manifest.json');
final extensionKeyTxt = File('extension_key.txt');
final extensionKey = await extensionKeyTxt.exists()
? await extensionKeyTxt.readAsString()
: null;
- _transformDevFile(manifestJson, (line) {
+ return _transformDevFile(manifestJson, (line) {
if (_matchesKey(line: line, key: 'name')) {
return [
- line,
+ _newKeyValue(
+ oldLine: line,
+ newKey: 'name',
+ newValue: '[DEV] MV3 Dart Debug Extension',
+ ),
if (extensionKey != null)
_newKeyValue(
oldLine: line,
diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart
index a971fb4..10436c6 100644
--- a/dwds/debug_extension_mv3/web/background.dart
+++ b/dwds/debug_extension_mv3/web/background.dart
@@ -5,30 +5,210 @@
@JS()
library background;
+import 'dart:async';
+import 'dart:html';
+
+import 'package:dwds/data/debug_info.dart';
+import 'package:dwds/data/extension_request.dart';
import 'package:js/js.dart';
+import 'data_types.dart';
+import 'debug_session.dart';
import 'chrome_api.dart';
+import 'cross_extension_communication.dart';
+import 'lifeline_ports.dart';
+import 'logger.dart';
import 'messaging.dart';
+import 'storage.dart';
+import 'utils.dart';
+import 'web_api.dart';
+
+const _authSuccessResponse = 'Dart Debug Authentication Success!';
void main() {
_registerListeners();
}
void _registerListeners() {
- chrome.runtime.onMessage.addListener(allowInterop(_handleRuntimeMessages));
+ 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),
+ );
+ chrome.tabs.onRemoved
+ .addListener(allowInterop((tabId, _) => maybeRemoveLifelinePort(tabId)));
+ // Update the extension icon on tab navigation:
+ chrome.tabs.onActivated.addListener(allowInterop((ActiveInfo info) {
+ _updateIcon(info.tabId);
+ }));
+ chrome.windows.onFocusChanged.addListener(allowInterop((_) async {
+ final currentTab = await _getTab();
+ if (currentTab?.id != null) {
+ _updateIcon(currentTab!.id);
+ }
+ }));
+ chrome.webNavigation.onCommitted
+ .addListener(allowInterop(_detectNavigationAwayFromDartApp));
+
+ // Detect clicks on the Dart Debug Extension icon.
+ chrome.action.onClicked.addListener(allowInterop(
+ (Tab tab) => _startDebugSession(
+ tab.id,
+ trigger: Trigger.extensionIcon,
+ ),
+ ));
+}
+
+Future<void> _startDebugSession(int tabId, {required Trigger trigger}) async {
+ final debugInfo = await _fetchDebugInfo(tabId);
+ final extensionUrl = debugInfo?.extensionUrl;
+ if (extensionUrl == null) {
+ _showWarningNotification('Can\'t debug Dart app. Extension URL not found.');
+ sendConnectFailureMessage(
+ ConnectFailureReason.noDartApp,
+ dartAppTabId: tabId,
+ );
+ return;
+ }
+ final isAuthenticated = await _authenticateUser(extensionUrl, tabId);
+ if (!isAuthenticated) {
+ sendConnectFailureMessage(
+ ConnectFailureReason.authentication,
+ dartAppTabId: tabId,
+ );
+ return;
+ }
+
+ maybeCreateLifelinePort(tabId);
+ attachDebugger(tabId, trigger: trigger);
+}
+
+Future<bool> _authenticateUser(String extensionUrl, int tabId) async {
+ final authUrl = _constructAuthUrl(extensionUrl).toString();
+ final response = await fetchRequest(authUrl);
+ final responseBody = response.body ?? '';
+ if (!responseBody.contains(_authSuccessResponse)) {
+ debugError('Not authenticated: ${response.status} / $responseBody',
+ verbose: true);
+ _showWarningNotification('Please re-authenticate and try again.');
+ await createTab(authUrl, inNewWindow: false);
+ return false;
+ }
+ return true;
+}
+
+Uri _constructAuthUrl(String extensionUrl) {
+ final authUri = Uri.parse(extensionUrl).replace(path: authenticationPath);
+ if (authUri.scheme == 'ws') {
+ return authUri.replace(scheme: 'http');
+ }
+ if (authUri.scheme == 'wss') {
+ return authUri.replace(scheme: 'https');
+ }
+ return authUri;
}
void _handleRuntimeMessages(
dynamic jsRequest, MessageSender sender, Function sendResponse) async {
if (jsRequest is! String) return;
- interceptMessage(
+ interceptMessage<DebugInfo>(
message: jsRequest,
+ expectedType: MessageType.debugInfo,
expectedSender: Script.detector,
expectedRecipient: Script.background,
- expectedType: MessageType.dartAppReady,
- messageHandler: (_) {
+ messageHandler: (DebugInfo debugInfo) async {
+ final dartTab = sender.tab;
+ if (dartTab == null) {
+ debugWarn('Received debug info but tab is missing.');
+ return;
+ }
+ // Save the debug info for the Dart app in storage:
+ await setStorageObject<DebugInfo>(
+ type: StorageObject.debugInfo, value: debugInfo, tabId: dartTab.id);
// Update the icon to show that a Dart app has been detected:
- chrome.action.setIcon(IconInfo(path: 'dart.png'), /*callback*/ null);
+ final currentTab = await _getTab();
+ if (currentTab?.id == dartTab.id) {
+ _setDebuggableIcon();
+ }
});
+
+ 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) {
+ _startDebugSession(tabId, trigger: Trigger.extensionPanel);
+ }
+ });
+}
+
+void _detectNavigationAwayFromDartApp(NavigationInfo navigationInfo) async {
+ final tabId = navigationInfo.tabId;
+ final debugInfo = await _fetchDebugInfo(navigationInfo.tabId);
+ if (debugInfo == null) return;
+ if (debugInfo.appUrl != navigationInfo.url) {
+ _setDefaultIcon();
+ await removeStorageObject(type: StorageObject.debugInfo, tabId: tabId);
+ detachDebugger(
+ tabId,
+ type: TabType.dartApp,
+ reason: DetachReason.navigatedAwayFromApp,
+ );
+ }
+}
+
+void _updateIcon(int activeTabId) async {
+ final debugInfo = await _fetchDebugInfo(activeTabId);
+ if (debugInfo != null) {
+ _setDebuggableIcon();
+ } else {
+ _setDefaultIcon();
+ }
+}
+
+void _setDebuggableIcon() {
+ chrome.action
+ .setIcon(IconInfo(path: 'static_assets/dart.png'), /*callback*/ null);
+}
+
+void _setDefaultIcon() {
+ final iconPath = isDevMode()
+ ? 'static_assets/dart_dev.png'
+ : 'static_assets/dart_grey.png';
+ chrome.action.setIcon(IconInfo(path: iconPath), /*callback*/ null);
+}
+
+Future<DebugInfo?> _fetchDebugInfo(int tabId) {
+ return fetchStorageObject<DebugInfo>(
+ type: StorageObject.debugInfo,
+ tabId: tabId,
+ );
+}
+
+void _showWarningNotification(String message) {
+ chrome.notifications.create(
+ /*notificationId*/ null,
+ NotificationOptions(
+ title: '[Error] Dart Debug Extension',
+ message: message,
+ iconUrl: 'static_assets/dart.png',
+ type: 'basic',
+ ),
+ /*callback*/ null,
+ );
+}
+
+Future<Tab?> _getTab() async {
+ final query = QueryInfo(active: true, currentWindow: true);
+ final tabs = List<Tab>.from(await promiseToFuture(chrome.tabs.query(query)));
+ return tabs.isNotEmpty ? tabs.first : null;
}
diff --git a/dwds/debug_extension_mv3/web/chrome_api.dart b/dwds/debug_extension_mv3/web/chrome_api.dart
index 82f33dd..77fc960 100644
--- a/dwds/debug_extension_mv3/web/chrome_api.dart
+++ b/dwds/debug_extension_mv3/web/chrome_api.dart
@@ -2,6 +2,8 @@
// 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 'package:js/js.dart';
@JS()
@@ -11,7 +13,15 @@
@anonymous
class Chrome {
external Action get action;
+ external Debugger get debugger;
+ external Devtools get devtools;
+ external Notifications get notifications;
external Runtime get runtime;
+ external Scripting get scripting;
+ external Storage get storage;
+ external Tabs get tabs;
+ external WebNavigation get webNavigation;
+ external Windows get windows;
}
/// chrome.action APIs
@@ -38,16 +48,164 @@
external factory IconInfo({String path});
}
+/// chrome.debugger APIs:
+/// https://developer.chrome.com/docs/extensions/reference/debugger
+
+@JS()
+@anonymous
+class Debugger {
+ external void attach(
+ Debuggee target, String requiredVersion, Function? callback);
+
+ external Object detach(Debuggee target);
+
+ external void sendCommand(Debuggee target, String method,
+ Object? commandParams, Function? callback);
+
+ external OnDetachHandler get onDetach;
+
+ external OnEventHandler get onEvent;
+}
+
+@JS()
+@anonymous
+class OnDetachHandler {
+ external void addListener(
+ void Function(Debuggee source, String reason) callback);
+}
+
+@JS()
+@anonymous
+class OnEventHandler {
+ external void addListener(
+ void Function(Debuggee source, String method, Object? params) callback);
+}
+
+@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});
+}
+
+/// chrome.devtools APIs:
+
+@JS()
+@anonymous
+class Devtools {
+ // https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow
+ external InspectedWindow get inspectedWindow;
+
+ // https://developer.chrome.com/docs/extensions/reference/devtools_panels/
+ external Panels get panels;
+}
+
+@JS()
+@anonymous
+class InspectedWindow {
+ external int get tabId;
+}
+
+@JS()
+@anonymous
+class Panels {
+ external String get themeName;
+
+ external void create(String title, String iconPath, String pagePath,
+ void Function(ExtensionPanel)? callback);
+}
+
+@JS()
+@anonymous
+class ExtensionPanel {
+ external OnHiddenHandler get onHidden;
+ external OnShownHandler get onShown;
+}
+
+@JS()
+@anonymous
+class OnHiddenHandler {
+ external void addListener(void Function() callback);
+}
+
+@JS()
+@anonymous
+class OnShownHandler {
+ external void addListener(void Function(Window window) callback);
+}
+
+/// chrome.notification APIs:
+/// https://developer.chrome.com/docs/extensions/reference/notifications
+
+@JS()
+@anonymous
+class Notifications {
+ external void create(
+ String? notificationId, NotificationOptions options, Function? callback);
+}
+
+@JS()
+@anonymous
+class NotificationOptions {
+ external factory NotificationOptions({
+ String title,
+ String message,
+ String iconUrl,
+ String type,
+ });
+}
+
/// chrome.runtime APIs:
/// https://developer.chrome.com/docs/extensions/reference/runtime
@JS()
@anonymous
class Runtime {
+ external void connect(String? extensionId, ConnectInfo info);
+
external void sendMessage(
String? id, Object? message, Object? options, Function? callback);
+ external Object getManifest();
+
+ external String getURL(String path);
+
+ // Note: Not checking the lastError when one occurs throws a runtime exception.
+ external ChromeError? get lastError;
+
+ external ConnectionHandler get onConnect;
+
external OnMessageHandler get onMessage;
+
+ external OnMessageHandler get onMessageExternal;
+}
+
+@JS()
+class ChromeError {
+ external String get message;
+}
+
+@JS()
+@anonymous
+class ConnectInfo {
+ external String? get name;
+ external factory ConnectInfo({String? name});
+}
+
+@JS()
+@anonymous
+class Port {
+ external String? get name;
+ external void disconnect();
+ external ConnectionHandler get onDisconnect;
+}
+
+@JS()
+@anonymous
+class ConnectionHandler {
+ external void addListener(void Function(Port) callback);
}
@JS()
@@ -66,9 +224,182 @@
external factory MessageSender({String? id, String? url, Tab? tab});
}
+/// chrome.scripting APIs
+/// https://developer.chrome.com/docs/extensions/reference/scripting
+
+@JS()
+@anonymous
+class Scripting {
+ external executeScript(InjectDetails details, Function? callback);
+}
+
+@JS()
+@anonymous
+class InjectDetails<T, U> {
+ external Target get target;
+ external T? get func;
+ external List<U?>? get args;
+ external List<String>? get files;
+ external factory InjectDetails({
+ Target target,
+ T? func,
+ List<U>? args,
+ List<String>? files,
+ });
+}
+
+@JS()
+@anonymous
+class Target {
+ external int get tabId;
+ external factory Target({int tabId});
+}
+
+/// chrome.storage APIs
+/// https://developer.chrome.com/docs/extensions/reference/storage
+
+@JS()
+@anonymous
+class Storage {
+ external StorageArea get local;
+
+ external StorageArea get session;
+
+ external OnChangedHandler get onChanged;
+}
+
+@JS()
+@anonymous
+class StorageArea {
+ external Object get(List<String> keys, void Function(Object result) callback);
+
+ external Object set(Object items, void Function()? callback);
+
+ external Object remove(List<String> keys, void Function()? callback);
+}
+
+@JS()
+@anonymous
+class OnChangedHandler {
+ external void addListener(
+ void Function(Object changes, String areaName) callback,
+ );
+}
+
+/// chrome.tabs APIs
+/// https://developer.chrome.com/docs/extensions/reference/tabs
+
+@JS()
+@anonymous
+class Tabs {
+ external Object query(QueryInfo queryInfo);
+
+ external Object create(TabInfo tabInfo);
+
+ external Object get(int tabId);
+
+ external Object remove(int tabId);
+
+ external OnActivatedHandler get onActivated;
+
+ external OnRemovedHandler get onRemoved;
+}
+
+@JS()
+@anonymous
+class OnActivatedHandler {
+ external void addListener(void Function(ActiveInfo activeInfo) callback);
+}
+
+@JS()
+@anonymous
+class OnRemovedHandler {
+ external void addListener(void Function(int tabId, dynamic info) callback);
+}
+
+@JS()
+@anonymous
+class ActiveInfo {
+ external int get tabId;
+}
+
+@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 String get url;
+ external factory QueryInfo({bool? active, bool? currentWindow, String? url});
+}
+
@JS()
@anonymous
class Tab {
external int get id;
external String get url;
}
+
+/// chrome.webNavigation APIs
+/// https://developer.chrome.com/docs/extensions/reference/webNavigation
+
+@JS()
+@anonymous
+class WebNavigation {
+ // https://developer.chrome.com/docs/extensions/reference/webNavigation/#event-onCommitted
+ external OnCommittedHandler get onCommitted;
+}
+
+@JS()
+@anonymous
+class OnCommittedHandler {
+ external void addListener(void Function(NavigationInfo details) callback);
+}
+
+@JS()
+@anonymous
+class NavigationInfo {
+ external String get transitionType;
+ external int get tabId;
+ external String get url;
+}
+
+/// chrome.windows APIs
+/// https://developer.chrome.com/docs/extensions/reference/windows
+
+@JS()
+@anonymous
+class Windows {
+ external Object create(WindowInfo? createData);
+
+ external OnFocusChangedHandler get onFocusChanged;
+}
+
+@JS()
+@anonymous
+class OnFocusChangedHandler {
+ external void addListener(void Function(int windowId) callback);
+}
+
+@JS()
+@anonymous
+class WindowInfo {
+ external bool? get focused;
+ external String? get url;
+ external factory WindowInfo({bool? focused, String? url});
+}
+
+@JS()
+@anonymous
+class WindowObj {
+ external int get id;
+ external List<Tab> get tabs;
+}
diff --git a/dwds/debug_extension_mv3/web/cross_extension_communication.dart b/dwds/debug_extension_mv3/web/cross_extension_communication.dart
new file mode 100644
index 0000000..77faf5e
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/cross_extension_communication.dart
@@ -0,0 +1,160 @@
+// 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',
+};
+
+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') {
+ _respondWithEncodedUri(message.tabId, sendResponse);
+ } else if (message.name == 'dwds.startDebugging') {
+ 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);
+ }
+}
+
+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);
+}
diff --git a/dwds/debug_extension_mv3/web/data_serializers.dart b/dwds/debug_extension_mv3/web/data_serializers.dart
new file mode 100644
index 0000000..9c4e314
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/data_serializers.dart
@@ -0,0 +1,28 @@
+// 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 'package:built_collection/built_collection.dart';
+import 'package:built_value/serializer.dart';
+import 'package:dwds/data/debug_info.dart';
+import 'package:dwds/data/devtools_request.dart';
+import 'package:dwds/data/extension_request.dart';
+
+import 'data_types.dart';
+
+part 'data_serializers.g.dart';
+
+/// Serializers for all the data types used in the Dart Debug Extension.
+@SerializersFor([
+ BatchedEvents,
+ ConnectFailure,
+ DebugInfo,
+ DebugStateChange,
+ DevToolsOpener,
+ DevToolsUrl,
+ DevToolsRequest,
+ ExtensionEvent,
+ ExtensionRequest,
+ ExtensionResponse,
+])
+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
new file mode 100644
index 0000000..7c15ed1
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/data_serializers.g.dart
@@ -0,0 +1,25 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'data_serializers.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+Serializers _$serializers = (new Serializers().toBuilder()
+ ..add(BatchedEvents.serializer)
+ ..add(ConnectFailure.serializer)
+ ..add(DebugInfo.serializer)
+ ..add(DebugStateChange.serializer)
+ ..add(DevToolsOpener.serializer)
+ ..add(DevToolsRequest.serializer)
+ ..add(DevToolsUrl.serializer)
+ ..add(ExtensionEvent.serializer)
+ ..add(ExtensionRequest.serializer)
+ ..add(ExtensionResponse.serializer)
+ ..addBuilderFactory(
+ const FullType(BuiltList, const [const FullType(ExtensionEvent)]),
+ () => new ListBuilder<ExtensionEvent>()))
+ .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/data_types.dart b/dwds/debug_extension_mv3/web/data_types.dart
new file mode 100644
index 0000000..eb22eb4
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/data_types.dart
@@ -0,0 +1,70 @@
+// 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 'package:built_value/built_value.dart';
+import 'package:built_value/serializer.dart';
+
+part 'data_types.g.dart';
+
+abstract class ConnectFailure
+ implements Built<ConnectFailure, ConnectFailureBuilder> {
+ static Serializer<ConnectFailure> get serializer =>
+ _$connectFailureSerializer;
+
+ factory ConnectFailure([Function(ConnectFailureBuilder) updates]) =
+ _$ConnectFailure;
+
+ ConnectFailure._();
+
+ int get tabId;
+
+ String? get reason;
+}
+
+abstract class DevToolsOpener
+ implements Built<DevToolsOpener, DevToolsOpenerBuilder> {
+ static Serializer<DevToolsOpener> get serializer =>
+ _$devToolsOpenerSerializer;
+
+ factory DevToolsOpener([Function(DevToolsOpenerBuilder) updates]) =
+ _$DevToolsOpener;
+
+ DevToolsOpener._();
+
+ bool get newWindow;
+}
+
+abstract class DevToolsUrl implements Built<DevToolsUrl, DevToolsUrlBuilder> {
+ static Serializer<DevToolsUrl> get serializer => _$devToolsUrlSerializer;
+
+ factory DevToolsUrl([Function(DevToolsUrlBuilder) updates]) = _$DevToolsUrl;
+
+ DevToolsUrl._();
+
+ int get tabId;
+
+ String get url;
+}
+
+abstract class DebugStateChange
+ implements Built<DebugStateChange, DebugStateChangeBuilder> {
+ static const startDebugging = 'start-debugging';
+ static const stopDebugging = 'stop-debugging';
+ static const failedToConnect = 'failed-to-connect';
+
+ static Serializer<DebugStateChange> get serializer =>
+ _$debugStateChangeSerializer;
+
+ factory DebugStateChange([Function(DebugStateChangeBuilder) updates]) =
+ _$DebugStateChange;
+
+ DebugStateChange._();
+
+ int get tabId;
+
+ /// Can only be [startDebugging] or [stopDebugging].
+ String get newState;
+
+ String? get reason;
+}
diff --git a/dwds/debug_extension_mv3/web/data_types.g.dart b/dwds/debug_extension_mv3/web/data_types.g.dart
new file mode 100644
index 0000000..34c78b4
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/data_types.g.dart
@@ -0,0 +1,588 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'data_types.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+Serializer<ConnectFailure> _$connectFailureSerializer =
+ new _$ConnectFailureSerializer();
+Serializer<DevToolsOpener> _$devToolsOpenerSerializer =
+ new _$DevToolsOpenerSerializer();
+Serializer<DevToolsUrl> _$devToolsUrlSerializer = new _$DevToolsUrlSerializer();
+Serializer<DebugStateChange> _$debugStateChangeSerializer =
+ new _$DebugStateChangeSerializer();
+
+class _$ConnectFailureSerializer
+ implements StructuredSerializer<ConnectFailure> {
+ @override
+ final Iterable<Type> types = const [ConnectFailure, _$ConnectFailure];
+ @override
+ final String wireName = 'ConnectFailure';
+
+ @override
+ Iterable<Object?> serialize(Serializers serializers, ConnectFailure object,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = <Object?>[
+ 'tabId',
+ serializers.serialize(object.tabId, specifiedType: const FullType(int)),
+ ];
+ Object? value;
+ value = object.reason;
+ if (value != null) {
+ result
+ ..add('reason')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ return result;
+ }
+
+ @override
+ ConnectFailure deserialize(
+ Serializers serializers, Iterable<Object?> serialized,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = new ConnectFailureBuilder();
+
+ final iterator = serialized.iterator;
+ while (iterator.moveNext()) {
+ final key = iterator.current! as String;
+ iterator.moveNext();
+ final Object? value = iterator.current;
+ switch (key) {
+ case 'tabId':
+ result.tabId = serializers.deserialize(value,
+ specifiedType: const FullType(int))! as int;
+ break;
+ case 'reason':
+ result.reason = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ }
+ }
+
+ return result.build();
+ }
+}
+
+class _$DevToolsOpenerSerializer
+ implements StructuredSerializer<DevToolsOpener> {
+ @override
+ final Iterable<Type> types = const [DevToolsOpener, _$DevToolsOpener];
+ @override
+ final String wireName = 'DevToolsOpener';
+
+ @override
+ Iterable<Object?> serialize(Serializers serializers, DevToolsOpener object,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = <Object?>[
+ 'newWindow',
+ serializers.serialize(object.newWindow,
+ specifiedType: const FullType(bool)),
+ ];
+
+ return result;
+ }
+
+ @override
+ DevToolsOpener deserialize(
+ Serializers serializers, Iterable<Object?> serialized,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = new DevToolsOpenerBuilder();
+
+ final iterator = serialized.iterator;
+ while (iterator.moveNext()) {
+ final key = iterator.current! as String;
+ iterator.moveNext();
+ final Object? value = iterator.current;
+ switch (key) {
+ case 'newWindow':
+ result.newWindow = serializers.deserialize(value,
+ specifiedType: const FullType(bool))! as bool;
+ break;
+ }
+ }
+
+ return result.build();
+ }
+}
+
+class _$DevToolsUrlSerializer implements StructuredSerializer<DevToolsUrl> {
+ @override
+ final Iterable<Type> types = const [DevToolsUrl, _$DevToolsUrl];
+ @override
+ final String wireName = 'DevToolsUrl';
+
+ @override
+ Iterable<Object?> serialize(Serializers serializers, DevToolsUrl object,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = <Object?>[
+ 'tabId',
+ serializers.serialize(object.tabId, specifiedType: const FullType(int)),
+ 'url',
+ serializers.serialize(object.url, specifiedType: const FullType(String)),
+ ];
+
+ return result;
+ }
+
+ @override
+ DevToolsUrl deserialize(Serializers serializers, Iterable<Object?> serialized,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = new DevToolsUrlBuilder();
+
+ final iterator = serialized.iterator;
+ while (iterator.moveNext()) {
+ final key = iterator.current! as String;
+ iterator.moveNext();
+ final Object? value = iterator.current;
+ switch (key) {
+ case 'tabId':
+ result.tabId = serializers.deserialize(value,
+ specifiedType: const FullType(int))! as int;
+ break;
+ case 'url':
+ result.url = serializers.deserialize(value,
+ specifiedType: const FullType(String))! as String;
+ break;
+ }
+ }
+
+ return result.build();
+ }
+}
+
+class _$DebugStateChangeSerializer
+ implements StructuredSerializer<DebugStateChange> {
+ @override
+ final Iterable<Type> types = const [DebugStateChange, _$DebugStateChange];
+ @override
+ final String wireName = 'DebugStateChange';
+
+ @override
+ Iterable<Object?> serialize(Serializers serializers, DebugStateChange object,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = <Object?>[
+ 'tabId',
+ serializers.serialize(object.tabId, specifiedType: const FullType(int)),
+ 'newState',
+ serializers.serialize(object.newState,
+ specifiedType: const FullType(String)),
+ ];
+ Object? value;
+ value = object.reason;
+ if (value != null) {
+ result
+ ..add('reason')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ return result;
+ }
+
+ @override
+ DebugStateChange deserialize(
+ Serializers serializers, Iterable<Object?> serialized,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = new DebugStateChangeBuilder();
+
+ final iterator = serialized.iterator;
+ while (iterator.moveNext()) {
+ final key = iterator.current! as String;
+ iterator.moveNext();
+ final Object? value = iterator.current;
+ switch (key) {
+ case 'tabId':
+ result.tabId = serializers.deserialize(value,
+ specifiedType: const FullType(int))! as int;
+ break;
+ case 'newState':
+ result.newState = serializers.deserialize(value,
+ specifiedType: const FullType(String))! as String;
+ break;
+ case 'reason':
+ result.reason = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ }
+ }
+
+ return result.build();
+ }
+}
+
+class _$ConnectFailure extends ConnectFailure {
+ @override
+ final int tabId;
+ @override
+ final String? reason;
+
+ factory _$ConnectFailure([void Function(ConnectFailureBuilder)? updates]) =>
+ (new ConnectFailureBuilder()..update(updates))._build();
+
+ _$ConnectFailure._({required this.tabId, this.reason}) : super._() {
+ BuiltValueNullFieldError.checkNotNull(tabId, r'ConnectFailure', 'tabId');
+ }
+
+ @override
+ ConnectFailure rebuild(void Function(ConnectFailureBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ ConnectFailureBuilder toBuilder() =>
+ new ConnectFailureBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is ConnectFailure &&
+ tabId == other.tabId &&
+ reason == other.reason;
+ }
+
+ @override
+ int get hashCode {
+ return $jf($jc($jc(0, tabId.hashCode), reason.hashCode));
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'ConnectFailure')
+ ..add('tabId', tabId)
+ ..add('reason', reason))
+ .toString();
+ }
+}
+
+class ConnectFailureBuilder
+ implements Builder<ConnectFailure, ConnectFailureBuilder> {
+ _$ConnectFailure? _$v;
+
+ int? _tabId;
+ int? get tabId => _$this._tabId;
+ set tabId(int? tabId) => _$this._tabId = tabId;
+
+ String? _reason;
+ String? get reason => _$this._reason;
+ set reason(String? reason) => _$this._reason = reason;
+
+ ConnectFailureBuilder();
+
+ ConnectFailureBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ _tabId = $v.tabId;
+ _reason = $v.reason;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(ConnectFailure other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$ConnectFailure;
+ }
+
+ @override
+ void update(void Function(ConnectFailureBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ ConnectFailure build() => _build();
+
+ _$ConnectFailure _build() {
+ final _$result = _$v ??
+ new _$ConnectFailure._(
+ tabId: BuiltValueNullFieldError.checkNotNull(
+ tabId, r'ConnectFailure', 'tabId'),
+ reason: reason);
+ replace(_$result);
+ return _$result;
+ }
+}
+
+class _$DevToolsOpener extends DevToolsOpener {
+ @override
+ final bool newWindow;
+
+ factory _$DevToolsOpener([void Function(DevToolsOpenerBuilder)? updates]) =>
+ (new DevToolsOpenerBuilder()..update(updates))._build();
+
+ _$DevToolsOpener._({required this.newWindow}) : super._() {
+ BuiltValueNullFieldError.checkNotNull(
+ newWindow, r'DevToolsOpener', 'newWindow');
+ }
+
+ @override
+ DevToolsOpener rebuild(void Function(DevToolsOpenerBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ DevToolsOpenerBuilder toBuilder() =>
+ new DevToolsOpenerBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is DevToolsOpener && newWindow == other.newWindow;
+ }
+
+ @override
+ int get hashCode {
+ return $jf($jc(0, newWindow.hashCode));
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'DevToolsOpener')
+ ..add('newWindow', newWindow))
+ .toString();
+ }
+}
+
+class DevToolsOpenerBuilder
+ implements Builder<DevToolsOpener, DevToolsOpenerBuilder> {
+ _$DevToolsOpener? _$v;
+
+ bool? _newWindow;
+ bool? get newWindow => _$this._newWindow;
+ set newWindow(bool? newWindow) => _$this._newWindow = newWindow;
+
+ DevToolsOpenerBuilder();
+
+ DevToolsOpenerBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ _newWindow = $v.newWindow;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(DevToolsOpener other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$DevToolsOpener;
+ }
+
+ @override
+ void update(void Function(DevToolsOpenerBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ DevToolsOpener build() => _build();
+
+ _$DevToolsOpener _build() {
+ final _$result = _$v ??
+ new _$DevToolsOpener._(
+ newWindow: BuiltValueNullFieldError.checkNotNull(
+ newWindow, r'DevToolsOpener', 'newWindow'));
+ replace(_$result);
+ return _$result;
+ }
+}
+
+class _$DevToolsUrl extends DevToolsUrl {
+ @override
+ final int tabId;
+ @override
+ final String url;
+
+ factory _$DevToolsUrl([void Function(DevToolsUrlBuilder)? updates]) =>
+ (new DevToolsUrlBuilder()..update(updates))._build();
+
+ _$DevToolsUrl._({required this.tabId, required this.url}) : super._() {
+ BuiltValueNullFieldError.checkNotNull(tabId, r'DevToolsUrl', 'tabId');
+ BuiltValueNullFieldError.checkNotNull(url, r'DevToolsUrl', 'url');
+ }
+
+ @override
+ DevToolsUrl rebuild(void Function(DevToolsUrlBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ DevToolsUrlBuilder toBuilder() => new DevToolsUrlBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is DevToolsUrl && tabId == other.tabId && url == other.url;
+ }
+
+ @override
+ int get hashCode {
+ return $jf($jc($jc(0, tabId.hashCode), url.hashCode));
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'DevToolsUrl')
+ ..add('tabId', tabId)
+ ..add('url', url))
+ .toString();
+ }
+}
+
+class DevToolsUrlBuilder implements Builder<DevToolsUrl, DevToolsUrlBuilder> {
+ _$DevToolsUrl? _$v;
+
+ int? _tabId;
+ int? get tabId => _$this._tabId;
+ set tabId(int? tabId) => _$this._tabId = tabId;
+
+ String? _url;
+ String? get url => _$this._url;
+ set url(String? url) => _$this._url = url;
+
+ DevToolsUrlBuilder();
+
+ DevToolsUrlBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ _tabId = $v.tabId;
+ _url = $v.url;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(DevToolsUrl other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$DevToolsUrl;
+ }
+
+ @override
+ void update(void Function(DevToolsUrlBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ DevToolsUrl build() => _build();
+
+ _$DevToolsUrl _build() {
+ final _$result = _$v ??
+ new _$DevToolsUrl._(
+ tabId: BuiltValueNullFieldError.checkNotNull(
+ tabId, r'DevToolsUrl', 'tabId'),
+ url: BuiltValueNullFieldError.checkNotNull(
+ url, r'DevToolsUrl', 'url'));
+ replace(_$result);
+ return _$result;
+ }
+}
+
+class _$DebugStateChange extends DebugStateChange {
+ @override
+ final int tabId;
+ @override
+ final String newState;
+ @override
+ final String? reason;
+
+ factory _$DebugStateChange(
+ [void Function(DebugStateChangeBuilder)? updates]) =>
+ (new DebugStateChangeBuilder()..update(updates))._build();
+
+ _$DebugStateChange._(
+ {required this.tabId, required this.newState, this.reason})
+ : super._() {
+ BuiltValueNullFieldError.checkNotNull(tabId, r'DebugStateChange', 'tabId');
+ BuiltValueNullFieldError.checkNotNull(
+ newState, r'DebugStateChange', 'newState');
+ }
+
+ @override
+ DebugStateChange rebuild(void Function(DebugStateChangeBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ DebugStateChangeBuilder toBuilder() =>
+ new DebugStateChangeBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is DebugStateChange &&
+ tabId == other.tabId &&
+ newState == other.newState &&
+ reason == other.reason;
+ }
+
+ @override
+ int get hashCode {
+ return $jf(
+ $jc($jc($jc(0, tabId.hashCode), newState.hashCode), reason.hashCode));
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'DebugStateChange')
+ ..add('tabId', tabId)
+ ..add('newState', newState)
+ ..add('reason', reason))
+ .toString();
+ }
+}
+
+class DebugStateChangeBuilder
+ implements Builder<DebugStateChange, DebugStateChangeBuilder> {
+ _$DebugStateChange? _$v;
+
+ int? _tabId;
+ int? get tabId => _$this._tabId;
+ set tabId(int? tabId) => _$this._tabId = tabId;
+
+ String? _newState;
+ String? get newState => _$this._newState;
+ set newState(String? newState) => _$this._newState = newState;
+
+ String? _reason;
+ String? get reason => _$this._reason;
+ set reason(String? reason) => _$this._reason = reason;
+
+ DebugStateChangeBuilder();
+
+ DebugStateChangeBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ _tabId = $v.tabId;
+ _newState = $v.newState;
+ _reason = $v.reason;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(DebugStateChange other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$DebugStateChange;
+ }
+
+ @override
+ void update(void Function(DebugStateChangeBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ DebugStateChange build() => _build();
+
+ _$DebugStateChange _build() {
+ final _$result = _$v ??
+ new _$DebugStateChange._(
+ tabId: BuiltValueNullFieldError.checkNotNull(
+ tabId, r'DebugStateChange', 'tabId'),
+ newState: BuiltValueNullFieldError.checkNotNull(
+ newState, r'DebugStateChange', 'newState'),
+ reason: reason);
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// 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/debug_info.dart b/dwds/debug_extension_mv3/web/debug_info.dart
new file mode 100644
index 0000000..b827a41
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/debug_info.dart
@@ -0,0 +1,33 @@
+// 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 debug_info;
+
+import 'dart:convert';
+import 'dart:html';
+import 'dart:js';
+
+import 'package:dwds/data/debug_info.dart';
+import 'package:dwds/data/serializers.dart';
+import 'package:js/js.dart';
+
+void main() {
+ final debugInfoJson = _readDartDebugInfo();
+ document.dispatchEvent(CustomEvent('dart-app-ready', detail: debugInfoJson));
+}
+
+String _readDartDebugInfo() {
+ final windowContext = JsObject.fromBrowserObject(window);
+
+ return jsonEncode(serializers.serialize(DebugInfo((b) => b
+ ..appEntrypointPath = windowContext['\$dartEntrypointPath']
+ ..appId = windowContext['\$dartAppId']
+ ..appInstanceId = windowContext['\$dartAppInstanceId']
+ ..appOrigin = window.location.origin
+ ..appUrl = window.location.href
+ ..extensionUrl = windowContext['\$dartExtensionUri']
+ ..isInternalBuild = windowContext['\$isInternalBuild']
+ ..isFlutterApp = windowContext['\$isFlutterApp'])));
+}
diff --git a/dwds/debug_extension_mv3/web/debug_session.dart b/dwds/debug_extension_mv3/web/debug_session.dart
new file mode 100644
index 0000000..8c19869
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/debug_session.dart
@@ -0,0 +1,490 @@
+// 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 debug_session;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+
+import 'package:built_collection/built_collection.dart';
+import 'package:collection/collection.dart' show IterableExtension;
+import 'package:dwds/data/debug_info.dart';
+import 'package:dwds/data/devtools_request.dart';
+import 'package:dwds/data/extension_request.dart';
+import 'package:dwds/src/sockets.dart';
+// TODO(https://github.com/dart-lang/sdk/issues/49973): Use conditional imports
+// in .../utilities/batched_stream so that we don't need to import a copy.
+import 'package:dwds/src/web_utilities/batched_stream.dart';
+import 'package:js/js.dart';
+import 'package:js/js_util.dart' as js_util;
+import 'package:sse/client/sse_client.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import 'chrome_api.dart';
+import 'cross_extension_communication.dart';
+import 'data_serializers.dart';
+import 'data_types.dart';
+import 'logger.dart';
+import 'messaging.dart';
+import 'storage.dart';
+import 'utils.dart';
+import 'web_api.dart';
+
+const _notADartAppAlert = 'No Dart application detected.'
+ ' Are you trying to debug an application that includes a Chrome hosted app'
+ ' (an application listed in chrome://apps)? If so, debugging is disabled.'
+ ' You can fix this by removing the application from chrome://apps. Please'
+ ' see https://bugs.chromium.org/p/chromium/issues/detail?id=885025#c11.';
+
+const _devToolsAlreadyOpenedAlert =
+ 'DevTools is already opened on a different window.';
+
+final _debugSessions = <_DebugSession>[];
+final _tabIdToTrigger = <int, Trigger>{};
+
+enum DetachReason {
+ canceledByUser,
+ connectionErrorEvent,
+ connectionDoneEvent,
+ devToolsTabClosed,
+ navigatedAwayFromApp,
+ unknown;
+
+ factory DetachReason.fromString(String value) {
+ return DetachReason.values.byName(value);
+ }
+}
+
+enum ConnectFailureReason {
+ authentication,
+ noDartApp,
+ timeout,
+ unknown;
+
+ factory ConnectFailureReason.fromString(String value) {
+ return ConnectFailureReason.values.byName(value);
+ }
+}
+
+enum TabType {
+ dartApp,
+ devTools,
+}
+
+enum Trigger {
+ angularDartDevTools,
+ extensionPanel,
+ extensionIcon,
+}
+
+void attachDebugger(int dartAppTabId, {required Trigger trigger}) {
+ _tabIdToTrigger[dartAppTabId] = trigger;
+ _registerDebugEventListeners();
+ chrome.debugger.attach(
+ Debuggee(tabId: dartAppTabId),
+ '1.3',
+ allowInterop(
+ () => _enableExecutionContextReporting(dartAppTabId),
+ ),
+ );
+}
+
+void detachDebugger(
+ int tabId, {
+ required TabType type,
+ required DetachReason reason,
+}) async {
+ final debugSession = _debugSessionForTab(tabId, type: type);
+ if (debugSession == null) return;
+ final debuggee = Debuggee(tabId: debugSession.appTabId);
+ final detachPromise = chrome.debugger.detach(debuggee);
+ await promiseToFuture(detachPromise);
+ final error = chrome.runtime.lastError;
+ if (error != null) {
+ debugWarn(
+ 'Error detaching tab for reason: $reason. Error: ${error.message}');
+ } else {
+ _handleDebuggerDetach(debuggee, reason);
+ }
+}
+
+void _registerDebugEventListeners() {
+ chrome.debugger.onEvent.addListener(allowInterop(_onDebuggerEvent));
+ chrome.debugger.onDetach.addListener(allowInterop(
+ (source, _) => _handleDebuggerDetach(
+ source,
+ DetachReason.canceledByUser,
+ ),
+ ));
+ chrome.tabs.onRemoved.addListener(allowInterop(
+ (tabId, _) => detachDebugger(
+ tabId,
+ type: TabType.devTools,
+ reason: DetachReason.devToolsTabClosed,
+ ),
+ ));
+}
+
+_enableExecutionContextReporting(int tabId) {
+ // Runtime.enable enables reporting of execution contexts creation by means of
+ // executionContextCreated event. When the reporting gets enabled the event
+ // will be sent immediately for each existing execution context:
+ chrome.debugger.sendCommand(
+ Debuggee(tabId: tabId), 'Runtime.enable', EmptyParam(), allowInterop((_) {
+ final chromeError = chrome.runtime.lastError;
+ if (chromeError != null) {
+ final errorMessage = _translateChromeError(chromeError.message);
+ chrome.notifications.create(/*notificationId*/ null,
+ NotificationOptions(message: errorMessage), /*callback*/ null);
+ return;
+ }
+ }));
+}
+
+String _translateChromeError(String chromeErrorMessage) {
+ if (chromeErrorMessage.contains('Cannot access') ||
+ chromeErrorMessage.contains('Cannot attach')) {
+ return _notADartAppAlert;
+ }
+ return _devToolsAlreadyOpenedAlert;
+}
+
+Future<void> _onDebuggerEvent(
+ Debuggee source, String method, Object? params) async {
+ maybeForwardMessageToAngularDartDevTools(
+ method: method, params: params, tabId: source.tabId);
+
+ if (method == 'Runtime.executionContextCreated') {
+ return _maybeConnectToDwds(source.tabId, params);
+ }
+
+ return _forwardChromeDebuggerEventToDwds(source, method, params);
+}
+
+Future<void> _maybeConnectToDwds(int tabId, Object? params) async {
+ final context = json.decode(JSON.stringify(params))['context'];
+ final contextOrigin = context['origin'] as String?;
+ if (contextOrigin == null) return;
+ if (contextOrigin.contains(('chrome-extension:'))) return;
+ final debugInfo = await fetchStorageObject<DebugInfo>(
+ type: StorageObject.debugInfo,
+ tabId: tabId,
+ );
+ if (debugInfo == null) return;
+ if (contextOrigin != debugInfo.appOrigin) return;
+ final contextId = context['id'] as int;
+ final connected = await _connectToDwds(
+ dartAppContextId: contextId,
+ dartAppTabId: tabId,
+ debugInfo: debugInfo,
+ );
+ if (!connected) {
+ debugWarn('Failed to connect to DWDS for $contextOrigin.');
+ sendConnectFailureMessage(ConnectFailureReason.unknown,
+ dartAppTabId: tabId);
+ }
+}
+
+Future<bool> _connectToDwds({
+ required int dartAppContextId,
+ required int dartAppTabId,
+ required DebugInfo debugInfo,
+}) async {
+ if (debugInfo.extensionUrl == null) {
+ debugWarn('Can\'t connect to DWDS without an extension URL.');
+ return false;
+ }
+ final uri = Uri.parse(debugInfo.extensionUrl!);
+ // Start the client connection with DWDS:
+ final client = uri.isScheme('ws') || uri.isScheme('wss')
+ ? WebSocketClient(WebSocketChannel.connect(uri))
+ : SseSocketClient(SseClient(uri.toString(), debugKey: 'DebugExtension'));
+ final trigger = _tabIdToTrigger[dartAppTabId];
+ final debugSession = _DebugSession(
+ client: client,
+ appTabId: dartAppTabId,
+ trigger: trigger,
+ onIncoming: (data) => _routeDwdsEvent(data, client, dartAppTabId),
+ onDone: () {
+ detachDebugger(
+ dartAppTabId,
+ type: TabType.dartApp,
+ reason: DetachReason.connectionDoneEvent,
+ );
+ },
+ onError: (err) {
+ debugWarn('Connection error: $err', verbose: true);
+ detachDebugger(
+ dartAppTabId,
+ type: TabType.dartApp,
+ reason: DetachReason.connectionErrorEvent,
+ );
+ },
+ cancelOnError: true,
+ );
+ _debugSessions.add(debugSession);
+ final tabUrl = await _getTabUrl(dartAppTabId);
+ // Send a DevtoolsRequest to the event stream:
+ debugSession.sendEvent(DevToolsRequest((b) => b
+ ..appId = debugInfo.appId
+ ..instanceId = debugInfo.appInstanceId
+ ..contextId = dartAppContextId
+ ..tabUrl = tabUrl
+ ..uriOnly = true));
+ return true;
+}
+
+void _routeDwdsEvent(String eventData, SocketClient client, int tabId) {
+ final message = serializers.deserialize(jsonDecode(eventData));
+ if (message is ExtensionRequest) {
+ _forwardDwdsEventToChromeDebugger(message, client, tabId);
+ } else if (message is ExtensionEvent) {
+ maybeForwardMessageToAngularDartDevTools(
+ method: message.method, params: message.params, tabId: tabId);
+ if (message.method == 'dwds.devtoolsUri') {
+ _openDevTools(message.params, dartAppTabId: tabId);
+ }
+ if (message.method == 'dwds.encodedUri') {
+ setStorageObject(
+ type: StorageObject.encodedUri,
+ value: message.params,
+ tabId: tabId,
+ );
+ }
+ }
+}
+
+void _forwardDwdsEventToChromeDebugger(
+ ExtensionRequest message, SocketClient client, int tabId) {
+ final messageParams = message.commandParams ?? '{}';
+ final params = BuiltMap<String, Object>(json.decode(messageParams)).toMap();
+ chrome.debugger.sendCommand(
+ Debuggee(tabId: tabId), message.command, js_util.jsify(params),
+ allowInterop(([e]) {
+ // No arguments indicate that an error occurred.
+ if (e == null) {
+ client.sink
+ .add(jsonEncode(serializers.serialize(ExtensionResponse((b) => b
+ ..id = message.id
+ ..success = false
+ ..result = JSON.stringify(chrome.runtime.lastError)))));
+ } else {
+ client.sink
+ .add(jsonEncode(serializers.serialize(ExtensionResponse((b) => b
+ ..id = message.id
+ ..success = true
+ ..result = JSON.stringify(e)))));
+ }
+ }));
+}
+
+void _forwardChromeDebuggerEventToDwds(
+ Debuggee source, String method, dynamic params) {
+ final debugSession = _debugSessions
+ .firstWhereOrNull((session) => session.appTabId == source.tabId);
+ if (debugSession == null) return;
+ final event = _extensionEventFor(method, params);
+ if (method == 'Debugger.scriptParsed') {
+ debugSession.sendBatchedEvent(event);
+ } else {
+ debugSession.sendEvent(event);
+ }
+}
+
+void _openDevTools(String devToolsUri, {required int dartAppTabId}) async {
+ if (devToolsUri.isEmpty) {
+ debugError('DevTools URI is empty.');
+ return;
+ }
+ final debugSession = _debugSessionForTab(dartAppTabId, type: TabType.dartApp);
+ if (debugSession == null) {
+ debugError('Debug session not found.');
+ return;
+ }
+ // Save the DevTools URI so that the extension panels have access to it:
+ await setStorageObject(
+ type: StorageObject.devToolsUri,
+ value: devToolsUri,
+ tabId: dartAppTabId,
+ );
+ // Open a separate tab / window if triggered through the extension icon or
+ // through AngularDart DevTools:
+ if (debugSession.trigger == Trigger.extensionIcon ||
+ debugSession.trigger == Trigger.angularDartDevTools) {
+ final devToolsOpener = await fetchStorageObject<DevToolsOpener>(
+ type: StorageObject.devToolsOpener);
+ final devToolsTab = await createTab(
+ devToolsUri,
+ inNewWindow: devToolsOpener?.newWindow ?? false,
+ );
+ debugSession.devToolsTabId = devToolsTab.id;
+ }
+}
+
+void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
+ final tabId = source.tabId;
+ debugLog(
+ 'Debugger detached due to: $reason',
+ verbose: true,
+ prefix: '$tabId',
+ );
+ final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
+ if (debugSession == null) return;
+ debugLog('Removing debug session...');
+ _removeDebugSession(debugSession);
+ // Notify the extension panels that the debug session has ended:
+ _sendStopDebuggingMessage(reason, dartAppTabId: source.tabId);
+ // Remove the DevTools URI and encoded URI from storage:
+ await removeStorageObject(type: StorageObject.devToolsUri, tabId: tabId);
+ await removeStorageObject(type: StorageObject.encodedUri, tabId: tabId);
+ // Maybe close the associated DevTools tab as well:
+ final devToolsTabId = debugSession.devToolsTabId;
+ if (devToolsTabId == null) return;
+ final devToolsTab = await getTab(devToolsTabId);
+ if (devToolsTab != null) {
+ debugLog('Closing DevTools tab...');
+ chrome.tabs.remove(devToolsTabId);
+ }
+}
+
+void _removeDebugSession(_DebugSession debugSession) {
+ // Note: package:sse will try to keep the connection alive, even after the
+ // client has been closed. Therefore the extension sends an event to notify
+ // DWDS that we should close the connection, instead of relying on the done
+ // event sent when the client is closed. See details:
+ // https://github.com/dart-lang/webdev/pull/1595#issuecomment-1116773378
+ final event =
+ _extensionEventFor('DebugExtension.detached', js_util.jsify({}));
+ debugSession.sendEvent(event);
+ debugSession.close();
+ final removed = _debugSessions.remove(debugSession);
+ if (!removed) {
+ debugWarn('Could not remove debug session.');
+ }
+}
+
+void sendConnectFailureMessage(ConnectFailureReason reason,
+ {required int dartAppTabId}) async {
+ final json = jsonEncode(serializers.serialize(ConnectFailure((b) => b
+ ..tabId = dartAppTabId
+ ..reason = reason.name)));
+ sendRuntimeMessage(
+ type: MessageType.connectFailure,
+ body: json,
+ sender: Script.background,
+ recipient: Script.debuggerPanel);
+}
+
+void _sendStopDebuggingMessage(DetachReason reason,
+ {required int dartAppTabId}) async {
+ final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
+ ..tabId = dartAppTabId
+ ..reason = reason.name
+ ..newState = DebugStateChange.stopDebugging)));
+ sendRuntimeMessage(
+ type: MessageType.debugStateChange,
+ body: json,
+ sender: Script.background,
+ recipient: Script.debuggerPanel);
+}
+
+_DebugSession? _debugSessionForTab(tabId, {required TabType type}) {
+ switch (type) {
+ case TabType.dartApp:
+ return _debugSessions
+ .firstWhereOrNull((session) => session.appTabId == tabId);
+ case TabType.devTools:
+ return _debugSessions
+ .firstWhereOrNull((session) => session.devToolsTabId == tabId);
+ }
+}
+
+/// Construct an [ExtensionEvent] from [method] and [params].
+ExtensionEvent _extensionEventFor(String method, dynamic params) {
+ return ExtensionEvent((b) => b
+ ..params = jsonEncode(json.decode(JSON.stringify(params)))
+ ..method = jsonEncode(method));
+}
+
+Future<String> _getTabUrl(int tabId) async {
+ final tab = await getTab(tabId);
+ return tab?.url ?? '';
+}
+
+@JS()
+@anonymous
+class EmptyParam {
+ external factory EmptyParam();
+}
+
+class _DebugSession {
+ // The tab ID that contains the running Dart application.
+ final int appTabId;
+
+ // What triggered the debug session (debugger panel, extension icon, etc.)
+ final Trigger? trigger;
+
+ // Socket client for communication with dwds extension backend.
+ late final SocketClient _socketClient;
+
+ // How often to send batched events.
+ static const int _batchDelayMilliseconds = 1000;
+
+ // The tab ID that contains the corresponding Dart DevTools, if it exists.
+ int? devToolsTabId;
+
+ // Collect events into batches to be send periodically to the server.
+ final _batchController =
+ BatchedStreamController<ExtensionEvent>(delay: _batchDelayMilliseconds);
+ late final StreamSubscription<List<ExtensionEvent>> _batchSubscription;
+
+ _DebugSession({
+ required client,
+ required this.appTabId,
+ required this.trigger,
+ required void Function(String data) onIncoming,
+ required void Function() onDone,
+ required void Function(dynamic error) onError,
+ required bool cancelOnError,
+ }) : _socketClient = client {
+ // Collect extension events and send them periodically to the server.
+ _batchSubscription = _batchController.stream.listen((events) {
+ _socketClient.sink.add(jsonEncode(serializers.serialize(BatchedEvents(
+ (b) => b.events = ListBuilder<ExtensionEvent>(events)))));
+ });
+ // Listen for incoming events:
+ _socketClient.stream.listen(
+ onIncoming,
+ onDone: onDone,
+ onError: onError,
+ cancelOnError: cancelOnError,
+ );
+ }
+
+ set socketClient(SocketClient client) {
+ _socketClient = client;
+
+ // Collect extension events and send them periodically to the server.
+ _batchSubscription = _batchController.stream.listen((events) {
+ _socketClient.sink.add(jsonEncode(serializers.serialize(BatchedEvents(
+ (b) => b.events = ListBuilder<ExtensionEvent>(events)))));
+ });
+ }
+
+ void sendEvent<T>(T event) {
+ _socketClient.sink.add(jsonEncode(serializers.serialize(event)));
+ }
+
+ void sendBatchedEvent(ExtensionEvent event) {
+ _batchController.sink.add(event);
+ }
+
+ void close() {
+ _socketClient.close();
+ _batchSubscription.cancel();
+ _batchController.close();
+ }
+}
diff --git a/dwds/debug_extension_mv3/web/detector.dart b/dwds/debug_extension_mv3/web/detector.dart
index d30964e..7689983 100644
--- a/dwds/debug_extension_mv3/web/detector.dart
+++ b/dwds/debug_extension_mv3/web/detector.dart
@@ -6,9 +6,11 @@
library detector;
import 'dart:html';
+import 'dart:js_util';
import 'package:js/js.dart';
import 'chrome_api.dart';
+import 'logger.dart';
import 'messaging.dart';
void main() {
@@ -20,26 +22,38 @@
}
void _onDartAppReadyEvent(Event event) {
- _sendMessageToBackgroundScript(
- type: MessageType.dartAppReady,
- body: 'Dart app ready!',
- );
+ final debugInfo = getProperty(event, 'detail') as String?;
+ if (debugInfo == null) {
+ debugWarn(
+ 'No debug info sent with ready event, instead reading from Window.');
+ _injectDebugInfoScript();
+ } else {
+ _sendMessageToBackgroundScript(
+ type: MessageType.debugInfo,
+ body: debugInfo,
+ );
+ }
+}
+
+// TODO(elliette): Remove once DWDS 17.0.0 is in Flutter stable. If we are on an
+// older version of DWDS, then the debug info is not sent along with the ready
+// event. Therefore we must read it from the Window object, which is slower.
+void _injectDebugInfoScript() {
+ final script = document.createElement('script');
+ final scriptSrc = chrome.runtime.getURL('debug_info.dart.js');
+ script.setAttribute('src', scriptSrc);
+ script.setAttribute('defer', true);
+ document.head?.append(script);
}
void _sendMessageToBackgroundScript({
required MessageType type,
required String body,
}) {
- final message = Message(
- to: Script.background,
- from: Script.detector,
+ sendRuntimeMessage(
type: type,
body: body,
- );
- chrome.runtime.sendMessage(
- /*id*/ null,
- message.toJSON(),
- /*options*/ null,
- /*callback*/ null,
+ sender: Script.detector,
+ recipient: Script.background,
);
}
diff --git a/dwds/debug_extension_mv3/web/devtools.dart b/dwds/debug_extension_mv3/web/devtools.dart
new file mode 100644
index 0000000..a8b37b3
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/devtools.dart
@@ -0,0 +1,72 @@
+// 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 devtools;
+
+import 'dart:html';
+import 'package:js/js.dart';
+import 'package:dwds/data/debug_info.dart';
+
+import 'chrome_api.dart';
+import 'logger.dart';
+import 'storage.dart';
+import 'utils.dart';
+
+bool panelsExist = false;
+
+void main() async {
+ _registerListeners();
+ _maybeCreatePanels();
+}
+
+void _registerListeners() {
+ chrome.storage.onChanged.addListener(allowInterop((
+ Object _,
+ String storageArea,
+ ) {
+ if (storageArea != 'session') return;
+ _maybeCreatePanels();
+ }));
+}
+
+void _maybeCreatePanels() async {
+ if (panelsExist) return;
+ final tabId = chrome.devtools.inspectedWindow.tabId;
+ final debugInfo = await fetchStorageObject<DebugInfo>(
+ type: StorageObject.debugInfo,
+ tabId: tabId,
+ );
+ if (debugInfo == null) return;
+ final isInternalBuild = debugInfo.isInternalBuild ?? false;
+ if (!isInternalBuild) return;
+ // Create a Debugger panel for all internal apps:
+ chrome.devtools.panels.create(
+ isDevMode() ? '[DEV] Dart Debugger' : 'Dart Debugger',
+ '',
+ 'static_assets/debugger_panel.html',
+ allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
+ );
+ // Create an inspector panel for internal Flutter apps:
+ final isFlutterApp = debugInfo.isFlutterApp ?? false;
+ if (isFlutterApp) {
+ chrome.devtools.panels.create(
+ isDevMode() ? '[DEV] Flutter Inspector' : 'Flutter Inspector',
+ '',
+ 'static_assets/inspector_panel.html',
+ allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
+ );
+ }
+ panelsExist = true;
+}
+
+void _onPanelAdded(ExtensionPanel panel, DebugInfo debugInfo) {
+ panel.onShown.addListener(allowInterop((Window window) {
+ if (window.origin != debugInfo.appOrigin) {
+ debugWarn('Page at ${window.origin} is no longer a Dart app.');
+ // TODO(elliette): Display banner that panel is not applicable. See:
+ // https://stackoverflow.com/questions/18927147/how-to-close-destroy-chrome-devtools-extensionpanel-programmatically
+ }
+ }));
+}
diff --git a/dwds/debug_extension_mv3/web/lifeline_connection.dart b/dwds/debug_extension_mv3/web/lifeline_connection.dart
new file mode 100644
index 0000000..3096a95
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/lifeline_connection.dart
@@ -0,0 +1,26 @@
+// 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 'chrome_api.dart';
+import 'logger.dart';
+
+void main() async {
+ _connectToLifelinePort();
+}
+
+void _connectToLifelinePort() {
+ debugLog(
+ 'Connecting to lifeline port at ${_currentTime()}.',
+ prefix: 'Dart Debug Extension',
+ );
+ chrome.runtime.connect(
+ /*extensionId=*/ null,
+ ConnectInfo(name: 'keepAlive'),
+ );
+}
+
+String _currentTime() {
+ final date = DateTime.now();
+ return '${date.hour}:${date.minute}::${date.second}';
+}
diff --git a/dwds/debug_extension_mv3/web/lifeline_ports.dart b/dwds/debug_extension_mv3/web/lifeline_ports.dart
new file mode 100644
index 0000000..23f57ed
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/lifeline_ports.dart
@@ -0,0 +1,107 @@
+// 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.');
+ }
+}
diff --git a/dwds/debug_extension_mv3/web/logger.dart b/dwds/debug_extension_mv3/web/logger.dart
new file mode 100644
index 0000000..d0599ec
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/logger.dart
@@ -0,0 +1,77 @@
+// 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 logger;
+
+import 'package:js/js.dart';
+
+import 'utils.dart';
+
+enum _LogLevel {
+ info,
+ warn,
+ error,
+}
+
+debugLog(
+ String msg, {
+ String? prefix,
+ bool verbose = false,
+}) {
+ _log(msg, prefix: prefix, verbose: verbose);
+}
+
+debugWarn(
+ String msg, {
+ String? prefix,
+ bool verbose = false,
+}) {
+ _log(msg, prefix: prefix, level: _LogLevel.warn, verbose: verbose);
+}
+
+debugError(
+ String msg, {
+ String? prefix,
+ bool verbose = false,
+}) {
+ _log(msg, prefix: prefix, level: _LogLevel.error, verbose: verbose);
+}
+
+void _log(
+ String msg, {
+ bool verbose = false,
+ _LogLevel? level,
+ String? prefix,
+}) {
+ if (!verbose && !isDevMode()) return;
+ final logMsg = prefix != null ? '[$prefix] $msg' : msg;
+ final logLevel = level ?? _LogLevel.info;
+ switch (logLevel) {
+ case _LogLevel.error:
+ _console.error(logMsg);
+ break;
+ case _LogLevel.warn:
+ _console.warn(logMsg);
+ break;
+ case _LogLevel.info:
+ _console.log(logMsg);
+ }
+}
+
+@JS('console')
+external _Console get _console;
+
+@JS()
+@anonymous
+class _Console {
+ external void log(String header,
+ [String style1, String style2, String style3]);
+
+ external void warn(String header,
+ [String style1, String style2, String style3]);
+
+ external void error(String header,
+ [String style1, String style2, String style3]);
+}
diff --git a/dwds/debug_extension_mv3/web/manifest.json b/dwds/debug_extension_mv3/web/manifest.json
index 2492e46..d16a32e 100644
--- a/dwds/debug_extension_mv3/web/manifest.json
+++ b/dwds/debug_extension_mv3/web/manifest.json
@@ -1,41 +1,38 @@
{
- "name": "[MV3] Dart Debug Extension",
- "version": "1.0",
- "manifest_version": 3,
- "action": {
- "default_icon": "dart_dev.png"
- },
- "permissions": [
- "scripting",
- "tabs",
- "debugger"
- ],
- "host_permissions": [
- "<all_urls>"
- ],
- "web_accessible_resources": [
- {
- "matches": [
- "<all_urls>"
- ],
- "resources": [
- "iframe.html",
- "iframe_injector.dart.js"
- ]
- }
- ],
- "background": {
- "service_worker": "background.dart.js"
- },
- "content_scripts": [
- {
- "matches": [
- "<all_urls>"
- ],
- "js": [
- "detector.dart.js"
- ],
- "run_at": "document_end"
- }
- ]
-}
\ No newline at end of file
+ "name": "Dart Debug Extension",
+ "version": "1.31",
+ "manifest_version": 3,
+ "devtools_page": "static_assets/devtools.html",
+ "action": {
+ "default_icon": "static_assets/dart_dev.png"
+ },
+ "externally_connectable": {
+ "ids": ["nbkbficgbembimioedhceniahniffgpl"]
+ },
+ "permissions": [
+ "debugger",
+ "notifications",
+ "scripting",
+ "storage",
+ "tabs",
+ "webNavigation"
+ ],
+ "host_permissions": ["<all_urls>"],
+ "background": {
+ "service_worker": "background.dart.js"
+ },
+ "content_scripts": [
+ {
+ "matches": ["<all_urls>"],
+ "js": ["detector.dart.js"],
+ "run_at": "document_end"
+ }
+ ],
+ "web_accessible_resources": [
+ {
+ "matches": ["<all_urls>"],
+ "resources": ["debug_info.dart.js"]
+ }
+ ],
+ "options_page": "static_assets/settings.html"
+}
diff --git a/dwds/debug_extension_mv3/web/messaging.dart b/dwds/debug_extension_mv3/web/messaging.dart
index 08128a1..5aa551a 100644
--- a/dwds/debug_extension_mv3/web/messaging.dart
+++ b/dwds/debug_extension_mv3/web/messaging.dart
@@ -9,10 +9,13 @@
import 'package:js/js.dart';
-import 'web_api.dart';
+import 'data_serializers.dart';
+import 'chrome_api.dart';
+import 'logger.dart';
enum Script {
background,
+ debuggerPanel,
detector;
factory Script.fromString(String value) {
@@ -21,7 +24,10 @@
}
enum MessageType {
- dartAppReady;
+ connectFailure,
+ debugInfo,
+ debugStateChange,
+ devToolsUrl;
factory MessageType.fromString(String value) {
return MessageType.values.byName(value);
@@ -57,34 +63,53 @@
String toJSON() {
return jsonEncode({
- 'type': type.name,
'to': to.name,
'from': from.name,
- 'encodedBody': body,
+ 'type': type.name,
+ 'body': body,
if (error != null) 'error': error,
});
}
}
-void interceptMessage({
+void interceptMessage<T>({
required String? message,
required MessageType expectedType,
required Script expectedSender,
required Script expectedRecipient,
- required void Function(String message) messageHandler,
+ required void Function(T message) messageHandler,
}) {
+ if (message == null) return;
try {
- if (message == null) return;
final decodedMessage = Message.fromJSON(message);
if (decodedMessage.type != expectedType ||
decodedMessage.to != expectedRecipient ||
decodedMessage.from != expectedSender) {
return;
}
- messageHandler(decodedMessage.body);
+ messageHandler(
+ serializers.deserialize(jsonDecode(decodedMessage.body)) as T);
} catch (error) {
- console.warn(
- 'Error intercepting $expectedType message from $expectedSender to $expectedRecipient: $error');
- return;
+ debugError(
+ 'Error intercepting $expectedType from $expectedSender to $expectedRecipient: $error');
}
}
+
+void sendRuntimeMessage(
+ {required MessageType type,
+ required String body,
+ required Script sender,
+ required Script recipient}) {
+ final message = Message(
+ to: recipient,
+ from: sender,
+ type: type,
+ body: body,
+ );
+ chrome.runtime.sendMessage(
+ /*id*/ null,
+ message.toJSON(),
+ /*options*/ null,
+ /*callback*/ null,
+ );
+}
diff --git a/dwds/debug_extension_mv3/web/panel.dart b/dwds/debug_extension_mv3/web/panel.dart
new file mode 100644
index 0000000..d2ed9a6
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/panel.dart
@@ -0,0 +1,275 @@
+// 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 panel;
+
+import 'dart:convert';
+import 'dart:html';
+
+import 'package:dwds/data/debug_info.dart';
+import 'package:js/js.dart';
+
+import 'chrome_api.dart';
+import 'data_serializers.dart';
+import 'data_types.dart';
+import 'debug_session.dart';
+import 'logger.dart';
+import 'messaging.dart';
+import 'storage.dart';
+
+bool connecting = false;
+String devToolsBackgroundColor = darkColor;
+bool isDartApp = true;
+
+const bugLinkId = 'bugLink';
+const darkColor = '202125';
+const darkThemeClass = 'dark-theme';
+const hiddenClass = 'hidden';
+const iframeContainerId = 'iframeContainer';
+const landingPageId = 'landingPage';
+const launchDebugConnectionButtonId = 'launchDebugConnectionButton';
+const lightColor = 'ffffff';
+const lightThemeClass = 'light-theme';
+const loadingSpinnerId = 'loadingSpinner';
+const panelAttribute = 'data-panel';
+const panelBodyId = 'panelBody';
+const showClass = 'show';
+const warningBannerId = 'warningBanner';
+const warningMsgId = 'warningMsg';
+
+int get _tabId => chrome.devtools.inspectedWindow.tabId;
+
+void main() {
+ _registerListeners();
+ _setColorThemeToMatchChromeDevTools();
+ _maybeUpdateFileABugLink();
+}
+
+void _registerListeners() {
+ chrome.storage.onChanged.addListener(allowInterop(_handleStorageChanges));
+ chrome.runtime.onMessage.addListener(allowInterop(_handleRuntimeMessages));
+ final launchDebugConnectionButton =
+ document.getElementById(launchDebugConnectionButtonId) as ButtonElement;
+ launchDebugConnectionButton.addEventListener('click', _launchDebugConnection);
+
+ _maybeInjectDevToolsIframe();
+}
+
+void _handleRuntimeMessages(
+ dynamic jsRequest, MessageSender sender, Function sendResponse) async {
+ if (jsRequest is! String) return;
+
+ interceptMessage<DebugStateChange>(
+ message: jsRequest,
+ expectedType: MessageType.debugStateChange,
+ expectedSender: Script.background,
+ expectedRecipient: Script.debuggerPanel,
+ messageHandler: (DebugStateChange debugStateChange) async {
+ if (debugStateChange.tabId != _tabId) {
+ debugWarn(
+ 'Received debug state change request, but Dart app tab does not match current tab.');
+ return;
+ }
+ if (debugStateChange.newState == DebugStateChange.stopDebugging) {
+ _handleDebugConnectionLost(debugStateChange.reason);
+ }
+ });
+
+ interceptMessage<ConnectFailure>(
+ message: jsRequest,
+ expectedType: MessageType.connectFailure,
+ expectedSender: Script.background,
+ expectedRecipient: Script.debuggerPanel,
+ messageHandler: (ConnectFailure connectFailure) async {
+ debugLog(
+ 'Received connect failure for ${connectFailure.tabId} vs $_tabId');
+ if (connectFailure.tabId != _tabId) {
+ return;
+ }
+ connecting = false;
+ _handleConnectFailure(
+ ConnectFailureReason.fromString(connectFailure.reason ?? 'unknown'),
+ );
+ });
+}
+
+void _handleStorageChanges(Object storageObj, String storageArea) {
+ // We only care about session storage objects:
+ if (storageArea != 'session') return;
+
+ interceptStorageChange<DebugInfo>(
+ storageObj: storageObj,
+ expectedType: StorageObject.debugInfo,
+ tabId: _tabId,
+ changeHandler: _handleDebugInfoChanges,
+ );
+ interceptStorageChange<String>(
+ storageObj: storageObj,
+ expectedType: StorageObject.devToolsUri,
+ tabId: _tabId,
+ changeHandler: _handleDevToolsUriChanges,
+ );
+}
+
+void _handleDebugInfoChanges(DebugInfo? debugInfo) async {
+ if (debugInfo == null && isDartApp) {
+ isDartApp = false;
+ _showWarningBanner('Dart app is no longer open.');
+ }
+ if (debugInfo != null && !isDartApp) {
+ isDartApp = true;
+ _hideWarningBanner();
+ }
+}
+
+void _handleDevToolsUriChanges(String? devToolsUri) async {
+ if (devToolsUri != null) {
+ _injectDevToolsIframe(devToolsUri);
+ }
+}
+
+void _maybeUpdateFileABugLink() async {
+ final debugInfo = await fetchStorageObject<DebugInfo>(
+ type: StorageObject.debugInfo,
+ tabId: _tabId,
+ );
+ final isInternal = debugInfo?.isInternalBuild ?? false;
+ if (isInternal) {
+ final bugLink = document.getElementById(bugLinkId);
+ if (bugLink == null) return;
+ bugLink.setAttribute(
+ 'href', 'http://b/issues/new?component=775375&template=1369639');
+ }
+}
+
+void _setColorThemeToMatchChromeDevTools() async {
+ final chromeTheme = chrome.devtools.panels.themeName;
+ final panelBody = document.getElementById(panelBodyId);
+ if (chromeTheme == 'dark') {
+ devToolsBackgroundColor = darkColor;
+ _updateColorThemeForElement(panelBody, isDarkTheme: true);
+ } else {
+ devToolsBackgroundColor = lightColor;
+ _updateColorThemeForElement(panelBody, isDarkTheme: false);
+ }
+}
+
+void _updateColorThemeForElement(
+ Element? element, {
+ required bool isDarkTheme,
+}) {
+ if (element == null) return;
+ final classToRemove = isDarkTheme ? lightThemeClass : darkThemeClass;
+ if (element.classes.contains(classToRemove)) {
+ element.classes.remove(classToRemove);
+ final classToAdd = isDarkTheme ? darkThemeClass : lightThemeClass;
+ element.classes.add(classToAdd);
+ }
+}
+
+void _handleDebugConnectionLost(String? reason) {
+ final detachReason = DetachReason.fromString(reason ?? 'unknown');
+ _removeDevToolsIframe();
+ _updateElementVisibility(landingPageId, visible: true);
+ if (detachReason != DetachReason.canceledByUser) {
+ _showWarningBanner('Lost connection.');
+ }
+}
+
+void _handleConnectFailure(ConnectFailureReason reason) {
+ switch (reason) {
+ case ConnectFailureReason.authentication:
+ _showWarningBanner('Please re-authenticate and try again.');
+ break;
+ case ConnectFailureReason.noDartApp:
+ _showWarningBanner('No Dart app detected.');
+ break;
+ case ConnectFailureReason.timeout:
+ _showWarningBanner('Connection timed out.');
+ break;
+ default:
+ _showWarningBanner('Failed to connect, please try again.');
+ }
+ _updateElementVisibility(launchDebugConnectionButtonId, visible: true);
+ _updateElementVisibility(loadingSpinnerId, visible: false);
+}
+
+void _showWarningBanner(String message) {
+ final warningMsg = document.getElementById(warningMsgId);
+ warningMsg?.setInnerHtml(message);
+ print(warningMsg);
+ final warningBanner = document.getElementById(warningBannerId);
+ warningBanner?.classes.add(showClass);
+}
+
+void _hideWarningBanner() {
+ final warningBanner = document.getElementById(warningBannerId);
+ warningBanner?.classes.remove(showClass);
+}
+
+void _launchDebugConnection(Event _) async {
+ _updateElementVisibility(launchDebugConnectionButtonId, visible: false);
+ _updateElementVisibility(loadingSpinnerId, visible: true);
+ final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
+ ..tabId = _tabId
+ ..newState = DebugStateChange.startDebugging)));
+ sendRuntimeMessage(
+ type: MessageType.debugStateChange,
+ body: json,
+ sender: Script.debuggerPanel,
+ recipient: Script.background);
+ _maybeHandleConnectionTimeout();
+}
+
+void _maybeHandleConnectionTimeout() async {
+ connecting = true;
+ await Future.delayed(Duration(seconds: 10));
+ if (connecting == true) {
+ _handleConnectFailure(ConnectFailureReason.timeout);
+ }
+}
+
+void _maybeInjectDevToolsIframe() async {
+ final devToolsUri = await fetchStorageObject<String>(
+ type: StorageObject.devToolsUri, tabId: _tabId);
+ if (devToolsUri != null) {
+ _injectDevToolsIframe(devToolsUri);
+ }
+}
+
+void _injectDevToolsIframe(String devToolsUri) {
+ connecting = false;
+ final iframeContainer = document.getElementById(iframeContainerId);
+ if (iframeContainer == null) return;
+ final panelBody = document.getElementById(panelBodyId);
+ final panelType = panelBody?.getAttribute(panelAttribute) ?? 'debugger';
+ final iframe = document.createElement('iframe');
+ iframe.setAttribute(
+ 'src',
+ '$devToolsUri&embed=true&page=$panelType&backgroundColor=$devToolsBackgroundColor',
+ );
+ _hideWarningBanner();
+ _updateElementVisibility(landingPageId, visible: false);
+ _updateElementVisibility(loadingSpinnerId, visible: false);
+ _updateElementVisibility(launchDebugConnectionButtonId, visible: true);
+ iframeContainer.append(iframe);
+}
+
+void _removeDevToolsIframe() {
+ final iframeContainer = document.getElementById(iframeContainerId);
+ final iframe = iframeContainer?.firstChild;
+ if (iframe == null) return;
+ iframe.remove();
+}
+
+void _updateElementVisibility(String elementId, {required bool visible}) {
+ final element = document.getElementById(elementId);
+ if (element == null) return;
+ if (visible) {
+ element.classes.remove(hiddenClass);
+ } else {
+ element.classes.add(hiddenClass);
+ }
+}
diff --git a/dwds/debug_extension_mv3/web/settings.dart b/dwds/debug_extension_mv3/web/settings.dart
new file mode 100644
index 0000000..450b190
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/settings.dart
@@ -0,0 +1,64 @@
+// 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 settings;
+
+import 'dart:async';
+import 'dart:html';
+
+import 'package:js/js.dart';
+
+import 'data_types.dart';
+import 'storage.dart';
+
+void main() {
+ _registerListeners();
+}
+
+void _registerListeners() {
+ document.addEventListener('DOMContentLoaded', _updateSettingsFromStorage);
+ final saveButton = document.getElementById('saveButton') as ButtonElement;
+ saveButton.addEventListener('click', _saveSettingsToStorage);
+}
+
+void _updateSettingsFromStorage(Event _) async {
+ final devToolsOpener = await fetchStorageObject<DevToolsOpener>(
+ type: StorageObject.devToolsOpener);
+ final openInNewWindow = devToolsOpener?.newWindow ?? false;
+ _getRadioButton('windowOpt').checked = openInNewWindow;
+ _getRadioButton('tabOpt').checked = !openInNewWindow;
+}
+
+void _saveSettingsToStorage(Event event) async {
+ event.preventDefault();
+ _maybeHideSavedMsg();
+ final form = document.querySelector("form") as FormElement;
+ final data = FormData(form);
+ final devToolsOpenerValue = data.get('devToolsOpener') as String;
+ await setStorageObject<DevToolsOpener>(
+ type: StorageObject.devToolsOpener,
+ value: DevToolsOpener(
+ (b) => b..newWindow = devToolsOpenerValue == 'window'));
+ _showSavedMsg();
+}
+
+void _showSavedMsg() {
+ final snackbar = document.getElementById('savedSnackbar');
+ if (snackbar == null) return;
+ snackbar.classes.add('show');
+ Timer(Duration(seconds: 3), () {
+ _maybeHideSavedMsg();
+ });
+}
+
+void _maybeHideSavedMsg() {
+ final snackbar = document.getElementById('savedSnackbar');
+ if (snackbar == null) return;
+ snackbar.classes.remove('show');
+}
+
+RadioButtonInputElement _getRadioButton(String id) {
+ return document.getElementById(id) as RadioButtonInputElement;
+}
diff --git a/dwds/debug_extension_mv3/web/dart.png b/dwds/debug_extension_mv3/web/static_assets/dart.png
similarity index 100%
rename from dwds/debug_extension_mv3/web/dart.png
rename to dwds/debug_extension_mv3/web/static_assets/dart.png
Binary files differ
diff --git a/dwds/debug_extension_mv3/web/dart_dev.png b/dwds/debug_extension_mv3/web/static_assets/dart_dev.png
similarity index 100%
rename from dwds/debug_extension_mv3/web/dart_dev.png
rename to dwds/debug_extension_mv3/web/static_assets/dart_dev.png
Binary files differ
diff --git a/dwds/debug_extension_mv3/web/static_assets/dart_grey.png b/dwds/debug_extension_mv3/web/static_assets/dart_grey.png
new file mode 100644
index 0000000..3afba3f
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/static_assets/dart_grey.png
Binary files differ
diff --git a/dwds/debug_extension_mv3/web/static_assets/debugger_settings.png b/dwds/debug_extension_mv3/web/static_assets/debugger_settings.png
new file mode 100644
index 0000000..39a0cae
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/static_assets/debugger_settings.png
Binary files differ
diff --git a/dwds/debug_extension_mv3/web/static_assets/inspect_widget.png b/dwds/debug_extension_mv3/web/static_assets/inspect_widget.png
new file mode 100644
index 0000000..98f10cb
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/static_assets/inspect_widget.png
Binary files differ
diff --git a/dwds/debug_extension_mv3/web/storage.dart b/dwds/debug_extension_mv3/web/storage.dart
new file mode 100644
index 0000000..d748027
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/storage.dart
@@ -0,0 +1,142 @@
+// 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 storage;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:js_util';
+
+import 'package:js/js.dart';
+
+import 'chrome_api.dart';
+import 'data_serializers.dart';
+import 'logger.dart';
+
+enum StorageObject {
+ debugInfo,
+ devToolsOpener,
+ devToolsUri,
+ encodedUri;
+
+ Persistance get persistance {
+ switch (this) {
+ case StorageObject.debugInfo:
+ return Persistance.sessionOnly;
+ case StorageObject.devToolsOpener:
+ return Persistance.acrossSessions;
+ case StorageObject.devToolsUri:
+ return Persistance.sessionOnly;
+ case StorageObject.encodedUri:
+ return Persistance.sessionOnly;
+ }
+ }
+}
+
+enum Persistance {
+ sessionOnly,
+ acrossSessions;
+}
+
+Future<bool> setStorageObject<T>({
+ required StorageObject type,
+ required T value,
+ int? tabId,
+ void Function()? callback,
+}) {
+ final storageKey = _createStorageKey(type, tabId);
+ final json =
+ value is String ? value : jsonEncode(serializers.serialize(value));
+ final storageObj = <String, String>{storageKey: json};
+ final completer = Completer<bool>();
+ final storageArea = _getStorageArea(type.persistance);
+ storageArea.set(jsify(storageObj), allowInterop(() {
+ if (callback != null) {
+ callback();
+ }
+ debugLog('Set: $json', prefix: storageKey);
+ completer.complete(true);
+ }));
+ return completer.future;
+}
+
+Future<T?> fetchStorageObject<T>({required StorageObject type, int? tabId}) {
+ final storageKey = _createStorageKey(type, tabId);
+ final completer = Completer<T?>();
+ final storageArea = _getStorageArea(type.persistance);
+ storageArea.get([storageKey], allowInterop((Object? storageObj) {
+ if (storageObj == null) {
+ debugWarn('Does not exist.', prefix: storageKey);
+ completer.complete(null);
+ return;
+ }
+ final json = getProperty(storageObj, storageKey) as String?;
+ if (json == null) {
+ debugWarn('Does not exist.', prefix: storageKey);
+ completer.complete(null);
+ } else {
+ debugLog('Fetched: $json', prefix: storageKey);
+ if (T == String) {
+ completer.complete(json as T);
+ } else {
+ final value = serializers.deserialize(jsonDecode(json)) as T;
+ completer.complete(value);
+ }
+ }
+ }));
+ return completer.future;
+}
+
+Future<bool> removeStorageObject<T>({required StorageObject type, int? tabId}) {
+ final storageKey = _createStorageKey(type, tabId);
+ final completer = Completer<bool>();
+ final storageArea = _getStorageArea(type.persistance);
+ storageArea.remove([storageKey], allowInterop(() {
+ debugLog('Removed object.', prefix: storageKey);
+ completer.complete(true);
+ }));
+ return completer.future;
+}
+
+void interceptStorageChange<T>({
+ required Object storageObj,
+ required StorageObject expectedType,
+ required void Function(T? storageObj) changeHandler,
+ int? tabId,
+}) {
+ try {
+ final expectedStorageKey = _createStorageKey(expectedType, tabId);
+ final isExpected = hasProperty(storageObj, expectedStorageKey);
+ if (!isExpected) return;
+
+ final objProp = getProperty(storageObj, expectedStorageKey);
+ final json = getProperty(objProp, 'newValue') as String?;
+ T? decodedObj;
+ if (json == null || T == String) {
+ decodedObj = json as T?;
+ } else {
+ decodedObj = serializers.deserialize(jsonDecode(json)) as T?;
+ }
+ debugLog('Intercepted $expectedStorageKey change: $json');
+ return changeHandler(decodedObj);
+ } catch (error) {
+ debugError(
+ 'Error intercepting storage object with type $expectedType: $error');
+ }
+}
+
+StorageArea _getStorageArea(Persistance persistance) {
+ switch (persistance) {
+ case Persistance.acrossSessions:
+ return chrome.storage.local;
+ case Persistance.sessionOnly:
+ return chrome.storage.session;
+ }
+}
+
+String _createStorageKey(StorageObject type, int? tabId) {
+ if (tabId == null) return type.name;
+ return '$tabId-${type.name}';
+}
diff --git a/dwds/debug_extension_mv3/web/utils.dart b/dwds/debug_extension_mv3/web/utils.dart
new file mode 100644
index 0000000..c75549e
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/utils.dart
@@ -0,0 +1,49 @@
+// 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 utils;
+
+import 'dart:async';
+import 'dart:js_util';
+
+import 'package:js/js.dart';
+
+import 'chrome_api.dart';
+
+Future<Tab> createTab(String url, {bool inNewWindow = false}) async {
+ if (inNewWindow) {
+ final windowPromise = chrome.windows.create(
+ WindowInfo(focused: true, url: url),
+ );
+ final windowObj = await promiseToFuture<WindowObj>(windowPromise);
+ return windowObj.tabs.first;
+ }
+ final tabPromise = chrome.tabs.create(TabInfo(
+ active: true,
+ url: url,
+ ));
+ return promiseToFuture<Tab>(tabPromise);
+}
+
+Future<Tab?> getTab(int tabId) {
+ return promiseToFuture<Tab?>(chrome.tabs.get(tabId));
+}
+
+Future<Tab?> getActiveTab() async {
+ final query = QueryInfo(active: true, currentWindow: true);
+ final tabs = List<Tab>.from(await promiseToFuture(chrome.tabs.query(query)));
+ return tabs.isNotEmpty ? tabs.first : null;
+}
+
+bool? _isDevMode;
+
+bool isDevMode() {
+ if (_isDevMode != null) {
+ return _isDevMode!;
+ }
+ final extensionManifest = chrome.runtime.getManifest();
+ final extensionName = getProperty(extensionManifest, 'name') ?? '';
+ return extensionName.contains('DEV');
+}
diff --git a/dwds/debug_extension_mv3/web/web_api.dart b/dwds/debug_extension_mv3/web/web_api.dart
index 938a0a1..1676fed 100644
--- a/dwds/debug_extension_mv3/web/web_api.dart
+++ b/dwds/debug_extension_mv3/web/web_api.dart
@@ -1,18 +1,63 @@
// 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 'package:js/js.dart';
+import 'dart:js_util' as js_util;
@JS()
-external Console get console;
+// ignore: non_constant_identifier_names
+external Json get JSON;
@JS()
@anonymous
-class Console {
- external void log(String header,
- [String style1, String style2, String style3]);
+class Json {
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
+ external String stringify(o);
+}
- external void warn(String header,
- [String style1, String style2, String style3]);
+// Custom implementation of Fetch API until the Dart implementation supports
+// credentials. See https://github.com/dart-lang/http/issues/595.
+@JS('fetch')
+external Object _nativeJsFetch(String resourceUrl, FetchOptions options);
+
+Future<FetchResponse> fetchRequest(String resourceUrl) async {
+ try {
+ final options = FetchOptions(
+ method: 'GET',
+ credentials: 'include',
+ );
+ final response =
+ await promiseToFuture(_nativeJsFetch(resourceUrl, options));
+ final body =
+ await promiseToFuture(js_util.callMethod(response, 'text', []));
+ final ok = js_util.getProperty<bool>(response, 'ok');
+ final status = js_util.getProperty<int>(response, 'status');
+ return FetchResponse(status: status, ok: ok, body: body);
+ } catch (error) {
+ return FetchResponse(
+ status: 400, ok: false, body: 'Error fetching $resourceUrl: $error');
+ }
+}
+
+@JS()
+@anonymous
+class FetchOptions {
+ external factory FetchOptions({
+ required String method, // e.g., 'GET', 'POST'
+ required String credentials, // e.g., 'omit', 'same-origin', 'include'
+ });
+}
+
+class FetchResponse {
+ final int status;
+ final bool ok;
+ final String? body;
+
+ FetchResponse({
+ required this.status,
+ required this.ok,
+ required this.body,
+ });
}
diff --git a/dwds/lib/dart_web_debug_service.dart b/dwds/lib/dart_web_debug_service.dart
index b650041..5dce5f3 100644
--- a/dwds/lib/dart_web_debug_service.dart
+++ b/dwds/lib/dart_web_debug_service.dart
@@ -84,11 +84,13 @@
bool enableDevtoolsLaunch = true,
DevtoolsLauncher? devtoolsLauncher,
bool launchDevToolsInNewWindow = true,
- SdkConfigurationProvider? sdkConfigurationProvider,
+ SdkConfigurationProvider sdkConfigurationProvider =
+ const DefaultSdkConfigurationProvider(),
bool emitDebugEvents = true,
+ bool isInternalBuild = false,
+ bool isFlutterApp = false,
}) async {
globalLoadStrategy = loadStrategy;
- sdkConfigurationProvider ??= DefaultSdkConfigurationProvider();
DevTools? devTools;
Future<String>? extensionUri;
@@ -127,6 +129,8 @@
extensionUri: extensionUri,
enableDevtoolsLaunch: enableDevtoolsLaunch,
emitDebugEvents: emitDebugEvents,
+ isInternalBuild: isInternalBuild,
+ isFlutterApp: isFlutterApp,
);
final devHandler = DevHandler(
diff --git a/dwds/lib/data/README.md b/dwds/lib/data/README.md
new file mode 100644
index 0000000..955d4cc
--- /dev/null
+++ b/dwds/lib/data/README.md
@@ -0,0 +1,16 @@
+# How to generate data files:
+
+## Creating a new data file:
+
+1. Create a new file for your data type in the `/data` directory with the
+ `.dart` extension
+1. Create an abstract class for your data type (see existing files for examples)
+1. Add the new data type to `/data/serializers.dart` 4 Run:
+ `dart run build_runner build` from DWDS root (this will generate the
+ `.g.dart` file)
+
+## To update an existing data file:
+
+1. Make your changes
+1. Run: `dart run build_runner clean` from DWDS root
+1. Run: `dart run build_runner build` from DWDS root
diff --git a/dwds/lib/data/debug_info.dart b/dwds/lib/data/debug_info.dart
new file mode 100644
index 0000000..288d1e7
--- /dev/null
+++ b/dwds/lib/data/debug_info.dart
@@ -0,0 +1,26 @@
+// 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 'package:built_value/built_value.dart';
+import 'package:built_value/serializer.dart';
+
+part 'debug_info.g.dart';
+
+abstract class DebugInfo implements Built<DebugInfo, DebugInfoBuilder> {
+ static Serializer<DebugInfo> get serializer => _$debugInfoSerializer;
+
+ factory DebugInfo([Function(DebugInfoBuilder) updates]) = _$DebugInfo;
+
+ DebugInfo._();
+
+ String? get appEntrypointPath;
+ String? get appId;
+ String? get appInstanceId;
+ String? get appOrigin;
+ String? get appUrl;
+ String? get dwdsVersion;
+ String? get extensionUrl;
+ bool? get isInternalBuild;
+ bool? get isFlutterApp;
+}
diff --git a/dwds/lib/data/debug_info.g.dart b/dwds/lib/data/debug_info.g.dart
new file mode 100644
index 0000000..3807db2
--- /dev/null
+++ b/dwds/lib/data/debug_info.g.dart
@@ -0,0 +1,323 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'debug_info.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+Serializer<DebugInfo> _$debugInfoSerializer = new _$DebugInfoSerializer();
+
+class _$DebugInfoSerializer implements StructuredSerializer<DebugInfo> {
+ @override
+ final Iterable<Type> types = const [DebugInfo, _$DebugInfo];
+ @override
+ final String wireName = 'DebugInfo';
+
+ @override
+ Iterable<Object?> serialize(Serializers serializers, DebugInfo object,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = <Object?>[];
+ Object? value;
+ value = object.appEntrypointPath;
+ if (value != null) {
+ result
+ ..add('appEntrypointPath')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ value = object.appId;
+ if (value != null) {
+ result
+ ..add('appId')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ value = object.appInstanceId;
+ if (value != null) {
+ result
+ ..add('appInstanceId')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ value = object.appOrigin;
+ if (value != null) {
+ result
+ ..add('appOrigin')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ value = object.appUrl;
+ if (value != null) {
+ result
+ ..add('appUrl')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ value = object.dwdsVersion;
+ if (value != null) {
+ result
+ ..add('dwdsVersion')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ value = object.extensionUrl;
+ if (value != null) {
+ result
+ ..add('extensionUrl')
+ ..add(serializers.serialize(value,
+ specifiedType: const FullType(String)));
+ }
+ value = object.isInternalBuild;
+ if (value != null) {
+ result
+ ..add('isInternalBuild')
+ ..add(
+ serializers.serialize(value, specifiedType: const FullType(bool)));
+ }
+ value = object.isFlutterApp;
+ if (value != null) {
+ result
+ ..add('isFlutterApp')
+ ..add(
+ serializers.serialize(value, specifiedType: const FullType(bool)));
+ }
+ return result;
+ }
+
+ @override
+ DebugInfo deserialize(Serializers serializers, Iterable<Object?> serialized,
+ {FullType specifiedType = FullType.unspecified}) {
+ final result = new DebugInfoBuilder();
+
+ final iterator = serialized.iterator;
+ while (iterator.moveNext()) {
+ final key = iterator.current! as String;
+ iterator.moveNext();
+ final Object? value = iterator.current;
+ switch (key) {
+ case 'appEntrypointPath':
+ result.appEntrypointPath = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ case 'appId':
+ result.appId = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ case 'appInstanceId':
+ result.appInstanceId = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ case 'appOrigin':
+ result.appOrigin = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ case 'appUrl':
+ result.appUrl = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ case 'dwdsVersion':
+ result.dwdsVersion = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ case 'extensionUrl':
+ result.extensionUrl = serializers.deserialize(value,
+ specifiedType: const FullType(String)) as String?;
+ break;
+ case 'isInternalBuild':
+ result.isInternalBuild = serializers.deserialize(value,
+ specifiedType: const FullType(bool)) as bool?;
+ break;
+ case 'isFlutterApp':
+ result.isFlutterApp = serializers.deserialize(value,
+ specifiedType: const FullType(bool)) as bool?;
+ break;
+ }
+ }
+
+ return result.build();
+ }
+}
+
+class _$DebugInfo extends DebugInfo {
+ @override
+ final String? appEntrypointPath;
+ @override
+ final String? appId;
+ @override
+ final String? appInstanceId;
+ @override
+ final String? appOrigin;
+ @override
+ final String? appUrl;
+ @override
+ final String? dwdsVersion;
+ @override
+ final String? extensionUrl;
+ @override
+ final bool? isInternalBuild;
+ @override
+ final bool? isFlutterApp;
+
+ factory _$DebugInfo([void Function(DebugInfoBuilder)? updates]) =>
+ (new DebugInfoBuilder()..update(updates))._build();
+
+ _$DebugInfo._(
+ {this.appEntrypointPath,
+ this.appId,
+ this.appInstanceId,
+ this.appOrigin,
+ this.appUrl,
+ this.dwdsVersion,
+ this.extensionUrl,
+ this.isInternalBuild,
+ this.isFlutterApp})
+ : super._();
+
+ @override
+ DebugInfo rebuild(void Function(DebugInfoBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ DebugInfoBuilder toBuilder() => new DebugInfoBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is DebugInfo &&
+ appEntrypointPath == other.appEntrypointPath &&
+ appId == other.appId &&
+ appInstanceId == other.appInstanceId &&
+ appOrigin == other.appOrigin &&
+ appUrl == other.appUrl &&
+ dwdsVersion == other.dwdsVersion &&
+ extensionUrl == other.extensionUrl &&
+ isInternalBuild == other.isInternalBuild &&
+ isFlutterApp == other.isFlutterApp;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, appEntrypointPath.hashCode);
+ _$hash = $jc(_$hash, appId.hashCode);
+ _$hash = $jc(_$hash, appInstanceId.hashCode);
+ _$hash = $jc(_$hash, appOrigin.hashCode);
+ _$hash = $jc(_$hash, appUrl.hashCode);
+ _$hash = $jc(_$hash, dwdsVersion.hashCode);
+ _$hash = $jc(_$hash, extensionUrl.hashCode);
+ _$hash = $jc(_$hash, isInternalBuild.hashCode);
+ _$hash = $jc(_$hash, isFlutterApp.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'DebugInfo')
+ ..add('appEntrypointPath', appEntrypointPath)
+ ..add('appId', appId)
+ ..add('appInstanceId', appInstanceId)
+ ..add('appOrigin', appOrigin)
+ ..add('appUrl', appUrl)
+ ..add('dwdsVersion', dwdsVersion)
+ ..add('extensionUrl', extensionUrl)
+ ..add('isInternalBuild', isInternalBuild)
+ ..add('isFlutterApp', isFlutterApp))
+ .toString();
+ }
+}
+
+class DebugInfoBuilder implements Builder<DebugInfo, DebugInfoBuilder> {
+ _$DebugInfo? _$v;
+
+ String? _appEntrypointPath;
+ String? get appEntrypointPath => _$this._appEntrypointPath;
+ set appEntrypointPath(String? appEntrypointPath) =>
+ _$this._appEntrypointPath = appEntrypointPath;
+
+ String? _appId;
+ String? get appId => _$this._appId;
+ set appId(String? appId) => _$this._appId = appId;
+
+ String? _appInstanceId;
+ String? get appInstanceId => _$this._appInstanceId;
+ set appInstanceId(String? appInstanceId) =>
+ _$this._appInstanceId = appInstanceId;
+
+ String? _appOrigin;
+ String? get appOrigin => _$this._appOrigin;
+ set appOrigin(String? appOrigin) => _$this._appOrigin = appOrigin;
+
+ String? _appUrl;
+ String? get appUrl => _$this._appUrl;
+ set appUrl(String? appUrl) => _$this._appUrl = appUrl;
+
+ String? _dwdsVersion;
+ String? get dwdsVersion => _$this._dwdsVersion;
+ set dwdsVersion(String? dwdsVersion) => _$this._dwdsVersion = dwdsVersion;
+
+ String? _extensionUrl;
+ String? get extensionUrl => _$this._extensionUrl;
+ set extensionUrl(String? extensionUrl) => _$this._extensionUrl = extensionUrl;
+
+ bool? _isInternalBuild;
+ bool? get isInternalBuild => _$this._isInternalBuild;
+ set isInternalBuild(bool? isInternalBuild) =>
+ _$this._isInternalBuild = isInternalBuild;
+
+ bool? _isFlutterApp;
+ bool? get isFlutterApp => _$this._isFlutterApp;
+ set isFlutterApp(bool? isFlutterApp) => _$this._isFlutterApp = isFlutterApp;
+
+ DebugInfoBuilder();
+
+ DebugInfoBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ _appEntrypointPath = $v.appEntrypointPath;
+ _appId = $v.appId;
+ _appInstanceId = $v.appInstanceId;
+ _appOrigin = $v.appOrigin;
+ _appUrl = $v.appUrl;
+ _dwdsVersion = $v.dwdsVersion;
+ _extensionUrl = $v.extensionUrl;
+ _isInternalBuild = $v.isInternalBuild;
+ _isFlutterApp = $v.isFlutterApp;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(DebugInfo other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$DebugInfo;
+ }
+
+ @override
+ void update(void Function(DebugInfoBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ DebugInfo build() => _build();
+
+ _$DebugInfo _build() {
+ final _$result = _$v ??
+ new _$DebugInfo._(
+ appEntrypointPath: appEntrypointPath,
+ appId: appId,
+ appInstanceId: appInstanceId,
+ appOrigin: appOrigin,
+ appUrl: appUrl,
+ dwdsVersion: dwdsVersion,
+ extensionUrl: extensionUrl,
+ isInternalBuild: isInternalBuild,
+ isFlutterApp: isFlutterApp);
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/dwds/lib/data/serializers.dart b/dwds/lib/data/serializers.dart
index 903ef59..25f4e2a 100644
--- a/dwds/lib/data/serializers.dart
+++ b/dwds/lib/data/serializers.dart
@@ -8,6 +8,7 @@
import 'build_result.dart';
import 'connect_request.dart';
import 'debug_event.dart';
+import 'debug_info.dart';
import 'devtools_request.dart';
import 'error_response.dart';
import 'extension_request.dart';
@@ -24,6 +25,7 @@
BuildResult,
ConnectRequest,
DebugEvent,
+ DebugInfo,
DevToolsRequest,
DevToolsResponse,
IsolateExit,
diff --git a/dwds/lib/data/serializers.g.dart b/dwds/lib/data/serializers.g.dart
index 6810d86..584ce7e 100644
--- a/dwds/lib/data/serializers.g.dart
+++ b/dwds/lib/data/serializers.g.dart
@@ -13,6 +13,7 @@
..add(BuildStatus.serializer)
..add(ConnectRequest.serializer)
..add(DebugEvent.serializer)
+ ..add(DebugInfo.serializer)
..add(DevToolsRequest.serializer)
..add(DevToolsResponse.serializer)
..add(ErrorResponse.serializer)
diff --git a/dwds/lib/dwds.dart b/dwds/lib/dwds.dart
index 84bf219..3b67f1a 100644
--- a/dwds/lib/dwds.dart
+++ b/dwds/lib/dwds.dart
@@ -29,4 +29,4 @@
export 'src/services/expression_compiler_service.dart'
show ExpressionCompilerService;
export 'src/utilities/sdk_configuration.dart'
- show SdkConfiguration, SdkConfigurationProvider;
+ show SdkLayout, SdkConfiguration, SdkConfigurationProvider;
diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart
index 4f56293..0f3f754 100644
--- a/dwds/lib/src/debugging/debugger.dart
+++ b/dwds/lib/src/debugging/debugger.dart
@@ -5,8 +5,8 @@
import 'dart:async';
import 'dart:math' as math;
+import 'package:dwds/src/utilities/synchronized.dart';
import 'package:logging/logging.dart';
-import 'package:pool/pool.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
hide StackTrace;
@@ -373,7 +373,7 @@
// Filter out variables that do not come from dart code, such as native
// JavaScript objects
return boundVariables
- .where((bv) => bv != null && !isNativeJsObject(bv.value as InstanceRef))
+ .where((bv) => isDisplayableObject(bv?.value))
.toList()
.cast();
}
@@ -744,6 +744,9 @@
return result;
}
+bool isDisplayableObject(Object? object) =>
+ object is Sentinel || object is InstanceRef && !isNativeJsObject(object);
+
bool isNativeJsObject(InstanceRef instanceRef) {
// New type representation of JS objects reifies them to a type suffixed with
// JavaScriptObject.
@@ -776,7 +779,7 @@
final _bpByDartId = <String, Future<Breakpoint>>{};
- final _pool = Pool(1);
+ final _queue = AtomicQueue();
final Locations locations;
final RemoteDebugger remoteDebugger;
@@ -865,7 +868,7 @@
final urlRegex = '.*${location.jsLocation.module}.*';
// Prevent `Aww, snap!` errors when setting multiple breakpoints
// simultaneously by serializing the requests.
- return _pool.withResource(() async {
+ return _queue.run(() async {
final breakPointId = await sendCommandAndValidateResult<String>(
remoteDebugger,
method: 'Debugger.setBreakpointByUrl',
diff --git a/dwds/lib/src/debugging/frame_computer.dart b/dwds/lib/src/debugging/frame_computer.dart
index 22573a9..00d7ca2 100644
--- a/dwds/lib/src/debugging/frame_computer.dart
+++ b/dwds/lib/src/debugging/frame_computer.dart
@@ -2,7 +2,7 @@
// 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 'package:pool/pool.dart';
+import 'package:dwds/src/utilities/synchronized.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
@@ -11,9 +11,9 @@
class FrameComputer {
final Debugger debugger;
- // To ensure that the frames are computed only once, we use a pool to guard
- // the work. Frames are computed sequentially.
- final _pool = Pool(1);
+ // To ensure that the frames are computed only once, we use an atomic queue
+ // to guard the work. Frames are computed sequentially.
+ final _queue = AtomicQueue();
final List<WipCallFrame> _callFrames;
final List<Frame> _computedFrames = [];
@@ -36,7 +36,7 @@
/// Translates Chrome callFrames contained in [DebuggerPausedEvent] into Dart
/// [Frame]s.
Future<List<Frame>> calculateFrames({int? limit}) async {
- return _pool.withResource(() async {
+ return _queue.run(() async {
if (limit != null && _computedFrames.length >= limit) {
return _computedFrames.take(limit).toList();
}
diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart
index 3b1e22b..083a0ef 100644
--- a/dwds/lib/src/debugging/inspector.dart
+++ b/dwds/lib/src/debugging/inspector.dart
@@ -83,6 +83,13 @@
/// Regex used to extract the message from an exception description.
static final exceptionMessageRegex = RegExp(r'^.*$', multiLine: true);
+ /// Flutter widget inspector library.
+ Future<LibraryRef?> get flutterWidgetInspectorLibrary => _libraryHelper
+ .libraryRefFor('package:flutter/src/widgets/widget_inspector.dart');
+
+ /// Regex used to extract a stack trace line from the exception description.
+ static final stackTraceLineRegex = RegExp(r'^\s*at\s.*$', multiLine: true);
+
AppInspector._(
this._appConnection,
this._isolate,
@@ -611,8 +618,17 @@
if (mappedStack == null || mappedStack.isEmpty) {
return description;
}
- var message = exceptionMessageRegex.firstMatch(description)?.group(0);
- message = (message != null) ? '$message\n' : '';
+ final message = _allLinesBeforeStackTrace(description);
return '$message$mappedStack';
}
+
+ String _allLinesBeforeStackTrace(String description) {
+ var message = '';
+ for (final match in exceptionMessageRegex.allMatches(description)) {
+ final isStackTraceLine = stackTraceLineRegex.hasMatch(match[0] ?? '');
+ if (isStackTraceLine) break;
+ message += '${match[0]}\n';
+ }
+ return message;
+ }
}
diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart
index 03851d9..4f41d91 100644
--- a/dwds/lib/src/debugging/instance.dart
+++ b/dwds/lib/src/debugging/instance.dart
@@ -180,7 +180,7 @@
var boundFields = await Future.wait(
dartProperties.map<Future<BoundField>>((p) => _fieldFor(p, classRef)));
boundFields = boundFields
- .where((bv) => !isNativeJsObject(bv.value as InstanceRef))
+ .where((bv) => isDisplayableObject(bv.value))
.toList()
..sort(_compareBoundFields);
final result = Instance(
diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart
index cb7c5b7..4ead6d7 100644
--- a/dwds/lib/src/dwds_vm_client.dart
+++ b/dwds/lib/src/dwds_vm_client.dart
@@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:convert';
+import 'package:dwds/src/utilities/synchronized.dart';
import 'package:logging/logging.dart';
import 'package:uuid/uuid.dart';
import 'package:vm_service/vm_service.dart';
@@ -32,6 +33,9 @@
/// All subsequent calls to [close] will return this future.
Future<void>? _closed;
+ /// Synchronizes hot restarts to avoid races.
+ final _hotRestartQueue = AtomicQueue();
+
DwdsVmClient(this.client, this._requestController, this._responseController);
Future<void> close() => _closed ??= () async {
@@ -60,6 +64,9 @@
final chromeProxyService =
debugService.chromeProxyService as ChromeProxyService;
+ final dwdsVmClient =
+ DwdsVmClient(client, requestController, responseController);
+
// Register '_flutter.listViews' method on the chrome proxy service vm.
// In native world, this method is provided by the engine, but the web
// engine is not aware of the VM uri or the isolates.
@@ -85,7 +92,7 @@
client.registerServiceCallback(
'hotRestart',
(request) => captureElapsedTime(
- () => _hotRestart(chromeProxyService, client),
+ () => dwdsVmClient.hotRestart(chromeProxyService, client),
(_) => DwdsEvent.hotRestart()));
await client.registerService('hotRestart', 'DWDS');
@@ -131,14 +138,19 @@
'error': {
'code': kFeatureDisabled,
'message': kFeatureDisabledMessage,
- 'details':
+ 'data':
'Existing VM service clients prevent DDS from taking control.',
},
};
});
await client.registerService('_yieldControlToDDS', 'DWDS');
- return DwdsVmClient(client, requestController, responseController);
+ return dwdsVmClient;
+ }
+
+ Future<Map<String, dynamic>> hotRestart(
+ ChromeProxyService chromeProxyService, VmService client) async {
+ return _hotRestartQueue.run(() => _hotRestart(chromeProxyService, client));
}
}
@@ -153,22 +165,7 @@
final action = payload?['action'] as String?;
final screen = payload?['screen'] as String?;
if (screen != null && action == 'pageReady') {
- if (dwdsStats.isFirstDebuggerReady) {
- final debuggerReadyTime = DateTime.now()
- .difference(dwdsStats.devToolsStart)
- .inMilliseconds;
- emitEvent(DwdsEvent.devToolsLoad(debuggerReadyTime, screen));
- _logger.fine('DevTools load time: $debuggerReadyTime ms');
- final debuggerStartTime = DateTime.now()
- .difference(dwdsStats.debuggerStart)
- .inMilliseconds;
- emitEvent(DwdsEvent.debuggerReady(debuggerStartTime, screen));
- _logger.fine('Debugger ready time: $debuggerStartTime ms');
- } else {
- _logger
- .finest('Debugger and DevTools startup times already recorded.'
- ' Ignoring $event.');
- }
+ _recordDwdsStats(dwdsStats, screen);
} else {
_logger.finest('Ignoring unknown event: $event');
}
@@ -176,6 +173,27 @@
}
}
+void _recordDwdsStats(DwdsStats dwdsStats, String screen) {
+ if (dwdsStats.isFirstDebuggerReady) {
+ final devToolsStart = dwdsStats.devToolsStart;
+ final debuggerStart = dwdsStats.debuggerStart;
+ if (devToolsStart != null) {
+ final devToolLoadTime =
+ DateTime.now().difference(devToolsStart).inMilliseconds;
+ emitEvent(DwdsEvent.devToolsLoad(devToolLoadTime, screen));
+ _logger.fine('DevTools load time: $devToolLoadTime ms');
+ }
+ if (debuggerStart != null) {
+ final debuggerReadyTime =
+ DateTime.now().difference(debuggerStart).inMilliseconds;
+ emitEvent(DwdsEvent.debuggerReady(debuggerReadyTime, screen));
+ _logger.fine('Debugger ready time: $debuggerReadyTime ms');
+ }
+ } else {
+ _logger.finest('Debugger and DevTools stats are already recorded.');
+ }
+}
+
Future<Map<String, dynamic>> _hotRestart(
ChromeProxyService chromeProxyService, VmService client) async {
_logger.info('Attempting a hot restart');
diff --git a/dwds/lib/src/events.dart b/dwds/lib/src/events.dart
index 9cdb13b..8634a62 100644
--- a/dwds/lib/src/events.dart
+++ b/dwds/lib/src/events.dart
@@ -8,12 +8,12 @@
class DwdsStats {
/// The time when the user starts the debugger.
- late DateTime _debuggerStart;
- DateTime get debuggerStart => _debuggerStart;
+ DateTime? _debuggerStart;
+ DateTime? get debuggerStart => _debuggerStart;
/// The time when dwds launches DevTools.
- late DateTime _devToolsStart;
- DateTime get devToolsStart => _devToolsStart;
+ DateTime? _devToolsStart;
+ DateTime? get devToolsStart => _devToolsStart;
/// Records and returns weither the debugger is ready.
bool _isFirstDebuggerReady = true;
diff --git a/dwds/lib/src/handlers/injector.dart b/dwds/lib/src/handlers/injector.dart
index 9ad89cb..b486cc0 100644
--- a/dwds/lib/src/handlers/injector.dart
+++ b/dwds/lib/src/handlers/injector.dart
@@ -36,6 +36,8 @@
final bool _enableDevtoolsLaunch;
final bool _useSseForInjectedClient;
final bool _emitDebugEvents;
+ final bool _isInternalBuild;
+ final bool _isFlutterApp;
DwdsInjector(
this._loadStrategy, {
@@ -43,10 +45,14 @@
bool enableDevtoolsLaunch = false,
bool useSseForInjectedClient = true,
bool emitDebugEvents = true,
+ bool isInternalBuild = false,
+ bool isFlutterApp = false,
}) : _extensionUri = extensionUri,
_enableDevtoolsLaunch = enableDevtoolsLaunch,
_useSseForInjectedClient = useSseForInjectedClient,
- _emitDebugEvents = emitDebugEvents;
+ _emitDebugEvents = emitDebugEvents,
+ _isInternalBuild = isInternalBuild,
+ _isFlutterApp = isFlutterApp;
/// Returns the embedded dev handler paths.
///
@@ -111,6 +117,8 @@
_loadStrategy,
_enableDevtoolsLaunch,
_emitDebugEvents,
+ _isInternalBuild,
+ _isFlutterApp,
);
body += await _loadStrategy.bootstrapFor(entrypoint);
_logger.info('Injected debugging metadata for '
@@ -136,15 +144,16 @@
/// Returns the provided body with the main function hoisted into a global
/// variable and a snippet of JS that loads the injected client.
String _injectClientAndHoistMain(
- String body,
- String appId,
- String devHandlerPath,
- String entrypointPath,
- String? extensionUri,
- LoadStrategy loadStrategy,
- bool enableDevtoolsLaunch,
- bool emitDebugEvents,
-) {
+ String body,
+ String appId,
+ String devHandlerPath,
+ String entrypointPath,
+ String? extensionUri,
+ LoadStrategy loadStrategy,
+ bool enableDevtoolsLaunch,
+ bool emitDebugEvents,
+ bool isInternalBuild,
+ bool isFlutterApp) {
final bodyLines = body.split('\n');
final extensionIndex =
bodyLines.indexWhere((line) => line.contains(mainExtensionMarker));
@@ -157,14 +166,15 @@
// application to be in a ready state, that is the main function is hoisted
// and the Dart SDK is loaded.
final injectedClientSnippet = _injectedClientSnippet(
- appId,
- devHandlerPath,
- entrypointPath,
- extensionUri,
- loadStrategy,
- enableDevtoolsLaunch,
- emitDebugEvents,
- );
+ appId,
+ devHandlerPath,
+ entrypointPath,
+ extensionUri,
+ loadStrategy,
+ enableDevtoolsLaunch,
+ emitDebugEvents,
+ isInternalBuild,
+ isFlutterApp);
result += '''
// Injected by dwds for debugging support.
if(!window.\$dwdsInitialized) {
@@ -198,6 +208,8 @@
LoadStrategy loadStrategy,
bool enableDevtoolsLaunch,
bool emitDebugEvents,
+ bool isInternalBuild,
+ bool isFlutterApp,
) {
var injectedBody = 'window.\$dartAppId = "$appId";\n'
'window.\$dartReloadConfiguration = "${loadStrategy.reloadConfiguration}";\n'
@@ -208,6 +220,8 @@
'window.\$dwdsEnableDevtoolsLaunch = $enableDevtoolsLaunch;\n'
'window.\$dartEntrypointPath = "$entrypointPath";\n'
'window.\$dartEmitDebugEvents = $emitDebugEvents;\n'
+ 'window.\$isInternalBuild = $isInternalBuild;\n'
+ 'window.\$isFlutterApp = $isFlutterApp;\n'
'${loadStrategy.loadClientSnippet(_clientScript)}';
if (extensionUri != null) {
injectedBody += 'window.\$dartExtensionUri = "$extensionUri";\n';
diff --git a/dwds/lib/src/services/batched_expression_evaluator.dart b/dwds/lib/src/services/batched_expression_evaluator.dart
index 64338fd..81bf7f0 100644
--- a/dwds/lib/src/services/batched_expression_evaluator.dart
+++ b/dwds/lib/src/services/batched_expression_evaluator.dart
@@ -31,6 +31,7 @@
final Debugger _debugger;
final _requestController =
BatchedStreamController<EvaluateRequest>(delay: 200);
+ bool _closed = false;
BatchedExpressionEvaluator(
String entrypoint,
@@ -45,8 +46,10 @@
@override
void close() {
+ if (_closed) return;
_logger.fine('Closed');
_requestController.close();
+ _closed = true;
}
@override
@@ -55,7 +58,11 @@
String? libraryUri,
String expression,
Map<String, String>? scope,
- ) {
+ ) async {
+ if (_closed) {
+ return createError(
+ ErrorKind.internal, 'Batched expression evaluator closed');
+ }
final request = EvaluateRequest(isolateId, libraryUri, expression, scope);
_requestController.sink.add(request);
return request.completer.future;
@@ -121,15 +128,22 @@
final request = requests[i];
if (request.completer.isCompleted) continue;
_logger.fine('Getting result out of a batch for ${request.expression}');
- _debugger
- .getProperties(list.objectId!,
- offset: i, count: 1, length: requests.length)
- .then((v) {
- final result = v.first.value;
- _logger.fine(
- 'Got result out of a batch for ${request.expression}: $result');
- request.completer.complete(result);
- });
+
+ final listId = list.objectId;
+ if (listId == null) {
+ final error =
+ createError(ErrorKind.internal, 'No batch result object ID.');
+ request.completer.complete(error);
+ } else {
+ unawaited(_debugger
+ .getProperties(listId, offset: i, count: 1, length: requests.length)
+ .then((v) {
+ final result = v.first.value;
+ _logger.fine(
+ 'Got result out of a batch for ${request.expression}: $result');
+ request.completer.complete(result);
+ }));
+ }
}
}
}
diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart
index 6325cfa..766f3e9 100644
--- a/dwds/lib/src/services/chrome_proxy_service.dart
+++ b/dwds/lib/src/services/chrome_proxy_service.dart
@@ -203,6 +203,34 @@
}
}
+ Future<void> _prewarmExpressionCompilerCache() async {
+ // Exit early if the expression evaluation is not enabled.
+ if (_compiler == null || _expressionEvaluator == null) {
+ return;
+ }
+ // Wait until the inspector is ready.
+ await isInitialized;
+
+ // Pre-warm the flutter framework module cache in the compiler.
+ //
+ // Flutter inspector relies on evaluations in widget_inspector
+ // library, which is a part of the flutter framework module, to
+ // produce widget trees, draw the layout explorer, show hover
+ // cards etc.
+ // Pre-warming the cache while DevTools is still loading helps
+ // Flutter Inspector start faster.
+ final libraryToCache = await inspector.flutterWidgetInspectorLibrary;
+ if (libraryToCache != null) {
+ final isolateId = inspector.isolateRef.id;
+ final libraryId = libraryToCache.id;
+ if (isolateId != null && libraryId != null) {
+ _logger.finest(
+ 'Caching ${libraryToCache.uri} in expression compiler worker');
+ await evaluate(isolateId, libraryId, 'true');
+ }
+ }
+ }
+
/// Creates a new isolate.
///
/// Only one isolate at a time is supported, but they should be cleaned up
@@ -251,19 +279,15 @@
compiler,
);
+ unawaited(_prewarmExpressionCompilerCache());
+
await debugger.reestablishBreakpoints(
_previousBreakpoints, _disabledBreakpoints);
_disabledBreakpoints.clear();
unawaited(appConnection.onStart.then((_) async {
await debugger.resumeFromStart();
- if (!_startedCompleter.isCompleted) {
- _startedCompleter.complete();
- } else {
- // See https://github.com/flutter/flutter/issues/117676:
- _logger
- .warning('Unexpected state, debugging may not work as expected.');
- }
+ _startedCompleter.complete();
}));
unawaited(appConnection.onDone.then((_) => destroyIsolate()));
@@ -312,6 +336,7 @@
void destroyIsolate() {
_logger.fine('Destroying isolate');
if (!_isIsolateRunning) return;
+
final isolate = inspector.isolate;
final isolateRef = inspector.isolateRef;
@@ -419,10 +444,20 @@
return _rpcNotSupportedFuture('clearVMTimeline');
}
- Future<Response> _getEvaluationResult(
+ Future<Response> _getEvaluationResult(String isolateId,
Future<RemoteObject> Function() evaluation, String expression) async {
try {
final result = await evaluation();
+ if (!_isIsolateRunning || isolateId != inspector.isolate.id) {
+ _logger.fine('Cannot get evaluation result for isolate $isolateId: '
+ ' isolate exited.');
+ return ErrorRef(
+ kind: 'error',
+ message: 'Isolate exited',
+ id: createId(),
+ );
+ }
+
// Handle compilation errors, internal errors,
// and reference errors from JavaScript evaluation in chrome.
if (result.type.contains('Error')) {
@@ -473,6 +508,7 @@
final library = await inspector.getLibrary(targetId);
return await _getEvaluationResult(
+ isolateId,
() => evaluator.evaluateExpression(
isolateId, library?.uri, expression, scope),
expression);
@@ -496,6 +532,7 @@
_checkIsolate('evaluateInFrame', isolateId);
return await _getEvaluationResult(
+ isolateId,
() => evaluator.evaluateExpressionInFrame(
isolateId, frameIndex, expression, scope),
expression);
@@ -752,20 +789,25 @@
}
}
+ /// This method is deprecated in vm_service package.
+ ///
+ /// TODO(annagrin): remove after dart-code and IntelliJ stop using this API.
+ /// Issue: https://github.com/dart-lang/webdev/issues/1868
+ ///
+ // ignore: annotate_overrides
+ Future<Success> setExceptionPauseMode(
+ String isolateId, /*ExceptionPauseMode*/ String mode) =>
+ setIsolatePauseMode(isolateId, exceptionPauseMode: mode);
+
@override
Future<Success> setIsolatePauseMode(String isolateId,
{String? exceptionPauseMode, bool? shouldPauseOnExit}) async {
// TODO(elliette): Is there a way to respect the shouldPauseOnExit parameter
// in Chrome?
- return setExceptionPauseMode(
- isolateId, exceptionPauseMode ?? ExceptionPauseMode.kNone);
- }
-
- @override
- Future<Success> setExceptionPauseMode(String isolateId, String mode) async {
await isInitialized;
- _checkIsolate('setExceptionPauseMode', isolateId);
- return (await debuggerFuture).setExceptionPauseMode(mode);
+ _checkIsolate('setIsolatePauseMode', isolateId);
+ return (await debuggerFuture)
+ .setExceptionPauseMode(exceptionPauseMode ?? ExceptionPauseMode.kNone);
}
@override
diff --git a/dwds/lib/src/services/expression_compiler_service.dart b/dwds/lib/src/services/expression_compiler_service.dart
index 8d738fe..e94be84 100644
--- a/dwds/lib/src/services/expression_compiler_service.dart
+++ b/dwds/lib/src/services/expression_compiler_service.dart
@@ -68,15 +68,21 @@
String moduleFormat,
bool soundNullSafety,
SdkConfiguration sdkConfiguration,
+ List<String> experiments,
bool verbose,
) async {
- sdkConfiguration.validate();
+ sdkConfiguration.validateSdkDir();
+ if (soundNullSafety) {
+ sdkConfiguration.validateSoundSummaries();
+ } else {
+ sdkConfiguration.validateWeakSummaries();
+ }
final librariesUri = sdkConfiguration.librariesUri!;
final workerUri = sdkConfiguration.compilerWorkerUri!;
final sdkSummaryUri = soundNullSafety
? sdkConfiguration.soundSdkSummaryUri!
- : sdkConfiguration.unsoundSdkSummaryUri!;
+ : sdkConfiguration.weakSdkSummaryUri!;
final args = [
'--experimental-expression-compiler',
@@ -92,6 +98,7 @@
moduleFormat,
if (verbose) '--verbose',
soundNullSafety ? '--sound-null-safety' : '--no-sound-null-safety',
+ for (var experiment in experiments) '--enable-experiment=$experiment',
];
_logger.info('Starting...');
@@ -230,16 +237,20 @@
final _compiler = Completer<_Compiler>();
final String _address;
final FutureOr<int> _port;
+ final List<String> experiments;
final bool _verbose;
final SdkConfigurationProvider _sdkConfigurationProvider;
- ExpressionCompilerService(this._address, this._port,
- {bool verbose = false,
- SdkConfigurationProvider? sdkConfigurationProvider})
- : _verbose = verbose,
- _sdkConfigurationProvider =
- sdkConfigurationProvider ?? DefaultSdkConfigurationProvider();
+ ExpressionCompilerService(
+ this._address,
+ this._port, {
+ bool verbose = false,
+ SdkConfigurationProvider sdkConfigurationProvider =
+ const DefaultSdkConfigurationProvider(),
+ this.experiments = const [],
+ }) : _verbose = verbose,
+ _sdkConfigurationProvider = sdkConfigurationProvider;
@override
Future<ExpressionCompilationResult> compileExpressionToJs(
@@ -265,6 +276,7 @@
moduleFormat,
soundNullSafety,
await _sdkConfigurationProvider.configuration,
+ experiments,
_verbose,
);
diff --git a/dwds/lib/src/services/expression_evaluator.dart b/dwds/lib/src/services/expression_evaluator.dart
index 35aef1c..e932775 100644
--- a/dwds/lib/src/services/expression_evaluator.dart
+++ b/dwds/lib/src/services/expression_evaluator.dart
@@ -2,8 +2,6 @@
// 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:async';
-
import 'package:dwds/src/utilities/domain.dart';
import 'package:logging/logging.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
@@ -43,6 +41,7 @@
final Modules _modules;
final ExpressionCompiler _compiler;
final _logger = Logger('ExpressionEvaluator');
+ bool _closed = false;
/// Strip synthetic library name from compiler error messages.
static final _syntheticNameFilterRegex =
@@ -58,12 +57,14 @@
ExpressionEvaluator(this._entrypoint, this._inspector, this._debugger,
this._locations, this._modules, this._compiler);
- RemoteObject _createError(ErrorKind severity, String message) {
+ RemoteObject createError(ErrorKind severity, String message) {
return RemoteObject(
<String, String>{'type': '$severity', 'value': message});
}
- void close() {}
+ void close() {
+ _closed = true;
+ }
/// Evaluate dart expression inside a given library.
///
@@ -82,19 +83,23 @@
String expression,
Map<String, String>? scope,
) async {
+ if (_closed) {
+ return createError(ErrorKind.internal, 'expression evaluator closed.');
+ }
+
scope ??= {};
if (expression.isEmpty) {
- return _createError(ErrorKind.invalidInput, expression);
+ return createError(ErrorKind.invalidInput, expression);
}
if (libraryUri == null) {
- return _createError(ErrorKind.invalidInput, 'no library uri');
+ return createError(ErrorKind.invalidInput, 'no library uri');
}
final module = await _modules.moduleForLibrary(libraryUri);
if (module == null) {
- return _createError(ErrorKind.internal, 'no module for $libraryUri');
+ return createError(ErrorKind.internal, 'no module for $libraryUri');
}
// Wrap the expression in a lambda so we can call it as a function.
@@ -120,7 +125,8 @@
var result = await _inspector.callFunction(jsCode, scope.values);
result = await _formatEvaluationError(result);
- _logger.finest('Evaluated "$expression" to "$result"');
+ _logger
+ .finest('Evaluated "$expression" to "$result" for isolate $isolateId');
return result;
}
@@ -141,20 +147,20 @@
if (scope != null) {
// TODO(annagrin): Implement scope support.
// Issue: https://github.com/dart-lang/webdev/issues/1344
- return _createError(
+ return createError(
ErrorKind.internal,
'Using scope for expression evaluation in frame '
'is not supported.');
}
if (expression.isEmpty) {
- return _createError(ErrorKind.invalidInput, expression);
+ return createError(ErrorKind.invalidInput, expression);
}
// Get JS scope and current JS location.
final jsFrame = _debugger.jsFrameForIndex(frameIndex);
if (jsFrame == null) {
- return _createError(
+ return createError(
ErrorKind.internal,
'Expression evaluation in async frames '
'is not supported. No frame with index $frameIndex.');
@@ -169,12 +175,12 @@
// Find corresponding dart location and scope.
final url = _debugger.urlForScriptId(jsScriptId);
if (url == null) {
- return _createError(
+ return createError(
ErrorKind.internal, 'Cannot find url for JS script: $jsScriptId');
}
final locationMap = await _locations.locationForJs(url, jsLine, jsColumn);
if (locationMap == null) {
- return _createError(
+ return createError(
ErrorKind.internal,
'Cannot find Dart location for JS location: '
'url: $url, '
@@ -187,13 +193,13 @@
final dartSourcePath = dartLocation.uri.serverPath;
final libraryUri = await _modules.libraryForSource(dartSourcePath);
if (libraryUri == null) {
- return _createError(
+ return createError(
ErrorKind.internal, 'no libraryUri for $dartSourcePath');
}
final module = await _modules.moduleForLibrary(libraryUri.toString());
if (module == null) {
- return _createError(
+ return createError(
ErrorKind.internal, 'no module for $libraryUri ($dartSourcePath)');
}
@@ -228,7 +234,7 @@
var result = await _debugger.evaluateJsOnCallFrameIndex(frameIndex, jsCode);
result = await _formatEvaluationError(result);
- _logger.finest('Evaluated "$expression" to "$result"');
+ _logger.finest('Evaluated "$expression" to "${result.json}"');
return result;
}
@@ -248,10 +254,10 @@
}
if (error.contains('InternalError: ')) {
error = error.replaceAll('InternalError: ', '');
- return _createError(ErrorKind.internal, error);
+ return createError(ErrorKind.internal, error);
}
error = error.replaceAll(_syntheticNameFilterRegex, '');
- return _createError(ErrorKind.compilation, error);
+ return createError(ErrorKind.compilation, error);
}
Future<RemoteObject> _formatEvaluationError(RemoteObject result) async {
@@ -259,10 +265,10 @@
var error = '${result.value}';
if (error.startsWith('ReferenceError: ')) {
error = error.replaceFirst('ReferenceError: ', '');
- return _createError(ErrorKind.reference, error);
+ return createError(ErrorKind.reference, error);
} else if (error.startsWith('TypeError: ')) {
error = error.replaceFirst('TypeError: ', '');
- return _createError(ErrorKind.type, error);
+ return createError(ErrorKind.type, error);
} else if (error.startsWith('NetworkError: ')) {
var modulePath = _loadModuleErrorRegex.firstMatch(error)?.group(1);
final module = modulePath != null
@@ -273,7 +279,7 @@
error = 'Module is not loaded : $module (path: $modulePath). '
'Accessing libraries that have not yet been used in the '
'application is not supported during expression evaluation.';
- return _createError(ErrorKind.loadModule, error);
+ return createError(ErrorKind.loadModule, error);
}
}
return result;
diff --git a/dwds/lib/src/utilities/sdk_configuration.dart b/dwds/lib/src/utilities/sdk_configuration.dart
index 0439e01..4e20b73 100644
--- a/dwds/lib/src/utilities/sdk_configuration.dart
+++ b/dwds/lib/src/utilities/sdk_configuration.dart
@@ -2,6 +2,7 @@
// 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:async';
import 'dart:io';
import 'package:file/file.dart';
@@ -24,11 +25,128 @@
/// SDK configuration provider interface.
///
/// Supports lazily populated configurations by allowing to create
-/// configuration asyncronously.
+/// configuration asynchronously.
abstract class SdkConfigurationProvider {
+ const SdkConfigurationProvider();
+
Future<SdkConfiguration> get configuration;
}
+/// Sdk layout.
+///
+/// Contains definition of the default SDK layout.
+/// We keep all the path constants in one place for ease of update.
+class SdkLayout {
+ static final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
+ static final defaultSdkLayout = createDefault(sdkDir);
+
+ static SdkLayout createDefault(String sdkDirectory) {
+ final sdkJsWeakFileName = 'dart_sdk.js';
+ final sdkJsMapWeakFileName = 'dart_sdk.js.map';
+ final sdkJsSoundFileName = 'dart_sdk_sound.js';
+ final sdkJsMapSoundFileName = 'dart_sdk_sound.js.map';
+ final sdkSummarySoundFileName = 'ddc_outline.dill';
+ final sdkSummaryWeakFileName = 'ddc_outline_unsound.dill';
+ final sdkFullDillSoundFileName = 'ddc_platform.dill';
+ final sdkFullDillWeakFileName = 'ddc_platform_unsound.dill';
+
+ final sdkSummaryDirectory = p.join(sdkDirectory, 'lib', '_internal');
+ final sdkJsDirectory =
+ p.join(sdkDirectory, 'lib', 'dev_compiler', 'kernel', 'amd');
+
+ final soundSummaryPath =
+ p.join(sdkSummaryDirectory, sdkSummarySoundFileName);
+ final soundFullDillPath =
+ p.join(sdkSummaryDirectory, sdkFullDillSoundFileName);
+ final soundJsPath = p.join(sdkJsDirectory, sdkJsSoundFileName);
+ final soundJsMapPath = p.join(sdkJsDirectory, sdkJsMapSoundFileName);
+
+ final weakSummaryPath = p.join(sdkSummaryDirectory, sdkSummaryWeakFileName);
+ final weakFullDillPath =
+ p.join(sdkSummaryDirectory, sdkFullDillWeakFileName);
+ final weakJsPath = p.join(sdkJsDirectory, sdkJsWeakFileName);
+ final weakJsMapPath = p.join(sdkJsDirectory, sdkJsMapWeakFileName);
+
+ final librariesPath = p.join(sdkDirectory, 'lib', 'libraries.json');
+ final dartdevcSnapshotPath =
+ p.join(sdkDirectory, 'bin', 'snapshots', 'dartdevc.dart.snapshot');
+ final kernelWorkerSnapshotPath =
+ p.join(sdkDirectory, 'bin', 'snapshots', 'kernel_worker.dart.snapshot');
+
+ return SdkLayout(
+ sdkJsWeakFileName: sdkJsWeakFileName,
+ sdkJsMapWeakFileName: sdkJsMapWeakFileName,
+ sdkJsSoundFileName: sdkJsSoundFileName,
+ sdkJsMapSoundFileName: sdkJsMapSoundFileName,
+ sdkSummarySoundFileName: sdkSummarySoundFileName,
+ sdkSummaryWeakFileName: sdkSummaryWeakFileName,
+ sdkFullDillSoundFileName: sdkFullDillSoundFileName,
+ sdkFullDillWeakFileName: sdkFullDillWeakFileName,
+ sdkDirectory: sdkDirectory,
+ soundSummaryPath: soundSummaryPath,
+ soundFullDillPath: soundFullDillPath,
+ soundJsPath: soundJsPath,
+ soundJsMapPath: soundJsMapPath,
+ weakSummaryPath: weakSummaryPath,
+ weakFullDillPath: weakFullDillPath,
+ weakJsPath: weakJsPath,
+ weakJsMapPath: weakJsMapPath,
+ librariesPath: librariesPath,
+ dartdevcSnapshotPath: dartdevcSnapshotPath,
+ kernelWorkerSnapshotPath: kernelWorkerSnapshotPath,
+ );
+ }
+
+ final String sdkJsWeakFileName;
+ final String sdkJsMapWeakFileName;
+ final String sdkJsSoundFileName;
+ final String sdkJsMapSoundFileName;
+ final String sdkSummarySoundFileName;
+ final String sdkSummaryWeakFileName;
+ final String sdkFullDillSoundFileName;
+ final String sdkFullDillWeakFileName;
+
+ final String sdkDirectory;
+
+ final String soundSummaryPath;
+ final String soundFullDillPath;
+ final String soundJsPath;
+ final String soundJsMapPath;
+
+ final String weakSummaryPath;
+ final String weakFullDillPath;
+ final String weakJsPath;
+ final String weakJsMapPath;
+
+ final String librariesPath;
+
+ final String dartdevcSnapshotPath;
+ final String kernelWorkerSnapshotPath;
+
+ SdkLayout({
+ required this.sdkJsWeakFileName,
+ required this.sdkJsMapWeakFileName,
+ required this.sdkJsSoundFileName,
+ required this.sdkJsMapSoundFileName,
+ required this.sdkSummarySoundFileName,
+ required this.sdkSummaryWeakFileName,
+ required this.sdkFullDillSoundFileName,
+ required this.sdkFullDillWeakFileName,
+ required this.sdkDirectory,
+ required this.soundSummaryPath,
+ required this.soundFullDillPath,
+ required this.soundJsPath,
+ required this.soundJsMapPath,
+ required this.weakSummaryPath,
+ required this.weakFullDillPath,
+ required this.weakJsPath,
+ required this.weakJsMapPath,
+ required this.librariesPath,
+ required this.dartdevcSnapshotPath,
+ required this.kernelWorkerSnapshotPath,
+ });
+}
+
/// Data class describing the SDK layout.
///
/// Provides helpers to convert paths to uris that work on all platforms.
@@ -36,30 +154,42 @@
/// Call [validate] method to make sure the files in the configuration
/// layout exist before reading the files.
class SdkConfiguration {
- // TODO(annagrin): update the tests to take those parameters
- // and make all of the paths required (except for the compilerWorkerPath
- // that is not used in Flutter).
+ static final defaultSdkLayout = SdkLayout.defaultSdkLayout;
+ static final defaultConfiguration =
+ SdkConfiguration.fromSdkLayout(defaultSdkLayout);
+
String? sdkDirectory;
- String? unsoundSdkSummaryPath;
+ String? weakSdkSummaryPath;
String? soundSdkSummaryPath;
String? librariesPath;
String? compilerWorkerPath;
SdkConfiguration({
this.sdkDirectory,
- this.unsoundSdkSummaryPath,
+ this.weakSdkSummaryPath,
this.soundSdkSummaryPath,
this.librariesPath,
this.compilerWorkerPath,
});
+ SdkConfiguration.empty() : this();
+
+ SdkConfiguration.fromSdkLayout(SdkLayout sdkLayout)
+ : this(
+ sdkDirectory: sdkLayout.sdkDirectory,
+ weakSdkSummaryPath: sdkLayout.weakSummaryPath,
+ soundSdkSummaryPath: sdkLayout.soundSummaryPath,
+ librariesPath: sdkLayout.librariesPath,
+ compilerWorkerPath: sdkLayout.dartdevcSnapshotPath,
+ );
+
static Uri? _toUri(String? path) => path == null ? null : p.toUri(path);
static Uri? _toAbsoluteUri(String? path) =>
path == null ? null : p.toUri(p.absolute(path));
Uri? get sdkDirectoryUri => _toUri(sdkDirectory);
Uri? get soundSdkSummaryUri => _toUri(soundSdkSummaryPath);
- Uri? get unsoundSdkSummaryUri => _toUri(unsoundSdkSummaryPath);
+ Uri? get weakSdkSummaryUri => _toUri(weakSdkSummaryPath);
Uri? get librariesUri => _toUri(librariesPath);
/// Note: has to be ///file: Uri to run in an isolate.
@@ -85,14 +215,23 @@
}
void validateSummaries({FileSystem fileSystem = const LocalFileSystem()}) {
- if (unsoundSdkSummaryPath == null ||
- !fileSystem.file(unsoundSdkSummaryPath).existsSync()) {
- throw InvalidSdkConfigurationException(
- 'Sdk summary $unsoundSdkSummaryPath does not exist');
- }
+ validateSoundSummaries(fileSystem: fileSystem);
+ validateWeakSummaries(fileSystem: fileSystem);
+ }
- if (soundSdkSummaryPath == null ||
- !fileSystem.file(soundSdkSummaryPath).existsSync()) {
+ void validateWeakSummaries(
+ {FileSystem fileSystem = const LocalFileSystem()}) {
+ if (weakSdkSummaryPath == null ||
+ !fileSystem.file(weakSdkSummaryPath).existsSync()) {
+ throw InvalidSdkConfigurationException(
+ 'Sdk summary $weakSdkSummaryPath does not exist');
+ }
+ }
+
+ void validateSoundSummaries(
+ {FileSystem fileSystem = const LocalFileSystem()}) {
+ if ((soundSdkSummaryPath == null ||
+ !fileSystem.file(soundSdkSummaryPath).existsSync())) {
throw InvalidSdkConfigurationException(
'Sdk summary $soundSdkSummaryPath does not exist');
}
@@ -116,27 +255,10 @@
}
}
-/// Implementation for the default SDK configuration layout.
class DefaultSdkConfigurationProvider extends SdkConfigurationProvider {
- DefaultSdkConfigurationProvider();
+ const DefaultSdkConfigurationProvider();
- late final SdkConfiguration _configuration = _create();
-
- /// Create and validate configuration matching the default SDK layout.
@override
- Future<SdkConfiguration> get configuration async => _configuration;
-
- SdkConfiguration _create() {
- final binDir = p.dirname(Platform.resolvedExecutable);
- final sdkDir = p.dirname(binDir);
-
- return SdkConfiguration(
- sdkDirectory: sdkDir,
- unsoundSdkSummaryPath: p.join(sdkDir, 'lib', '_internal', 'ddc_sdk.dill'),
- soundSdkSummaryPath:
- p.join(sdkDir, 'lib', '_internal', 'ddc_outline_sound.dill'),
- librariesPath: p.join(sdkDir, 'lib', 'libraries.json'),
- compilerWorkerPath: p.join(binDir, 'snapshots', 'dartdevc.dart.snapshot'),
- );
- }
+ Future<SdkConfiguration> get configuration async =>
+ SdkConfiguration.defaultConfiguration;
}
diff --git a/dwds/lib/src/utilities/synchronized.dart b/dwds/lib/src/utilities/synchronized.dart
new file mode 100644
index 0000000..e318817
--- /dev/null
+++ b/dwds/lib/src/utilities/synchronized.dart
@@ -0,0 +1,16 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:pool/pool.dart';
+
+class AtomicQueue {
+ final _pool = Pool(1);
+
+ AtomicQueue();
+
+ // Executes tasks sequentially.
+ Future<T> run<T>(FutureOr<T> Function() task) => _pool.withResource(task);
+}
diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart
index 4b410c7..7b88465 100644
--- a/dwds/lib/src/version.dart
+++ b/dwds/lib/src/version.dart
@@ -1,2 +1,2 @@
// Generated code. Do not modify.
-const packageVersion = '16.0.3';
+const packageVersion = '17.0.0';
diff --git a/dwds/mono_pkg.yaml b/dwds/mono_pkg.yaml
index d5dc531..15b5504 100644
--- a/dwds/mono_pkg.yaml
+++ b/dwds/mono_pkg.yaml
@@ -6,20 +6,53 @@
- analyze: --fatal-infos .
- test: test/build/ensure_version_test.dart
sdk: dev
- - group:
- - analyze: .
- - test: test/build/min_sdk_test.dart --run-skipped
- sdk: stable
- unit_test:
+ # Linux extension tests:
+ # Note: `Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &` must be
+ # run first for Linux.
- group:
- command: Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- - test:
+ - test: --tags=extension
sdk:
- - stable
- - test:
- os: windows
+ - dev
+ - main
+ os:
+ - linux
+ # Windows extension tests:
+ - group:
+ - test: --tags=extension
sdk:
- - stable
+ - dev
+ - main
+ os:
+ - windows
+ # First test shard:
+ - group:
+ - test: --total-shards 3 --shard-index 0 --exclude-tags=extension
+ sdk:
+ - dev
+ - main
+ os:
+ - linux
+ - windows
+ # Second test shard:
+ - group:
+ - test: --total-shards 3 --shard-index 1 --exclude-tags=extension
+ sdk:
+ - dev
+ - main
+ os:
+ - linux
+ - windows
+ # Third test shard:
+ - group:
+ - test: --total-shards 3 --shard-index 2 --exclude-tags=extension
+ sdk:
+ - dev
+ - main
+ os:
+ - linux
+ - windows
- beta_cron:
- analyze: .
sdk: beta
diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml
index 2e2d46e..3a8d084 100644
--- a/dwds/pubspec.yaml
+++ b/dwds/pubspec.yaml
@@ -1,13 +1,13 @@
name: dwds
# Every time this changes you need to run `dart run build_runner build`.
-version: 16.0.3
+version: 17.0.0
description: >-
A service that proxies between the Chrome debug protocol and the Dart VM
service protocol.
repository: https://github.com/dart-lang/webdev/tree/master/dwds
environment:
- sdk: ">=2.19.0 <3.0.0"
+ sdk: ">=3.0.0-134.0.dev <4.0.0"
dependencies:
async: ^2.9.0
@@ -32,7 +32,7 @@
shelf_web_socket: ^1.0.1
source_maps: ^0.10.10
stack_trace: ^1.10.0
- sse: ^4.1.0
+ sse: ^4.1.2
uuid: ^3.0.6
vm_service: ">=10.1.0 <12.0.0"
web_socket_channel: ^2.2.0
@@ -42,9 +42,9 @@
args: ^2.3.1
build: ^2.3.0
build_daemon: ^3.1.0
- build_runner: ^2.1.10
+ build_runner: ^2.4.0
build_version: ^2.1.1
- build_web_compilers: ^3.2.3
+ build_web_compilers: ^4.0.0
built_value_generator: ^8.3.0
graphs: ^2.1.0
frontend_server_common:
@@ -52,6 +52,7 @@
js: ^0.6.4
lints: ^2.0.0
pubspec_parse: ^1.2.0
+ puppeteer: ^2.19.0
stream_channel: ^2.1.0
test: ^1.21.1
webdriver: ^3.0.0
diff --git a/dwds/web/client.dart b/dwds/web/client.dart
index c89091f..61e1cdd 100644
--- a/dwds/web/client.dart
+++ b/dwds/web/client.dart
@@ -8,11 +8,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:html';
+import 'dart:js';
import 'package:built_collection/built_collection.dart';
import 'package:dwds/data/build_result.dart';
import 'package:dwds/data/connect_request.dart';
import 'package:dwds/data/debug_event.dart';
+import 'package:dwds/data/debug_info.dart';
import 'package:dwds/data/devtools_request.dart';
import 'package:dwds/data/error_response.dart';
import 'package:dwds/data/register_event.dart';
@@ -22,6 +24,7 @@
// NOTE(annagrin): using 'package:dwds/src/utilities/batched_stream.dart'
// makes dart2js skip creating background.js, so we use a copy instead.
// import 'package:dwds/src/utilities/batched_stream.dart';
+// Issue: https://github.com/dart-lang/sdk/issues/49973
import 'package:dwds/src/web_utilities/batched_stream.dart';
import 'package:js/js.dart';
import 'package:sse/client/sse_client.dart';
@@ -49,7 +52,7 @@
final fixedUri = Uri.parse(fixedPath);
final client = fixedUri.isScheme('ws') || fixedUri.isScheme('wss')
? WebSocketClient(WebSocketChannel.connect(fixedUri))
- : SseSocketClient(SseClient(fixedPath));
+ : SseSocketClient(SseClient(fixedPath, debugKey: 'InjectedClient'));
Restarter restarter;
if (dartModuleStrategy == 'require-js') {
@@ -170,7 +173,18 @@
// If not Chromium we just invoke main, devtools aren't supported.
runMain();
}
- dispatchEvent(CustomEvent('dart-app-ready'));
+ final windowContext = JsObject.fromBrowserObject(window);
+ final debugInfoJson = jsonEncode(serializers.serialize(DebugInfo((b) => b
+ ..appEntrypointPath = dartEntrypointPath
+ ..appId = windowContext['\$dartAppId']
+ ..appInstanceId = dartAppInstanceId
+ ..appOrigin = window.location.origin
+ ..appUrl = window.location.href
+ ..extensionUrl = windowContext['\$dartExtensionUri']
+ ..isInternalBuild = windowContext['\$isInternalBuild']
+ ..isFlutterApp = windowContext['\$isFlutterApp'])));
+
+ dispatchEvent(CustomEvent('dart-app-ready', detail: debugInfoJson));
}, (error, stackTrace) {
print('''
Unhandled error detected in the injected client.js script.
@@ -262,4 +276,10 @@
@JS(r'$emitRegisterEvent')
external set emitRegisterEvent(void Function(String) func);
+@JS(r'$isInternalBuild')
+external bool get isInternalBuild;
+
+@JS(r'$isFlutterApp')
+external bool get isFlutterApp;
+
bool get _isChromium => window.navigator.vendor.contains('Google');
diff --git a/fixnum/BUILD.gn b/fixnum/BUILD.gn
index f573508..0f5414b 100644
--- a/fixnum/BUILD.gn
+++ b/fixnum/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for fixnum-1.0.1
+# This file is generated by package_importer.py for fixnum-1.1.0
import("//build/dart/dart_library.gni")
dart_library("fixnum") {
package_name = "fixnum"
- language_version = "2.12"
+ language_version = "2.19"
disable_analysis = true
@@ -17,5 +17,6 @@
"src/int32.dart",
"src/int64.dart",
"src/intx.dart",
+ "src/utilities.dart",
]
}
diff --git a/fixnum/CHANGELOG.md b/fixnum/CHANGELOG.md
index 441c466..f731296 100644
--- a/fixnum/CHANGELOG.md
+++ b/fixnum/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 1.1.0
+
+* Add `tryParseRadix`, `tryParseInt` and `tryParseHex` static methods
+ to both `Int32` and `Int64`.
+* Document exception and overflow behavior of parse functions,
+ and of `toHexString`.
+* Make `Int32` parse functions consistent with documentation (accept
+ leading minus sign, do not accept empty inputs).
+* Update the minimum SDK constraint to 2.19.
+* Update to package:lints 2.0.0.
+
## 1.0.1
* Switch to using `package:lints`.
diff --git a/fixnum/README.md b/fixnum/README.md
index 33bdcc0..332d900 100644
--- a/fixnum/README.md
+++ b/fixnum/README.md
@@ -7,3 +7,8 @@
Provides data types for signed 32- and 64-bit integers.
The integer implementations in this library are designed to work identically
whether executed on the Dart VM or compiled to JavaScript.
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
diff --git a/fixnum/analysis_options.yaml b/fixnum/analysis_options.yaml
index 7b7d7ce..2e3ed19 100644
--- a/fixnum/analysis_options.yaml
+++ b/fixnum/analysis_options.yaml
@@ -1,8 +1,8 @@
include: package:lints/recommended.yaml
analyzer:
- strong-mode:
- implicit-casts: false
+ language:
+ strict-casts: true
linter:
rules:
@@ -15,7 +15,6 @@
- cascade_invocations
- comment_references
- directives_ordering
- - invariant_booleans
- join_return_with_assignment
- lines_longer_than_80_chars
- missing_whitespace_between_adjacent_strings
diff --git a/fixnum/lib/fixnum.dart b/fixnum/lib/fixnum.dart
index 72f9742..eeb6def 100644
--- a/fixnum/lib/fixnum.dart
+++ b/fixnum/lib/fixnum.dart
@@ -8,6 +8,6 @@
/// identically whether executed on the Dart VM or compiled to JavaScript.
library fixnum;
-part 'src/intx.dart';
-part 'src/int32.dart';
-part 'src/int64.dart';
+export 'src/int32.dart';
+export 'src/int64.dart';
+export 'src/intx.dart';
diff --git a/fixnum/lib/src/int32.dart b/fixnum/lib/src/int32.dart
index 760bc22..8045bc1 100644
--- a/fixnum/lib/src/int32.dart
+++ b/fixnum/lib/src/int32.dart
@@ -4,7 +4,9 @@
// ignore_for_file: constant_identifier_names
-part of fixnum;
+import 'int64.dart';
+import 'intx.dart';
+import 'utilities.dart' as u;
/// An immutable 32-bit signed integer, in the range [-2^31, 2^31 - 1].
/// Arithmetic operations may overflow in order to maintain this range.
@@ -26,92 +28,138 @@
/// An [Int32] constant equal to 2.
static const Int32 TWO = Int32._internal(2);
- // Hex digit char codes
- static const int _CC_0 = 48; // '0'.codeUnitAt(0)
- static const int _CC_9 = 57; // '9'.codeUnitAt(0)
- static const int _CC_a = 97; // 'a'.codeUnitAt(0)
- static const int _CC_z = 122; // 'z'.codeUnitAt(0)
- static const int _CC_A = 65; // 'A'.codeUnitAt(0)
- static const int _CC_Z = 90; // 'Z'.codeUnitAt(0)
+ // Mask to 32-bits.
+ static const int _MASK_U32 = 0xFFFFFFFF;
- static int _decodeDigit(int c) {
- if (c >= _CC_0 && c <= _CC_9) {
- return c - _CC_0;
- } else if (c >= _CC_a && c <= _CC_z) {
- return c - _CC_a + 10;
- } else if (c >= _CC_A && c <= _CC_Z) {
- return c - _CC_A + 10;
- } else {
- return -1; // bad char code
- }
- }
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// integer numeral in base [radix].
+ static Int32 parseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), true)!;
- static int _validateRadix(int radix) {
- if (2 <= radix && radix <= 36) return radix;
- throw RangeError.range(radix, 2, 36, 'radix');
- }
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// integer numeral in base [radix].
+ static Int32? tryParseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), false);
- /// Parses a [String] in a given [radix] between 2 and 16 and returns an
- /// [Int32].
// TODO(rice) - Make this faster by converting several digits at once.
- static Int32 parseRadix(String s, int radix) {
- _validateRadix(radix);
- var x = ZERO;
- for (var i = 0; i < s.length; i++) {
- var c = s.codeUnitAt(i);
- var digit = _decodeDigit(c);
- if (digit < 0 || digit >= radix) {
- throw FormatException('Non-radix code unit: $c');
- }
- x = (x * radix) + digit as Int32;
+ static Int32? _parseRadix(String s, int radix, bool throwOnError) {
+ var index = 0;
+ var negative = false;
+ if (s.startsWith('-')) {
+ negative = true;
+ index = 1;
}
- return x;
+ if (index == s.length) {
+ if (!throwOnError) return null;
+ throw FormatException('No digits', s, index);
+ }
+ var result = 0;
+ for (; index < s.length; index++) {
+ var c = s.codeUnitAt(index);
+ var digit = u.decodeDigit(c);
+ if (digit < radix) {
+ /// Doesn't matter whether the result is unsigned
+ /// or signed (as on the web), only the bits matter
+ /// to the [Int32.new] constructor.
+ result = (result * radix + digit) & _MASK_U32;
+ } else {
+ if (!throwOnError) return null;
+ throw FormatException('Non radix code unit', s, index);
+ }
+ }
+ if (negative) result = -result;
+ return Int32(result);
}
- /// Parses a decimal [String] and returns an [Int32].
- static Int32 parseInt(String s) => Int32(int.parse(s));
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// decimal integer numeral.
+ static Int32 parseInt(String source) => _parseRadix(source, 10, true)!;
- /// Parses a hexadecimal [String] and returns an [Int32].
- static Int32 parseHex(String s) => parseRadix(s, 16);
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// decimal integer numeral.
+ static Int32? tryParseInt(String source) => _parseRadix(source, 10, false);
- // Assumes i is <= 32-bit.
- static int _bitCount(int i) {
- // See "Hacker's Delight", section 5-1, "Counting 1-Bits".
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int32 parseHex(String source) => _parseRadix(source, 16, true)!;
- // The basic strategy is to use "divide and conquer" to
- // add pairs (then quads, etc.) of bits together to obtain
- // sub-counts.
- //
- // A straightforward approach would look like:
- //
- // i = (i & 0x55555555) + ((i >> 1) & 0x55555555);
- // i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
- // i = (i & 0x0F0F0F0F) + ((i >> 4) & 0x0F0F0F0F);
- // i = (i & 0x00FF00FF) + ((i >> 8) & 0x00FF00FF);
- // i = (i & 0x0000FFFF) + ((i >> 16) & 0x0000FFFF);
- //
- // The code below removes unnecessary &'s and uses a
- // trick to remove one instruction in the first line.
-
- i -= (i >> 1) & 0x55555555;
- i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
- i = (i + (i >> 4)) & 0x0F0F0F0F;
- i += i >> 8;
- i += i >> 16;
- return i & 0x0000003F;
- }
-
- // Assumes i is <= 32-bit
- static int _numberOfLeadingZeros(int i) {
- i |= i >> 1;
- i |= i >> 2;
- i |= i >> 4;
- i |= i >> 8;
- i |= i >> 16;
- return _bitCount(~i);
- }
-
- static int _numberOfTrailingZeros(int i) => _bitCount((i & -i) - 1);
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int32? tryParseHex(String source) => _parseRadix(source, 16, false);
// The internal value, kept in the range [MIN_VALUE, MAX_VALUE].
final int _i;
@@ -130,7 +178,7 @@
} else if (val is int) {
return val;
}
- throw ArgumentError(val);
+ throw ArgumentError.value(val, 'other', 'Not an int, Int32 or Int64');
}
// The +, -, * , &, |, and ^ operaters deal with types as follows:
@@ -369,10 +417,10 @@
}
@override
- int numberOfLeadingZeros() => _numberOfLeadingZeros(_i);
+ int numberOfLeadingZeros() => u.numberOfLeadingZeros(_i);
@override
- int numberOfTrailingZeros() => _numberOfTrailingZeros(_i);
+ int numberOfTrailingZeros() => u.numberOfTrailingZeros(_i);
@override
Int32 toSigned(int width) {
diff --git a/fixnum/lib/src/int64.dart b/fixnum/lib/src/int64.dart
index 307a08f..e4e2ba4 100644
--- a/fixnum/lib/src/int64.dart
+++ b/fixnum/lib/src/int64.dart
@@ -9,7 +9,9 @@
//
// ignore_for_file: omit_local_variable_types
-part of fixnum;
+import 'int32.dart';
+import 'intx.dart';
+import 'utilities.dart' as u;
/// An immutable 64-bit signed integer, in the range [-2^63, 2^63 - 1].
/// Arithmetic operations may overflow in order to maintain this range.
@@ -57,45 +59,76 @@
/// is performed.
const Int64._bits(this._l, this._m, this._h);
- /// Parses a [String] in a given [radix] between 2 and 36 and returns an
- /// [Int64].
- static Int64 parseRadix(String s, int radix) =>
- _parseRadix(s, Int32._validateRadix(radix));
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not recognized as a valid
+ /// integer numeral.
+ static Int64 parseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), true)!;
- static Int64 _parseRadix(String s, int radix) {
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not recognized as a valid
+ /// integer numeral.
+ static Int64? tryParseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), false);
+
+ static Int64? _parseRadix(String s, int radix, bool throwOnError) {
int i = 0;
bool negative = false;
- if (i < s.length && s[0] == '-') {
+ if (s.startsWith('-')) {
negative = true;
i++;
}
- // TODO(https://github.com/dart-lang/sdk/issues/38728). Replace with "if (i
- // >= s.length)".
- if (!(i < s.length)) {
- throw FormatException("No digits in '$s'");
+ if (i >= s.length) {
+ if (!throwOnError) return null;
+ throw FormatException('No digits', s, i);
}
int d0 = 0, d1 = 0, d2 = 0; // low, middle, high components.
for (; i < s.length; i++) {
int c = s.codeUnitAt(i);
- int digit = Int32._decodeDigit(c);
- if (digit < 0 || digit >= radix) {
- throw FormatException('Non-radix char code: $c');
+ int digit = u.decodeDigit(c);
+ if (digit < radix) {
+ // [radix] and [digit] are at most 6 bits, component is 22, so we can
+ // multiply and add within 30 bit temporary values.
+ d0 = d0 * radix + digit;
+ int carry = d0 >> _BITS;
+ d0 = _MASK & d0;
+
+ d1 = d1 * radix + carry;
+ carry = d1 >> _BITS;
+ d1 = _MASK & d1;
+
+ d2 = d2 * radix + carry;
+ d2 = _MASK2 & d2;
+ } else {
+ if (!throwOnError) return null;
+ throw FormatException('Not radix digit', s, i);
}
-
- // [radix] and [digit] are at most 6 bits, component is 22, so we can
- // multiply and add within 30 bit temporary values.
- d0 = d0 * radix + digit;
- int carry = d0 >> _BITS;
- d0 = _MASK & d0;
-
- d1 = d1 * radix + carry;
- carry = d1 >> _BITS;
- d1 = _MASK & d1;
-
- d2 = d2 * radix + carry;
- d2 = _MASK2 & d2;
}
if (negative) return _negate(d0, d1, d2);
@@ -103,11 +136,69 @@
return Int64._masked(d0, d1, d2);
}
- /// Parses a decimal [String] and returns an [Int64].
- static Int64 parseInt(String s) => _parseRadix(s, 10);
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// decimal integer numeral.
+ static Int64 parseInt(String source) => _parseRadix(source, 10, true)!;
- /// Parses a hexadecimal [String] and returns an [Int64].
- static Int64 parseHex(String s) => _parseRadix(s, 16);
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// decimal integer numeral.
+ static Int64? tryParseInt(String source) => _parseRadix(source, 10, false);
+
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int64 parseHex(String source) => _parseRadix(source, 16, true)!;
+
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int64? tryParseHex(String source) => _parseRadix(source, 16, false);
//
// Public constructors
@@ -135,43 +226,32 @@
}
factory Int64.fromBytes(List<int> bytes) {
- int top = bytes[7] & 0xff;
- top <<= 8;
- top |= bytes[6] & 0xff;
- top <<= 8;
- top |= bytes[5] & 0xff;
- top <<= 8;
- top |= bytes[4] & 0xff;
-
- int bottom = bytes[3] & 0xff;
- bottom <<= 8;
- bottom |= bytes[2] & 0xff;
- bottom <<= 8;
- bottom |= bytes[1] & 0xff;
- bottom <<= 8;
- bottom |= bytes[0] & 0xff;
-
- return Int64.fromInts(top, bottom);
+ // 20 bits into top, 22 into middle and bottom.
+ var split1 = bytes[5] & 0xFF;
+ var high =
+ ((bytes[7] & 0xFF) << 12) | ((bytes[6] & 0xFF) << 4) | (split1 >> 4);
+ var split2 = bytes[2] & 0xFF;
+ var middle = (split1 << 18) |
+ ((bytes[4] & 0xFF) << 10) |
+ ((bytes[3] & 0xFF) << 2) |
+ (split2 >> 6);
+ var low = (split2 << 16) | ((bytes[1] & 0xFF) << 8) | (bytes[0] & 0xFF);
+ // Top bits from above will be masked off here.
+ return Int64._masked(low, middle, high);
}
factory Int64.fromBytesBigEndian(List<int> bytes) {
- int top = bytes[0] & 0xff;
- top <<= 8;
- top |= bytes[1] & 0xff;
- top <<= 8;
- top |= bytes[2] & 0xff;
- top <<= 8;
- top |= bytes[3] & 0xff;
-
- int bottom = bytes[4] & 0xff;
- bottom <<= 8;
- bottom |= bytes[5] & 0xff;
- bottom <<= 8;
- bottom |= bytes[6] & 0xff;
- bottom <<= 8;
- bottom |= bytes[7] & 0xff;
-
- return Int64.fromInts(top, bottom);
+ var split1 = bytes[2] & 0xFF;
+ var high =
+ ((bytes[0] & 0xFF) << 12) | ((bytes[1] & 0xFF) << 4) | (split1 >> 4);
+ var split2 = bytes[5] & 0xFF;
+ var middle = (split1 << 18) |
+ ((bytes[3] & 0xFF) << 10) |
+ ((bytes[4] & 0xFF) << 2) |
+ (split2 >> 6);
+ var low = (split2 << 16) | ((bytes[6] & 0xFF) << 8) | (bytes[7] & 0xFF);
+ // Top bits from above will be masked off here.
+ return Int64._masked(low, middle, high);
}
/// Constructs an [Int64] from a pair of 32-bit integers having the value
@@ -195,7 +275,7 @@
} else if (value is Int32) {
return value.toInt64();
}
- throw ArgumentError.value(value);
+ throw ArgumentError.value(value, 'other', 'not an int, Int32 or Int64');
}
@override
@@ -558,11 +638,11 @@
/// between 0 and 64.
@override
int numberOfLeadingZeros() {
- int b2 = Int32._numberOfLeadingZeros(_h);
+ int b2 = u.numberOfLeadingZeros(_h);
if (b2 == 32) {
- int b1 = Int32._numberOfLeadingZeros(_m);
+ int b1 = u.numberOfLeadingZeros(_m);
if (b1 == 32) {
- return Int32._numberOfLeadingZeros(_l) + 32;
+ return u.numberOfLeadingZeros(_l) + 32;
} else {
return b1 + _BITS2 - (32 - _BITS);
}
@@ -575,17 +655,17 @@
/// between 0 and 64.
@override
int numberOfTrailingZeros() {
- int zeros = Int32._numberOfTrailingZeros(_l);
+ int zeros = u.numberOfTrailingZeros(_l);
if (zeros < 32) {
return zeros;
}
- zeros = Int32._numberOfTrailingZeros(_m);
+ zeros = u.numberOfTrailingZeros(_m);
if (zeros < 32) {
return _BITS + zeros;
}
- zeros = Int32._numberOfTrailingZeros(_h);
+ zeros = u.numberOfTrailingZeros(_h);
if (zeros < 32) {
return _BITS01 + zeros;
}
@@ -672,7 +752,6 @@
@override
String toString() => _toRadixString(10);
- // TODO(rice) - Make this faster by avoiding arithmetic.
@override
String toHexString() {
if (isZero) return '0';
@@ -692,11 +771,10 @@
@pragma('dart2js:noInline')
String toRadixStringUnsigned(int radix) =>
- _toRadixStringUnsigned(Int32._validateRadix(radix), _l, _m, _h, '');
+ _toRadixStringUnsigned(u.validateRadix(radix), _l, _m, _h, '');
@override
- String toRadixString(int radix) =>
- _toRadixString(Int32._validateRadix(radix));
+ String toRadixString(int radix) => _toRadixString(u.validateRadix(radix));
String _toRadixString(int radix) {
int d0 = _l;
@@ -861,8 +939,8 @@
String toDebugString() => 'Int64[_l=$_l, _m=$_m, _h=$_h]';
- static Int64 _masked(int a0, int a1, int a2) =>
- Int64._bits(_MASK & a0, _MASK & a1, _MASK2 & a2);
+ static Int64 _masked(int low, int medium, int high) =>
+ Int64._bits(_MASK & low, _MASK & medium, _MASK2 & high);
static Int64 _sub(int a0, int a1, int a2, int b0, int b1, int b2) {
int diff0 = a0 - b0;
@@ -893,8 +971,7 @@
static Int64 _divide(Int64 a, other, int what) {
Int64 b = _promote(other);
if (b.isZero) {
- // ignore: deprecated_member_use
- throw const IntegerDivisionByZeroException();
+ throw UnsupportedError('Division by zero');
}
if (a.isZero) return ZERO;
diff --git a/fixnum/lib/src/intx.dart b/fixnum/lib/src/intx.dart
index bedb3e1..d51a583 100644
--- a/fixnum/lib/src/intx.dart
+++ b/fixnum/lib/src/intx.dart
@@ -2,7 +2,8 @@
// 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.
-part of fixnum;
+import 'int32.dart';
+import 'int64.dart';
/// A fixed-precision integer.
abstract class IntX implements Comparable<Object> {
@@ -188,7 +189,14 @@
String toString();
/// Returns a string representing the value of this integer in hexadecimal
- /// notation; example: `'0xd'`.
+ /// notation.
+ ///
+ /// Example: `Int64(0xf01d).toHexString()` returns `'F01D'`.
+ ///
+ /// The string may interprets the number as *unsigned*, and has no leading
+ /// minus, even if the value [isNegative].
+ ///
+ /// Example: `Int64(-1).toHexString()` returns `'FFFFFFFFFFFFFFFF'`.
String toHexString();
/// Returns a string representing the value of this integer in the given
diff --git a/fixnum/lib/src/utilities.dart b/fixnum/lib/src/utilities.dart
new file mode 100644
index 0000000..d603b57
--- /dev/null
+++ b/fixnum/lib/src/utilities.dart
@@ -0,0 +1,72 @@
+// 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.
+
+// Shared functionality used by multiple classes and their implementations.
+
+int validateRadix(int radix) =>
+ RangeError.checkValueInInterval(radix, 2, 36, 'radix');
+
+/// Converts radix digits into their numeric values.
+///
+/// Converts the characters `0`-`9` into the values 0 through 9,
+/// and the letters `a`-`z` or `A`-`Z` into values 10 through 35,
+/// and return that value.
+/// Any other character returns a value above 35, which means it's
+/// not a valid digit in any radix in the range 2 through 36.
+int decodeDigit(int c) {
+ // Hex digit char codes
+ const int c0 = 48; // '0'.codeUnitAt(0)
+ const int ca = 97; // 'a'.codeUnitAt(0)
+
+ int digit = c ^ c0;
+ if (digit < 10) return digit;
+ int letter = (c | 0x20) - ca;
+ if (letter >= 0) {
+ // Returns values above 36 for invalid digits.
+ // The value is checked against the actual radix where the return
+ // value is used, so this is safe.
+ return letter + 10;
+ } else {
+ return 255; // Never a valid radix.
+ }
+}
+
+// Assumes i is <= 32-bit
+int numberOfLeadingZeros(int i) {
+ i |= i >> 1;
+ i |= i >> 2;
+ i |= i >> 4;
+ i |= i >> 8;
+ i |= i >> 16;
+ return bitCount(~i);
+}
+
+int numberOfTrailingZeros(int i) => bitCount((i & -i) - 1);
+
+// Assumes i is <= 32-bit.
+int bitCount(int i) {
+ // See "Hacker's Delight", section 5-1, "Counting 1-Bits".
+
+ // The basic strategy is to use "divide and conquer" to
+ // add pairs (then quads, etc.) of bits together to obtain
+ // sub-counts.
+ //
+ // A straightforward approach would look like:
+ //
+ // i = (i & 0x55555555) + ((i >> 1) & 0x55555555);
+ // i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
+ // i = (i & 0x0F0F0F0F) + ((i >> 4) & 0x0F0F0F0F);
+ // i = (i & 0x00FF00FF) + ((i >> 8) & 0x00FF00FF);
+ // i = (i & 0x0000FFFF) + ((i >> 16) & 0x0000FFFF);
+ //
+ // The code below removes unnecessary &'s and uses a
+ // trick to remove one instruction in the first line.
+
+ i -= (i >> 1) & 0x55555555;
+ i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
+ i = (i + (i >> 4)) & 0x0F0F0F0F;
+ i += i >> 8;
+ i += i >> 16;
+ return i & 0x0000003F;
+}
diff --git a/fixnum/pubspec.yaml b/fixnum/pubspec.yaml
index 7353636..3180c25 100644
--- a/fixnum/pubspec.yaml
+++ b/fixnum/pubspec.yaml
@@ -1,13 +1,13 @@
name: fixnum
-version: 1.0.1
+version: 1.1.0
description: >-
Library for 32- and 64-bit signed fixed-width integers with consistent
behavior between native and JS runtimes.
repository: https://github.com/dart-lang/fixnum
environment:
- sdk: '>=2.12.0 <3.0.0'
+ sdk: '>=2.19.0 <3.0.0'
dev_dependencies:
- lints: ^1.0.0
+ lints: ^2.0.0
test: ^1.16.0
diff --git a/meta/BUILD.gn b/meta/BUILD.gn
index 808fec1..8d41990 100644
--- a/meta/BUILD.gn
+++ b/meta/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for meta-1.8.0
+# This file is generated by package_importer.py for meta-1.9.0
import("//build/dart/dart_library.gni")
diff --git a/meta/CHANGELOG.md b/meta/CHANGELOG.md
index 1d01881..9efa6a9 100644
--- a/meta/CHANGELOG.md
+++ b/meta/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 1.9.0
+
+* Introduce `@reopen` to annotate class or mixin declarations that can safely
+ extend classes marked `base`, `final` or `interface`.
+* Introduce `@MustBeOverridden` to annotate class or mixin members which must be
+ overridden in all subclasses.
+* Deprecate `@alwaysThrows`, which can be replaced by using a return type of
+ 'Never'.
+
## 1.8.0
* Add `@UseResult.unless`.
diff --git a/meta/analysis_options.yaml b/meta/analysis_options.yaml
new file mode 100644
index 0000000..572dd23
--- /dev/null
+++ b/meta/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:lints/recommended.yaml
diff --git a/meta/lib/meta.dart b/meta/lib/meta.dart
index ba14be9..93dc857 100644
--- a/meta/lib/meta.dart
+++ b/meta/lib/meta.dart
@@ -2,6 +2,8 @@
// 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.
+// ignore_for_file: library_private_types_in_public_api
+
/// Annotations that developers can use to express the intentions that otherwise
/// can't be deduced by statically analyzing the source code.
///
@@ -49,6 +51,7 @@
/// Tools, such as the analyzer, can also expect this contract to be enforced;
/// that is, tools may emit warnings if a function with this annotation
/// _doesn't_ always throw.
+@Deprecated("Use a return type of 'Never' instead")
const _AlwaysThrows alwaysThrows = _AlwaysThrows();
/// Used to annotate a parameter of an instance method that overrides another
@@ -169,19 +172,40 @@
/// constructor is not a compile-time constant.
const _Literal literal = _Literal();
-/// Used to annotate an instance method `m`. Indicates that every invocation of
-/// a method that overrides `m` must also invoke `m`. In addition, every method
-/// that overrides `m` is implicitly annotated with this same annotation.
+/// Used to annotate an instance member `m` declared on a class or mixin `C`.
+/// Indicates that every subclass of `C`, concrete or abstract, must directly
+/// override `m`.
///
-/// Note that private methods with this annotation cannot be validly overridden
-/// outside of the library that defines the annotated method.
+/// This annotation places no restrictions on the overriding members. In
+/// particular, it does not require that the overriding members invoke the
+/// overridden member. The annotation [mustCallSuper] can be used to add that
+/// requirement.
///
/// Tools, such as the analyzer, can provide feedback if
///
-/// * the annotation is associated with anything other than an instance method,
+/// * the annotation is associated with anything other than an instance member
+/// (a method, operator, field, getter, or setter) of a class or of a mixin,
/// or
-/// * a method that overrides a method that has this annotation can return
-/// without invoking the overridden method.
+/// * the annotation is associated with a member `m` in class or mixin `C`, and
+/// there is a class or mixin `D` which is a subclass of `C` (directly or
+/// indirectly), and `D` does not directly declare a concrete override of `m`
+/// and does not directly declare a concrete override of `noSuchMethod`.
+const _MustBeOverridden mustBeOverridden = _MustBeOverridden();
+
+/// Used to annotate an instance member (method, getter, setter, operator, or
+/// field) `m`. Indicates that every invocation of a member that overrides `m`
+/// must also invoke `m`. In addition, every method that overrides `m` is
+/// implicitly annotated with this same annotation.
+///
+/// Note that private members with this annotation cannot be validly overridden
+/// outside of the library that defines the annotated member.
+///
+/// Tools, such as the analyzer, can provide feedback if
+///
+/// * the annotation is associated with anything other than an instance member,
+/// or
+/// * a member that overrides a member that has this annotation can return
+/// without invoking the overridden member.
const _MustCallSuper mustCallSuper = _MustCallSuper();
/// Used to annotate an instance member (method, getter, setter, operator, or
@@ -244,6 +268,44 @@
// "referenced."
const _Protected protected = _Protected();
+/// Annotation for intentionally loosening restrictions on subtyping.
+///
+/// Indicates that the annotated class or mixin declaration
+/// intentionally allows subclasses to implement or extend it, even
+/// though it has a superclass which does not allow that.
+///
+/// A declaration annotated with `@reopen` will not generate warnings from the
+/// `implicit_reopen` lint. That lint will otherwise warn when a subclass *C*
+/// removes some of the restrictions that a superclass has.
+///
+/// * A class or mixin prevents inheritance if it's marked interface, or if it
+/// is marked sealed and it extends or mixes in another class which prevents
+/// inheritance.
+/// * We give a warning if a subclass extends or mixes in another class which
+/// prevents inheritance, and the subclass is marked base, or is not marked
+/// `final`, `interface` or `sealed`.
+/// * A class or mixin requires inheritance if it's marked `base`, or if it is
+/// marked `sealed` and it extends or mixes in another class or mixin which
+/// requires inheritance.
+/// * We give a warning if a subclass extends or mixes in another class which
+/// requires inheritance, and the subclass has no modifier or is marked
+/// `interface`.
+/// * A class or mixin prevents subclassing if it's marked `final`, or if it is
+/// marked `sealed` and it extends, mixes in, or implements the interface of
+/// another class or mixin which prevents subclassing.
+/// * We give a warning if a subclass or sub-mixin extends, mixes in, implements
+/// the interface of, or has as an on type a class or mixin which prevents
+/// subclassing, and the subclass or sub-mixin has no modifier or is marked
+/// `interface` or `base`.
+///
+/// In addition, tools, such as the analyzer, can provide feedback if
+///
+/// * The annotation is applied to anything other than a class or mixin.
+/// * The annotation is applied to a class or mixin which does not require it.
+/// (The intent to reopen was not satisfied.)
+@experimental // todo(pq): remove before publishing for 3.0 (https://github.com/dart-lang/sdk/issues/51059)
+const _Reopen reopen = _Reopen();
+
/// Used to annotate a named parameter `p` in a method or function `f`.
/// Indicates that every invocation of `f` must include an argument
/// corresponding to `p`, despite the fact that `p` would otherwise be an
@@ -425,6 +487,22 @@
const _Literal();
}
+@Target({
+ TargetKind.field,
+ TargetKind.getter,
+ TargetKind.method,
+ TargetKind.setter,
+})
+class _MustBeOverridden {
+ const _MustBeOverridden();
+}
+
+@Target({
+ TargetKind.field,
+ TargetKind.getter,
+ TargetKind.method,
+ TargetKind.setter,
+})
class _MustCallSuper {
const _MustCallSuper();
}
@@ -449,6 +527,14 @@
const _Protected();
}
+@Target({
+ TargetKind.classType,
+ TargetKind.mixinType,
+})
+class _Reopen {
+ const _Reopen();
+}
+
class _Sealed {
const _Sealed();
}
diff --git a/meta/pubspec.yaml b/meta/pubspec.yaml
index 4c5deeb..8e42bc2 100644
--- a/meta/pubspec.yaml
+++ b/meta/pubspec.yaml
@@ -1,6 +1,6 @@
name: meta
# Note, because version `2.0.0` was mistakenly released, the next major version must be `3.x.y`.
-version: 1.8.0
+version: 1.9.0
description: >-
Annotations used to express developer intentions that can't otherwise be
deduced by statically analyzing source code.
@@ -8,3 +8,10 @@
environment:
sdk: ">=2.12.0 <3.0.0"
+
+# We use 'any' version constraints here as we get our package versions from
+# the dart-lang/sdk repo's DEPS file. Note that this is a special case; the
+# best practice for packages is to specify their compatible version ranges.
+# See also https://dart.dev/tools/pub/dependencies.
+dev_dependencies:
+ lints: any
diff --git a/multicast_dns/BUILD.gn b/multicast_dns/BUILD.gn
index 5eb612d..c0b16f9 100644
--- a/multicast_dns/BUILD.gn
+++ b/multicast_dns/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for multicast_dns-0.3.2+2
+# This file is generated by package_importer.py for multicast_dns-0.3.2+3
import("//build/dart/dart_library.gni")
dart_library("multicast_dns") {
package_name = "multicast_dns"
- language_version = "2.14"
+ language_version = "2.17"
disable_analysis = true
diff --git a/multicast_dns/CHANGELOG.md b/multicast_dns/CHANGELOG.md
index 76837a4..55ee390 100644
--- a/multicast_dns/CHANGELOG.md
+++ b/multicast_dns/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.3.2+3
+
+* Removes use of `runtimeType.toString()`.
+* Updates minimum SDK version to Flutter 3.0.
+
## 0.3.2+2
* Fixes lints warnings.
diff --git a/multicast_dns/lib/src/resource_record.dart b/multicast_dns/lib/src/resource_record.dart
index 65320d5..d58da79 100644
--- a/multicast_dns/lib/src/resource_record.dart
+++ b/multicast_dns/lib/src/resource_record.dart
@@ -167,7 +167,7 @@
@override
String toString() =>
- '$runtimeType{$fullyQualifiedName, type: ${ResourceRecordType.toDebugString(resourceRecordType)}, isMulticast: $isMulticast}';
+ 'ResourceRecordQuery{$fullyQualifiedName, type: ${ResourceRecordType.toDebugString(resourceRecordType)}, isMulticast: $isMulticast}';
}
/// Base implementation of DNS resource records (RRs).
diff --git a/multicast_dns/pubspec.yaml b/multicast_dns/pubspec.yaml
index 04ed3aa..8aa9b77 100644
--- a/multicast_dns/pubspec.yaml
+++ b/multicast_dns/pubspec.yaml
@@ -2,10 +2,10 @@
description: Dart package for performing mDNS queries (e.g. Bonjour, Avahi).
repository: https://github.com/flutter/packages/tree/main/packages/multicast_dns
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+multicast_dns%22
-version: 0.3.2+2
+version: 0.3.2+3
environment:
- sdk: ">=2.14.0 <3.0.0"
+ sdk: ">=2.17.0 <3.0.0"
dependencies:
meta: ^1.3.0
diff --git a/package_config.json b/package_config.json
index 777e3cc..5861dbe 100644
--- a/package_config.json
+++ b/package_config.json
@@ -159,7 +159,7 @@
"rootUri": "./devtools_shared/"
},
{
- "languageVersion": "2.19",
+ "languageVersion": "3.0",
"name": "dwds",
"packageUri": "lib/",
"rootUri": "./dwds/"
@@ -189,7 +189,7 @@
"rootUri": "./file/"
},
{
- "languageVersion": "2.12",
+ "languageVersion": "2.19",
"name": "fixnum",
"packageUri": "lib/",
"rootUri": "./fixnum/"
@@ -369,7 +369,7 @@
"rootUri": "./mockito/"
},
{
- "languageVersion": "2.14",
+ "languageVersion": "2.17",
"name": "multicast_dns",
"packageUri": "lib/",
"rootUri": "./multicast_dns/"