[wgpu-headers] Introduce 2nd userdata to popErrorScope callback.

- Updates the wire to use the new APIs
- Adds new infra needed for C++ lambda helpers
- Updates relevant usages to use new C++ helpers

Bug: 42241407
Change-Id: I6fcca48180bea515374a07737280bbe1d64fc66c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/186863
Reviewed-by: Shrek Shao <shrekshao@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/generator/templates/api_cpp.h b/generator/templates/api_cpp.h
index 2af158e..9f94756 100644
--- a/generator/templates/api_cpp.h
+++ b/generator/templates/api_cpp.h
@@ -266,6 +266,39 @@
     {{as_cppType(types["callback mode"].name)}} mode, F callback, T userdata) const
 {%- endmacro %}
 
+//* This rendering macro should ONLY be used for callback info type functions.
+{% macro render_cpp_callback_info_lambda_method_declaration(type, method, dfn=False) %}
+    {% set CppType = as_cppType(type.name) %}
+    {% set OriginalMethodName = method.name.CamelCase() %}
+    {% set MethodName = OriginalMethodName[:-1] if method.name.chunks[-1] == "2" else OriginalMethodName %}
+    {% set MethodName = CppType + "::" + MethodName if dfn else MethodName %}
+    //* Stripping the 2 at the end of the callback functions for now until we can deprecate old ones.
+    //* TODO: crbug.com/dawn/2509 - Remove name handling once old APIs are deprecated.
+    {% set CallbackInfoType = (method.arguments|last).type %}
+    {% set CallbackType = (CallbackInfoType.members|first).type %}
+    {% set SfinaeArg = " = std::enable_if_t<std::is_convertible_v<L, Cb>>" if not dfn else "" %}
+    template <typename L,
+              typename Cb
+                {%- if not dfn -%}
+                    {{" "}}= std::function<void(
+                        {%- for arg in CallbackType.arguments -%}
+                            {%- if not loop.first %}, {% endif -%}
+                            {{as_annotated_cppType(arg)}}
+                        {%- endfor -%}
+                    )>
+                {%- endif -%},
+              typename{{SfinaeArg}}>
+    {{as_cppType(method.return_type.name)}} {{MethodName}}(
+        {%- for arg in method.arguments if arg.type.category != "callback info" -%}
+            {%- if arg.type.category == "object" and arg.annotation == "value" -%}
+                {{as_cppType(arg.type.name)}} const& {{as_varName(arg.name)}}{{ ", "}}
+            {%- else -%}
+                {{as_annotated_cppType(arg)}}{{ ", "}}
+            {%- endif -%}
+        {%- endfor -%}
+    {{as_cppType(types["callback mode"].name)}} mode, L callback) const
+{%- endmacro %}
+
 //* This rendering macro should NOT be used for callback info type functions.
 {% macro render_cpp_method_declaration(type, method, dfn=False) %}
     {% set CppType = as_cppType(type.name) %}
@@ -332,10 +365,71 @@
         callbackInfo.userdata1 = reinterpret_cast<void*>(+callback);
         callbackInfo.userdata2 = reinterpret_cast<void*>(userdata);
         auto result = {{as_cMethod(type.name, method.name)}}(Get(){{", "}}
-            {%- for arg in method.arguments if arg.type.category != "callback info" -%}{{render_c_actual_arg(arg)}}{{", "}}
+            {%- for arg in method.arguments if arg.type.category != "callback info" -%}
+                {{render_c_actual_arg(arg)}}{{", "}}
             {%- endfor -%}
         callbackInfo);
-        return {{convert_cType_to_cppType(method.return_type, 'value', 'result') | indent(8)}};
+        return {{convert_cType_to_cppType(method.return_type, 'value', 'result') | indent(4)}};
+    }
+{%- endmacro %}
+
+{% macro render_cpp_callback_info_lambda_method_impl(type, method) %}
+    {{render_cpp_callback_info_lambda_method_declaration(type, method, dfn=True)}} {
+        {% set CallbackInfoType = (method.arguments|last).type %}
+        {% set CallbackType = (CallbackInfoType.members|first).type %}
+        using F = void (
+            {%- for arg in CallbackType.arguments -%}
+                {%- if not loop.first %}, {% endif -%}
+                {{as_annotated_cppType(arg)}}
+            {%- endfor -%}
+        );
+
+        {{as_cType(CallbackInfoType.name)}} callbackInfo = {};
+        callbackInfo.mode = static_cast<{{as_cType(types["callback mode"].name)}}>(mode);
+        if constexpr (std::is_convertible_v<L, F*>) {
+            callbackInfo.callback = [](
+            {%- for arg in CallbackType.arguments -%}
+                {{as_annotated_cType(arg)}}{{", "}}
+            {%- endfor -%}
+            void* callback, void*) {
+                auto cb = reinterpret_cast<F*>(callback);
+                (*cb)(
+                    {%- for arg in CallbackType.arguments -%}
+                        {%- if not loop.first %}, {% endif -%}
+                        {{convert_cType_to_cppType(arg.type, arg.annotation, as_varName(arg.name))}}
+                    {%- endfor -%});
+            };
+            callbackInfo.userdata1 = reinterpret_cast<void*>(+callback);
+            callbackInfo.userdata2 = nullptr;
+            auto result = {{as_cMethod(type.name, method.name)}}(Get(){{", "}}
+            {%- for arg in method.arguments if arg.type.category != "callback info" -%}
+                {{render_c_actual_arg(arg)}}{{", "}}
+            {%- endfor -%}
+            callbackInfo);
+            return {{convert_cType_to_cppType(method.return_type, 'value', 'result') | indent(8)}};
+        } else {
+            auto* lambda = new L(callback);
+            callbackInfo.callback = [](
+                {%- for arg in CallbackType.arguments -%}
+                    {{as_annotated_cType(arg)}}{{", "}}
+                {%- endfor -%}
+            void* callback, void*) {
+                std::unique_ptr<L> lambda(reinterpret_cast<L*>(callback));
+                (*lambda)(
+                    {%- for arg in CallbackType.arguments -%}
+                        {%- if not loop.first %}, {% endif -%}
+                        {{convert_cType_to_cppType(arg.type, arg.annotation, as_varName(arg.name))}}
+                    {%- endfor -%});
+            };
+            callbackInfo.userdata1 = reinterpret_cast<void*>(lambda);
+            callbackInfo.userdata2 = nullptr;
+            auto result = {{as_cMethod(type.name, method.name)}}(Get(){{", "}}
+            {%- for arg in method.arguments if arg.type.category != "callback info" -%}
+                {{render_c_actual_arg(arg)}}{{", "}}
+            {%- endfor -%}
+            callbackInfo);
+            return {{convert_cType_to_cppType(method.return_type, 'value', 'result') | indent(8)}};
+        }
     }
 {%- endmacro %}
 
