dawn::wire::client Make Device::Destroy cancel mappings

This is technically visible when a succesful mapping would be returned
for a MapAsync, but the Device::Destroy comes first, cancelling the
mapping.

Bug: 344963953
Change-Id: I64f98a9212626dd6b1f2cba32b7a8f2249ab9a98
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/197456
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/dawn_wire.json b/src/dawn/dawn_wire.json
index 4d7f652..8610a38 100644
--- a/src/dawn/dawn_wire.json
+++ b/src/dawn/dawn_wire.json
@@ -275,6 +275,7 @@
             "BufferDestroy",
             "BufferUnmap",
             "DeviceCreateErrorBuffer",
+            "DeviceDestroy",
             "DeviceGetAdapter",
             "DeviceGetQueue",
             "DeviceGetSupportedSurfaceUsage",
diff --git a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
index a3c541a..31923c6 100644
--- a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
@@ -330,6 +330,21 @@
     DefaultApiDeviceWasReleased();
 }
 
+// Check the map callback is called with "DestroyedBeforeCallback" when the map request would have
+// worked, but the device was destroyed.
+TEST_P(WireBufferMappingTests, DeviceDestroyedTooEarly) {
+    TestEarlyMapCancelled([&]() { wgpuDeviceDestroy(device); },
+                          [&]() { EXPECT_CALL(api, DeviceDestroy(apiDevice)); },
+                          WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, false);
+}
+
+// Check that if device is destroyed early client-side, we disregard server-side validation errors.
+TEST_P(WireBufferMappingTests, DeviceDestroyedTooEarlyServerSideError) {
+    TestEarlyMapErrorCancelled([&]() { wgpuDeviceDestroy(device); },
+                               [&]() { EXPECT_CALL(api, DeviceDestroy(apiDevice)); },
+                               WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, false);
+}
+
 // Test that the callback isn't fired twice when Unmap() is called inside the callback.
 TEST_P(WireBufferMappingTests, UnmapInsideMapCallback) {
     TestCancelInCallback(&wgpuBufferUnmap, [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); });
diff --git a/src/dawn/wire/client/Device.cpp b/src/dawn/wire/client/Device.cpp
index 62cc0f4..1323967 100644
--- a/src/dawn/wire/client/Device.cpp
+++ b/src/dawn/wire/client/Device.cpp
@@ -609,4 +609,12 @@
         .SetFutureReady<CreateRenderPipelineEvent>(future.id, status, message);
 }
 
+void Device::Destroy() {
+    DeviceDestroyCmd cmd;
+    cmd.self = ToAPI(this);
+    GetClient()->SerializeCommand(cmd);
+
+    mIsAlive = false;
+}
+
 }  // namespace dawn::wire::client
diff --git a/src/dawn/wire/client/Device.h b/src/dawn/wire/client/Device.h
index 7a9e8c1..a811437 100644
--- a/src/dawn/wire/client/Device.h
+++ b/src/dawn/wire/client/Device.h
@@ -98,6 +98,8 @@
     size_t EnumerateFeatures(WGPUFeatureName* features) const;
     WGPUQueue GetQueue();
 
+    void Destroy();
+
   private:
     void WillDropLastExternalRef() override;
     template <typename Event,