[WGPUFuture] Update MapAsync with futures.

- Small update in EventManager to pass through the future id when
  completing an event. (Useful for validation and in mapAsync case to
  determine whether the request was the same one.
- Updates TrackedEvent to share request information with the Buffer so
  that we can make sure that the transitions in the Buffer happen when
  the callbacks are triggered. This wasn't a problem before because the
  callbacks always use to trigger when the server responded, but now
  they may need to wait for a WaitAny or ProcessEvents call.
- Updates all the MapAsync tests (and audit the tests in general) to use
  the new Future test infra. Lots of test cleanups and modernizations
  to make sure we have concise coverage.

Bug: dawn:1987
Change-Id: I6bb044e8a958fd72bdb670647ec4590d4a669459
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/157184
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/dawn.json b/dawn.json
index fbef432..9aa4089 100644
--- a/dawn.json
+++ b/dawn.json
@@ -482,6 +482,18 @@
                 ]
             },
             {
+                "name": "map async f",
+                "_comment": "TODO(crbug.com/dawn/2021): This is dawn/emscripten-only until we rename it to replace the old API. See bug for details.",
+                "tags": ["dawn", "emscripten"],
+                "returns": "future",
+                "args": [
+                    {"name": "mode", "type": "map mode"},
+                    {"name": "offset", "type": "size_t"},
+                    {"name": "size", "type": "size_t"},
+                    {"name": "callback info", "type": "buffer map callback info"}
+                ]
+            },
+            {
                 "name": "get mapped range",
                 "returns": "void *",
                 "args": [
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index be53301..6749fa3 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -487,6 +487,14 @@
     GetDevice()->GetQueue()->TrackTask(std::move(request), mLastUsageSerial);
 }
 
+Future BufferBase::APIMapAsyncF(wgpu::MapMode mode,
+                                size_t offset,
+                                size_t size,
+                                const BufferMapCallbackInfo& callbackInfo) {
+    // TODO(dawn:1987) Implement this.
+    DAWN_CHECK(false);
+}
+
 void* BufferBase::APIGetMappedRange(size_t offset, size_t size) {
     return GetMappedRange(offset, size, true);
 }
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h
index bb117e8..9721d18 100644
--- a/src/dawn/native/Buffer.h
+++ b/src/dawn/native/Buffer.h
@@ -101,6 +101,10 @@
                      size_t size,
                      WGPUBufferMapCallback callback,
                      void* userdata);
+    Future APIMapAsyncF(wgpu::MapMode mode,
+                        size_t offset,
+                        size_t size,
+                        const BufferMapCallbackInfo& callbackInfo);
     void* APIGetMappedRange(size_t offset, size_t size);
     const void* APIGetConstMappedRange(size_t offset, size_t size);
     void APIUnmap();
diff --git a/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp b/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp
index 0f37f22..7319827 100644
--- a/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp
+++ b/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp
@@ -35,7 +35,7 @@
 #include "dawn/utils/TextureUtils.h"
 #include "dawn/utils/WGPUHelpers.h"
 
-// TODO: Remove these stream operators if we move them to a standard location.
+// TODO(dawn:2205) Remove these stream operators if we move them to a standard location.
 namespace wgpu {
 std::ostream& operator<<(std::ostream& o, Origin3D origin) {
     o << origin.x << ", " << origin.y << ", " << origin.z;
diff --git a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
index 8b6bdd1..d010aea 100644
--- a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
@@ -29,59 +29,77 @@
 #include <memory>
 
 #include "dawn/common/Assert.h"
+#include "dawn/tests/unittests/wire/WireFutureTest.h"
 #include "dawn/tests/unittests/wire/WireTest.h"
 #include "dawn/wire/WireClient.h"
 
+// Define a stream operator for WGPUMapMode outside namespace scope so that it can be found on
+// resolution for test name generation.
+// TODO(dawn:2205) Remove this in favor of custom serializer.
+std::ostream& operator<<(std::ostream& os, const WGPUMapMode& param) {
+    switch (param) {
+        case WGPUMapMode_Read:
+            os << "Read";
+            break;
+        case WGPUMapMode_Write:
+            os << "Write";
+            break;
+        default:
+            DAWN_UNREACHABLE();
+    }
+    return os;
+}
+
 namespace dawn::wire {
 namespace {
 
 using testing::_;
 using testing::InvokeWithoutArgs;
-using testing::Mock;
 using testing::Return;
-using testing::StrictMock;
 
-// Mock class to add expectations on the wire calling callbacks
-class MockBufferMapCallback {
-  public:
-    MOCK_METHOD(void, Call, (WGPUBufferMapAsyncStatus status, void* userdata));
-};
+// For the buffer tests, we make passing a map mode optional to reuse the same test fixture for
+// tests that test multiple modes and tests that are mode specific. By making it an optional, it
+// allows us to determine whether the map mode is necessary when generating the test names.
+using MapMode = std::optional<WGPUMapMode>;
+DAWN_WIRE_FUTURE_TEST_PARAM_STRUCT(WireBufferParam, MapMode);
 
-std::unique_ptr<StrictMock<MockBufferMapCallback>> mockBufferMapCallback;
-void ToMockBufferMapCallback(WGPUBufferMapAsyncStatus status, void* userdata) {
-    mockBufferMapCallback->Call(status, userdata);
-}
+using WireBufferMappingTestBase = WireFutureTestWithParams<WGPUBufferMapCallback,
+                                                           WGPUBufferMapCallbackInfo,
+                                                           wgpuBufferMapAsync,
+                                                           wgpuBufferMapAsyncF,
+                                                           WireBufferParam>;
 
-class WireBufferMappingTests : public WireTest {
-  public:
-    WireBufferMappingTests() {}
-    ~WireBufferMappingTests() override = default;
-
+// General mapping tests that either do not care about the specific mapping mode, or apply to both.
+class WireBufferMappingTests : public WireBufferMappingTestBase {
+  protected:
     void SetUp() override {
-        WireTest::SetUp();
-
-        mockBufferMapCallback = std::make_unique<StrictMock<MockBufferMapCallback>>();
+        WireBufferMappingTestBase::SetUp();
         apiBuffer = api.GetNewBuffer();
     }
 
-    void TearDown() override {
-        WireTest::TearDown();
-
-        // Delete mock so that expectations are checked
-        mockBufferMapCallback = nullptr;
+    // Overridden version of wgpuBufferMapAsync that defers to the API call based on the
+    // test callback mode.
+    void BufferMapAsync(WGPUBuffer b,
+                        WGPUMapMode mode,
+                        size_t offset,
+                        size_t size,
+                        void* userdata = nullptr) {
+        CallImpl(userdata, b, mode, offset, size);
     }
 
-    void FlushClient() {
-        WireTest::FlushClient();
-        Mock::VerifyAndClearExpectations(&mockBufferMapCallback);
+    WGPUMapMode GetMapMode() {
+        DAWN_ASSERT(GetParam().mMapMode);
+        return *GetParam().mMapMode;
     }
 
-    void FlushServer() {
-        WireTest::FlushServer();
-        Mock::VerifyAndClearExpectations(&mockBufferMapCallback);
-    }
+    void SetupBuffer(WGPUMapMode mapMode) {
+        WGPUBufferUsageFlags usage = WGPUBufferUsage_MapRead;
+        if (mapMode == WGPUMapMode_Read) {
+            usage = WGPUBufferUsage_MapRead;
+        } else if (mapMode == WGPUMapMode_Write) {
+            usage = WGPUBufferUsage_MapWrite;
+        }
 
-    void SetupBuffer(WGPUBufferUsageFlags usage) {
         WGPUBufferDescriptor descriptor = {};
         descriptor.size = kBufferSize;
         descriptor.usage = usage;
@@ -94,99 +112,247 @@
         FlushClient();
     }
 
-  protected:
+    // Sets up the correct mapped range call expectations given the map mode.
+    void ExpectMappedRangeCall(uint64_t bufferSize, void* bufferContent) {
+        WGPUMapMode mapMode = GetMapMode();
+        if (mapMode == WGPUMapMode_Read) {
+            EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, bufferSize))
+                .WillOnce(Return(bufferContent));
+        } else if (mapMode == WGPUMapMode_Write) {
+            EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, bufferSize))
+                .WillOnce(Return(bufferContent));
+        }
+    }
+
+    // Test to exercise client functions that should override server response for callbacks.
+    template <typename ExpFn>
+    void TestEarlyMapCancelled(void (*cancelFn)(WGPUBuffer),
+                               ExpFn cancelExp,
+                               WGPUBufferMapAsyncStatus expected) {
+        WGPUMapMode mapMode = GetMapMode();
+        SetupBuffer(mapMode);
+        BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+
+        uint32_t bufferContent = 31337;
+        EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
+            .WillOnce(InvokeWithoutArgs([&] {
+                api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success);
+            }));
+        ExpectMappedRangeCall(kBufferSize, &bufferContent);
+        cancelExp();
+
+        // The callback should get called with the expected status, regardless if the server has
+        // responded.
+        if (IsSpontaneous()) {
+            // In spontaneous mode, the callback gets called as a part of the cancel function.
+            ExpectWireCallbacksWhen([&](auto& mockCb) {
+                EXPECT_CALL(mockCb, Call(expected, _)).Times(1);
+
+                cancelFn(buffer);
+            });
+            FlushClient();
+            FlushCallbacks();
+        } else {
+            // Otherwise, the callback will fire when we flush them.
+            cancelFn(buffer);
+            FlushClient();
+            ExpectWireCallbacksWhen([&](auto& mockCb) {
+                EXPECT_CALL(mockCb, Call(expected, _)).Times(1);
+
+                FlushCallbacks();
+            });
+        }
+    }
+
+    // Test to exercise client functions that should override server error response for callbacks.
+    template <typename ExpFn>
+    void TestEarlyMapErrorCancelled(void (*cancelFn)(WGPUBuffer),
+                                    ExpFn cancelExp,
+                                    WGPUBufferMapAsyncStatus expected) {
+        WGPUMapMode mapMode = GetMapMode();
+        SetupBuffer(mapMode);
+        BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+
+        EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
+            .WillOnce(InvokeWithoutArgs([&] {
+                api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
+            }));
+
+        // Ensure that the server had a chance to respond if relevant.
+        FlushClient();
+        FlushFutures();
+
+        cancelExp();
+
+        // The callback should get called with the expected status status, not server-side error,
+        // even if the request fails on the server side.
+        if (IsSpontaneous()) {
+            // In spontaneous mode, the callback gets called as a part of the cancel function.
+            ExpectWireCallbacksWhen([&](auto& mockCb) {
+                EXPECT_CALL(mockCb, Call(expected, _)).Times(1);
+
+                cancelFn(buffer);
+            });
+            FlushClient();
+            FlushCallbacks();
+        } else {
+            // Otherwise, the callback will fire when we flush them.
+            cancelFn(buffer);
+            FlushClient();
+            ExpectWireCallbacksWhen([&](auto& mockCb) {
+                EXPECT_CALL(mockCb, Call(expected, _)).Times(1);
+
+                FlushCallbacks();
+            });
+        }
+    }
+
+    // Test to exercise client functions that would cancel callbacks don't cause the callback to be
+    // fired twice.
+    template <typename ExpFn>
+    void TestCancelInCallback(void (*cancelFn)(WGPUBuffer), ExpFn cancelExp) {
+        WGPUMapMode mapMode = GetMapMode();
+        SetupBuffer(mapMode);
+        BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+
+        uint32_t bufferContent = 31337;
+        EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
+            .WillOnce(InvokeWithoutArgs([&] {
+                api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success);
+            }));
+        ExpectMappedRangeCall(kBufferSize, &bufferContent);
+
+        // Ensure that the server had a chance to respond if relevant.
+        FlushClient();
+        FlushFutures();
+
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() {
+                cancelFn(buffer);
+            });
+
+            FlushCallbacks();
+        });
+
+        // Make sure that the cancel function is called and flush more callbacks to ensure that
+        // nothing else happens.
+        cancelExp();
+        FlushClient();
+        FlushFutures();
+        FlushCallbacks();
+    }
+
     static constexpr uint64_t kBufferSize = sizeof(uint32_t);
     // A successfully created buffer
     WGPUBuffer buffer;
     WGPUBuffer apiBuffer;
 };
 