@@ -364,6 +458,7 @@
         {% for method in type.methods %}
             {% if has_callbackInfoStruct(method) %}
                 {{render_cpp_callback_info_template_method_declaration(type, method)|indent}};
+                {{render_cpp_callback_info_lambda_method_declaration(type, method)|indent}};
             {% else %}
                 inline {{render_cpp_method_declaration(type, method)}};
             {% endif %}
@@ -540,6 +635,7 @@
     {% for method in type.methods %}
         {% if has_callbackInfoStruct(method) %}
             {{render_cpp_callback_info_template_method_impl(type, method)}}
+            {{render_cpp_callback_info_lambda_method_impl(type, method)}}
         {% else %}
             {{render_cpp_method_impl(type, method)}}
         {% endif %}
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 2acb36d..b2208f65 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -1491,6 +1491,15 @@
                 ]
             },
             {
+                "name": "pop error scope 2",
+                "_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": "callback info", "type": "pop error scope callback info 2"}
+                ]
+            },
+            {
                 "name": "set label",
                 "returns": "void",
                 "args": [
@@ -1586,6 +1595,14 @@
             {"name": "userdata", "type": "void *"}
         ]
     },
+    "pop error scope callback 2": {
+        "category": "callback function",
+        "args": [
+            {"name": "status", "type": "pop error scope status"},
+            {"name": "type", "type": "error type"},
+            {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"}
+        ]
+    },
     "pop error scope callback info": {
         "category": "structure",
         "extensible": "in",
@@ -1596,6 +1613,12 @@
             {"name": "userdata", "type": "void *", "default": "nullptr"}
         ]
     },
