Dawn.node: Emit errors as events

Forward errors to disptchEvent as appropriate.

Bug: 419128706
Depends-On: Ifeeb54bd20342c104b1e8bbd8253010bcf957d67
Change-Id: I1d62fb0c04c9a132d1e7ed1776a364f1ce121470
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/243916
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Gregg Tavares <gman@chromium.org>
diff --git a/src/dawn/node/binding/GPUAdapter.cpp b/src/dawn/node/binding/GPUAdapter.cpp
index 0f180ab..9914431 100644
--- a/src/dawn/node/binding/GPUAdapter.cpp
+++ b/src/dawn/node/binding/GPUAdapter.cpp
@@ -119,42 +119,6 @@
     return interop::GPUAdapterInfo::Create<GPUAdapterInfo>(env, info);
 }
 
-namespace {
-// Returns a string representation of the wgpu::ErrorType
-const char* str(wgpu::ErrorType ty) {
-    switch (ty) {
-        case wgpu::ErrorType::NoError:
-            return "no error";
-        case wgpu::ErrorType::Validation:
-            return "validation";
-        case wgpu::ErrorType::OutOfMemory:
-            return "out of memory";
-        case wgpu::ErrorType::Internal:
-            return "internal";
-        case wgpu::ErrorType::Unknown:
-        default:
-            return "unknown";
-    }
-}
-
-// There's something broken with Node when attempting to write more than 65536 bytes to cout.
-// Split the string up into writes of 4k chunks.
-// Likely related: https://github.com/nodejs/node/issues/12921
-void chunkedWrite(wgpu::StringView msg) {
-    while (msg.length != 0) {
-        int n;
-        if (msg.length > 4096) {
-            n = printf("%.4096s", msg.data);
-        } else {
-            n = printf("%.*s", static_cast<int>(msg.length), msg.data);
-        }
-        msg.data += n;
-        msg.length -= n;
-    }
-}
-
-}  // namespace
-
 interop::Promise<interop::Interface<interop::GPUDevice>> GPUAdapter::requestDevice(
     Napi::Env env,
     interop::GPUDeviceDescriptor descriptor) {
@@ -239,11 +203,7 @@
             }
         },
         device_lost_ctx);
-    desc.SetUncapturedErrorCallback(
-        [](const wgpu::Device&, ErrorType type, wgpu::StringView message) {
-            printf("%s:\n", str(type));
-            chunkedWrite(message);
-        });
+    desc.SetUncapturedErrorCallback(GPUDevice::handleUncapturedErrorCallback);
 
     // Propagate enabled/disabled dawn features
     TogglesLoader togglesLoader(flags_);
diff --git a/src/dawn/node/binding/GPUDevice.cpp b/src/dawn/node/binding/GPUDevice.cpp
index 0c182ef..c33522d 100644
--- a/src/dawn/node/binding/GPUDevice.cpp
+++ b/src/dawn/node/binding/GPUDevice.cpp
@@ -30,6 +30,7 @@
 #include <cassert>
 #include <memory>
 #include <type_traits>
+#include <unordered_map>
 #include <utility>
 #include <vector>
 
@@ -74,6 +75,23 @@
     }
 }
 
+// Returns a string representation of the wgpu::ErrorType
+const char* str(wgpu::ErrorType ty) {
+    switch (ty) {
+        case wgpu::ErrorType::NoError:
+            return "no error";
+        case wgpu::ErrorType::Validation:
+            return "validation";
+        case wgpu::ErrorType::OutOfMemory:
+            return "out of memory";
+        case wgpu::ErrorType::Internal:
+            return "internal";
+        case wgpu::ErrorType::Unknown:
+        default:
+            return "unknown";
+    }
+}
+
 // There's something broken with Node when attempting to write more than 65536 bytes to cout.
 // Split the string up into writes of 4k chunks.
 // Likely related: https://github.com/nodejs/node/issues/12921
@@ -90,35 +108,43 @@
     }
 }
 
-class OOMError : public interop::GPUOutOfMemoryError {
-  public:
-    explicit OOMError(std::string message) : message_(std::move(message)) {}
+std::optional<interop::Interface<interop::GPUError>>
+createErrorFromWGPUError(Napi::Env env, wgpu::ErrorType type, wgpu::StringView message) {
+    auto constructors = interop::ConstructorsFor(env);
+    auto msg = Napi::String::New(env, std::string(message.data, message.length));
 
-    std::string getMessage(Napi::Env) override { return message_; }
+    switch (type) {
+        case wgpu::ErrorType::NoError:
+            return {};
+        case wgpu::ErrorType::OutOfMemory:
+            return interop::Interface<interop::GPUError>(
+                constructors->GPUOutOfMemoryError_ctor.New({msg}));
+        case wgpu::ErrorType::Validation:
+            return interop::Interface<interop::GPUError>(
+                constructors->GPUValidationError_ctor.New({msg}));
+        case wgpu::ErrorType::Internal:
+            return interop::Interface<interop::GPUError>(
+                constructors->GPUInternalError_ctor.New({msg}));
+        case wgpu::ErrorType::Unknown:
+            // This error type is reserved for when translating an error type from a newer
+            // implementation (e.g. the browser added a new error type) to another (e.g.
+            // you're using an older version of Emscripten). It shouldn't happen in Dawn.
+            assert(false);
+            return {};
+    }
+}
 
-  private:
-    std::string message_;
-};
+static std::mutex s_device_to_js_map_mutex_;
+static std::unordered_map<WGPUDevice, GPUDevice*> s_device_to_js_map_;
 