-// Tests specific to mapping for reading
-class WireBufferMappingReadTests : public WireBufferMappingTests {
-  public:
-    WireBufferMappingReadTests() {}
-    ~WireBufferMappingReadTests() override = default;
+DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappingTests, {WGPUMapMode_Read, WGPUMapMode_Write});
 
-    void SetUp() override {
-        WireBufferMappingTests::SetUp();
+// Check that things work correctly when a validation error happens when mapping the buffer.
+TEST_P(WireBufferMappingTests, ErrorWhileMapping) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
+    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
 
-        SetupBuffer(WGPUBufferUsage_MapRead);
-    }
-};
-
-// Check mapping for reading a succesfully created buffer
-TEST_F(WireBufferMappingReadTests, MappingForReadSuccessBuffer) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    FlushClient();
-
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
-
-    FlushServer();
-
-    EXPECT_EQ(bufferContent,
-              *static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
-
-    wgpuBufferUnmap(buffer);
-    EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
-
-    FlushClient();
-}
-
-// Check that things work correctly when a validation error happens when mapping the buffer for
-// reading
-TEST_F(WireBufferMappingReadTests, ErrorWhileMappingForRead) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
+    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
         }));
 
     FlushClient();
+    FlushFutures();
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
 
-    FlushServer();
+        FlushCallbacks();
+    });
 
     EXPECT_EQ(nullptr, wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize));
 }
 
-// Check that the map read callback is called with UNKNOWN when the buffer is destroyed before
-// the request is finished
-TEST_F(WireBufferMappingReadTests, DestroyBeforeReadRequestEnd) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    // Return success
-    uint32_t bufferContent = 0;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    // Destroy before the client gets the success, so the callback is called with
-    // DestroyedBeforeCallback.
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
-        .Times(1);
-    wgpuBufferRelease(buffer);
-    EXPECT_CALL(api, BufferRelease(apiBuffer));
-
-    FlushClient();
-    FlushServer();
+// Check the map callback is called with "UnmappedBeforeCallback" when the map request would have
+// worked, but Unmap() was called.
+TEST_P(WireBufferMappingTests, UnmapCalledTooEarly) {
+    TestEarlyMapCancelled(
+        &wgpuBufferUnmap, [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); },
+        WGPUBufferMapAsyncStatus_UnmappedBeforeCallback);
 }
 