+    "pop error scope callback info 2": {
+        "category": "callback info",
+        "members": [
+            {"name": "callback", "type": "pop error scope callback 2"}
+        ]
+    },
     "limits": {
         "category": "structure",
         "members": [
diff --git a/src/dawn/dawn_wire.json b/src/dawn/dawn_wire.json
index 62d42bd..d554885 100644
--- a/src/dawn/dawn_wire.json
+++ b/src/dawn/dawn_wire.json
@@ -229,6 +229,7 @@
             "DeviceEnumerateFeatures",
             "DevicePopErrorScope",
             "DevicePopErrorScopeF",
+            "DevicePopErrorScope2",
             "DeviceSetDeviceLostCallback",
             "DeviceSetUncapturedErrorCallback",
             "DeviceSetLoggingCallback",
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 531996c..e4bdc28 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -827,32 +827,42 @@
 void DeviceBase::APIPopErrorScope(wgpu::ErrorCallback callback, void* userdata) {
     static wgpu::ErrorCallback kDefaultCallback = [](WGPUErrorType, char const*, void*) {};
 
-    PopErrorScopeCallbackInfo callbackInfo = {};
-    callbackInfo.mode = wgpu::CallbackMode::AllowProcessEvents;
-    callbackInfo.oldCallback = callback != nullptr ? callback : kDefaultCallback;
-    callbackInfo.userdata = userdata;
-    APIPopErrorScopeF(callbackInfo);
+    APIPopErrorScope2({nullptr, WGPUCallbackMode_AllowProcessEvents,
+                       [](WGPUPopErrorScopeStatus, WGPUErrorType type, char const* message,
+                          void* callback, void* userdata) {
+                           auto cb = reinterpret_cast<wgpu::ErrorCallback>(callback);
+                           cb(type, message, userdata);
+                       },
+                       reinterpret_cast<void*>(callback != nullptr ? callback : kDefaultCallback),
+                       userdata});
 }
 
 Future DeviceBase::APIPopErrorScopeF(const PopErrorScopeCallbackInfo& callbackInfo) {
+    return APIPopErrorScope2({nullptr, ToAPI(callbackInfo.mode),
+                              [](WGPUPopErrorScopeStatus status, WGPUErrorType type,
+                                 char const* message, void* callback, void* userdata) {
+                                  auto cb = reinterpret_cast<WGPUPopErrorScopeCallback>(callback);
+                                  cb(status, type, message, userdata);
+                              },
+                              reinterpret_cast<void*>(callbackInfo.callback),
+                              callbackInfo.userdata});
+}
+
+Future DeviceBase::APIPopErrorScope2(const WGPUPopErrorScopeCallbackInfo2& callbackInfo) {
     struct PopErrorScopeEvent final : public EventManager::TrackedEvent {
-        // TODO(crbug.com/dawn/2021) Remove the old callback type.
-        WGPUPopErrorScopeCallback mCallback;
-        WGPUErrorCallback mOldCallback;
-        void* mUserdata;
+        WGPUPopErrorScopeCallback2 mCallback;
+        raw_ptr<void> mUserdata1;
+        raw_ptr<void> mUserdata2;
         std::optional<ErrorScope> mScope;
 
-        PopErrorScopeEvent(const PopErrorScopeCallbackInfo& callbackInfo,
+        PopErrorScopeEvent(const WGPUPopErrorScopeCallbackInfo2& callbackInfo,
                            std::optional<ErrorScope>&& scope)
-            : TrackedEvent(callbackInfo.mode, TrackedEvent::Completed{}),
+            : TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode),
+                           TrackedEvent::Completed{}),
               mCallback(callbackInfo.callback),
-              mOldCallback(callbackInfo.oldCallback),
-              mUserdata(callbackInfo.userdata),
-              mScope(scope) {
-            // Exactly 1 callback should be set.
-            DAWN_ASSERT((mCallback != nullptr && mOldCallback == nullptr) ||
-                        (mCallback == nullptr && mOldCallback != nullptr));
-        }
+              mUserdata1(callbackInfo.userdata1),
+              mUserdata2(callbackInfo.userdata2),
+              mScope(scope) {}
 
         ~PopErrorScopeEvent() override { EnsureComplete(EventCompletionType::Shutdown); }
 
@@ -870,11 +880,8 @@
                 message = "No error scopes to pop";
             }
 
-            if (mCallback) {
-                mCallback(status, type, message, mUserdata);
-            } else {
-                mOldCallback(type, message, mUserdata);
-            }
+            mCallback(status, type, message, mUserdata1.ExtractAsDangling(),
+                      mUserdata2.ExtractAsDangling());
         }
     };
 
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index d8b014d..831d0b8 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -301,6 +301,7 @@
     void APIPushErrorScope(wgpu::ErrorFilter filter);
     void APIPopErrorScope(wgpu::ErrorCallback callback, void* userdata);
     Future APIPopErrorScopeF(const PopErrorScopeCallbackInfo& callbackInfo);
+    Future APIPopErrorScope2(const WGPUPopErrorScopeCallbackInfo2& callbackInfo);
 
     MaybeError ValidateIsAlive() const;
 
diff --git a/src/dawn/tests/MockCallback.h b/src/dawn/tests/MockCallback.h
index 81f484c..1638eb3 100644
--- a/src/dawn/tests/MockCallback.h
+++ b/src/dawn/tests/MockCallback.h
@@ -113,6 +113,23 @@
     std::set<std::unique_ptr<MockAndUserdata>> mUserdatas;
 };
 
+template <typename F>
+class MockCppCallback;
+
+// Helper wrapper class for C++ entry point callbacks.
+// Example Usage:
+//   MockCppCallback<void (*)(wgpu::PopErrorScopeStatus, wgpu::ErrorType, const char*)> mock;
+//
+//   device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mock.Callback());
+//   EXPECT_CALL(mock, Call(wgpu::PopErrorScopeStatus::Success, _, _));
+template <typename R, typename... Args>
+class MockCppCallback<R (*)(Args...)> : public ::testing::MockFunction<R(Args...)> {
+  public:
+    auto Callback() {
+        return [this](Args... args) -> R { return this->Call(args...); };
+    }
+};
+
 }  // namespace testing
 
 #endif  // SRC_DAWN_TESTS_MOCKCALLBACK_H_
