Reland "[WGPUFuture] Move the EventManager in the wire to appear in the Instance."

- Remove attempts to serialize the event manager handle for fuzzing. It
  shouldn't be important because its a value that is just passed through
  from the client to the server so that the client can identify the
  event manager later, but since the fuzz tests don't actually run a
  Dawn wire client, the value is unimportant.

This is a reland of commit 6ecbeedd4e016dfa304082bc7e3639b54d179ce1

Original change's description:
> [WGPUFuture] Move the EventManager in the wire to appear in the Instance.
>
> - Note that the EventManager only appears to be in the Instance because
>   it may need to outlive the Instance, and so actually lives on the
>   Client.
>
> Bug: dawn:2061
> Change-Id: I601a27cd7ee90e4dcb53fd9e45144a6059c00054
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/167840
> Commit-Queue: Loko Kung <lokokung@google.com>
> Reviewed-by: Kai Ninomiya <kainino@chromium.org>
> Reviewed-by: Austin Eng <enga@chromium.org>
> Kokoro: Kokoro <noreply+kokoro@google.com>

Bug: dawn:2061
Change-Id: I7d24a9dd44057d90ecf3b0a562c0eeee5f2b308e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/169063
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp
index 102f498..7d0e990 100644
--- a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp
+++ b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp
@@ -69,7 +69,10 @@
     {% elif member.type.name.get() == "ObjectId" %}
         {{ convert_objectid(member, in, out, access) }}
     {% elif member.type.name.get() == "ObjectHandle" %}
-        {{ convert_objecthandle(member, in, out, in_access) }}
+        //* Only convert the handle if it maps to an object. Otherwise don't serialize it at all.
+        {% if member.handle_type %}
+            {{ convert_objecthandle(member, in, out, in_access) }}
+        {% endif %}
     {% else %}
         {{out}} = {{in}}({{in_access}});
     {% endif %}