-// Check the map read callback is called with "UnmappedBeforeCallback" when the map request
-// would have worked, but Unmap was called
-TEST_F(WireBufferMappingReadTests, UnmapCalledTooEarlyForRead) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+// Check that if Unmap() was called early client-side, we disregard server-side validation errors.
+TEST_P(WireBufferMappingTests, UnmapCalledTooEarlyServerSideError) {
+    TestEarlyMapErrorCancelled(
+        &wgpuBufferUnmap, [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); },
+        WGPUBufferMapAsyncStatus_UnmappedBeforeCallback);
+}
+
+// Check the map callback is called with "DestroyedBeforeCallback" when the map request would have
+// worked, but Destroy() was called.
+TEST_P(WireBufferMappingTests, DestroyCalledTooEarly) {
+    TestEarlyMapCancelled(
+        &wgpuBufferDestroy, [&]() { EXPECT_CALL(api, BufferDestroy(apiBuffer)); },
+        WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
+}
+
+// Check that if Destroy() was called early client-side, we disregard server-side validation errors.
+TEST_P(WireBufferMappingTests, DestroyCalledTooEarlyServerSideError) {
+    TestEarlyMapErrorCancelled(
+        &wgpuBufferDestroy, [&]() { EXPECT_CALL(api, BufferDestroy(apiBuffer)); },
+        WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
+}
+
+// Check the map callback is called with "DestroyedBeforeCallback" when the map request would have
+// worked, but Release() was called on the last ref.
+TEST_P(WireBufferMappingTests, ReleaseCalledTooEarly) {
+    TestEarlyMapCancelled(
+        &wgpuBufferRelease, [&]() { EXPECT_CALL(api, BufferRelease(apiBuffer)); },
+        WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
+}
+
+// Check that if Release() was called early client-side on the last ref, we disregard server-side
+// validation errors.
+TEST_P(WireBufferMappingTests, ReleaseCalledTooEarlyServerSideError) {
+    TestEarlyMapErrorCancelled(
+        &wgpuBufferRelease, [&]() { EXPECT_CALL(api, BufferRelease(apiBuffer)); },
+        WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
+}
+
+// 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)); });
+}
+
+// Test that the callback isn't fired twice when Destroy() is called inside the callback.
+TEST_P(WireBufferMappingTests, DestroyInsideMapCallback) {
+    TestCancelInCallback(&wgpuBufferDestroy, [&]() { EXPECT_CALL(api, BufferDestroy(apiBuffer)); });
+}
+
+// Test that the callback isn't fired twice when Release() is called inside the callback with the
+// last ref.
+TEST_P(WireBufferMappingTests, ReleaseInsideMapCallback) {
+    // TODO(dawn:1621): Suppressed because the mapping handling still touches the buffer after it is
+    // destroyed triggering an ASAN error when in MapWrite mode.
+    DAWN_SKIP_TEST_IF(GetMapMode() == WGPUMapMode_Write);
+
+    TestCancelInCallback(&wgpuBufferRelease, [&]() { EXPECT_CALL(api, BufferRelease(apiBuffer)); });
+}
+
+// Tests specific to mapping for reading.
+class WireBufferMappingReadTests : public WireBufferMappingTests {
+  protected:
+    void SetUp() override {
+        WireBufferMappingTests::SetUp();
+        SetupBuffer(WGPUMapMode_Read);
+    }
+};
+
+DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappingReadTests);
+
+// Check mapping for reading a succesfully created buffer.
+TEST_P(WireBufferMappingReadTests, MappingSuccess) {
+    BufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, nullptr);
 
     uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
@@ -195,99 +361,26 @@
     EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
         .WillOnce(Return(&bufferContent));
 
-    // The callback should get called immediately with UnmappedBeforeCallback status
-    // even if the request succeeds on the server side
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_UnmappedBeforeCallback, _))
-        .Times(1);
+    FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
 
-    // Oh no! We are calling Unmap too early! The callback should get fired immediately
-    // before we get an answer from the server.
+        FlushCallbacks();
+    });
+
+    EXPECT_EQ(bufferContent,
+              *static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
     wgpuBufferUnmap(buffer);
-    EXPECT_CALL(api, BufferUnmap(apiBuffer));
-
+    EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
     FlushClient();
-    FlushServer();
-}
-
-// Check that even if Unmap() was called early client-side, we correctly surface server-side
-// validation errors.
-TEST_F(WireBufferMappingReadTests, UnmapCalledTooEarlyForReadButServerSideError) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs([&] {
-            api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
-        }));
-
-    // The callback should get called immediately with UnmappedBeforeCallback status,
-    // not server-side error, even if the request fails on the server side
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_UnmappedBeforeCallback, _))
-        .Times(1);
-
-    // Oh no! We are calling Unmap too early! The callback should get fired immediately
-    // before we get an answer from the server that the mapAsync call was an error.
-    wgpuBufferUnmap(buffer);
-    EXPECT_CALL(api, BufferUnmap(apiBuffer));
-
-    FlushClient();
-    FlushServer();
-}
-
-// Check the map read callback is called with "DestroyedBeforeCallback" when the map request
-// would have worked, but Destroy was called
-TEST_F(WireBufferMappingReadTests, DestroyCalledTooEarlyForRead) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    // The callback should get called immediately with DestroyedBeforeCallback status
-    // even if the request succeeds on the server side
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
-        .Times(1);
-
-    // Oh no! We are calling Destroy too early! The callback should get fired immediately
-    // before we get an answer from the server.
-    wgpuBufferDestroy(buffer);
-    EXPECT_CALL(api, BufferDestroy(apiBuffer));
-
-    FlushClient();
-    FlushServer();
-}
-
-// Check that even if Destroy() was called early client-side, we correctly surface server-side
-// validation errors.
-TEST_F(WireBufferMappingReadTests, DestroyCalledTooEarlyForReadButServerSideError) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs([&] {
-            api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
-        }));
-
-    // The callback should be called with the server-side error and not the
-    // DestroyedBeforCallback..
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
-        .Times(1);
-
-    // Oh no! We are calling Destroy too early! The callback should get fired immediately
-    // before we get an answer from the server that the mapAsync call was an error.
-    wgpuBufferDestroy(buffer);
-    EXPECT_CALL(api, BufferDestroy(apiBuffer));
-
-    FlushClient();
-    FlushServer();
 }
 
 // Check that an error map read while a buffer is already mapped won't changed the result of get
-// mapped range
-TEST_F(WireBufferMappingReadTests, MappingForReadingErrorWhileAlreadyMappedUnchangeMapData) {
+// mapped range.
+TEST_P(WireBufferMappingReadTests, MappingErrorWhileAlreadyMapped) {
     // Successful map
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+    BufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, nullptr);
 
     uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
@@ -297,91 +390,47 @@
         .WillOnce(Return(&bufferContent));
 
     FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
-
-    FlushServer();
+        FlushCallbacks();
+    });
 
     // Map failure while the buffer is already mapped
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+    BufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, nullptr);
+
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
         }));
 
     FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
-
-    FlushServer();
+        FlushCallbacks();
+    });
 
     EXPECT_EQ(bufferContent,
               *static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
 }
 
-// Test that the MapReadCallback isn't fired twice when unmap() is called inside the callback
-TEST_F(WireBufferMappingReadTests, UnmapInsideMapReadCallback) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    FlushClient();
-
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _))
-        .WillOnce(InvokeWithoutArgs([&] { wgpuBufferUnmap(buffer); }));
-
-    FlushServer();
-
-    EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
-
-    FlushClient();
-}
-
-// Test that the MapReadCallback isn't fired twice the buffer external refcount reaches 0 in the
-// callback
-TEST_F(WireBufferMappingReadTests, DestroyInsideMapReadCallback) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    FlushClient();
-
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _))
-        .WillOnce(InvokeWithoutArgs([&] { wgpuBufferRelease(buffer); }));
-
-    FlushServer();
-
-    EXPECT_CALL(api, BufferRelease(apiBuffer));
-
-    FlushClient();
-}
-
-// Tests specific to mapping for writing
+// Tests specific to mapping for writing.
 class WireBufferMappingWriteTests : public WireBufferMappingTests {
-  public:
-    WireBufferMappingWriteTests() {}
-    ~WireBufferMappingWriteTests() override = default;
-
+  protected:
     void SetUp() override {
         WireBufferMappingTests::SetUp();
-
-        SetupBuffer(WGPUBufferUsage_MapWrite);
+        SetupBuffer(WGPUMapMode_Write);
     }
 };
 
-// Check mapping for writing a succesfully created buffer
-TEST_F(WireBufferMappingWriteTests, MappingForWriteSuccessBuffer) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappingWriteTests);
+
+// Check mapping for writing a succesfully created buffer.
+TEST_P(WireBufferMappingWriteTests, MappingSuccess) {
+    BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr);
 
     uint32_t serverBufferContent = 31337;
     uint32_t updatedContent = 4242;