diff --git a/src/dawn/tests/end2end/DeviceLifetimeTests.cpp b/src/dawn/tests/end2end/DeviceLifetimeTests.cpp
index 170c42b..4fc14e2 100644
--- a/src/dawn/tests/end2end/DeviceLifetimeTests.cpp
+++ b/src/dawn/tests/end2end/DeviceLifetimeTests.cpp
@@ -102,9 +102,11 @@
     bool done = false;
 
     device.PopErrorScope(
-        [](WGPUErrorType type, const char*, void* userdata) {
-            *static_cast<bool*>(userdata) = true;
-            EXPECT_EQ(type, WGPUErrorType_NoError);
+        wgpu::CallbackMode::AllowProcessEvents,
+        [](wgpu::PopErrorScopeStatus status, wgpu::ErrorType type, const char*, bool* done) {
+            *done = true;
+            EXPECT_EQ(status, wgpu::PopErrorScopeStatus::Success);
+            EXPECT_EQ(type, wgpu::ErrorType::NoError);
         },
         &done);
     device = nullptr;
@@ -125,10 +127,13 @@
     // Ask for a popErrorScope callback and drop the device inside the callback.
     Userdata data = Userdata{std::move(device), false};
     data.device.PopErrorScope(
-        [](WGPUErrorType type, const char*, void* userdata) {
-            EXPECT_EQ(type, WGPUErrorType_NoError);
-            static_cast<Userdata*>(userdata)->device = nullptr;
-            static_cast<Userdata*>(userdata)->done = true;
+        wgpu::CallbackMode::AllowProcessEvents,
+        [](wgpu::PopErrorScopeStatus status, wgpu::ErrorType type, const char*,
+           Userdata* userdata) {
+            EXPECT_EQ(status, wgpu::PopErrorScopeStatus::Success);
+            EXPECT_EQ(type, wgpu::ErrorType::NoError);
+            userdata->device = nullptr;
+            userdata->done = true;
         },
         &data);
 
@@ -555,9 +560,9 @@
     // The following callback will drop the 2nd device. It won't be triggered until
     // instance.ProcessEvents() is called.
     device.PopErrorScope(
-        [](WGPUErrorType type, const char*, void* userdataPtr) {
-            auto userdata = static_cast<UserData*>(userdataPtr);
-
+        wgpu::CallbackMode::AllowProcessEvents,
+        [](wgpu::PopErrorScopeStatus status, wgpu::ErrorType type, const char*,
+           UserData* userdata) {
             userdata->device2 = nullptr;
             userdata->done = true;
         },
diff --git a/src/dawn/tests/end2end/EventTests.cpp b/src/dawn/tests/end2end/EventTests.cpp
index 4f8e15a..a84f5fc 100644
--- a/src/dawn/tests/end2end/EventTests.cpp
+++ b/src/dawn/tests/end2end/EventTests.cpp
@@ -647,9 +647,8 @@
 
     // PopErrorScope is implemented via a signal.
     device.PushErrorScope(wgpu::ErrorFilter::Validation);
-    device.PopErrorScope({nullptr, wgpu::CallbackMode::AllowProcessEvents,
-                          [](WGPUPopErrorScopeStatus, WGPUErrorType, char const*, void*) {},
-                          nullptr, nullptr});
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents,
+                         [](wgpu::PopErrorScopeStatus, wgpu::ErrorType, const char*) {});
 
     instance.ProcessEvents();
 }
diff --git a/src/dawn/tests/end2end/MaxLimitTests.cpp b/src/dawn/tests/end2end/MaxLimitTests.cpp
index 742be91..928452d 100644
--- a/src/dawn/tests/end2end/MaxLimitTests.cpp
+++ b/src/dawn/tests/end2end/MaxLimitTests.cpp
@@ -220,14 +220,14 @@
         bufDesc.usage = usage | wgpu::BufferUsage::CopyDst;
         wgpu::Buffer buffer = device.CreateBuffer(&bufDesc);
 
-        WGPUErrorType oomResult;
-        device.PopErrorScope([](WGPUErrorType type, const char*,
-                                void* userdata) { *static_cast<WGPUErrorType*>(userdata) = type; },
-                             &oomResult);
-        instance.ProcessEvents();
+        wgpu::ErrorType oomResult;
+        device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents,
+                             [&oomResult](wgpu::PopErrorScopeStatus, wgpu::ErrorType type,
+                                          const char*) { oomResult = type; });
         FlushWire();
+        instance.ProcessEvents();
         // Max buffer size is smaller than the max buffer binding size.
-        DAWN_TEST_UNSUPPORTED_IF(oomResult == WGPUErrorType_OutOfMemory);
+        DAWN_TEST_UNSUPPORTED_IF(oomResult == wgpu::ErrorType::OutOfMemory);
 
         wgpu::BufferDescriptor resultBufDesc;
         resultBufDesc.size = 8;
diff --git a/src/dawn/tests/end2end/MultithreadTests.cpp b/src/dawn/tests/end2end/MultithreadTests.cpp
index cddcb36..29d18d1 100644
--- a/src/dawn/tests/end2end/MultithreadTests.cpp
+++ b/src/dawn/tests/end2end/MultithreadTests.cpp
@@ -168,30 +168,25 @@
     // Create threads
     utils::RunInParallel(static_cast<uint32_t>(devices.size()), [&devices, this](uint32_t index) {
         auto additionalDevice = std::move(devices[index]);
-        struct UserData {
-            wgpu::Device device2ndRef;
-            std::atomic_bool isCompleted{false};
-        } userData;
-
-        userData.device2ndRef = additionalDevice;
+        wgpu::Device device2ndRef = additionalDevice;
+        std::atomic_bool isCompleted{false};
 
         // Drop the last ref inside a callback.
         additionalDevice.PushErrorScope(wgpu::ErrorFilter::Validation);
         additionalDevice.PopErrorScope(
-            [](WGPUErrorType type, const char*, void* userdataPtr) {
-                auto userdata = static_cast<UserData*>(userdataPtr);
-                userdata->device2ndRef = nullptr;
-                userdata->isCompleted = true;
-            },
-            &userData);
+            wgpu::CallbackMode::AllowProcessEvents,
+            [&device2ndRef, &isCompleted](wgpu::PopErrorScopeStatus, wgpu::ErrorType, const char*) {
+                device2ndRef = nullptr;
+                isCompleted = true;
+            });
         // main ref dropped.
         additionalDevice = nullptr;
 
         do {
             WaitABit();
-        } while (!userData.isCompleted.load());
+        } while (!isCompleted.load());
 
-        EXPECT_EQ(userData.device2ndRef, nullptr);
+        EXPECT_EQ(device2ndRef, nullptr);
     });
 }
 
