Fix Flutter crash by catching WipError on resume and mapping to RPC error (#2188)

diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index 338e0a8..be666de 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -4,6 +4,7 @@
 - Update SDK constraint to `>=3.1.0-254.0.dev <4.0.0`. - [#2169](https://github.com/dart-lang/webdev/pull/2169)
 - Require min `build_web_compilers` version `4.0.4` - [#2171](https://github.com/dart-lang/webdev/pull/2171)
 - Switch to using new debugging API from DDC to support new type system. - [#2159](https://github.com/dart-lang/webdev/pull/2159)
+- Fix Flutter crash when calling `resume` when app is not paused. - [#2188](https://github.com/dart-lang/webdev/pull/2188)
 
 ## 19.0.2
 
diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart
index 27e4c1b..e506205 100644
--- a/dwds/lib/src/debugging/debugger.dart
+++ b/dwds/lib/src/debugging/debugger.dart
@@ -121,32 +121,45 @@
   /// Note that stepping will automatically continue until Chrome is paused at
   /// a location for which we have source information.
   Future<Success> resume({String? step, int? frameIndex}) async {
-    if (frameIndex != null) {
-      throw ArgumentError('FrameIndex is currently unsupported.');
-    }
-    WipResponse? result;
-    if (step != null) {
-      _isStepping = true;
-      switch (step) {
-        case 'Over':
-          result = await _remoteDebugger.stepOver();
-          break;
-        case 'Out':
-          result = await _remoteDebugger.stepOut();
-          break;
-        case 'Into':
-          result = await _remoteDebugger.stepInto();
-          break;
-        default:
-          throwInvalidParam('resume', 'Unexpected value for step: $step');
+    try {
+      if (frameIndex != null) {
+        throw ArgumentError('FrameIndex is currently unsupported.');
       }
-    } else {
-      _isStepping = false;
-      _previousSteppingLocation = null;
-      result = await _remoteDebugger.resume();
+      WipResponse? result;
+      if (step != null) {
+        _isStepping = true;
+        switch (step) {
+          case 'Over':
+            result = await _remoteDebugger.stepOver();
+            break;
+          case 'Out':
+            result = await _remoteDebugger.stepOut();
+            break;
+          case 'Into':
+            result = await _remoteDebugger.stepInto();
+            break;
+          default:
+            throwInvalidParam('resume', 'Unexpected value for step: $step');
+        }
+      } else {
+        _isStepping = false;
+        _previousSteppingLocation = null;
+        result = await _remoteDebugger.resume();
+      }
+      handleErrorIfPresent(result);
+      return Success();
+    } on WipError catch (e) {
+      final errorMessage = e.message;
+      if (errorMessage != null &&
+          errorMessage.contains('Can only perform operation while paused')) {
+        throw RPCError(
+          'resume',
+          RPCErrorKind.kIsolateMustBePaused.code,
+          errorMessage,
+        );
+      }
+      rethrow;
     }
-    handleErrorIfPresent(result);
-    return Success();
   }
 
   /// Returns the current Dart stack for the paused debugger.
diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart
index 9a23983..968d15d 100644
--- a/dwds/lib/src/services/chrome_proxy_service.dart
+++ b/dwds/lib/src/services/chrome_proxy_service.dart
@@ -1088,33 +1088,20 @@
     String? step,
     int? frameIndex,
   }) async {
-    try {
-      if (inspector.appConnection.isStarted) {
-        return captureElapsedTime(
-          () async {
-            await isInitialized;
-            await isStarted;
-            _checkIsolate('resume', isolateId);
-            return await (await debuggerFuture)
-                .resume(step: step, frameIndex: frameIndex);
-          },
-          (result) => DwdsEvent.resume(step),
-        );
-      } else {
-        inspector.appConnection.runMain();
-        return Success();
-      }
-    } on WipError catch (e) {
-      final errorMessage = e.message;
-      if (errorMessage != null &&
-          errorMessage.contains('Can only perform operation while paused')) {
-        throw RPCError(
-          'resume',
-          RPCErrorKind.kIsolateMustBePaused.code,
-          errorMessage,
-        );
-      }
-      rethrow;
+    if (inspector.appConnection.isStarted) {
+      return captureElapsedTime(
+        () async {
+          await isInitialized;
+          await isStarted;
+          _checkIsolate('resume', isolateId);
+          return await (await debuggerFuture)
+              .resume(step: step, frameIndex: frameIndex);
+        },
+        (result) => DwdsEvent.resume(step),
+      );
+    } else {
+      inspector.appConnection.runMain();
+      return Success();
     }
   }
 
diff --git a/dwds/test/chrome_proxy_service_test.dart b/dwds/test/chrome_proxy_service_test.dart
index 8fa93fa..ae4ae41 100644
--- a/dwds/test/chrome_proxy_service_test.dart
+++ b/dwds/test/chrome_proxy_service_test.dart
@@ -1339,12 +1339,17 @@
         expect(pauseBreakpoints, hasLength(1));
         expect(pauseBreakpoints.first.id, bp.id);
         await service.removeBreakpoint(isolateId!, bp.id!);
-      });
-
-      tearDown(() async {
         // Resume execution to not impact other tests.
         await service.resume(isolateId!);
       });
+
+      test('resuming throws kIsolateMustBePaused error if not paused',
+          () async {
+        await expectLater(
+          service.resume(isolateId!),
+          throwsRPCErrorWithCode(RPCErrorKind.kIsolateMustBePaused.code),
+        );
+      });
     });
 
     group('Step', () {
diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart
index f2f27a6..c926ca0 100644
--- a/dwds/test/fixtures/context.dart
+++ b/dwds/test/fixtures/context.dart
@@ -55,6 +55,10 @@
 Matcher throwsRPCErrorWithMessage(String message) =>
     throwsA(isRPCErrorWithMessage(message));
 
+Matcher isRPCErrorWithCode(int code) =>
+    isA<RPCError>().having((e) => e.code, 'code', equals(code));
+Matcher throwsRPCErrorWithCode(int code) => throwsA(isRPCErrorWithCode(code));
+
 enum CompilationMode { buildDaemon, frontendServer }
 
 class TestContext {