@@ -392,12 +441,14 @@
     EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
         .WillOnce(Return(&serverBufferContent));
 
-    FlushClient();
-
     // The map write callback always gets a buffer full of zeroes.
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
+    FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
 
-    FlushServer();
+        FlushCallbacks();
+    });
 
     uint32_t* lastMapWritePointer =
         static_cast<uint32_t*>(wgpuBufferGetMappedRange(buffer, 0, kBufferSize));
@@ -415,76 +466,10 @@
     ASSERT_EQ(serverBufferContent, updatedContent);
 }
 
-// Check that things work correctly when a validation error happens when mapping the buffer for
-// writing
-TEST_F(WireBufferMappingWriteTests, ErrorWhileMappingForWrite) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs([&] {
-            api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
-        }));
-
-    FlushClient();
-
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
-
-    FlushServer();
-
-    EXPECT_EQ(nullptr, wgpuBufferGetMappedRange(buffer, 0, kBufferSize));
-}
-
-// Check that the map write callback is called with "DestroyedBeforeCallback" when the buffer is
-// destroyed before the request is finished
-TEST_F(WireBufferMappingWriteTests, DestroyBeforeWriteRequestEnd) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    // Return success
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    // Destroy before the client gets the success, so the callback is called with
-    // DestroyedBeforeCallback.
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
-        .Times(1);
-    wgpuBufferRelease(buffer);
-    EXPECT_CALL(api, BufferRelease(apiBuffer));
-
-    FlushClient();
-    FlushServer();
-}
-
-// Check the map write callback is called with "UnmappedBeforeCallback" when the map request
-// would have worked, but Unmap was called
-TEST_F(WireBufferMappingWriteTests, UnmapCalledTooEarlyForWrite) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    FlushClient();
-
-    // Oh no! We are calling Unmap too early!
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_UnmappedBeforeCallback, _))
-        .Times(1);
-    wgpuBufferUnmap(buffer);
-
-    // The callback shouldn't get called, even when the request succeeded on the server side
-    FlushServer();
-}
-
-// Check that an error map write while a buffer is already mapped
-TEST_F(WireBufferMappingWriteTests, MappingForWritingErrorWhileAlreadyMapped) {
+// Check that an error map write while a buffer is already mapped.
+TEST_P(WireBufferMappingWriteTests, MappingErrorWhileAlreadyMapped) {
     // Successful map
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+    BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr);
 
     uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
@@ -494,79 +479,39 @@
         .WillOnce(Return(&bufferContent));
 
     FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
-
-    FlushServer();
+        FlushCallbacks();
+    });
 
     // Map failure while the buffer is already mapped
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+    BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr);
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
         }));
 
     FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
-
-    FlushServer();
+        FlushCallbacks();
+    });
 
     EXPECT_NE(nullptr,
               static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
 }
 
-// Test that the MapWriteCallback isn't fired twice when unmap() is called inside the callback
-TEST_F(WireBufferMappingWriteTests, UnmapInsideMapWriteCallback) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+// Tests specific to mapped at creation.
+class WireBufferMappedAtCreationTests : public WireBufferMappingTests {};
 
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    FlushClient();
-
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _))
-        .WillOnce(InvokeWithoutArgs([&] { wgpuBufferUnmap(buffer); }));
-
-    FlushServer();
-
-    EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
-
-    FlushClient();
-}
-
-// Test that the MapWriteCallback isn't fired twice the buffer external refcount reaches 0 in
-// the callback
-// TODO(dawn:1621): Suppressed because the mapping handling still touches the buffer after it is
-// destroyed triggering an ASAN error.
-TEST_F(WireBufferMappingWriteTests, DISABLED_DestroyInsideMapWriteCallback) {
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
-
-    uint32_t bufferContent = 31337;
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
-
-    FlushClient();
-
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _))
-        .WillOnce(InvokeWithoutArgs([&] { wgpuBufferRelease(buffer); }));
-
-    FlushServer();
-
-    EXPECT_CALL(api, BufferRelease(apiBuffer));
-
-    FlushClient();
-}
+DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappedAtCreationTests);
 
 // Test successful buffer creation with mappedAtCreation=true
-TEST_F(WireBufferMappingTests, MappedAtCreationSuccess) {
+TEST_F(WireBufferMappedAtCreationTests, Success) {
     WGPUBufferDescriptor descriptor = {};
     descriptor.size = 4;
     descriptor.mappedAtCreation = true;
@@ -588,7 +533,7 @@
 }
 
 // Test that releasing a buffer mapped at creation does not call Unmap
-TEST_F(WireBufferMappingTests, MappedAtCreationReleaseBeforeUnmap) {
+TEST_F(WireBufferMappedAtCreationTests, ReleaseBeforeUnmap) {
     WGPUBufferDescriptor descriptor = {};
     descriptor.size = 4;
     descriptor.mappedAtCreation = true;
@@ -609,8 +554,8 @@
     FlushClient();
 }
 
-// Test that it is valid to map a buffer after it is mapped at creation and unmapped
-TEST_F(WireBufferMappingTests, MappedAtCreationThenMapSuccess) {
+// Test that it is valid to map a buffer after it is mapped at creation and unmapped.
+TEST_P(WireBufferMappedAtCreationTests, MapSuccess) {
     WGPUBufferDescriptor descriptor = {};
     descriptor.size = 4;
     descriptor.usage = WGPUMapMode_Write;
@@ -631,7 +576,7 @@
 
     FlushClient();
 
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+    BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr);
 
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
         .WillOnce(InvokeWithoutArgs(
@@ -640,14 +585,16 @@
         .WillOnce(Return(&apiBufferData));
 
     FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
-
-    FlushServer();
+        FlushCallbacks();
+    });
 }
 
 // Test that it is invalid to map a buffer after mappedAtCreation but before Unmap
-TEST_F(WireBufferMappingTests, MappedAtCreationThenMapFailure) {
+TEST_P(WireBufferMappedAtCreationTests, MapFailure) {
     WGPUBufferDescriptor descriptor = {};
     descriptor.size = 4;
     descriptor.mappedAtCreation = true;
@@ -662,18 +609,22 @@
 
     FlushClient();
 
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
+    BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr);
 
+    // Note that the validation logic is entirely on the native side so we inject the validation
+    // error here and flush the server response to mock the expected behavior.
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
         }));
 
     FlushClient();
+    FlushFutures();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1);
-
-    FlushServer();
+        FlushCallbacks();
+    });
 
     EXPECT_NE(nullptr,
               static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
@@ -725,71 +676,120 @@
 
 // Test that registering a callback then wire disconnect calls the callback with
 // DeviceLost.
-TEST_F(WireBufferMappingTests, MapThenDisconnect) {
-    SetupBuffer(WGPUMapMode_Write);
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, this);
+TEST_P(WireBufferMappingTests, MapThenDisconnect) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
+    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
 
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
+    uint32_t bufferContent = 0;
+    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
         .WillOnce(InvokeWithoutArgs(
             [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)).Times(1);
+    ExpectMappedRangeCall(kBufferSize, &bufferContent);
 
     FlushClient();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DeviceLost, _)).Times(1);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DeviceLost, this)).Times(1);
-    GetWireClient()->Disconnect();
+        GetWireClient()->Disconnect();
+    });
 }
 
 // Test that registering a callback after wire disconnect calls the callback with
 // DeviceLost.
-TEST_F(WireBufferMappingTests, MapAfterDisconnect) {
-    SetupBuffer(WGPUMapMode_Read);
+TEST_P(WireBufferMappingTests, MapAfterDisconnect) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
 
     GetWireClient()->Disconnect();
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DeviceLost, this)).Times(1);
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, this);
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DeviceLost, _)).Times(1);
+
+        BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+    });
 }
 