@@ -1330,12 +1325,12 @@
 
         std::atomic<bool> errorThrown(false);
         device.PopErrorScope(
-            [](WGPUErrorType type, char const* message, void* userdata) {
-                EXPECT_EQ(type, WGPUErrorType_Validation);
-                auto error = static_cast<std::atomic<bool>*>(userdata);
-                *error = true;
-            },
-            &errorThrown);
+            wgpu::CallbackMode::AllowProcessEvents,
+            [&errorThrown](wgpu::PopErrorScopeStatus status, wgpu::ErrorType type, char const*) {
+                EXPECT_EQ(status, wgpu::PopErrorScopeStatus::Success);
+                EXPECT_EQ(type, wgpu::ErrorType::Validation);
+                errorThrown = true;
+            });
         instance.ProcessEvents();
         EXPECT_TRUE(errorThrown.load());
 
diff --git a/src/dawn/tests/unittests/validation/ErrorScopeValidationTests.cpp b/src/dawn/tests/unittests/validation/ErrorScopeValidationTests.cpp
index f4ae097..60ad2ac 100644
--- a/src/dawn/tests/unittests/validation/ErrorScopeValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/ErrorScopeValidationTests.cpp
@@ -33,30 +33,8 @@
 #include "gmock/gmock.h"
 
 using testing::_;
-using testing::MockCallback;
-using testing::Sequence;
-
-class MockDevicePopErrorScopeCallback {
-  public:
-    MOCK_METHOD(void, Call, (WGPUErrorType type, const char* message, void* userdata));
-};
-
-static std::unique_ptr<MockDevicePopErrorScopeCallback> mockDevicePopErrorScopeCallback;
-static void ToMockDevicePopErrorScopeCallback(WGPUErrorType type,
-                                              const char* message,
-                                              void* userdata) {
-    mockDevicePopErrorScopeCallback->Call(type, message, userdata);
-}
-
-class MockQueueWorkDoneCallback {
-  public:
-    MOCK_METHOD(void, Call, (WGPUQueueWorkDoneStatus status, void* userdata));
-};
-
-static std::unique_ptr<MockQueueWorkDoneCallback> mockQueueWorkDoneCallback;
-static void ToMockQueueWorkDone(WGPUQueueWorkDoneStatus status, void* userdata) {
-    mockQueueWorkDoneCallback->Call(status, userdata);
-}
+using testing::MockCppCallback;
+using testing::NotNull;
 
 class ErrorScopeValidationTest : public ValidationTest {
   protected:
@@ -65,40 +43,18 @@
         instance.ProcessEvents();
     }
 
-    // Generate new void* pointer for use as `userdata` in callback. The pointer
-    // is valid for the whole duration of the test. This avoids dangling
-    // pointers checks to trigger.
-    void* CreateUserData() {
-        mUserData.push_back(std::make_unique<bool>());
-        return reinterpret_cast<void*>(mUserData.back().get());
-    }
-
-  private:
-    void SetUp() override {
-        ValidationTest::SetUp();
-        mockDevicePopErrorScopeCallback = std::make_unique<MockDevicePopErrorScopeCallback>();
-        mockQueueWorkDoneCallback = std::make_unique<MockQueueWorkDoneCallback>();
-    }
-
-    void TearDown() override {
-        ValidationTest::TearDown();
-
-        // Delete mocks so that expectations are checked
-        mockDevicePopErrorScopeCallback = nullptr;
-        mockQueueWorkDoneCallback = nullptr;
-    }
-
-    std::vector<std::unique_ptr<bool>> mUserData;
+    MockCppCallback<void (*)(wgpu::PopErrorScopeStatus, wgpu::ErrorType, const char*)>
+        mPopErrorScopeCb;
 };
 
 // Test the simple success case.
 TEST_F(ErrorScopeValidationTest, Success) {
     device.PushErrorScope(wgpu::ErrorFilter::Validation);
 
-    void* userdata = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, userdata))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::NoError, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 }
 
@@ -110,10 +66,10 @@
     desc.usage = static_cast<wgpu::BufferUsage>(WGPUBufferUsage_Force32);
     device.CreateBuffer(&desc);
 
-    void* userdata = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Validation, _, userdata))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::Validation, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 }
 
@@ -127,17 +83,17 @@
     device.CreateBuffer(&desc);
 
     // OutOfMemory does not match Validation error.
-    void* userdata_1 = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, userdata_1))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::NoError, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata_1);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 
     // Parent validation error scope captures the error.
-    void* userdata_2 = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Validation, _, userdata_2))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::Validation, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata_2);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 }
 
@@ -151,17 +107,17 @@
     device.CreateBuffer(&desc);
 
     // Inner scope catches the error.
-    void* userdata_1 = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Validation, _, userdata_1))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::Validation, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata_1);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 
     // Parent scope does not see the error.
-    void* userdata_2 = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, userdata_2))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::NoError, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata_2);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 }
 
