dawn_node: Implement setlike keys()

Used by a number of CTS tests (webgpu:idl,constants,flags:*)

Bug: dawn:1143
Change-Id: Idd76c35a4debf54d5c53df11f59419de90f90162
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/65660
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_node/binding/GPUAdapter.cpp b/src/dawn_node/binding/GPUAdapter.cpp
index b86d747..30b69fb 100644
--- a/src/dawn_node/binding/GPUAdapter.cpp
+++ b/src/dawn_node/binding/GPUAdapter.cpp
@@ -14,6 +14,8 @@
 
 #include "src/dawn_node/binding/GPUAdapter.h"
 
+#include <unordered_set>
+
 #include "src/dawn_node/binding/GPUDevice.h"
 #include "src/dawn_node/binding/GPUSupportedLimits.h"
 
@@ -27,26 +29,27 @@
         ////////////////////////////////////////////////////////////////////////////////
         class Features : public interop::GPUSupportedFeatures {
           public:
-            Features(WGPUDeviceProperties properties) : properties_(std::move(properties)) {
+            Features(WGPUDeviceProperties properties) {
+                if (properties.depthClamping) {
+                    enabled_.emplace(interop::GPUFeatureName::kDepthClamping);
+                }
+                if (properties.pipelineStatisticsQuery) {
+                    enabled_.emplace(interop::GPUFeatureName::kPipelineStatisticsQuery);
+                }
+                if (properties.textureCompressionBC) {
+                    enabled_.emplace(interop::GPUFeatureName::kTextureCompressionBc);
+                }
+                if (properties.timestampQuery) {
+                    enabled_.emplace(interop::GPUFeatureName::kTimestampQuery);
+                }
+
+                // TODO(crbug.com/dawn/1130)
+                // interop::GPUFeatureName::kDepth24UnormStencil8:
+                // interop::GPUFeatureName::kDepth32FloatStencil8:
             }
 
             bool has(interop::GPUFeatureName feature) {
-                switch (feature) {
-                    case interop::GPUFeatureName::kDepthClamping:
-                        return properties_.depthClamping;
-                    case interop::GPUFeatureName::kDepth24UnormStencil8:
-                        return false;  // TODO(crbug.com/dawn/1130)
-                    case interop::GPUFeatureName::kDepth32FloatStencil8:
-                        return false;  // TODO(crbug.com/dawn/1130)
-                    case interop::GPUFeatureName::kPipelineStatisticsQuery:
-                        return properties_.pipelineStatisticsQuery;
-                    case interop::GPUFeatureName::kTextureCompressionBc:
-                        return properties_.textureCompressionBC;
-                    case interop::GPUFeatureName::kTimestampQuery:
-                        return properties_.timestampQuery;
-                }
-                UNIMPLEMENTED("feature: ", feature);
-                return false;
+                return enabled_.count(feature) != 0;
             }
 
             // interop::GPUSupportedFeatures compliance
@@ -57,9 +60,17 @@
                 }
                 return false;
             }
+            std::vector<std::string> keys(Napi::Env) override {
+                std::vector<std::string> out;
+                out.reserve(enabled_.size());
+                for (auto feature : enabled_) {
+                    out.push_back(interop::Converter<interop::GPUFeatureName>::ToString(feature));
+                }
+                return out;
+            }
 
           private:
-            WGPUDeviceProperties properties_;
+            std::unordered_set<interop::GPUFeatureName> enabled_;
         };
 
     }  // namespace
diff --git a/src/dawn_node/binding/GPUDevice.cpp b/src/dawn_node/binding/GPUDevice.cpp
index 1e902eb..21cd40d 100644
--- a/src/dawn_node/binding/GPUDevice.cpp
+++ b/src/dawn_node/binding/GPUDevice.cpp
@@ -118,6 +118,9 @@
             bool has(Napi::Env, std::string feature) override {
                 UNIMPLEMENTED();
             }
+            std::vector<std::string> keys(Napi::Env) override {
+                UNIMPLEMENTED();
+            }
         };
         return interop::GPUSupportedFeatures::Create<Features>(env);
     }
diff --git a/src/dawn_node/interop/WebGPU.cpp.tmpl b/src/dawn_node/interop/WebGPU.cpp.tmpl
index 388bb96..2270255 100644
--- a/src/dawn_node/interop/WebGPU.cpp.tmpl
+++ b/src/dawn_node/interop/WebGPU.cpp.tmpl
@@ -143,6 +143,7 @@
       return DefineClass(env, "{{$.Name}}", {
 {{   if $s := SetlikeOf $}}
         InstanceMethod("has", &W{{$.Name}}::has),
+        InstanceMethod("keys", &W{{$.Name}}::keys),
 {{-  end}}
 {{-  range $m := MethodsOf $}}
         InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}),
@@ -169,6 +170,9 @@
       Napi::Error::New(info.Env(), "invalid arguments to has()").ThrowAsJavaScriptException();
       return {};
     }
+    Napi::Value keys(const Napi::CallbackInfo& info) {
+      return ToJS(info.Env(), impl->keys(info.Env()));
+    }
 {{-  end}}
 {{-  range $m := MethodsOf $}}
     Napi::Value {{$m.Name}}(const Napi::CallbackInfo& info) {
@@ -330,26 +334,34 @@
 {{-  end}}
   return false;
 }
+
+const char* Converter<{{$.Name}}>::ToString({{$.Name}} value) {
+  switch (value) {
+{{-  range $e := $.Values}}
+  case {{$.Name}}::{{EnumEntryName $e.Value}}:
+    return {{$e.Value}};
+{{-  end}}
+  }
+  return nullptr;
+}
+
 bool Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
   return FromString(value.ToString(), out);
 }
+
 Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) {
   switch (value) {
 {{-  range $e := $.Values}}
   case {{$.Name}}::{{EnumEntryName $e.Value}}:
     return Napi::String::New(env, {{$e.Value}});
-    break;
 {{-  end}}
   }
   return env.Undefined();
 }
 
 std::ostream& operator<<(std::ostream& o, {{$.Name}} value) {
-  switch (value) {
-{{-  range $e := $.Values}}
-    case {{$.Name}}::{{EnumEntryName $e.Value}}:
-      return o << {{$e.Value}};
-{{-  end}}
+  if (auto* s = Converter<{{$.Name}}>::ToString(value)) {
+    return o << s;
   }
   return o << "undefined<{{$.Name}}>";
 }
diff --git a/src/dawn_node/interop/WebGPU.h.tmpl b/src/dawn_node/interop/WebGPU.h.tmpl
index 62a222c..883f33f 100644
--- a/src/dawn_node/interop/WebGPU.h.tmpl
+++ b/src/dawn_node/interop/WebGPU.h.tmpl
@@ -169,6 +169,7 @@
   static bool FromJS(Napi::Env, Napi::Value, {{$.Name}}&);
   static Napi::Value ToJS(Napi::Env, {{$.Name}});
   static bool FromString(std::string, {{$.Name}}&);
+  static const char* ToString({{$.Name}});
 };
 
 std::ostream& operator<<(std::ostream& o, {{$.Name}});
@@ -195,8 +196,9 @@
 */ -}}
 {{- define "InterfaceSetlike"}}
   virtual bool has(Napi::Env, {{template "Type" $.Elem}}) = 0;
+  virtual std::vector<{{template "Type" $.Elem}}> keys(Napi::Env) = 0;
 {{- /* TODO(crbug.com/dawn/1143):
-       entries, forEach, keys, size, values
+       entries, forEach, size, values
        read-write: add, clear, or delete
 */}}
 {{- end }}