-// Test that mapping again while pending map immediately cause an error
-TEST_F(WireBufferMappingTests, PendingMapImmediateError) {
-    SetupBuffer(WGPUMapMode_Read);
+// Test that mapping again while pending map cause an error on the callback.
+TEST_P(WireBufferMappingTests, PendingMapImmediateError) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
+    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
 
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, nullptr, this);
+    // Calls for the first successful map.
+    uint32_t bufferContent = 0;
+    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
+        .WillOnce(InvokeWithoutArgs(
+            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
+    ExpectMappedRangeCall(kBufferSize, &bufferContent);
 
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, this))
-        .Times(1);
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, this);
+    if (IsSpontaneous()) {
+        // In spontaneous mode, the second map on the pending immediately calls the callback.
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _)).Times(1);
+
+            BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+        });
+
+        FlushClient();
+        FlushFutures();
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
+
+            FlushCallbacks();
+        });
+    } else {
+        // Otherwise, the callback will fire alongside the success one when we flush the callbacks.
+        BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+
+        FlushClient();
+        FlushFutures();
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _)).Times(1);
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
+
+            FlushCallbacks();
+        });
+    }
 }
 
 // Test that GetMapState() returns map state as expected
-TEST_F(WireBufferMappingTests, GetMapState) {
-    SetupBuffer(WGPUMapMode_Read);
+TEST_P(WireBufferMappingTests, GetMapState) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
 
     // Server-side success case
     {
         uint32_t bufferContent = 31337;
-        EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
+        EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
             .WillOnce(InvokeWithoutArgs([&] {
                 api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success);
             }));
-        EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-            .WillOnce(Return(&bufferContent));
-        EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1);
+        ExpectMappedRangeCall(kBufferSize, &bufferContent);
 
+        // Map state should initially be unmapped.
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped);
-        wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback,
-                           nullptr);
+        BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
 
-        // map state should become pending immediately after map async call
+        // Map state should become pending immediately after map async call.
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending);
         FlushClient();
 
-        // map state should be pending until receiving a response from server
+        // Map state should be pending until receiving a response from server.
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending);
-        FlushServer();
+        FlushFutures();
 
-        // mapping succeeded
+        // Map state should still be pending until the callback has been called.
+        ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending);
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() {
+                ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Mapped);
+            });
+
+            FlushCallbacks();
+        });
+
+        // Mapping succeeded.
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Mapped);
     }
 
@@ -799,24 +799,32 @@
 
     // Server-side error case
     {
-        EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
+        EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
             .WillOnce(InvokeWithoutArgs([&] {
                 api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError);
             }));
-        EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_ValidationError, _))
-            .Times(1);
 
+        // Map state should initially be unmapped.
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped);
-        wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback,
-                           nullptr);
+        BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
 
-        // map state should become pending immediately after map async call
+        // Map state should become pending immediately after map async call.
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending);
         FlushClient();
 
-        // map state should be pending until receiving a response from server
+        // Map state should be pending until receiving a response from server.
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending);
-        FlushServer();
+        FlushFutures();
+
+        // Map state should still be pending until the callback has been called.
+        ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending);
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).WillOnce([&]() {
+                ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped);
+            });
+
+            FlushCallbacks();
+        });
 
         // mapping failed
         ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped);
@@ -824,111 +832,142 @@
 }
 
 #if defined(DAWN_ENABLE_ASSERTS)
-static void ToMockBufferMapCallbackWithAssertErrorRequest(WGPUBufferMapAsyncStatus status,
-                                                          void* userdata) {
-    WGPUBuffer* buffer = reinterpret_cast<WGPUBuffer*>(userdata);
+// Test that request inside user callbacks after object release is called.
+TEST_P(WireBufferMappingTests, MapInsideCallbackAfterRelease) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
+    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
 
-    mockBufferMapCallback->Call(status, buffer);
-    ASSERT_DEATH_IF_SUPPORTED(
-        {
-            // This map async should cause assertion error because of
-            // refcount == 0.
-            wgpuBufferMapAsync(*buffer, WGPUMapMode_Read, 0, sizeof(uint32_t),
-                               ToMockBufferMapCallback, nullptr);
-        },
-        "");
-}
-
-// Test that request inside user callbacks after object destruction is called
-TEST_F(WireBufferMappingTests, MapInsideCallbackAfterDestruction) {
-    SetupBuffer(WGPUMapMode_Read);
-
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize,
-                       ToMockBufferMapCallbackWithAssertErrorRequest, &buffer);
+    uint32_t bufferContent = 31337;
+    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
+        .WillOnce(InvokeWithoutArgs(
+            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
+    ExpectMappedRangeCall(kBufferSize, &bufferContent);
+    EXPECT_CALL(api, BufferRelease(apiBuffer));
 
     // By releasing the buffer the refcount reaches zero and pending map async
     // should fail with destroyed before callback status.
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
-        .Times(1);
-    wgpuBufferRelease(buffer);
+    if (IsSpontaneous()) {
+        // In spontaneous mode, the callback gets called as a part of the release.
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
+                .WillOnce([&]() {
+                    ASSERT_DEATH_IF_SUPPORTED(
+                        { BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); }, "");
+                });
+
+            wgpuBufferRelease(buffer);
+        });
+        FlushClient();
+        FlushCallbacks();
+    } else {
+        // Otherwise, the callback happens when we flush it.
+        wgpuBufferRelease(buffer);
+        FlushClient();
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _))
+                .WillOnce([&]() {
+                    ASSERT_DEATH_IF_SUPPORTED(
+                        { BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); }, "");
+                });
+
+            FlushCallbacks();
+        });
+    }
 }
 #endif  // defined(DAWN_ENABLE_ASSERTS)
 
-// Hack to pass in test context into user callback
-struct TestData {
-    WireBufferMappingTests* pTest;
-    WGPUBuffer* pTestBuffer;
-    size_t numRequests;
-};
+// Test that requests inside user callbacks before disconnect are called.
+TEST_P(WireBufferMappingTests, MapInsideCallbackBeforeDisconnect) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
+    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
 
-static void ToMockBufferMapCallbackWithNewRequests(WGPUBufferMapAsyncStatus status,
-                                                   void* userdata) {
-    TestData* testData = reinterpret_cast<TestData*>(userdata);
-    // Mimic the user callback is sending new requests
-    ASSERT_NE(testData, nullptr);
-    ASSERT_NE(testData->pTest, nullptr);
-    ASSERT_NE(testData->pTestBuffer, nullptr);
+    uint32_t bufferContent = 0;
+    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
+        .WillOnce(InvokeWithoutArgs(
+            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
+    ExpectMappedRangeCall(kBufferSize, &bufferContent);
 
-    mockBufferMapCallback->Call(status, testData->pTest);
+    FlushClient();
 
-    // Send the requests a number of times
-    for (size_t i = 0; i < testData->numRequests; i++) {
-        wgpuBufferMapAsync(*(testData->pTestBuffer), WGPUMapMode_Write, 0, sizeof(uint32_t),
-                           ToMockBufferMapCallback, testData->pTest);
+    static constexpr size_t kNumRequests = 10;
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DeviceLost, _))
+            .Times(kNumRequests + 1)
+            .WillOnce([&]() {
+                for (size_t i = 0; i < kNumRequests; i++) {
+                    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+                }
+            })
+            .WillRepeatedly(Return());
+
+        GetWireClient()->Disconnect();
+    });
+}
+
+// Test that requests inside user callbacks before object release are called.
+TEST_P(WireBufferMappingTests, MapInsideCallbackBeforeRelease) {
+    WGPUMapMode mapMode = GetMapMode();
+    SetupBuffer(mapMode);
+    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+
+    uint32_t bufferContent = 0;
+    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _, _))
+        .WillOnce(InvokeWithoutArgs(
+            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
+    ExpectMappedRangeCall(kBufferSize, &bufferContent);
+
+    FlushClient();
+    FlushFutures();
+
+    static constexpr size_t kNumRequests = 10;
+    if (IsSpontaneous()) {
+        // In spontaneous mode, when the success callback fires, the first MapAsync request
+        // generated by the callback is queued, then all subsequent requests' callbacks are
+        // immediately called with MappingAlreadyPending. Finally, when we call Release, the queued
+        // request's callback is then called with DestroyedBeforeCallback.
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() {
+                for (size_t i = 0; i < kNumRequests; i++) {
+                    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+                }
+            });
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _))
+                .Times(kNumRequests - 1);
+
+            FlushCallbacks();
+        });
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _)).Times(1);
+
+            wgpuBufferRelease(buffer);
+        });
+        FlushCallbacks();
+    } else {
+        // In non-spontaneous modes, the first callback doesn't trigger any other immediate
+        // callbacks, but internally, all but the first MapAsync call's callback is set to be ready
+        // with MappingAlreadyPending. When we call Release, the first pending request is then
+        // marked ready with DestroyedBeforeCallback. The callbacks all run when we flush them.
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() {
+                for (size_t i = 0; i < kNumRequests; i++) {
+                    BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr);
+                }
+            });
+
+            FlushCallbacks();
+        });
+        wgpuBufferRelease(buffer);
+        ExpectWireCallbacksWhen([&](auto& mockCb) {
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _))
+                .Times(kNumRequests - 1);
+            EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _)).Times(1);
+
+            FlushCallbacks();
+        });
     }
 }
 