@@ -173,10 +129,10 @@
     desc.usage = static_cast<wgpu::BufferUsage>(WGPUBufferUsage_Force32);
     ASSERT_DEVICE_ERROR(device.CreateBuffer(&desc));
 
-    void* userdata = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, userdata))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::NoError, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 }
 
@@ -184,63 +140,31 @@
 TEST_F(ErrorScopeValidationTest, PushPopBalanced) {
     // No error scopes to pop.
     {
-        void* userdata = CreateUserData();
-        EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Unknown, _, userdata))
+        EXPECT_CALL(mPopErrorScopeCb,
+                    Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::Unknown, NotNull()))
             .Times(1);
-        device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata);
+        device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
         FlushWireAndProcessEvents();
     }
     // Too many pops
     {
         device.PushErrorScope(wgpu::ErrorFilter::Validation);
 
-        void* userdata_1 = CreateUserData();
-        EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, userdata_1))
+        EXPECT_CALL(mPopErrorScopeCb,
+                    Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::NoError, _))
             .Times(1);
-        device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata_1);
+        device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
         FlushWireAndProcessEvents();
 
-        void* userdata_2 = CreateUserData();
-        EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Unknown, _, userdata_2))
+        EXPECT_CALL(mPopErrorScopeCb,
+                    Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::Unknown, NotNull()))
             .Times(1);
-        device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata_2);
+        device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
         FlushWireAndProcessEvents();
     }
 }
 
-// Test that parent error scopes also call their callbacks before an enclosed Queue::Submit
-// completes
-TEST_F(ErrorScopeValidationTest, EnclosedQueueSubmitNested) {
-    wgpu::Queue queue = device.GetQueue();
-
-    device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory);
-    device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory);
-
-    void* userdata_1 = CreateUserData();
-    queue.Submit(0, nullptr);
-    queue.OnSubmittedWorkDone(ToMockQueueWorkDone, userdata_1);
-
-    Sequence seq;
-
-    MockCallback<WGPUErrorCallback> errorScopeCallback2;
-    void* userdata_2 = CreateUserData();
-    EXPECT_CALL(errorScopeCallback2, Call(WGPUErrorType_NoError, _, userdata_2)).InSequence(seq);
-    device.PopErrorScope(errorScopeCallback2.Callback(),
-                         errorScopeCallback2.MakeUserdata(userdata_2));
-
-    MockCallback<WGPUErrorCallback> errorScopeCallback1;
-    void* userdata_3 = CreateUserData();
-    EXPECT_CALL(errorScopeCallback1, Call(WGPUErrorType_NoError, _, userdata_3)).InSequence(seq);
-    device.PopErrorScope(errorScopeCallback1.Callback(),
-                         errorScopeCallback1.MakeUserdata(userdata_3));
-
-    EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Success, userdata_1));
-    WaitForAllOperations(device);
-}
-
-// Test that if the device is destroyed before the callback occurs, it is called with NoError
-// in dawn_native, but Unknown in dawn_wire because the device is destroyed before the callback
-// message happens.
+// Test that if the device is destroyed before the callback occurs, it is called with NoError.
 TEST_F(ErrorScopeValidationTest, DeviceDestroyedBeforeCallback) {
     device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory);
     {
@@ -249,10 +173,10 @@
         queue.Submit(0, nullptr);
     }
 
-    void* userdata = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_NoError, _, userdata))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::NoError, _))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     ExpectDeviceDestruction();
     device = nullptr;
 
@@ -266,10 +190,10 @@
     device.Destroy();
     FlushWireAndProcessEvents();
 
-    void* userdata = CreateUserData();
-    EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_DeviceLost, _, userdata))
+    EXPECT_CALL(mPopErrorScopeCb,
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::DeviceLost, NotNull()))
         .Times(1);
-    device.PopErrorScope(ToMockDevicePopErrorScopeCallback, userdata);
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, mPopErrorScopeCb.Callback());
     FlushWireAndProcessEvents();
 }
 
diff --git a/src/dawn/tests/unittests/wire/WireErrorCallbackTests.cpp b/src/dawn/tests/unittests/wire/WireErrorCallbackTests.cpp
index 2a91393..7fad752 100644
--- a/src/dawn/tests/unittests/wire/WireErrorCallbackTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireErrorCallbackTests.cpp
@@ -213,17 +213,6 @@
         }
     }
 
-    // Overridden version of api.CallDevicePopErrorScopeCallback to defer to the correct callback
-    // depending on the test callback mode. Note that currently, the ServerDevice calls the native
-    // procs using the old-non-future signature. Once that changes, we can probably just inline
-    // api.CallDevicePopErrorScopeCallback calls in place of this function.
-    void CallDevicePopErrorScopeCallback(WGPUDevice d,
-                                         WGPUPopErrorScopeStatus status,
-                                         WGPUErrorType type,
-                                         char const* message) {
-        api.CallDevicePopErrorScopeOldCallback(d, type, message);
-    }
-
     void PushErrorScope(WGPUErrorFilter filter) {
         EXPECT_CALL(api, DevicePushErrorScope(apiDevice, filter)).Times(1);
         wgpuDevicePushErrorScope(device, filter);
@@ -259,9 +248,9 @@
         PushErrorScope(filter);
 
         DevicePopErrorScope(device, this);
-        EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _)).WillOnce([&] {
-            CallDevicePopErrorScopeCallback(apiDevice, WGPUPopErrorScopeStatus_Success, type,
-                                            "Some error message");
+        EXPECT_CALL(api, OnDevicePopErrorScope2(apiDevice, _)).WillOnce([&] {
+            api.CallDevicePopErrorScope2Callback(apiDevice, WGPUPopErrorScopeStatus_Success, type,
+                                                 "Some error message");
         });
 
         FlushClient();
@@ -288,7 +277,7 @@
     PushErrorScope(WGPUErrorFilter_Validation);
 
     DevicePopErrorScope(device, this);
-    EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _)).Times(1);
+    EXPECT_CALL(api, OnDevicePopErrorScope2(apiDevice, _)).Times(1);
 
     FlushClient();
     FlushFutures();