-class ValidationError : public interop::GPUValidationError {
-  public:
-    explicit ValidationError(std::string message) : message_(std::move(message)) {}
-
-    std::string getMessage(Napi::Env) override { return message_; }
-
-  private:
-    std::string message_;
-};
-
-class InternalError : public interop::GPUInternalError {
-  public:
-    explicit InternalError(std::string message) : message_(std::move(message)) {}
-
-    std::string getMessage(Napi::Env) override { return message_; }
-
-  private:
-    std::string message_;
-};
+GPUDevice* lookupGPUDeviceFromWGPUDevice(wgpu::Device device) {
+    std::lock_guard<std::mutex> lock(s_device_to_js_map_mutex_);
+    auto it = s_device_to_js_map_.find(device.Get());
+    if (it != s_device_to_js_map_.end()) {
+        return it->second;
+    }
+    return nullptr;
+}
 
 }  // namespace
 
@@ -154,6 +180,10 @@
         printf("%s:\n", str(type));
         chunkedWrite(message);
     });
+    {
+        std::lock_guard<std::mutex> lock(s_device_to_js_map_mutex_);
+        s_device_to_js_map_.insert({device_.Get(), this});
+    }
 }
 
 GPUDevice::~GPUDevice() {
@@ -166,6 +196,44 @@
         device_.Destroy();
         destroyed_ = true;
     }
+    {
+        std::lock_guard<std::mutex> lock(s_device_to_js_map_mutex_);
+        s_device_to_js_map_.erase(device_.Get());
+    }
+}
+
+void GPUDevice::handleUncapturedError(ErrorType type, wgpu::StringView message) {
+    Napi::HandleScope scope(env_);
+
+    auto error = createErrorFromWGPUError(env_, type, message);
+    if (!error.has_value()) {
+        fprintf(stderr,
+                "GPUDevice::handleUncapturedError: Failed to create GPUError object for error type "
+                "%s.\n",
+                str(type));
+        return;
+    }
+
+    Napi::Object event_init_dict = Napi::Object::New(env_);
+    event_init_dict.Set("error", error.value());
+    event_init_dict.Set("cancelable", Napi::Boolean::New(env_, true));
+
+    auto constructors = interop::ConstructorsFor(env_);
+    Napi::Object eventObj = constructors->GPUUncapturedErrorEvent_ctor.New(
+        {Napi::String::New(env_, "uncapturederror"), event_init_dict});
+
+    bool doDefault = dispatchEvent(env_, eventObj);
+    if (doDefault) {
+        printf("%s:\n", str(type));
+        chunkedWrite(message);
+    }
+}
+
+void GPUDevice::handleUncapturedErrorCallback(const wgpu::Device& device,
+                                              ErrorType type,
+                                              wgpu::StringView message) {
+    auto gpuDevice = lookupGPUDeviceFromWGPUDevice(device.Get());
+    gpuDevice->handleUncapturedError(type, message);
 }
 
 void GPUDevice::ForceLoss(wgpu::DeviceLostReason reason, const char* message) {
@@ -551,37 +619,7 @@
                     break;
             }
 
-            switch (type) {
-                case wgpu::ErrorType::NoError:
-                    ctx->promise.Resolve({});
-                    break;
-                case wgpu::ErrorType::OutOfMemory: {
-                    interop::Interface<interop::GPUError> err{
-                        interop::GPUOutOfMemoryError::Create<OOMError>(env, std::string(message))};
-                    ctx->promise.Resolve(err);
-                    break;
-                }
-                case wgpu::ErrorType::Validation: {
-                    interop::Interface<interop::GPUError> err{
-                        interop::GPUValidationError::Create<ValidationError>(env,
-                                                                             std::string(message))};
-                    ctx->promise.Resolve(err);
-                    break;
-                }
-                case wgpu::ErrorType::Internal: {
-                    interop::Interface<interop::GPUError> err{
-                        interop::GPUInternalError::Create<InternalError>(env,
-                                                                         std::string(message))};
-                    ctx->promise.Resolve(err);
-                    break;
-                }
-                case wgpu::ErrorType::Unknown:
-                    // This error type is reserved for when translating an error type from a newer
-                    // implementation (e.g. the browser added a new error type) to another (e.g.
-                    // you're using an older version of Emscripten). It shouldn't happen in Dawn.
-                    assert(false);
-                    break;
-            }
+            ctx->promise.Resolve(createErrorFromWGPUError(env, type, message));
         });
 
     return promise;
diff --git a/src/dawn/node/binding/GPUDevice.h b/src/dawn/node/binding/GPUDevice.h
index 1514889..80a7d69 100644
--- a/src/dawn/node/binding/GPUDevice.h
+++ b/src/dawn/node/binding/GPUDevice.h
@@ -125,6 +125,11 @@
     interop::Interface<interop::EventHandler> getOnuncapturederror(Napi::Env) override;
     void setOnuncapturederror(Napi::Env, interop::Interface<interop::EventHandler> value) override;
 
+    void handleUncapturedError(ErrorType type, wgpu::StringView message);
+    static void handleUncapturedErrorCallback(const wgpu::Device& device,
+                                              ErrorType type,
+                                              wgpu::StringView message);
+
   private:
     Napi::Env env_;
     wgpu::Device device_;
diff --git a/src/dawn/node/interop/WebGPU.cpp.tmpl b/src/dawn/node/interop/WebGPU.cpp.tmpl
index 9704e70..e11217d 100644
--- a/src/dawn/node/interop/WebGPU.cpp.tmpl
+++ b/src/dawn/node/interop/WebGPU.cpp.tmpl
@@ -64,6 +64,10 @@
 
 }  // namespace
 
+const Constructors* ConstructorsFor(Napi::Env env) {
+  return Wrappers::For(env);
+}
+
 {{ range $ := .Declarations}}
 {{-        if IsDictionary $}}{{template "Dictionary" $}}
 {{-   else if IsInterface  $}}