-// Test that requests inside user callbacks before disconnect are called
-TEST_F(WireBufferMappingTests, MapInsideCallbackBeforeDisconnect) {
-    SetupBuffer(WGPUMapMode_Write);
-    TestData testData = {this, &buffer, 10};
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize,
-                       ToMockBufferMapCallbackWithNewRequests, &testData);
-
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)).Times(1);
-
-    FlushClient();
-
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DeviceLost, this))
-        .Times(testData.numRequests + 1);
-    GetWireClient()->Disconnect();
-}
-
-// Test that requests inside user callbacks before object destruction are called
-TEST_F(WireBufferMappingWriteTests, MapInsideCallbackBeforeDestruction) {
-    SetupBuffer(WGPUMapMode_Write);
-    TestData testData = {this, &buffer, 10};
-    wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize,
-                       ToMockBufferMapCallbackWithNewRequests, &testData);
-
-    EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
-        .WillOnce(InvokeWithoutArgs(
-            [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)).Times(1);
-
-    FlushClient();
-
-    // The first map async call should succeed
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Success, this)).Times(1);
-
-    // The second or later map async calls in the map async callback
-    // should immediately fail because of pending map
-    EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, this))
-        .Times(testData.numRequests - 1);
-
-    // The first map async call in the map async callback should fail
-    // with destroyed before callback status due to buffer release below
-    EXPECT_CALL(*mockBufferMapCallback,
-                Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, this))
-        .Times(1);
-
-    FlushServer();
-
-    wgpuBufferRelease(buffer);
-}
-
 }  // anonymous namespace
 }  // namespace dawn::wire
diff --git a/src/dawn/wire/client/Buffer.cpp b/src/dawn/wire/client/Buffer.cpp
index 970d1e2..974d2c5 100644
--- a/src/dawn/wire/client/Buffer.cpp
+++ b/src/dawn/wire/client/Buffer.cpp
@@ -56,23 +56,42 @@
   public:
     static constexpr EventType kType = EventType::MapAsync;
 
-    explicit MapAsyncEvent(const WGPUBufferMapCallbackInfo& callbackInfo)
+    explicit MapAsyncEvent(const WGPUBufferMapCallbackInfo& callbackInfo,
+                           const Ref<MapStateData>& mapStateData)
         : TrackedEvent(callbackInfo.mode),
           mCallback(callbackInfo.callback),
-          mUserdata(callbackInfo.userdata) {}
+          mUserdata(callbackInfo.userdata),
+          mMapStateData(mapStateData) {
+        DAWN_ASSERT(mMapStateData.Get() != nullptr);
+    }
 
     EventType GetType() override { return kType; }
 
     void ReadyHook(WGPUBufferMapAsyncStatus status) { mStatus = status; }
 
   private:
-    void CompleteImpl(EventCompletionType completionType) override {
+    void CompleteImpl(FutureID futureID, EventCompletionType completionType) override {
         WGPUBufferMapAsyncStatus status = completionType == EventCompletionType::Shutdown
                                               ? WGPUBufferMapAsyncStatus_DeviceLost
                                               : WGPUBufferMapAsyncStatus_Success;
         if (mStatus) {
             status = *mStatus;
         }
+        if (mMapStateData->pendingRequest && futureID == mMapStateData->pendingRequest->futureID) {
+            if (status == WGPUBufferMapAsyncStatus_Success) {
+                switch (mMapStateData->pendingRequest->type) {
+                    case MapRequestType::Read:
+                        mMapStateData->mapState = MapState::MappedForRead;
+                        break;
+                    case MapRequestType::Write:
+                        mMapStateData->mapState = MapState::MappedForWrite;
+                        break;
+                    default:
+                        DAWN_UNREACHABLE();
+                }
+            }
+            mMapStateData->pendingRequest = std::nullopt;
+        }
         if (mCallback) {
             mCallback(status, mUserdata);
         }
@@ -82,6 +101,9 @@
     void* mUserdata;
 
     std::optional<WGPUBufferMapAsyncStatus> mStatus;
+
+    // Shared data with the Buffer that may be modified between event handling and user inputs.
+    Ref<MapStateData> mMapStateData;
 };
 
 }  // anonymous namespace
@@ -144,7 +166,7 @@
         // If the buffer is mapped at creation, a write handle is created and will be
         // destructed on unmap if the buffer doesn't have MapWrite usage
         // The buffer is mapped right now.
-        buffer->mMapState = MapState::MappedAtCreation;
+        buffer->mMapStateData->mapState = MapState::MappedAtCreation;
 
         // This flag is for write handle created by mappedAtCreation
         // instead of MapWrite usage. We don't have such a case for read handle
@@ -185,25 +207,32 @@
 
 Buffer::Buffer(const ObjectBaseParams& params, const WGPUBufferDescriptor* descriptor)
     : ObjectBase(params),