@@ -316,9 +305,9 @@
     PushErrorScope(WGPUErrorFilter_Validation);
 
     DevicePopErrorScope(device, this);
-    EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _)).WillOnce(InvokeWithoutArgs([&] {
-        CallDevicePopErrorScopeCallback(apiDevice, WGPUPopErrorScopeStatus_Success,
-                                        WGPUErrorType_Validation, "Some error message");
+    EXPECT_CALL(api, OnDevicePopErrorScope2(apiDevice, _)).WillOnce(InvokeWithoutArgs([&] {
+        api.CallDevicePopErrorScope2Callback(apiDevice, WGPUPopErrorScopeStatus_Success,
+                                             WGPUErrorType_Validation, "Some error message");
     }));
 
     FlushClient();
@@ -341,9 +330,9 @@
 // Empty stack (We are emulating the errors that would be callback-ed from native).
 TEST_P(WirePopErrorScopeCallbackTests, EmptyStack) {
     DevicePopErrorScope(device, this);
-    EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _)).WillOnce(InvokeWithoutArgs([&] {
-        CallDevicePopErrorScopeCallback(apiDevice, WGPUPopErrorScopeStatus_Success,
-                                        WGPUErrorType_Validation, "No error scopes to pop");
+    EXPECT_CALL(api, OnDevicePopErrorScope2(apiDevice, _)).WillOnce(InvokeWithoutArgs([&] {
+        api.CallDevicePopErrorScope2Callback(apiDevice, WGPUPopErrorScopeStatus_Success,
+                                             WGPUErrorType_Validation, "No error scopes to pop");
     }));
 
     FlushClient();
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
index 7951b51..534487c 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
@@ -772,7 +772,7 @@
 namespace {
 
 using testing::HasSubstr;
-using testing::MockCallback;
+using testing::MockCppCallback;
 
 template <typename T>
 T& AsNonConst(const T& rhs) {
@@ -788,12 +788,13 @@
     const auto& memories =
         GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount);
 
-    MockCallback<WGPUErrorCallback> popErrorScopeCallback;
+    MockCppCallback<void (*)(wgpu::PopErrorScopeStatus, wgpu::ErrorType, const char*)>
+        popErrorScopeCallback;
     EXPECT_CALL(popErrorScopeCallback,
-                Call(WGPUErrorType_Validation, HasSubstr("is not enabled"), this));
+                Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::Validation,
+                     HasSubstr("is not enabled")));
 
-    device.PopErrorScope(popErrorScopeCallback.Callback(),
-                         popErrorScopeCallback.MakeUserdata(this));
+    device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, popErrorScopeCallback.Callback());
 
     for (wgpu::SharedTextureMemory memory : memories) {
         ASSERT_DEVICE_ERROR_MSG(wgpu::Texture texture = memory.CreateTexture(),
diff --git a/src/dawn/wire/client/Device.cpp b/src/dawn/wire/client/Device.cpp
index 459d7a2..6e132f3 100644
--- a/src/dawn/wire/client/Device.cpp
+++ b/src/dawn/wire/client/Device.cpp
@@ -45,15 +45,11 @@
   public:
     static constexpr EventType kType = EventType::PopErrorScope;
 
-    explicit PopErrorScopeEvent(const WGPUPopErrorScopeCallbackInfo& callbackInfo)
+    explicit PopErrorScopeEvent(const WGPUPopErrorScopeCallbackInfo2& callbackInfo)
         : TrackedEvent(callbackInfo.mode),
           mCallback(callbackInfo.callback),
-          mOldCallback(callbackInfo.oldCallback),
-          mUserdata(callbackInfo.userdata) {
-        // Exactly 1 callback should be set.
-        DAWN_ASSERT((mCallback != nullptr && mOldCallback == nullptr) ||
-                    (mCallback == nullptr && mOldCallback != nullptr));
-    }
+          mUserdata1(callbackInfo.userdata1),
+          mUserdata2(callbackInfo.userdata2) {}
 
     EventType GetType() override { return kType; }
 
@@ -71,19 +67,16 @@
             mStatus = WGPUPopErrorScopeStatus_InstanceDropped;
             mMessage = std::nullopt;
         }
-        void* userdata = mUserdata.ExtractAsDangling();
-        if (mOldCallback) {
-            mOldCallback(mType, mMessage ? mMessage->c_str() : nullptr, userdata);
-        }
+        void* userdata1 = mUserdata1.ExtractAsDangling();
+        void* userdata2 = mUserdata2.ExtractAsDangling();
         if (mCallback) {
-            mCallback(mStatus, mType, mMessage ? mMessage->c_str() : nullptr, userdata);
+            mCallback(mStatus, mType, mMessage ? mMessage->c_str() : nullptr, userdata1, userdata2);
         }
     }
 
-    // TODO(crbug.com/dawn/2021) Remove the old callback type.
-    WGPUPopErrorScopeCallback mCallback;
-    WGPUErrorCallback mOldCallback;
-    raw_ptr<void> mUserdata;
+    WGPUPopErrorScopeCallback2 mCallback;
+    raw_ptr<void> mUserdata1;
+    raw_ptr<void> mUserdata2;
 
     WGPUPopErrorScopeStatus mStatus = WGPUPopErrorScopeStatus_Success;
     WGPUErrorType mType = WGPUErrorType_Unknown;
@@ -385,14 +378,29 @@
 }
 
 void Device::PopErrorScope(WGPUErrorCallback callback, void* userdata) {
-    WGPUPopErrorScopeCallbackInfo callbackInfo = {};
-    callbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
-    callbackInfo.oldCallback = callback;
-    callbackInfo.userdata = userdata;
-    PopErrorScopeF(callbackInfo);
+    static WGPUErrorCallback kDefaultCallback = [](WGPUErrorType, char const*, void*) {};
+
+    PopErrorScope2({nullptr, WGPUCallbackMode_AllowSpontaneous,
+                    [](WGPUPopErrorScopeStatus, WGPUErrorType type, char const* message,
+                       void* callback, void* userdata) {
+                        auto cb = reinterpret_cast<WGPUErrorCallback>(callback);
+                        cb(type, message, userdata);
+                    },
+                    reinterpret_cast<void*>(callback != nullptr ? callback : kDefaultCallback),
+                    userdata});
 }
 
 WGPUFuture Device::PopErrorScopeF(const WGPUPopErrorScopeCallbackInfo& callbackInfo) {
+    return PopErrorScope2({nullptr, callbackInfo.mode,
+                           [](WGPUPopErrorScopeStatus status, WGPUErrorType type,
+                              char const* message, void* callback, void* userdata) {
+                               auto cb = reinterpret_cast<WGPUPopErrorScopeCallback>(callback);
+                               cb(status, type, message, userdata);
+                           },
+                           reinterpret_cast<void*>(callbackInfo.callback), callbackInfo.userdata});
+}
+
+WGPUFuture Device::PopErrorScope2(const WGPUPopErrorScopeCallbackInfo2& callbackInfo) {
     Client* client = GetClient();
     auto [futureIDInternal, tracked] =
         GetEventManager().TrackEvent(std::make_unique<PopErrorScopeEvent>(callbackInfo));
diff --git a/src/dawn/wire/client/Device.h b/src/dawn/wire/client/Device.h
index ab07cc70..28418ba 100644
--- a/src/dawn/wire/client/Device.h
+++ b/src/dawn/wire/client/Device.h
@@ -61,6 +61,7 @@
     void InjectError(WGPUErrorType type, const char* message);
     void PopErrorScope(WGPUErrorCallback callback, void* userdata);
     WGPUFuture PopErrorScopeF(const WGPUPopErrorScopeCallbackInfo& callbackInfo);
+    WGPUFuture PopErrorScope2(const WGPUPopErrorScopeCallbackInfo2& callbackInfo);
     WGPUBuffer CreateBuffer(const WGPUBufferDescriptor* descriptor);
     void CreateComputePipelineAsync(WGPUComputePipelineDescriptor const* descriptor,
                                     WGPUCreateComputePipelineAsyncCallback callback,
diff --git a/src/dawn/wire/server/Server.h b/src/dawn/wire/server/Server.h
index a64d59f..a244b9d 100644
--- a/src/dawn/wire/server/Server.h
+++ b/src/dawn/wire/server/Server.h
@@ -250,6 +250,7 @@
                       WGPUDeviceLostReason reason,
                       const char* message);
     void OnDevicePopErrorScope(ErrorScopeUserdata* userdata,
+                               WGPUPopErrorScopeStatus status,
                                WGPUErrorType type,
                                const char* message);
     void OnBufferMapAsyncCallback(MapUserdata* userdata, WGPUBufferMapAsyncStatus status);
diff --git a/src/dawn/wire/server/ServerDevice.cpp b/src/dawn/wire/server/ServerDevice.cpp
index e974431..1c191b7 100644
--- a/src/dawn/wire/server/ServerDevice.cpp
+++ b/src/dawn/wire/server/ServerDevice.cpp
@@ -71,12 +71,14 @@
     userdata->eventManager = eventManager;
     userdata->future = future;
 
-    mProcs.devicePopErrorScope(device->handle, ForwardToServer<&Server::OnDevicePopErrorScope>,
-                               userdata.release());
+    mProcs.devicePopErrorScope2(device->handle, {nullptr, WGPUCallbackMode_AllowProcessEvents,
+                                                 ForwardToServer2<&Server::OnDevicePopErrorScope>,
+                                                 userdata.release(), nullptr});
     return WireResult::Success;
 }
 
 void Server::OnDevicePopErrorScope(ErrorScopeUserdata* userdata,
+                                   WGPUPopErrorScopeStatus status,
                                    WGPUErrorType type,
                                    const char* message) {
     ReturnDevicePopErrorScopeCallbackCmd cmd;