diff --git a/generator/templates/dawn/wire/client/ApiProcs.cpp b/generator/templates/dawn/wire/client/ApiProcs.cpp
index c1cff6d..6f27e18 100644
--- a/generator/templates/dawn/wire/client/ApiProcs.cpp
+++ b/generator/templates/dawn/wire/client/ApiProcs.cpp
@@ -36,6 +36,18 @@
 
 namespace dawn::wire::client {
 
+    // Template function for constexpr branching when creating new objects.
+    template <typename Parent, typename Child, typename... Args>
+    Child* Create(Parent p, Args... args) {
+        if constexpr (std::is_constructible_v<Child, const ObjectBaseParams&, decltype(args)...>) {
+            return p->GetClient()->template Make<Child>(args...);
+        } else if constexpr (std::is_constructible_v<Child, const ObjectBaseParams&, const ObjectHandle&, decltype(args)...>) {
+            return p->GetClient()->template Make<Child>(p->GetEventManagerHandle(), args...);
+        } else {
+            return p->GetClient()->template Make<Child>();
+        }
+    }
+
     //* Outputs an rvalue that's the number of elements a pointer member points to.
     {% macro member_length(member, accessor) -%}
         {%- if member.length == "constant" -%}
@@ -73,22 +85,11 @@
                     //* For object creation, store the object ID the client will use for the result.
                     {% if method.return_type.category == "object" %}
                         {% set ReturnObj = method.return_type.name.CamelCase() %}
-
-                        {{ReturnObj}}* returnObject;
-                        if constexpr (std::is_constructible_v<
-                            {{- ReturnObj}}, const ObjectBaseParams&
+                        {{ReturnObj}}* returnObject = Create<{{as_wireType(type)}}, {{ReturnObj}}>(self
                             {%- for arg in method.arguments -%}
-                                , decltype({{as_varName(arg.name)}})
+                                    , {{as_varName(arg.name)}}
                             {%- endfor -%}
-                        >) {
-                            returnObject = self->GetClient()->Make<{{ReturnObj}}>(
-                                {%- for arg in method.arguments -%}
-                                    {% if not loop.first %}, {% endif %}{{as_varName(arg.name)}}
-                                {%- endfor -%}
-                            );
-                        } else {
-                            returnObject = self->GetClient()->Make<{{ReturnObj}}>();
-                        }
+                        );
                         cmd.result = returnObject->GetWireHandle();
                     {% endif %}
 
diff --git a/include/dawn/wire/WireClient.h b/include/dawn/wire/WireClient.h
index 448318f..5b0a3fa 100644
--- a/include/dawn/wire/WireClient.h
+++ b/include/dawn/wire/WireClient.h
@@ -86,7 +86,7 @@
     ReservedTexture ReserveTexture(WGPUDevice device, const WGPUTextureDescriptor* descriptor);
     ReservedSwapChain ReserveSwapChain(WGPUDevice device,
                                        const WGPUSwapChainDescriptor* descriptor);
-    ReservedDevice ReserveDevice();
+    ReservedDevice ReserveDevice(WGPUInstance instance);
     ReservedInstance ReserveInstance(const WGPUInstanceDescriptor* descriptor = nullptr);
 
     void ReclaimTextureReservation(const ReservedTexture& reservation);
diff --git a/src/dawn/dawn_wire.json b/src/dawn/dawn_wire.json
index 09f0fad..d0ded98 100644
--- a/src/dawn/dawn_wire.json
+++ b/src/dawn/dawn_wire.json
@@ -33,6 +33,7 @@
     "commands": {
         "buffer map async": [
             { "name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
+            { "name": "event manager handle", "type": "ObjectHandle" },
             { "name": "future", "type": "future" },
             { "name": "mode", "type": "map mode" },
             { "name": "offset", "type": "uint64_t"},
@@ -76,6 +77,7 @@
         ],
         "queue on submitted work done": [
             { "name": "queue id", "type": "ObjectId", "id_type": "queue" },
+            { "name": "event manager handle", "type": "ObjectHandle" },
             { "name": "future", "type": "future" }
         ],
         "queue write buffer": [
@@ -99,6 +101,7 @@
         ],
         "instance request adapter": [
             { "name": "instance id", "type": "ObjectId", "id_type": "instance" },
+            { "name": "event manager handle", "type": "ObjectHandle" },
             { "name": "future", "type": "future" },
             { "name": "adapter object handle", "type": "ObjectHandle", "handle_type": "adapter"},
             { "name": "options", "type": "request adapter options", "annotation": "const*", "optional": true }
@@ -112,7 +115,8 @@
     },
     "return commands": {
         "buffer map async callback": [
-            { "name": "buffer", "type": "ObjectHandle", "handle_type": "buffer" },
+            { "name": "buffer", "type": "ObjectHandle", "handle_type": "buffer", "_comment": "TODO(dawn:2061) Remove this field once mapping is updated." },
+            { "name": "event manager", "type": "ObjectHandle" },
             { "name": "future", "type": "future" },
             { "name": "status", "type": "uint32_t" },
             { "name": "read data update info length", "type": "uint64_t" },
@@ -152,8 +156,8 @@
             { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" }
         ],
         "queue work done callback": [
-            { "name": "queue", "type": "ObjectHandle", "handle_type": "queue" },
-	    { "name": "future", "type": "future" },
+            { "name": "event manager", "type": "ObjectHandle" },
+            { "name": "future", "type": "future" },
             { "name": "status", "type": "queue work done status" }
         ],
         "shader module get compilation info callback": [
@@ -163,7 +167,7 @@
             { "name": "info", "type": "compilation info", "annotation": "const*", "optional": true }
         ],
         "instance request adapter callback": [
-            { "name": "instance", "type": "ObjectHandle", "handle_type": "instance" },
+            { "name": "event manager", "type": "ObjectHandle" },
             { "name": "future", "type": "future" },
             { "name": "status", "type": "request adapter status" },
             { "name": "message", "type": "char", "annotation": "const*", "length": "strlen", "optional": true },
diff --git a/src/dawn/samples/SampleUtils.cpp b/src/dawn/samples/SampleUtils.cpp
index 7c48112..f1aa2a9 100644
--- a/src/dawn/samples/SampleUtils.cpp
+++ b/src/dawn/samples/SampleUtils.cpp
@@ -236,7 +236,11 @@
             procs = dawn::wire::client::GetProcs();
             s2cBuf->SetHandler(wireClient);
 
-            auto deviceReservation = wireClient->ReserveDevice();
+            auto instanceReservation = wireClient->ReserveInstance();
+            wireServer->InjectInstance(instance->Get(), instanceReservation.id,
+                                       instanceReservation.generation);
+
+            auto deviceReservation = wireClient->ReserveDevice(instanceReservation.instance);
             wireServer->InjectDevice(backendDevice, deviceReservation.id,
                                      deviceReservation.generation);
             cDevice = deviceReservation.device;
diff --git a/src/dawn/tests/unittests/GetProcAddressTests.cpp b/src/dawn/tests/unittests/GetProcAddressTests.cpp
index b3ea500..1f8dabc 100644
--- a/src/dawn/tests/unittests/GetProcAddressTests.cpp
+++ b/src/dawn/tests/unittests/GetProcAddressTests.cpp
@@ -91,7 +91,8 @@
                 clientDesc.serializer = mC2sBuf.get();
                 mWireClient = std::make_unique<wire::WireClient>(clientDesc);
 
-                mDevice = wgpu::Device::Acquire(mWireClient->ReserveDevice().device);
+                mDevice = wgpu::Device::Acquire(
+                    mWireClient->ReserveDevice(ToAPI(mNativeInstance.Get())).device);
                 mProcs = wire::client::GetProcs();
                 break;
             }
diff --git a/src/dawn/tests/unittests/wire/WireAdapterTests.cpp b/src/dawn/tests/unittests/wire/WireAdapterTests.cpp
index d3c8af7..469ef93 100644
--- a/src/dawn/tests/unittests/wire/WireAdapterTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireAdapterTests.cpp
@@ -55,18 +55,9 @@
     void SetUp() override {
         WireTest::SetUp();
 
-        auto reservation = GetWireClient()->ReserveInstance();
-        instance = wgpu::Instance::Acquire(reservation.instance);
-
-        WGPUInstance apiInstance = api.GetNewInstance();
-        EXPECT_CALL(api, InstanceReference(apiInstance));
-        EXPECT_TRUE(
-            GetWireServer()->InjectInstance(apiInstance, reservation.id, reservation.generation));
-
-        wgpu::RequestAdapterOptions options = {};
+        WGPURequestAdapterOptions options = {};
         MockCallback<WGPURequestAdapterCallback> cb;
-        auto* userdata = cb.MakeUserdata(this);
-        instance.RequestAdapter(&options, cb.Callback(), userdata);
+        wgpuInstanceRequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
 
         // Expect the server to receive the message. Then, mock a fake reply.
         apiAdapter = api.GetNewAdapter();
@@ -111,12 +102,10 @@
 
     void TearDown() override {
         adapter = nullptr;
-        instance = nullptr;
         WireTest::TearDown();
     }
 
     WGPUAdapter apiAdapter;
-    wgpu::Instance instance;
     wgpu::Adapter adapter;
 };
 
diff --git a/src/dawn/tests/unittests/wire/WireDisconnectTests.cpp b/src/dawn/tests/unittests/wire/WireDisconnectTests.cpp
index d9c96b9..889b52f 100644
--- a/src/dawn/tests/unittests/wire/WireDisconnectTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireDisconnectTests.cpp
@@ -173,7 +173,7 @@
     // Expect release on all objects created by the client. Note: the device
     // should be deleted first because it may free its reference to the default queue
     // on deletion.
-    Sequence s1, s2, s3;
+    Sequence s1, s2, s3, s4;
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
@@ -183,10 +183,11 @@
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
         .Times(1)
         .InSequence(s1, s2);
-    EXPECT_CALL(api, DeviceRelease(apiDevice)).Times(1).InSequence(s1, s2, s3);
+    EXPECT_CALL(api, DeviceRelease(apiDevice)).Times(1).InSequence(s1, s2, s3, s4);
     EXPECT_CALL(api, QueueRelease(apiQueue)).Times(1).InSequence(s1);
     EXPECT_CALL(api, CommandEncoderRelease(apiCommandEncoder)).Times(1).InSequence(s2);
     EXPECT_CALL(api, SamplerRelease(apiSampler)).Times(1).InSequence(s3);
+    EXPECT_CALL(api, InstanceRelease(apiInstance)).Times(1).InSequence(s4);
     FlushClient();
 
     // Signal that we already released and cleared callbacks for |apiDevice|
diff --git a/src/dawn/tests/unittests/wire/WireFutureTest.h b/src/dawn/tests/unittests/wire/WireFutureTest.h
index b065ddd..042db62 100644
--- a/src/dawn/tests/unittests/wire/WireFutureTest.h
+++ b/src/dawn/tests/unittests/wire/WireFutureTest.h
@@ -96,20 +96,6 @@
   protected:
     using testing::WithParamInterface<Params>::GetParam;
 
-    void SetUp() override {
-        WireTest::SetUp();
-
-        auto reservation = GetWireClient()->ReserveInstance();
-        instance = reservation.instance;
-
-        apiInstance = api.GetNewInstance();
-        EXPECT_CALL(api, InstanceReference(apiInstance));
-        EXPECT_TRUE(
-            GetWireServer()->InjectInstance(apiInstance, reservation.id, reservation.generation));
-    }
-
-    void TearDown() override { WireTest::TearDown(); }
-
     // Calls the actual API that the test suite is exercising given the callback mode. This should
     // be used in favor of directly calling the API because the Async mode actually calls a
     // different entry point.
@@ -203,9 +189,6 @@
         }
     }
 
-    WGPUInstance instance;
-    WGPUInstance apiInstance;
-
   private:
     AsyncFT mAsyncF = AsyncF;
     FutureFT mFutureF = FutureF;
diff --git a/src/dawn/tests/unittests/wire/WireInjectDeviceTests.cpp b/src/dawn/tests/unittests/wire/WireInjectDeviceTests.cpp
index 2e129ae..5b0a92f 100644
--- a/src/dawn/tests/unittests/wire/WireInjectDeviceTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireInjectDeviceTests.cpp
@@ -47,7 +47,7 @@
 // Test that reserving and injecting a device makes calls on the client object forward to the
 // server object correctly.
 TEST_F(WireInjectDeviceTests, CallAfterReserveInject) {
-    ReservedDevice reservation = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation = GetWireClient()->ReserveDevice(instance);
 
     WGPUDevice serverDevice = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice));
@@ -73,8 +73,8 @@
 
 // Test that reserve correctly returns different IDs each time.
 TEST_F(WireInjectDeviceTests, ReserveDifferentIDs) {
-    ReservedDevice reservation1 = GetWireClient()->ReserveDevice();
-    ReservedDevice reservation2 = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation1 = GetWireClient()->ReserveDevice(instance);
+    ReservedDevice reservation2 = GetWireClient()->ReserveDevice(instance);
 
     ASSERT_NE(reservation1.id, reservation2.id);
     ASSERT_NE(reservation1.device, reservation2.device);
@@ -82,7 +82,7 @@
 
 // Test that injecting the same id without a destroy first fails.
 TEST_F(WireInjectDeviceTests, InjectExistingID) {
-    ReservedDevice reservation = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation = GetWireClient()->ReserveDevice(instance);
 
     WGPUDevice serverDevice = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice));
@@ -106,7 +106,7 @@
 
 // Test that the server only borrows the device and does a single reference-release
 TEST_F(WireInjectDeviceTests, InjectedDeviceLifetime) {
-    ReservedDevice reservation = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation = GetWireClient()->ReserveDevice(instance);
 
     // Injecting the device adds a reference
     WGPUDevice serverDevice = api.GetNewDevice();
@@ -133,7 +133,7 @@
 // Test that it is an error to get the primary queue of a device before it has been
 // injected on the server.
 TEST_F(WireInjectDeviceTests, GetQueueBeforeInject) {
-    ReservedDevice reservation = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation = GetWireClient()->ReserveDevice(instance);
 
     wgpuDeviceGetQueue(reservation.device);
     FlushClient(false);
@@ -142,7 +142,7 @@
 // Test that it is valid to get the primary queue of a device after it has been
 // injected on the server.
 TEST_F(WireInjectDeviceTests, GetQueueAfterInject) {
-    ReservedDevice reservation = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation = GetWireClient()->ReserveDevice(instance);
 
     WGPUDevice serverDevice = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice));
@@ -169,8 +169,8 @@
 // Test that the list of live devices can be reflected using GetDevice.
 TEST_F(WireInjectDeviceTests, ReflectLiveDevices) {
     // Reserve two devices.
-    ReservedDevice reservation1 = GetWireClient()->ReserveDevice();
-    ReservedDevice reservation2 = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation1 = GetWireClient()->ReserveDevice(instance);
+    ReservedDevice reservation2 = GetWireClient()->ReserveDevice(instance);
 
     // Inject both devices.
 
@@ -217,7 +217,7 @@
 // objects instead.
 TEST_F(WireInjectDeviceTests, TrackChildObjectsWithTwoReservedDevices) {
     // Reserve one device, inject it, and get the primary queue.
-    ReservedDevice reservation1 = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation1 = GetWireClient()->ReserveDevice(instance);
 
     WGPUDevice serverDevice1 = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice1));
@@ -236,7 +236,7 @@
     FlushClient();
 
     // Reserve a second device, and inject it.
-    ReservedDevice reservation2 = GetWireClient()->ReserveDevice();
+    ReservedDevice reservation2 = GetWireClient()->ReserveDevice(instance);
 
     WGPUDevice serverDevice2 = api.GetNewDevice();
     EXPECT_CALL(api, DeviceReference(serverDevice2));
@@ -267,17 +267,17 @@
 TEST_F(WireInjectDeviceTests, ReclaimDeviceReservation) {
     // Test that doing a reservation and full release is an error.
     {
-        ReservedDevice reservation = GetWireClient()->ReserveDevice();
+        ReservedDevice reservation = GetWireClient()->ReserveDevice(instance);
         wgpuDeviceRelease(reservation.device);
         FlushClient(false);
     }
 
     // Test that doing a reservation and then reclaiming it recycles the ID.
     {
-        ReservedDevice reservation1 = GetWireClient()->ReserveDevice();
+        ReservedDevice reservation1 = GetWireClient()->ReserveDevice(instance);
         GetWireClient()->ReclaimDeviceReservation(reservation1);
 
-        ReservedDevice reservation2 = GetWireClient()->ReserveDevice();
+        ReservedDevice reservation2 = GetWireClient()->ReserveDevice(instance);
 
         // The ID is the same, but the generation is still different.
         ASSERT_EQ(reservation1.id, reservation2.id);
diff --git a/src/dawn/tests/unittests/wire/WireTest.cpp b/src/dawn/tests/unittests/wire/WireTest.cpp
index c268bbd..479ebcc 100644
--- a/src/dawn/tests/unittests/wire/WireTest.cpp
+++ b/src/dawn/tests/unittests/wire/WireTest.cpp
@@ -53,7 +53,6 @@
 void WireTest::SetUp() {
     DawnProcTable mockProcs;
     api.GetProcTable(&mockProcs);
-    WGPUDevice mockDevice = api.GetNewDevice();
 
     // This SetCallback call cannot be ignored because it is done as soon as we start the server
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(_, _, _)).Times(Exactly(1));
@@ -81,12 +80,18 @@
 
     dawnProcSetProcs(&dawn::wire::client::GetProcs());
 
-    auto deviceReservation = mWireClient->ReserveDevice();
-    EXPECT_CALL(api, DeviceReference(mockDevice));
-    mWireServer->InjectDevice(mockDevice, deviceReservation.id, deviceReservation.generation);
+    auto instanceReservation = GetWireClient()->ReserveInstance();
+    instance = instanceReservation.instance;
+    apiInstance = api.GetNewInstance();
+    EXPECT_CALL(api, InstanceReference(apiInstance));
+    EXPECT_TRUE(GetWireServer()->InjectInstance(apiInstance, instanceReservation.id,
+                                                instanceReservation.generation));
 
+    auto deviceReservation = mWireClient->ReserveDevice(instance);
     device = deviceReservation.device;
-    apiDevice = mockDevice;
+    apiDevice = api.GetNewDevice();
+    EXPECT_CALL(api, DeviceReference(apiDevice));
+    mWireServer->InjectDevice(apiDevice, deviceReservation.id, deviceReservation.generation);
 
     // The GetQueue is done on WireClient startup so we expect it now.
     queue = wgpuDeviceGetQueue(device);
@@ -145,6 +150,7 @@
 void WireTest::DeleteServer() {
     EXPECT_CALL(api, QueueRelease(apiQueue)).Times(1);
     EXPECT_CALL(api, DeviceRelease(apiDevice)).Times(1);
+    EXPECT_CALL(api, InstanceRelease(apiInstance)).Times(1);
 
     if (mWireServer) {
         // These are called on server destruction to clear the callbacks. They must not be
diff --git a/src/dawn/tests/unittests/wire/WireTest.h b/src/dawn/tests/unittests/wire/WireTest.h
index e7d6710..a45c651 100644
--- a/src/dawn/tests/unittests/wire/WireTest.h
+++ b/src/dawn/tests/unittests/wire/WireTest.h
@@ -145,6 +145,8 @@
     void DefaultApiDeviceWasReleased();
 
     testing::StrictMock<MockProcTable> api;
+    WGPUInstance instance;
+    WGPUInstance apiInstance;
     WGPUDevice apiDevice;
     WGPUQueue apiQueue;
     WGPUDevice device;
diff --git a/src/dawn/wire/ObjectHandle.cpp b/src/dawn/wire/ObjectHandle.cpp
index 436901c..813dcd2 100644
--- a/src/dawn/wire/ObjectHandle.cpp
+++ b/src/dawn/wire/ObjectHandle.cpp
@@ -59,6 +59,10 @@
     return *this;
 }
 
+bool ObjectHandle::operator==(const ObjectHandle& other) const {
+    return id == other.id && generation == other.generation;
+}
+
 bool ObjectHandle::IsValid() const {
     return id > 0;
 }
diff --git a/src/dawn/wire/ObjectHandle.h b/src/dawn/wire/ObjectHandle.h
index 443df72..117a43a 100644
--- a/src/dawn/wire/ObjectHandle.h
+++ b/src/dawn/wire/ObjectHandle.h
@@ -28,8 +28,11 @@
 #ifndef DAWN_WIRE_OBJECTHANDLE_H_
 #define DAWN_WIRE_OBJECTHANDLE_H_
 
+#include <cstddef>
 #include <cstdint>
 
+#include "dawn/common/HashUtils.h"
+
 namespace dawn::wire {
 
 using ObjectId = uint32_t;
@@ -59,9 +62,20 @@
     ObjectHandle& AssignFrom(const ObjectHandle& rhs);
     ObjectHandle& AssignFrom(const volatile ObjectHandle& rhs);
 
+    bool operator==(const ObjectHandle& other) const;
+
     bool IsValid() const;
 };
 
 }  // namespace dawn::wire
 
+template <>
+struct std::hash<dawn::wire::ObjectHandle> {
+    size_t operator()(const dawn::wire::ObjectHandle& value) const {
+        size_t hash = dawn::Hash(value.id);
+        dawn::HashCombine(&hash, value.generation);
+        return hash;
+    }
+};
+
 #endif  // DAWN_WIRE_OBJECTHANDLE_H_
diff --git a/src/dawn/wire/WireClient.cpp b/src/dawn/wire/WireClient.cpp
index 21f62d5..313073f 100644
--- a/src/dawn/wire/WireClient.cpp
+++ b/src/dawn/wire/WireClient.cpp
@@ -51,8 +51,8 @@
     return mImpl->ReserveSwapChain(device, descriptor);
 }
 
-ReservedDevice WireClient::ReserveDevice() {
-    return mImpl->ReserveDevice();
+ReservedDevice WireClient::ReserveDevice(WGPUInstance instance) {
+    return mImpl->ReserveDevice(instance);
 }
 
 ReservedInstance WireClient::ReserveInstance(const WGPUInstanceDescriptor* descriptor) {
diff --git a/src/dawn/wire/client/Adapter.cpp b/src/dawn/wire/client/Adapter.cpp
index c98cc93..3a85769 100644
--- a/src/dawn/wire/client/Adapter.cpp
+++ b/src/dawn/wire/client/Adapter.cpp
@@ -163,7 +163,7 @@
 
     // The descriptor is passed so that the deviceLostCallback can be tracked client-side and called
     // when the device is lost.
-    Device* device = client->Make<Device>(descriptor);
+    Device* device = client->Make<Device>(GetEventManagerHandle(), descriptor);
     uint64_t serial = mRequestDeviceRequests.Add({callback, device->GetWireId(), userdata});
 
     // Ensure the device lost callback isn't serialized as part of the command, as it cannot be
diff --git a/src/dawn/wire/client/Adapter.h b/src/dawn/wire/client/Adapter.h
index b7cf683..dd88635 100644
--- a/src/dawn/wire/client/Adapter.h
+++ b/src/dawn/wire/client/Adapter.h
@@ -39,9 +39,9 @@
 
 namespace dawn::wire::client {
 
-class Adapter final : public ObjectBase {
+class Adapter final : public ObjectWithEventsBase {
   public:
-    using ObjectBase::ObjectBase;
+    using ObjectWithEventsBase::ObjectWithEventsBase;
     ~Adapter() override;
 
     void CancelCallbacksForDisconnect() override;
diff --git a/src/dawn/wire/client/Buffer.cpp b/src/dawn/wire/client/Buffer.cpp
index 974d2c5..d5e44c8 100644
--- a/src/dawn/wire/client/Buffer.cpp
+++ b/src/dawn/wire/client/Buffer.cpp
@@ -159,7 +159,7 @@
     // Create the buffer and send the creation command.
     // This must happen after any potential error buffer creation
     // as server expects allocating ids to be monotonically increasing
-    Buffer* buffer = wireClient->Make<Buffer>(descriptor);
+    Buffer* buffer = wireClient->Make<Buffer>(device->GetEventManagerHandle(), descriptor);
     buffer->mDestructWriteHandleOnUnmap = false;
 
     if (descriptor->mappedAtCreation) {
@@ -205,8 +205,10 @@
     return ToAPI(buffer);
 }
 
-Buffer::Buffer(const ObjectBaseParams& params, const WGPUBufferDescriptor* descriptor)
-    : ObjectBase(params),
+Buffer::Buffer(const ObjectBaseParams& params,
+               const ObjectHandle& eventManagerHandle,
+               const WGPUBufferDescriptor* descriptor)
+    : ObjectWithEventsBase(params, eventManagerHandle),
       mMapStateData(AcquireRef(new MapStateData{})),
       mSize(descriptor->size),
       mUsage(static_cast<WGPUBufferUsage>(descriptor->usage)) {}
@@ -218,8 +220,8 @@
 
 bool Buffer::SetFutureStatus(WGPUBufferMapAsyncStatus status) {
     DAWN_ASSERT(mMapStateData->pendingRequest);
-    return GetClient()->GetEventManager()->SetFutureReady<MapAsyncEvent>(
-               mMapStateData->pendingRequest->futureID, status) == WireResult::Success;
+    return GetEventManager().SetFutureReady<MapAsyncEvent>(mMapStateData->pendingRequest->futureID,
+                                                           status) == WireResult::Success;
 }
 
 void Buffer::SetFutureStatusAndClearPending(WGPUBufferMapAsyncStatus status) {
@@ -231,7 +233,7 @@
 
     FutureID futureID = mMapStateData->pendingRequest->futureID;
     mMapStateData->pendingRequest = std::nullopt;
-    DAWN_UNUSED(GetClient()->GetEventManager()->SetFutureReady<MapAsyncEvent>(futureID, status));
+    DAWN_UNUSED(GetEventManager().SetFutureReady<MapAsyncEvent>(futureID, status));
     return;
 }
 
@@ -254,14 +256,14 @@
     DAWN_ASSERT(GetRefcount() != 0);
 
     Client* client = GetClient();
-    auto [futureIDInternal, tracked] = client->GetEventManager()->TrackEvent(
-        std::make_unique<MapAsyncEvent>(callbackInfo, mMapStateData));
+    auto [futureIDInternal, tracked] =
+        GetEventManager().TrackEvent(std::make_unique<MapAsyncEvent>(callbackInfo, mMapStateData));
     if (!tracked) {
         return {futureIDInternal};
     }
 
     if (mMapStateData->pendingRequest) {
-        DAWN_UNUSED(client->GetEventManager()->SetFutureReady<MapAsyncEvent>(
+        DAWN_UNUSED(GetEventManager().SetFutureReady<MapAsyncEvent>(
             futureIDInternal, WGPUBufferMapAsyncStatus_MappingAlreadyPending));
         return {futureIDInternal};
     }
@@ -284,6 +286,7 @@
     // Serialize the command to send to the server.
     BufferMapAsyncCmd cmd;
     cmd.bufferId = GetWireId();
+    cmd.eventManagerHandle = GetEventManagerHandle();
     cmd.future = {futureIDInternal};
     cmd.mode = mode;
     cmd.offset = offset;
diff --git a/src/dawn/wire/client/Buffer.h b/src/dawn/wire/client/Buffer.h
index 5cf4c6a..f86c918 100644
--- a/src/dawn/wire/client/Buffer.h
+++ b/src/dawn/wire/client/Buffer.h
@@ -64,11 +64,13 @@
     MapState mapState = MapState::Unmapped;
 };
 
-class Buffer final : public ObjectBase {
+class Buffer final : public ObjectWithEventsBase {
   public:
     static WGPUBuffer Create(Device* device, const WGPUBufferDescriptor* descriptor);
 
-    Buffer(const ObjectBaseParams& params, const WGPUBufferDescriptor* descriptor);
+    Buffer(const ObjectBaseParams& params,
+           const ObjectHandle& eventManagerHandle,
+           const WGPUBufferDescriptor* descriptor);
     ~Buffer() override;
 
     bool OnMapAsyncCallback(WGPUFuture future,
diff --git a/src/dawn/wire/client/Client.cpp b/src/dawn/wire/client/Client.cpp
index 569976b..32d78d2 100644
--- a/src/dawn/wire/client/Client.cpp
+++ b/src/dawn/wire/client/Client.cpp
@@ -52,7 +52,6 @@
 
 Client::Client(CommandSerializer* serializer, MemoryTransferService* memoryTransferService)
     : ClientBase(), mSerializer(serializer), mMemoryTransferService(memoryTransferService) {
-    mEventManager = std::make_unique<EventManager>(this);
     if (mMemoryTransferService == nullptr) {
         // If a MemoryTransferService is not provided, fall back to inline memory.
         mOwnedMemoryTransferService = CreateInlineMemoryTransferService();
@@ -61,7 +60,6 @@
 }
 
 Client::~Client() {
-    mEventManager->ShutDown();
     DestroyAllObjects();
 }
 
@@ -121,8 +119,8 @@
     return result;
 }
 
-ReservedDevice Client::ReserveDevice() {
-    Device* device = Make<Device>(nullptr);
+ReservedDevice Client::ReserveDevice(WGPUInstance instance) {
+    Device* device = Make<Device>(FromAPI(instance)->GetEventManagerHandle(), nullptr);
 
     ReservedDevice result;
     result.device = ToAPI(device);
@@ -139,6 +137,10 @@
         return {nullptr, 0, 0};
     }
 
+    // Reserve an EventManager for the given instance and make the association in the map.
+    mEventManagers[ObjectHandle(instance->GetWireId(), instance->GetWireGeneration())] =
+        std::make_unique<EventManager>();
+
     ReservedInstance result;
     result.instance = ToAPI(instance);
     result.id = instance->GetWireId();
@@ -162,8 +164,10 @@
     Free(FromAPI(reservation.instance));
 }
 
-EventManager* Client::GetEventManager() {
-    return mEventManager.get();
+EventManager& Client::GetEventManager(const ObjectHandle& instance) {
+    auto it = mEventManagers.find(instance);
+    DAWN_ASSERT(it != mEventManagers.end());
+    return *it->second;
 }
 
 void Client::Disconnect() {
@@ -184,7 +188,11 @@
             object->value()->CancelCallbacksForDisconnect();
         }
     }
-    mEventManager->ShutDown();
+
+    // Transition all event managers to ClientDropped state.
+    for (auto& [_, eventManager] : mEventManagers) {
+        eventManager->TransitionTo(EventManager::State::ClientDropped);
+    }
 }
 
 bool Client::IsDisconnected() const {
diff --git a/src/dawn/wire/client/Client.h b/src/dawn/wire/client/Client.h
index 2e112f9..833cdba 100644
--- a/src/dawn/wire/client/Client.h
+++ b/src/dawn/wire/client/Client.h
@@ -29,6 +29,7 @@
 #define SRC_DAWN_WIRE_CLIENT_CLIENT_H_
 
 #include <memory>
+#include <unordered_map>
 #include <utility>
 
 #include "dawn/common/LinkedList.h"
@@ -88,7 +89,7 @@
     ReservedTexture ReserveTexture(WGPUDevice device, const WGPUTextureDescriptor* descriptor);
     ReservedSwapChain ReserveSwapChain(WGPUDevice device,
                                        const WGPUSwapChainDescriptor* descriptor);
-    ReservedDevice ReserveDevice();
+    ReservedDevice ReserveDevice(WGPUInstance instance);
     ReservedInstance ReserveInstance(const WGPUInstanceDescriptor* descriptor);
 
     void ReclaimTextureReservation(const ReservedTexture& reservation);
@@ -106,7 +107,7 @@
         mSerializer.SerializeCommand(cmd, *this, std::forward<Extensions>(es)...);
     }
 
-    EventManager* GetEventManager();
+    EventManager& GetEventManager(const ObjectHandle& instance);
 
     void Disconnect();
     bool IsDisconnected() const;
@@ -122,8 +123,14 @@
     MemoryTransferService* mMemoryTransferService = nullptr;
     std::unique_ptr<MemoryTransferService> mOwnedMemoryTransferService = nullptr;
     PerObjectType<LinkedList<ObjectBase>> mObjects;
-    // TODO(crbug.com/dawn/2061) Eventually we want an EventManager per instance not per client.
-    std::unique_ptr<EventManager> mEventManager = nullptr;
+    // Map of instance object handles to a corresponding event manager. Note that for now because we
+    // do not have an internal refcount on the instances, i.e. we don't know when the last object
+    // associated with a particular instance is destroyed, this map is not cleaned up until the
+    // client is destroyed. This should only be a problem for users that are creating many
+    // instances. We also cannot currently store the EventManger on the Instance because
+    // spontaneous mode callbacks outlive the instance. We also can't reuse the ObjectStore for the
+    // EventManagers because we need to track old instance handles even after they are reclaimed.
+    std::unordered_map<ObjectHandle, std::unique_ptr<EventManager>> mEventManagers;
     bool mDisconnected = false;
 };
 
diff --git a/src/dawn/wire/client/ClientDoers.cpp b/src/dawn/wire/client/ClientDoers.cpp
index 57602d9..210cc53 100644
--- a/src/dawn/wire/client/ClientDoers.cpp
+++ b/src/dawn/wire/client/ClientDoers.cpp
@@ -88,7 +88,9 @@
     return device->OnPopErrorScopeCallback(requestSerial, errorType, message);
 }
 
+// TODO(dawn:2061) May be able to move this to Buffer.cpp once we move all mapping logic.
 bool Client::DoBufferMapAsyncCallback(Buffer* buffer,
+                                      ObjectHandle eventManager,
                                       WGPUFuture future,
                                       uint32_t status,
                                       uint64_t readDataUpdateInfoLength,
@@ -100,16 +102,6 @@
     return buffer->OnMapAsyncCallback(future, status, readDataUpdateInfoLength, readDataUpdateInfo);
 }
 
-bool Client::DoQueueWorkDoneCallback(Queue* queue,
-                                     WGPUFuture future,
-                                     WGPUQueueWorkDoneStatus status) {
-    // The queue might have been deleted or recreated so this isn't an error.
-    if (queue == nullptr) {
-        return true;
-    }
-    return queue->OnWorkDoneCallback(future, status);
-}
-
 bool Client::DoDeviceCreateComputePipelineAsyncCallback(Device* device,
                                                         uint64_t requestSerial,
                                                         WGPUCreatePipelineAsyncStatus status,
diff --git a/src/dawn/wire/client/Device.cpp b/src/dawn/wire/client/Device.cpp
index 5000fd3..c87800c 100644
--- a/src/dawn/wire/client/Device.cpp
+++ b/src/dawn/wire/client/Device.cpp
@@ -36,8 +36,10 @@
 
 namespace dawn::wire::client {
 
-Device::Device(const ObjectBaseParams& params, const WGPUDeviceDescriptor* descriptor)
-    : ObjectBase(params), mIsAlive(std::make_shared<bool>()) {
+Device::Device(const ObjectBaseParams& params,
+               const ObjectHandle& eventManagerHandle,
+               const WGPUDeviceDescriptor* descriptor)
+    : ObjectWithEventsBase(params, eventManagerHandle), mIsAlive(std::make_shared<bool>()) {
     if (descriptor && descriptor->deviceLostCallback) {
         mDeviceLostCallback = descriptor->deviceLostCallback;
         mDeviceLostUserdata = descriptor->deviceLostUserdata;
@@ -232,7 +234,7 @@
     if (mQueue == nullptr) {
         // Get the primary queue for this device.
         Client* client = GetClient();
-        mQueue = client->Make<Queue>();
+        mQueue = client->Make<Queue>(GetEventManagerHandle());
 
         DeviceGetQueueCmd cmd;
         cmd.self = ToAPI(this);
diff --git a/src/dawn/wire/client/Device.h b/src/dawn/wire/client/Device.h
index 0ecbb02..12aca84 100644
--- a/src/dawn/wire/client/Device.h
+++ b/src/dawn/wire/client/Device.h
@@ -43,9 +43,11 @@
 class Client;
 class Queue;
 
-class Device final : public ObjectBase {
+class Device final : public ObjectWithEventsBase {
   public:
-    explicit Device(const ObjectBaseParams& params, const WGPUDeviceDescriptor* descriptor);
+    explicit Device(const ObjectBaseParams& params,
+                    const ObjectHandle& eventManagerHandle,
+                    const WGPUDeviceDescriptor* descriptor);
     ~Device() override;
 
     void SetUncapturedErrorCallback(WGPUErrorCallback errorCallback, void* errorUserdata);
diff --git a/src/dawn/wire/client/EventManager.cpp b/src/dawn/wire/client/EventManager.cpp
index 1614515..130a64e 100644
--- a/src/dawn/wire/client/EventManager.cpp
+++ b/src/dawn/wire/client/EventManager.cpp
@@ -65,14 +65,27 @@
 
 // EventManager
 
-EventManager::EventManager(Client* client) : mClient(client) {}
+EventManager::~EventManager() {
+    TransitionTo(State::ClientDropped);
+}
 
 std::pair<FutureID, bool> EventManager::TrackEvent(std::unique_ptr<TrackedEvent> event) {
     FutureID futureID = mNextFutureID++;
 
-    if (mClient->IsDisconnected()) {
-        event->Complete(futureID, EventCompletionType::Shutdown);
-        return {futureID, false};
+    switch (mState) {
+        case State::InstanceDropped: {
+            if (event->GetCallbackMode() != WGPUCallbackMode_AllowSpontaneous) {
+                event->Complete(futureID, EventCompletionType::Shutdown);
+                return {futureID, false};
+            }
+            break;
+        }
+        case State::ClientDropped: {
+            event->Complete(futureID, EventCompletionType::Shutdown);
+            return {futureID, false};
+        }
+        case State::Nominal:
+            break;
     }
 
     mTrackedEvents.Use([&](auto trackedEvents) {
@@ -83,22 +96,46 @@
     return {futureID, true};
 }
 
-void EventManager::ShutDown() {
-    // Call any outstanding callbacks before destruction.
+void EventManager::TransitionTo(EventManager::State state) {
+    // If the client is disconnected, this becomes a no-op.
+    if (mState == State::ClientDropped) {
+        return;
+    }
+
+    // Only forward state transitions are allowed.
+    DAWN_ASSERT(state > mState);
+    mState = state;
+
     while (true) {
         std::map<FutureID, std::unique_ptr<TrackedEvent>> events;
-        mTrackedEvents.Use([&](auto trackedEvents) { events = std::move(*trackedEvents); });
-
+        switch (state) {
+            case State::InstanceDropped: {
+                mTrackedEvents.Use([&](auto trackedEvents) {
+                    for (auto it = trackedEvents->begin(); it != trackedEvents->end();) {
+                        auto& event = it->second;
+                        if (event->GetCallbackMode() != WGPUCallbackMode_AllowSpontaneous) {
+                            events.emplace(it->first, std::move(event));
+                            it = trackedEvents->erase(it);
+                        }
+                    }
+                });
+                break;
+            }
+            case State::ClientDropped: {
+                mTrackedEvents.Use([&](auto trackedEvents) { events = std::move(*trackedEvents); });
+                break;
+            }
+            case State::Nominal:
+                // We always start in the nominal state so we should never be transitioning to it.
+                DAWN_UNREACHABLE();
+        }
         if (events.empty()) {
             break;
         }
-
-        // Ordering guaranteed because we are using a sorted map.
         for (auto& [futureID, event] : events) {
             event->Complete(futureID, EventCompletionType::Shutdown);
         }
     }
-    mIsShutdown = true;
 }
 
 void EventManager::ProcessPollEvents() {
diff --git a/src/dawn/wire/client/EventManager.h b/src/dawn/wire/client/EventManager.h
index 8c891ce..478c7ac 100644
--- a/src/dawn/wire/client/EventManager.h
+++ b/src/dawn/wire/client/EventManager.h
@@ -88,36 +88,35 @@
 // TODO(crbug.com/dawn/2060): This should probably be merged together with RequestTracker.
 class EventManager final : NonMovable {
   public:
-    explicit EventManager(Client*);
-    ~EventManager() = default;
+    ~EventManager();
+
+    // See mState for breakdown of these states.
+    enum class State { Nominal, InstanceDropped, ClientDropped };
 
     // Returns a pair of the FutureID and a bool that is true iff the event was successfuly tracked,
     // false otherwise. Events may not be tracked if the client is already disconnected.
     std::pair<FutureID, bool> TrackEvent(std::unique_ptr<TrackedEvent> event);
-    void ShutDown();
+
+    // Transitions the EventManager to the given state. Note that states can only go in one
+    // direction, i.e. once the EventManager transitions to InstanceDropped, it cannot transition
+    // back to Nominal, though it may transition to ClientDropped later on.
+    void TransitionTo(State state);
 
     template <typename Event, typename... ReadyArgs>
     WireResult SetFutureReady(FutureID futureID, ReadyArgs&&... readyArgs) {
         DAWN_ASSERT(futureID > 0);
-        // If already shutdown, then all the callbacks should already have fired so we don't need to
-        // fire the callback anymore. This may happen if cleanup/dtor functions try to call this
-        // unconditionally on objects.
-        if (mIsShutdown) {
-#if DAWN_ENABLE_ASSERTS
-            // Note we need to use an if clause here because otherwise the DAWN_ASSERT macro will
-            // generate code that results in the lambda being in an unevaluated context.
-            DAWN_ASSERT(mTrackedEvents.Use([&](auto trackedEvents) {
-                return trackedEvents->find(futureID) == trackedEvents->end();
-            }));
-#endif
-            return WireResult::Success;
+
+        // If the future id is greater than what we have assigned, it must be invalid.
+        if (futureID > mNextFutureID) {
+            return WireResult::FatalError;
         }
 
         std::unique_ptr<TrackedEvent> spontaneousEvent;
         WIRE_TRY(mTrackedEvents.Use([&](auto trackedEvents) {
             auto it = trackedEvents->find(futureID);
             if (it == trackedEvents->end()) {
-                return WireResult::FatalError;
+                // If the future is not found, it must've already been completed.
+                return WireResult::Success;
             }
             auto& trackedEvent = it->second;
 
@@ -150,8 +149,17 @@
     WGPUWaitStatus WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS);
 
   private:
-    Client* mClient;
-    bool mIsShutdown = false;
+    // Different states of the EventManager dictate how new incoming events are handled.
+    //   Nominal: Usual state of the manager. All events are tracked and callbacks are fired
+    //     depending on the callback modes.
+    //   InstanceDropped: Transitioned to this state if the last external reference of the Instance
+    //     is dropped. In this mode, any non-spontaneous events are no longer tracked and their
+    //     callbacks are immediately called since the user cannot call WaitAny or ProcessEvents
+    //     anymore. Any existing non-spontaneous events' callbacks are also called on transition.
+    //   ClientDropped: Transitioned to this state once the client is dropped. In this mode, no new
+    //     events are tracked and callbacks are all immediately fired. Any existing tracked events'
+    //     callbacks are also called on transition.
+    State mState = State::Nominal;
 
     // Tracks all kinds of events (for both WaitAny and ProcessEvents). We use an ordered map so
     // that in most cases, event ordering is already implicit when we iterate the map. (Not true for
diff --git a/src/dawn/wire/client/Instance.cpp b/src/dawn/wire/client/Instance.cpp
index 6c0597f0..280ea4b 100644
--- a/src/dawn/wire/client/Instance.cpp
+++ b/src/dawn/wire/client/Instance.cpp
@@ -134,6 +134,12 @@
 
 // Instance
 
+Instance::Instance(const ObjectBaseParams& params) : ObjectWithEventsBase(params, params.handle) {}
+
+Instance::~Instance() {
+    GetEventManager().TransitionTo(EventManager::State::InstanceDropped);
+}
+
 WireResult Instance::Initialize(const WGPUInstanceDescriptor* descriptor) {
     if (descriptor == nullptr) {
         return WireResult::Success;
@@ -185,15 +191,16 @@
 WGPUFuture Instance::RequestAdapterF(const WGPURequestAdapterOptions* options,
                                      const WGPURequestAdapterCallbackInfo& callbackInfo) {
     Client* client = GetClient();
-    Adapter* adapter = client->Make<Adapter>();
-    auto [futureIDInternal, tracked] = client->GetEventManager()->TrackEvent(
-        std::make_unique<RequestAdapterEvent>(callbackInfo, adapter));
+    Adapter* adapter = client->Make<Adapter>(GetEventManagerHandle());
+    auto [futureIDInternal, tracked] =
+        GetEventManager().TrackEvent(std::make_unique<RequestAdapterEvent>(callbackInfo, adapter));
     if (!tracked) {
         return {futureIDInternal};
     }
 
     InstanceRequestAdapterCmd cmd;
     cmd.instanceId = GetWireId();
+    cmd.eventManagerHandle = GetEventManagerHandle();
     cmd.future = {futureIDInternal};
     cmd.adapterObjectHandle = adapter->GetWireHandle();
     cmd.options = options;
@@ -202,7 +209,7 @@
     return {futureIDInternal};
 }
 
-bool Client::DoInstanceRequestAdapterCallback(Instance* instance,
+bool Client::DoInstanceRequestAdapterCallback(ObjectHandle eventManager,
                                               WGPUFuture future,
                                               WGPURequestAdapterStatus status,
                                               const char* message,
@@ -210,30 +217,13 @@
                                               const WGPUSupportedLimits* limits,
                                               uint32_t featuresCount,
                                               const WGPUFeatureName* features) {
-    // May have been deleted or recreated so this isn't an error.
-    if (instance == nullptr) {
-        return true;
-    }
-    return instance->OnRequestAdapterCallback(future, status, message, properties, limits,
-                                              featuresCount, features);
-}
-
-bool Instance::OnRequestAdapterCallback(WGPUFuture future,
-                                        WGPURequestAdapterStatus status,
-                                        const char* message,
-                                        const WGPUAdapterProperties* properties,
-                                        const WGPUSupportedLimits* limits,
-                                        uint32_t featuresCount,
-                                        const WGPUFeatureName* features) {
-    return GetClient()->GetEventManager()->SetFutureReady<RequestAdapterEvent>(
-               future.id, status, message, properties, limits, featuresCount, features) ==
-           WireResult::Success;
+    return GetEventManager(eventManager)
+               .SetFutureReady<RequestAdapterEvent>(future.id, status, message, properties, limits,
+                                                    featuresCount, features) == WireResult::Success;
 }
 
 void Instance::ProcessEvents() {
-    // TODO(crbug.com/dawn/2061): This should only process events for this Instance, not others
-    // on the same client. When EventManager is moved to Instance, this can be fixed.
-    GetClient()->GetEventManager()->ProcessPollEvents();
+    GetEventManager().ProcessPollEvents();
 
     // TODO(crbug.com/dawn/1987): The responsibility of ProcessEvents here is a bit mixed. It both
     // processes events coming in from the server, and also prompts the server to check for and
@@ -255,10 +245,7 @@
 }
 
 WGPUWaitStatus Instance::WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS) {
-    // In principle the EventManager should be on the Instance, not the Client.
-    // But it's hard to get from an object to its Instance right now, so we can
-    // store it on the Client.
-    return GetClient()->GetEventManager()->WaitAny(count, infos, timeoutNS);
+    return GetEventManager().WaitAny(count, infos, timeoutNS);
 }
 
 void Instance::GatherWGSLFeatures(const WGPUDawnWireWGSLControl* wgslControl,
diff --git a/src/dawn/wire/client/Instance.h b/src/dawn/wire/client/Instance.h
index 3fce377..43301ef 100644
--- a/src/dawn/wire/client/Instance.h
+++ b/src/dawn/wire/client/Instance.h
@@ -41,9 +41,10 @@
 WGPUBool ClientGetInstanceFeatures(WGPUInstanceFeatures* features);
 WGPUInstance ClientCreateInstance(WGPUInstanceDescriptor const* descriptor);
 
-class Instance final : public ObjectBase {
+class Instance final : public ObjectWithEventsBase {
   public:
-    using ObjectBase::ObjectBase;
+    explicit Instance(const ObjectBaseParams& params);
+    ~Instance() override;
 
     WireResult Initialize(const WGPUInstanceDescriptor* descriptor);
 
@@ -52,13 +53,6 @@
                         void* userdata);
     WGPUFuture RequestAdapterF(const WGPURequestAdapterOptions* options,
                                const WGPURequestAdapterCallbackInfo& callbackInfo);
-    bool OnRequestAdapterCallback(WGPUFuture future,
-                                  WGPURequestAdapterStatus status,
-                                  const char* message,
-                                  const WGPUAdapterProperties* properties,
-                                  const WGPUSupportedLimits* limits,
-                                  uint32_t featuresCount,
-                                  const WGPUFeatureName* features);
 
     void ProcessEvents();
     WGPUWaitStatus WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS);
diff --git a/src/dawn/wire/client/ObjectBase.cpp b/src/dawn/wire/client/ObjectBase.cpp
index 84b6af7..d66f521 100644
--- a/src/dawn/wire/client/ObjectBase.cpp
+++ b/src/dawn/wire/client/ObjectBase.cpp
@@ -28,6 +28,7 @@
 #include "dawn/wire/client/ObjectBase.h"
 
 #include "dawn/common/Assert.h"
+#include "dawn/wire/client/Client.h"
 
 namespace dawn::wire::client {
 
@@ -64,4 +65,16 @@
     return mRefcount == 0;
 }
 
+ObjectWithEventsBase::ObjectWithEventsBase(const ObjectBaseParams& params,
+                                           const ObjectHandle& eventManagerHandle)
+    : ObjectBase(params), mEventManagerHandle(eventManagerHandle) {}
+
+const ObjectHandle& ObjectWithEventsBase::GetEventManagerHandle() const {
+    return mEventManagerHandle;
+}
+
+EventManager& ObjectWithEventsBase::GetEventManager() const {
+    return GetClient()->GetEventManager(mEventManagerHandle);
+}
+
 }  // namespace dawn::wire::client
diff --git a/src/dawn/wire/client/ObjectBase.h b/src/dawn/wire/client/ObjectBase.h
index 5e1688c..8c90683 100644
--- a/src/dawn/wire/client/ObjectBase.h
+++ b/src/dawn/wire/client/ObjectBase.h
@@ -32,6 +32,7 @@
 
 #include "dawn/common/LinkedList.h"
 #include "dawn/wire/ObjectHandle.h"
+#include "dawn/wire/client/EventManager.h"
 
 namespace dawn::wire::client {
 
@@ -73,6 +74,23 @@
     uint32_t mRefcount;
 };
 
+// Compositable functionality for objects on the client side that need to have access to the event
+// manager.
+class ObjectWithEventsBase : public ObjectBase {
+  public:
+    // Note that the ObjectHandle associated with an EventManager is the same handle associated to
+    // the Instance that "owns" the EventManager.
+    ObjectWithEventsBase(const ObjectBaseParams& params, const ObjectHandle& eventManager);
+
+    const ObjectHandle& GetEventManagerHandle() const;
+    EventManager& GetEventManager() const;
+
+  private:
+    // The EventManager is owned by the client and long-lived. When the client is destroyed all
+    // objects are also freed.
+    ObjectHandle mEventManagerHandle;
+};
+
 }  // namespace dawn::wire::client
 
 #endif  // SRC_DAWN_WIRE_CLIENT_OBJECTBASE_H_
diff --git a/src/dawn/wire/client/Queue.cpp b/src/dawn/wire/client/Queue.cpp
index 89c0bb5..eee8a00 100644
--- a/src/dawn/wire/client/Queue.cpp
+++ b/src/dawn/wire/client/Queue.cpp
@@ -69,8 +69,10 @@
 
 Queue::~Queue() = default;
 
-bool Queue::OnWorkDoneCallback(WGPUFuture future, WGPUQueueWorkDoneStatus status) {
-    return GetClient()->GetEventManager()->SetFutureReady<WorkDoneEvent>(future.id, status) ==
+bool Client::DoQueueWorkDoneCallback(ObjectHandle eventManager,
+                                     WGPUFuture future,
+                                     WGPUQueueWorkDoneStatus status) {
+    return GetEventManager(eventManager).SetFutureReady<WorkDoneEvent>(future.id, status) ==
            WireResult::Success;
 }
 
@@ -89,13 +91,14 @@
 
     Client* client = GetClient();
     auto [futureIDInternal, tracked] =
-        client->GetEventManager()->TrackEvent(std::make_unique<WorkDoneEvent>(callbackInfo));
+        GetEventManager().TrackEvent(std::make_unique<WorkDoneEvent>(callbackInfo));
     if (!tracked) {
         return {futureIDInternal};
     }
 
     QueueOnSubmittedWorkDoneCmd cmd;
     cmd.queueId = GetWireId();
+    cmd.eventManagerHandle = GetEventManagerHandle();
     cmd.future = {futureIDInternal};
 
     client->SerializeCommand(cmd);
diff --git a/src/dawn/wire/client/Queue.h b/src/dawn/wire/client/Queue.h
index 0b47342..0d4cc55 100644
--- a/src/dawn/wire/client/Queue.h
+++ b/src/dawn/wire/client/Queue.h
@@ -36,13 +36,11 @@
 
 namespace dawn::wire::client {
 
-class Queue final : public ObjectBase {
+class Queue final : public ObjectWithEventsBase {
   public:
-    using ObjectBase::ObjectBase;
+    using ObjectWithEventsBase::ObjectWithEventsBase;
     ~Queue() override;
 
-    bool OnWorkDoneCallback(WGPUFuture future, WGPUQueueWorkDoneStatus status);
-
     // Dawn API
     void OnSubmittedWorkDone(WGPUQueueWorkDoneCallback callback, void* userdata);
     WGPUFuture OnSubmittedWorkDoneF(const WGPUQueueWorkDoneCallbackInfo& callbackInfo);
diff --git a/src/dawn/wire/server/Server.h b/src/dawn/wire/server/Server.h
index e20a300..5f21a51 100644
--- a/src/dawn/wire/server/Server.h
+++ b/src/dawn/wire/server/Server.h
@@ -105,6 +105,7 @@
 
     ObjectHandle buffer;
     WGPUBuffer bufferObj;
+    ObjectHandle eventManager;
     WGPUFuture future;
     uint64_t offset;
     uint64_t size;
@@ -129,6 +130,7 @@
     using CallbackUserdata::CallbackUserdata;
 
     ObjectHandle queue;
+    ObjectHandle eventManager;
     WGPUFuture future;
 };
 
@@ -144,6 +146,7 @@
     using CallbackUserdata::CallbackUserdata;
 
     ObjectHandle instance;
+    ObjectHandle eventManager;
     WGPUFuture future;
     ObjectId adapterObjectId;
 };
diff --git a/src/dawn/wire/server/ServerBuffer.cpp b/src/dawn/wire/server/ServerBuffer.cpp
index 5869a5d..ca1471c 100644
--- a/src/dawn/wire/server/ServerBuffer.cpp
+++ b/src/dawn/wire/server/ServerBuffer.cpp
@@ -65,6 +65,7 @@
 }
 
 WireResult Server::DoBufferMapAsync(Known<WGPUBuffer> buffer,
+                                    ObjectHandle eventManager,
                                     WGPUFuture future,
                                     WGPUMapModeFlags mode,
                                     uint64_t offset64,
@@ -73,6 +74,7 @@
     // client will require in the return command.
     std::unique_ptr<MapUserdata> userdata = MakeUserdata<MapUserdata>();
     userdata->buffer = buffer.AsHandle();
+    userdata->eventManager = eventManager;
     userdata->bufferObj = buffer->handle;
     userdata->future = future;
     userdata->mode = mode;
@@ -223,7 +225,9 @@
     bool isSuccess = status == WGPUBufferMapAsyncStatus_Success;
 
     ReturnBufferMapAsyncCallbackCmd cmd;
+    // TODO(dawn:2061) Should be able to remove buffer once mapping is updated.
     cmd.buffer = data->buffer;
+    cmd.eventManager = data->eventManager;
     cmd.future = data->future;
     cmd.status = status;
     cmd.readDataUpdateInfoLength = 0;
diff --git a/src/dawn/wire/server/ServerInstance.cpp b/src/dawn/wire/server/ServerInstance.cpp
index 3f3f8c4..660c339 100644
--- a/src/dawn/wire/server/ServerInstance.cpp
+++ b/src/dawn/wire/server/ServerInstance.cpp
@@ -34,6 +34,7 @@
 namespace dawn::wire::server {
 
 WireResult Server::DoInstanceRequestAdapter(Known<WGPUInstance> instance,
+                                            ObjectHandle eventManager,
                                             WGPUFuture future,
                                             ObjectHandle adapterHandle,
                                             const WGPURequestAdapterOptions* options) {
@@ -42,6 +43,7 @@
 
     auto userdata = MakeUserdata<RequestAdapterUserdata>();
     userdata->instance = instance.AsHandle();
+    userdata->eventManager = eventManager;
     userdata->future = future;
     userdata->adapterObjectId = adapter.id;
 
@@ -56,7 +58,7 @@
                                       WGPUAdapter adapter,
                                       const char* message) {
     ReturnInstanceRequestAdapterCallbackCmd cmd = {};
-    cmd.instance = data->instance;
+    cmd.eventManager = data->eventManager;
     cmd.future = data->future;
     cmd.status = status;
     cmd.message = message;
diff --git a/src/dawn/wire/server/ServerQueue.cpp b/src/dawn/wire/server/ServerQueue.cpp
index a4d9c0a..807861b 100644
--- a/src/dawn/wire/server/ServerQueue.cpp
+++ b/src/dawn/wire/server/ServerQueue.cpp
@@ -34,16 +34,19 @@
 
 void Server::OnQueueWorkDone(QueueWorkDoneUserdata* data, WGPUQueueWorkDoneStatus status) {
     ReturnQueueWorkDoneCallbackCmd cmd;
-    cmd.queue = data->queue;
+    cmd.eventManager = data->eventManager;
     cmd.future = data->future;
     cmd.status = status;
 
     SerializeCommand(cmd);
 }
 
-WireResult Server::DoQueueOnSubmittedWorkDone(Known<WGPUQueue> queue, WGPUFuture future) {
+WireResult Server::DoQueueOnSubmittedWorkDone(Known<WGPUQueue> queue,
+                                              ObjectHandle eventManager,
+                                              WGPUFuture future) {
     auto userdata = MakeUserdata<QueueWorkDoneUserdata>();
     userdata->queue = queue.AsHandle();
+    userdata->eventManager = eventManager;
     userdata->future = future;
 
     mProcs.queueOnSubmittedWorkDone(queue->handle, ForwardToServer<&Server::OnQueueWorkDone>,