+      mMapStateData(AcquireRef(new MapStateData{})),
       mSize(descriptor->size),
       mUsage(static_cast<WGPUBufferUsage>(descriptor->usage)) {}
 
 Buffer::~Buffer() {
     FreeMappedData();
-    InvokeAndClearCallback(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
+    SetFutureStatusAndClearPending(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
 }
 
-bool Buffer::InvokeAndClearCallback(WGPUBufferMapAsyncStatus status) {
-    if (!mPendingMapRequest) {
+bool Buffer::SetFutureStatus(WGPUBufferMapAsyncStatus status) {
+    DAWN_ASSERT(mMapStateData->pendingRequest);
+    return GetClient()->GetEventManager()->SetFutureReady<MapAsyncEvent>(
+               mMapStateData->pendingRequest->futureID, status) == WireResult::Success;
+}
+
+void Buffer::SetFutureStatusAndClearPending(WGPUBufferMapAsyncStatus status) {
+    if (!mMapStateData->pendingRequest) {
         // Since this is unconditionally called on destruction, we might not have a pending map
         // request all the time.
-        return true;
+        return;
     }
 
-    FutureID futureID = mPendingMapRequest->futureID;
-    mPendingMapRequest.reset();
-    return GetClient()->GetEventManager()->SetFutureReady<MapAsyncEvent>(futureID, status) ==
-           WireResult::Success;
+    FutureID futureID = mMapStateData->pendingRequest->futureID;
+    mMapStateData->pendingRequest = std::nullopt;
+    DAWN_UNUSED(GetClient()->GetEventManager()->SetFutureReady<MapAsyncEvent>(futureID, status));
+    return;
 }
 
 void Buffer::MapAsync(WGPUMapModeFlags mode,
@@ -225,13 +254,13 @@
     DAWN_ASSERT(GetRefcount() != 0);
 
     Client* client = GetClient();
-    auto [futureIDInternal, tracked] =
-        client->GetEventManager()->TrackEvent(std::make_unique<MapAsyncEvent>(callbackInfo));
+    auto [futureIDInternal, tracked] = client->GetEventManager()->TrackEvent(
+        std::make_unique<MapAsyncEvent>(callbackInfo, mMapStateData));
     if (!tracked) {
         return {futureIDInternal};
     }
 
-    if (mPendingMapRequest) {
+    if (mMapStateData->pendingRequest) {
         DAWN_UNUSED(client->GetEventManager()->SetFutureReady<MapAsyncEvent>(
             futureIDInternal, WGPUBufferMapAsyncStatus_MappingAlreadyPending));
         return {futureIDInternal};
@@ -249,7 +278,8 @@
     } else if (mode & WGPUMapMode_Write) {
         mapMode = MapRequestType::Write;
     }
-    mPendingMapRequest = {futureIDInternal, offset, size, mapMode};
+
+    mMapStateData->pendingRequest = {futureIDInternal, offset, size, mapMode};
 
     // Serialize the command to send to the server.
     BufferMapAsyncCmd cmd;
@@ -269,17 +299,21 @@
                                 const uint8_t* readDataUpdateInfo) {
     // Check that the response doesn't correspond to a request that has already been rejected by
     // unmap or destroy.
-    if (!mPendingMapRequest || mPendingMapRequest->futureID != future.id) {
+    if (!mMapStateData->pendingRequest) {
+        return true;
+    }
+    MapRequestData& pendingRequest = mMapStateData->pendingRequest.value();
+    if (pendingRequest.futureID != future.id) {
         return true;
     }
 
     auto FailRequest = [this]() -> bool {
-        InvokeAndClearCallback(WGPUBufferMapAsyncStatus_DeviceLost);
+        SetFutureStatus(WGPUBufferMapAsyncStatus_DeviceLost);
         return false;
     };
 
     if (status == WGPUBufferMapAsyncStatus_Success) {
-        switch (mPendingMapRequest->type) {
+        switch (pendingRequest.type) {
             case MapRequestType::Read: {
                 if (readDataUpdateInfoLength > std::numeric_limits<size_t>::max()) {
                     // This is the size of data deserialized from the command stream, which must
@@ -294,10 +328,9 @@
                 // Update user map data with server returned data
                 if (!mReadHandle->DeserializeDataUpdate(
                         readDataUpdateInfo, static_cast<size_t>(readDataUpdateInfoLength),
-                        mPendingMapRequest->offset, mPendingMapRequest->size)) {
+                        pendingRequest.offset, pendingRequest.size)) {
                     return FailRequest();
                 }
-                mMapState = MapState::MappedForRead;
                 mMappedData = const_cast<void*>(mReadHandle->GetData());
                 break;
             }
@@ -305,7 +338,6 @@
                 if (mWriteHandle == nullptr) {
                     return FailRequest();
                 }
-                mMapState = MapState::MappedForWrite;
                 mMappedData = mWriteHandle->GetData();
                 break;
             }
@@ -313,11 +345,11 @@
                 DAWN_UNREACHABLE();
         }
 
-        mMapOffset = mPendingMapRequest->offset;
-        mMapSize = mPendingMapRequest->size;
+        mMapOffset = pendingRequest.offset;
+        mMapSize = pendingRequest.size;
     }
 
-    return InvokeAndClearCallback(static_cast<WGPUBufferMapAsyncStatus>(status));
+    return SetFutureStatus(static_cast<WGPUBufferMapAsyncStatus>(status));
 }
 
 void* Buffer::GetMappedRange(size_t offset, size_t size) {
@@ -347,7 +379,8 @@
     Client* client = GetClient();
 
     // mWriteHandle can still be nullptr if buffer has been destroyed before unmap
-    if ((mMapState == MapState::MappedForWrite || mMapState == MapState::MappedAtCreation) &&
+    if ((mMapStateData->mapState == MapState::MappedForWrite ||
+         mMapStateData->mapState == MapState::MappedAtCreation) &&
         mWriteHandle != nullptr) {
         // Writes need to be flushed before Unmap is sent. Unmap calls all associated
         // in-flight callbacks which may read the updated data.
@@ -374,7 +407,7 @@
         // If mDestructWriteHandleOnUnmap is true, that means the write handle is merely
         // for mappedAtCreation usage. It is destroyed on unmap after flush to server
         // instead of at buffer destruction.
-        if (mMapState == MapState::MappedAtCreation && mDestructWriteHandleOnUnmap) {
+        if (mMapStateData->mapState == MapState::MappedAtCreation && mDestructWriteHandleOnUnmap) {
             mWriteHandle = nullptr;
             if (mReadHandle) {
                 // If it's both mappedAtCreation and MapRead we need to reset
@@ -386,7 +419,7 @@
     }
 
     // Free map access tokens
-    mMapState = MapState::Unmapped;
+    mMapStateData->mapState = MapState::Unmapped;
     mMapOffset = 0;
     mMapSize = 0;
 
@@ -394,7 +427,7 @@
     cmd.self = ToAPI(this);
     client->SerializeCommand(cmd);
 
-    InvokeAndClearCallback(WGPUBufferMapAsyncStatus_UnmappedBeforeCallback);
+    SetFutureStatusAndClearPending(WGPUBufferMapAsyncStatus_UnmappedBeforeCallback);
 }
 
 void Buffer::Destroy() {
@@ -402,13 +435,13 @@
 
     // Remove the current mapping and destroy Read/WriteHandles.
     FreeMappedData();
-    mMapState = MapState::Unmapped;
+    mMapStateData->mapState = MapState::Unmapped;
 
     BufferDestroyCmd cmd;
     cmd.self = ToAPI(this);
     client->SerializeCommand(cmd);
 
-    InvokeAndClearCallback(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
+    SetFutureStatusAndClearPending(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback);
 }
 
 WGPUBufferUsage Buffer::GetUsage() const {
@@ -420,13 +453,13 @@
 }
 
 WGPUBufferMapState Buffer::GetMapState() const {
-    switch (mMapState) {
+    switch (mMapStateData->mapState) {
         case MapState::MappedForRead:
         case MapState::MappedForWrite:
         case MapState::MappedAtCreation:
             return WGPUBufferMapState_Mapped;
         case MapState::Unmapped:
-            if (mPendingMapRequest) {
+            if (mMapStateData->pendingRequest) {
                 return WGPUBufferMapState_Pending;
             } else {
                 return WGPUBufferMapState_Unmapped;
@@ -436,11 +469,12 @@
 }
 
 bool Buffer::IsMappedForReading() const {
-    return mMapState == MapState::MappedForRead;
+    return mMapStateData->mapState == MapState::MappedForRead;
 }
 
 bool Buffer::IsMappedForWriting() const {
-    return mMapState == MapState::MappedForWrite || mMapState == MapState::MappedAtCreation;
+    return mMapStateData->mapState == MapState::MappedForWrite ||
+           mMapStateData->mapState == MapState::MappedAtCreation;
 }
 
 bool Buffer::CheckGetMappedRangeOffsetSize(size_t offset, size_t size) const {
diff --git a/src/dawn/wire/client/Buffer.h b/src/dawn/wire/client/Buffer.h
index 3a1fdbb..5cf4c6a 100644
--- a/src/dawn/wire/client/Buffer.h
+++ b/src/dawn/wire/client/Buffer.h
@@ -32,6 +32,8 @@
 #include <optional>
 
 #include "dawn/common/FutureUtils.h"
+#include "dawn/common/Ref.h"
+#include "dawn/common/RefCounted.h"
 #include "dawn/webgpu.h"
 #include "dawn/wire/WireClient.h"
 #include "dawn/wire/client/ObjectBase.h"
@@ -40,6 +42,28 @@
 
 class Device;
 
+enum class MapRequestType { None, Read, Write };
+
+enum class MapState {
+    Unmapped,
+    MappedForRead,
+    MappedForWrite,
+    MappedAtCreation,
+};
+
+struct MapRequestData {
+    FutureID futureID = kNullFutureID;
+    size_t offset = 0;
+    size_t size = 0;
+    MapRequestType type = MapRequestType::None;
+};
+
+struct MapStateData : public RefCounted {
+    // Up to only one request can exist at a single time. Other requests are rejected.
+    std::optional<MapRequestData> pendingRequest = std::nullopt;
+    MapState mapState = MapState::Unmapped;
+};
+
 class Buffer final : public ObjectBase {
   public:
     static WGPUBuffer Create(Device* device, const WGPUBufferDescriptor* descriptor);
@@ -73,7 +97,9 @@
     WGPUBufferMapState GetMapState() const;
 
   private:
-    bool InvokeAndClearCallback(WGPUBufferMapAsyncStatus status);
+    // Prepares the callbacks to be called and potentially calls them
+    bool SetFutureStatus(WGPUBufferMapAsyncStatus status);
+    void SetFutureStatusAndClearPending(WGPUBufferMapAsyncStatus status);
 
     bool IsMappedForReading() const;
     bool IsMappedForWriting() const;
@@ -81,23 +107,11 @@
 
     void FreeMappedData();
 
-    enum class MapRequestType { None, Read, Write };
-
-    enum class MapState {
-        Unmapped,
-        MappedForRead,
-        MappedForWrite,
-        MappedAtCreation,
-    };
-
-    // Up to only one request can exist at a single time. Other requests are rejected.
-    struct MapRequestData {
-        FutureID futureID = kNullFutureID;
-        size_t offset = 0;
-        size_t size = 0;
-        MapRequestType type = MapRequestType::None;
-    };
-    std::optional<MapRequestData> mPendingMapRequest;
+    // The map state is a shared resource with the TrackedEvent so that it is updated only when the
+    // callback is actually called. This is important for WaitAny and ProcessEvents cases where the
+    // server may have responded, but due to an early Unmap or Destroy before the corresponding
+    // WaitAny or ProcessEvents call, we need to update the callback result.
+    Ref<MapStateData> mMapStateData;
 
     uint64_t mSize = 0;
     WGPUBufferUsage mUsage;
@@ -106,7 +120,6 @@
     // TODO(enga): Use a tagged pointer to save space.
     std::unique_ptr<MemoryTransferService::ReadHandle> mReadHandle = nullptr;
     std::unique_ptr<MemoryTransferService::WriteHandle> mWriteHandle = nullptr;
-    MapState mMapState = MapState::Unmapped;
     bool mDestructWriteHandleOnUnmap = false;
 
     void* mMappedData = nullptr;
diff --git a/src/dawn/wire/client/EventManager.cpp b/src/dawn/wire/client/EventManager.cpp
index c11b58b..1614515 100644
--- a/src/dawn/wire/client/EventManager.cpp
+++ b/src/dawn/wire/client/EventManager.cpp
@@ -53,13 +53,13 @@
 }
 
 void TrackedEvent::SetReady() {
-    DAWN_ASSERT(mEventState == EventState::Pending);
+    DAWN_ASSERT(mEventState != EventState::Complete);
     mEventState = EventState::Ready;
 }
 
-void TrackedEvent::Complete(EventCompletionType type) {
+void TrackedEvent::Complete(FutureID futureID, EventCompletionType type) {
     DAWN_ASSERT(mEventState != EventState::Complete);
-    CompleteImpl(type);
+    CompleteImpl(futureID, type);
     mEventState = EventState::Complete;
 }
 
@@ -71,7 +71,7 @@
     FutureID futureID = mNextFutureID++;
 
     if (mClient->IsDisconnected()) {
-        event->Complete(EventCompletionType::Shutdown);
+        event->Complete(futureID, EventCompletionType::Shutdown);
         return {futureID, false};
     }
 
@@ -95,15 +95,14 @@
 
         // Ordering guaranteed because we are using a sorted map.
         for (auto& [futureID, event] : events) {
-            event->Complete(EventCompletionType::Shutdown);
+            event->Complete(futureID, EventCompletionType::Shutdown);
         }
     }
     mIsShutdown = true;
 }
 
 void EventManager::ProcessPollEvents() {
-    // Since events are already stored in an ordered map, this list must already be ordered.
-    std::vector<std::unique_ptr<TrackedEvent>> eventsToCompleteNow;
+    std::vector<std::pair<FutureID, std::unique_ptr<TrackedEvent>>> eventsToCompleteNow;
     mTrackedEvents.Use([&](auto trackedEvents) {
         for (auto it = trackedEvents->begin(); it != trackedEvents->end();) {
             auto& event = it->second;
@@ -115,13 +114,14 @@
                 ++it;
                 continue;
             }
-            eventsToCompleteNow.emplace_back(std::move(event));
+            eventsToCompleteNow.emplace_back(it->first, std::move(event));
             it = trackedEvents->erase(it);
         }
     });
 
-    for (auto& event : eventsToCompleteNow) {
-        event->Complete(EventCompletionType::Ready);
+    // Since events were initially stored and iterated from an ordered map, they must be ordered.
+    for (auto& [futureID, event] : eventsToCompleteNow) {
+        event->Complete(futureID, EventCompletionType::Ready);
     }
 }
 
@@ -167,9 +167,9 @@
         }
     });
 
-    for (auto& [_, event] : eventsToCompleteNow) {
+    for (auto& [futureID, event] : eventsToCompleteNow) {
         // .completed has already been set to true (before the callback, per API contract).
-        event->Complete(EventCompletionType::Ready);
+        event->Complete(futureID, EventCompletionType::Ready);
     }
 
     return anyCompleted ? WGPUWaitStatus_Success : WGPUWaitStatus_TimedOut;
diff --git a/src/dawn/wire/client/EventManager.h b/src/dawn/wire/client/EventManager.h
index 02b571a..277c088 100644
--- a/src/dawn/wire/client/EventManager.h
+++ b/src/dawn/wire/client/EventManager.h
@@ -64,10 +64,10 @@
     bool IsReady() const;
 
     void SetReady();
-    void Complete(EventCompletionType type);
+    void Complete(FutureID futureID, EventCompletionType type);
 
   protected:
-    virtual void CompleteImpl(EventCompletionType type) = 0;
+    virtual void CompleteImpl(FutureID futureID, EventCompletionType type) = 0;
 
     const WGPUCallbackMode mMode;
     enum class EventState {
@@ -138,7 +138,7 @@
 
         // Handle spontaneous completions.
         if (spontaneousEvent) {
-            spontaneousEvent->Complete(EventCompletionType::Ready);
+            spontaneousEvent->Complete(futureID, EventCompletionType::Ready);
         }
         return WireResult::Success;
     }
diff --git a/src/dawn/wire/client/Queue.cpp b/src/dawn/wire/client/Queue.cpp
index 6a956dd..89c0bb5 100644
--- a/src/dawn/wire/client/Queue.cpp
+++ b/src/dawn/wire/client/Queue.cpp
@@ -50,7 +50,7 @@
     void ReadyHook(WGPUQueueWorkDoneStatus status) { mStatus = status; }
 
   private:
-    void CompleteImpl(EventCompletionType completionType) override {
+    void CompleteImpl(FutureID futureID, EventCompletionType completionType) override {
         if (mStatus == WGPUQueueWorkDoneStatus_DeviceLost) {
             mStatus = WGPUQueueWorkDoneStatus_Success;
         }