dawn_node: Add template files

Templates used to generate the NodeJS interop classes for the WebGPU IDL.

Also includes a stub `Browser.idl` file that provides stub definitions for browser IDL declarations referenced by the WebGPU IDL.

Bug: dawn:1123
Change-Id: I4067cb186f63436a502c3516a879ed1c5cd30731
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/64902
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_node/interop/Browser.idl b/src/dawn_node/interop/Browser.idl
new file mode 100644
index 0000000..8208058
--- /dev/null
+++ b/src/dawn_node/interop/Browser.idl
@@ -0,0 +1,84 @@
+// Copyright 2021 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// An IDL file that provides stub definitions for dictionaries and interfaces
+// used by the webgpu.idl file
+
+dictionary EventInit {
+  boolean bubbles = false;
+  boolean cancelable = false;
+  boolean composed = false;
+};
+
+interface Navigator {
+  readonly attribute DOMString vendorSub;
+  readonly attribute DOMString productSub;
+  readonly attribute DOMString vendor;
+};
+
+interface Event {
+  readonly attribute boolean bubbles;
+  readonly attribute boolean cancelable;
+  attribute boolean returnValue;
+};
+
+interface WorkerNavigator{};
+
+interface EventListener {
+  undefined handleEvent(Event event);
+};
+
+interface EventTarget {
+  undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options);
+  undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
+  boolean dispatchEvent(Event event);
+};
+
+dictionary EventListenerOptions { boolean capture = false; };
+
+dictionary AddEventListenerOptions : EventListenerOptions {
+  boolean passive = false;
+  boolean once = false;
+};
+
+interface HTMLVideoElement {
+  attribute unsigned long width;
+  attribute unsigned long height;
+  readonly attribute unsigned long videoWidth;
+  readonly attribute unsigned long videoHeight;
+  attribute DOMString poster;
+};
+
+typedef(Int8Array or Int16Array or Int32Array or Uint8Array or Uint16Array or
+        Uint32Array or Float32Array or Float64Array or
+        DataView) ArrayBufferView;
+
+typedef(ArrayBufferView or ArrayBuffer) BufferSource;
+
+interface ImageBitmap {
+  readonly attribute unsigned long width;
+  readonly attribute unsigned long height;
+};
+
+interface HTMLCanvasElement {
+  attribute unsigned long width;
+  attribute unsigned long height;
+};
+
+interface OffscreenCanvas {
+  attribute unsigned long width;
+  attribute unsigned long height;
+};
+
+interface EventHandler{};
diff --git a/src/dawn_node/interop/WebGPU.cpp.tmpl b/src/dawn_node/interop/WebGPU.cpp.tmpl
new file mode 100644
index 0000000..d52a991
--- /dev/null
+++ b/src/dawn_node/interop/WebGPU.cpp.tmpl
@@ -0,0 +1,341 @@
+{{/*
+ Copyright 2021 The Dawn Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/}}
+
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with src/dawn_node/tools/cmd/idlgen/main.go to generate
+the WebGPU.cpp source file.
+
+See:
+* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST
+  types used by this template
+* src/dawn_node/tools/cmd/idlgen/main.go for additional structures and functions
+  used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Include "WebGPUCommon.tmpl" -}}
+
+#include "src/dawn_node/interop/WebGPU.h"
+
+#include <unordered_map>
+
+#include "src/dawn_node/utils/Debug.h"
+
+namespace wgpu {
+namespace interop {
+
+namespace {
+
+{{template "Wrappers" $}}
+
+}  // namespace
+
+{{ range $ := .Declarations}}
+{{-        if IsDictionary $}}{{template "Dictionary" $}}
+{{-   else if IsInterface  $}}{{template "Interface"  $}}
+{{-   else if IsEnum       $}}{{template "Enum"       $}}
+{{-   end}}
+{{- end}}
+
+
+void Initialize(Napi::Env env) {
+  auto* wrapper = Wrappers::Init(env);
+  auto global = env.Global();
+{{ range $ := .Declarations}}
+{{-   if IsInterfaceOrNamespace $}}
+  global.Set(Napi::String::New(env, "{{$.Name}}"), wrapper->{{$.Name}}_ctor.Value());
+{{-   end}}
+{{- end}}
+}
+
+}  // namespace interop
+}  // namespace wgpu
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Wrappers emits the C++ 'Wrappers' class, which holds all the interface and
+-- namespace interop wrapper classes.
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Wrappers"}}
+// Wrappers holds all the Napi class constructors, and Napi::ObjectWrap type
+// declarations, for each of the WebIDL interface and namespace types.
+class Wrappers {
+  Wrappers(Napi::Env env) {
+{{-   range $ := .Declarations}}
+{{-     if IsInterfaceOrNamespace $}}
+    {{$.Name}}_ctor = Napi::Persistent(W{{$.Name}}::Class(env));
+{{-     end}}
+{{-   end}}
+  }
+
+  static Wrappers* instance;
+
+public:
+{{-   range $ := .Declarations}}
+{{-     if IsInterfaceOrNamespace $}}{{template "Wrapper" $}}
+{{-     end}}
+{{-   end}}
+
+  // Allocates and constructs the Wrappers instance
+  static Wrappers* Init(Napi::Env env) {
+    instance = new Wrappers(env);
+    return instance;
+  }
+
+  // Destructs and frees the Wrappers instance
+  static void Term(Napi::Env env) {
+    delete instance;
+    instance = nullptr;
+  }
+
+  static Wrappers* For(Napi::Env env) {
+    // Currently Napi only actually supports a single Env, so there's no point
+    // maintaining a map of Env to Wrapper. Note: This might not always be true.
+    return instance;
+  }
+
+{{   range $ := .Declarations}}
+{{-     if IsInterfaceOrNamespace $}}
+  Napi::FunctionReference {{$.Name}}_ctor;
+{{-     end}}
+{{-   end}}
+};
+
+Wrappers* Wrappers::instance = nullptr;
+{{- end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Wrapper emits the C++ wrapper class for the given ast.Interface or
+-- ast.Namespace.
+-- This wrapper class inherits from Napi::ObjectWrap, which binds the lifetime
+-- of the JavaScript object to the lifetime of the wrapper class instance.
+-- If the wrapper is for an interface, the wrapper object holds a unique_ptr to
+-- the interface implementation, and delegates all exposed method calls on to
+-- the implementation.
+-- See: https://github.com/nodejs/node-addon-api/blob/main/doc/object_wrap.md
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Wrapper"}}
+  struct W{{$.Name}} : public Napi::ObjectWrap<W{{$.Name}}> {
+{{-  if IsInterface $}}
+    std::unique_ptr<{{$.Name}}> impl;
+{{-  end}}
+    static Napi::Function Class(Napi::Env env) {
+      return DefineClass(env, "{{$.Name}}", {
+{{-  range $m := MethodsOf $}}
+        InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}),
+{{-  end}}
+{{-  range $a := AttributesOf $}}
+        InstanceAccessor("{{$a.Name}}", &W{{$.Name}}::get{{Title $a.Name}},
+{{-    if $a.Readonly}} nullptr{{else}} &W{{$.Name}}::set{{Title $a.Name}}{{end -}}
+        ),
+{{-  end}}
+{{-  range $c := ConstantsOf $}}
+        StaticValue("{{$c.Name}}", ToJS(env, {{$.Name}}::{{$c.Name}})),
+{{-  end}}
+      });
+    }
+
+    W{{$.Name}}(const Napi::CallbackInfo& info) : ObjectWrap(info) {}
+{{-  range $m := MethodsOf $}}
+    Napi::Value {{$m.Name}}(const Napi::CallbackInfo& info) {
+{{-    range $overload_idx, $o := $m.Overloads}}
+      { // Overload {{$overload_idx}}
+        std::tuple<
+{{-        range $i, $p := $o.Parameters}}
+{{-          if $i}}, {{end}}
+{{-          if $p.Optional}}std::optional<{{template "Type" $p.Type}}>
+{{-          else          }}{{template "Type" $p.Type}}
+{{-          end}}
+{{-        end}}> args;
+        if (FromJS(info, args)) {
+          {{/* indent */}}INTEROP_LOG(
+{{-        range $i, $p := $o.Parameters}}
+{{-          if $i}}, ", {{$p.Name}}: "{{else}}"{{$p.Name}}: "{{end}}, std::get<{{$i}}>(args)
+{{-        end}});
+          {{/* indent */}}
+{{-      if not (IsUndefinedType $o.Type) }}auto result = {{end -}}
+          impl->{{$o.Name}}(info.Env(){{range $i, $_ := $o.Parameters}}, std::get<{{$i}}>(args){{end}});
+          {{/* indent */ -}}
+{{-      if   IsUndefinedType $o.Type}}return info.Env().Null();
+{{-      else                        }}return ToJS(info.Env(), result);
+{{-      end                         }}
+        }
+      }
+{{-    end}}
+      Napi::Error::New(info.Env(), "invalid arguments to {{$m.Name}}").ThrowAsJavaScriptException();
+      return {};
+    }
+{{-  end}}
+
+{{-  range $a := AttributesOf $}}
+    Napi::Value get{{Title $a.Name}}(const Napi::CallbackInfo& info) {
+      return ToJS(info.Env(), impl->get{{Title $a.Name}}(info.Env()));
+    }
+{{-   if not $a.Readonly}}
+    void set{{Title $a.Name}}(const Napi::CallbackInfo& info, const Napi::Value& value) {
+      {{template "Type" $a.Type}} v{};
+      if (FromJS(info.Env(), value, v)) {
+        impl->set{{Title $a.Name}}(info.Env(), std::move(v));
+      } else {
+        Napi::Error::New(info.Env(), "invalid value to {{$a.Name}}").ThrowAsJavaScriptException();
+      }
+    }
+{{-   end }}
+{{-  end}}
+  };
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Dictionary emits the C++ method implementations and associated functions of
+-- the interop type that defines the given ast.Dictionary
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Dictionary"}}
+bool Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
+  auto object = value.ToObject();
+  return true{{template "DictionaryMembersFromJS" $}};
+}
+
+Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) {
+  auto object = Napi::Object::New(env);
+{{- template "DictionaryMembersToJS" $}}
+  return object;
+}
+
+std::ostream& operator<<(std::ostream& o, const {{$.Name}}& dict) {
+    o << "{{$.Name}} {";
+{{-    range $i, $m := $.Members}}
+    o << {{if $i}}", "{{else}}" "{{end}} << "{{$m.Name}}: ";
+    utils::Write(o, dict.{{$m.Name}});
+{{-    end          }}
+    o << "}" << std::endl;
+    return o;
+}
+{{ end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- DictionaryMembersFromJS emits the C++ logic to convert each of the
+-- dictionary ast.Member fields from JavaScript to C++. Each call to FromJS() is
+-- prefixed with '&&' so that the combined expression is true iff all members
+-- are converted succesfully
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "DictionaryMembersFromJS"}}
+{{-    if $.Inherits}}{{template "DictionaryMembersFromJS" (Lookup $.Inherits)}}{{end}}
+{{-    range $i, $m := $.Members}} &&
+    {{/* indent */}}
+{{-      if   $m.Init }}interop::FromJSOptional(env, object.Get("{{$m.Name}}"), out.{{$m.Name}})
+{{-      else         }}interop::FromJS(env, object.Get("{{$m.Name}}"), out.{{$m.Name}})
+{{-      end          }}
+{{-    end}}
+{{- end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- DictionaryMembersToJS emits the C++ logic to convert each of the
+-- dictionary ast.Member fields to JavaScript from C++. Each call to ToJS() is
+-- emitted as a separate statement
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "DictionaryMembersToJS"}}
+{{-    if $.Inherits}}{{template "DictionaryMembersToJS" (Lookup $.Inherits)}}{{end}}
+{{-    range $m := $.Members}}
+  object.Set(Napi::String::New(env, "{{$m.Name}}"), interop::ToJS(env, value.{{$m.Name}}));
+{{-    end}}
+{{- end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Interface emits the C++ method implementations that define the given
+-- ast.Interface.
+-- Note: Most of the actual binding logic lives in the interface wrapper class.
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Interface"}}
+{{$.Name}}::{{$.Name}}() = default;
+
+{{$.Name}}* {{$.Name}}::Unwrap(Napi::Object object) {
+  auto* wrappers = Wrappers::For(object.Env());
+  if (!object.InstanceOf(wrappers->{{$.Name}}_ctor.Value())) {
+    return nullptr;
+  }
+  return Wrappers::W{{$.Name}}::Unwrap(object)->impl.get();
+}
+
+Interface<{{$.Name}}> {{$.Name}}::Bind(Napi::Env env, std::unique_ptr<{{$.Name}}>&& impl) {
+  auto* wrappers = Wrappers::For(env);
+  auto object = wrappers->{{$.Name}}_ctor.New({});
+  auto* wrapper = Wrappers::W{{$.Name}}::Unwrap(object);
+  wrapper->impl = std::move(impl);
+  return Interface<{{$.Name}}>(object);
+}
+
+{{$.Name}}::~{{$.Name}}() = default;
+{{ end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Enum emits the C++ associated functions of the interop type that defines the
+-- given ast.Enum
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Enum"}}
+bool Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
+  std::string str = value.ToString();
+{{-  range $e := $.Values}}
+  if (str == {{$e.Value}}) {
+    out = {{$.Name}}::{{EnumEntryName $e.Value}};
+    return true;
+  }
+{{-  end}}
+  return false;
+}
+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}}
+  }
+  return o << "undefined<{{$.Name}}>";
+}
+
+{{end}}
diff --git a/src/dawn_node/interop/WebGPU.h.tmpl b/src/dawn_node/interop/WebGPU.h.tmpl
new file mode 100644
index 0000000..7c791c6
--- /dev/null
+++ b/src/dawn_node/interop/WebGPU.h.tmpl
@@ -0,0 +1,263 @@
+{{/*
+ Copyright 2021 The Dawn Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/}}
+
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with src/dawn_node/tools/cmd/idlgen/main.go to generate
+the WebGPU.h header file.
+
+See:
+* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST
+  types used by this template
+* src/dawn_node/tools/cmd/idlgen/main.go for additional structures and functions
+  used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Include "WebGPUCommon.tmpl" -}}
+
+#ifndef DAWN_NODE_GEN_INTEROP_WEBGPU_H_
+#define DAWN_NODE_GEN_INTEROP_WEBGPU_H_
+
+#include "src/dawn_node/interop/Core.h"
+
+namespace wgpu {
+namespace interop {
+
+// Initialize() registers the WebGPU types with the Napi environment.
+void Initialize(Napi::Env env);
+
+{{  range $ := .Declarations}}
+{{-        if IsDictionary $}}{{template "Dictionary" $}}
+{{-   else if IsNamespace  $}}{{template "Namespace" $}}
+{{-   else if IsInterface  $}}{{template "Interface" $}}
+{{-   else if IsEnum       $}}{{template "Enum" $}}
+{{-   else if IsTypedef    $}}{{template "Typedef" $}}
+{{-   end}}
+{{- end}}
+
+}  // namespace interop
+}  // namespace wgpu
+
+#endif // DAWN_NODE_GEN_INTEROP_WEBGPU_H_
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Dictionary emits the C++ header declaration that defines the interop type for
+-- the given ast.Dictionary
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Dictionary"}}
+// dictionary {{$.Name}}
+class {{$.Name}} {{- if $.Inherits }} : public {{$.Inherits}}{{end}} {
+public:
+{{   range $m := $.Members}}
+{{-    if      IsConstructor $m}}  {{$.Name}}();
+{{     else if IsMember      $m}}  {{template "DictionaryMember" $m}}
+{{     end}}
+{{-  end -}}
+};
+
+template<>
+class Converter<{{$.Name}}> {
+public:
+  static bool FromJS(Napi::Env, Napi::Value, {{$.Name}}&);
+  static Napi::Value ToJS(Napi::Env, {{$.Name}});
+};
+
+std::ostream& operator<<(std::ostream& o, const {{$.Name}}& desc);
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Namespace emits the C++ header declaration that defines the interop type for
+-- the given ast.Namespace
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Namespace"}}
+// namespace {{$.Name}}
+class {{$.Name}} {
+public:
+  virtual ~{{$.Name}}();
+  {{$.Name}}();
+{{-  range $c := ConstantsOf $}}
+{{-    template "Constant" $c}}
+{{-  end}}
+};
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Interface emits the C++ header declaration that defines the interop type for
+-- the given ast.Interface
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Interface"}}
+// interface {{$.Name}}
+class {{$.Name}} {{- if $.Inherits }} : public {{$.Inherits}}{{end}} {
+public:
+  static Interface<{{$.Name}}> Bind(Napi::Env, std::unique_ptr<{{$.Name}}>&&);
+  static {{$.Name}}* Unwrap(Napi::Object);
+
+  template<typename T, typename ... ARGS>
+  static inline Interface<{{$.Name}}> Create(Napi::Env env, ARGS&& ... args) {
+    return Bind(env, std::make_unique<T>(std::forward<ARGS>(args)...));
+  }
+
+  virtual ~{{$.Name}}();
+  {{$.Name}}();
+{{-  range $m := MethodsOf $}}
+{{-    template "InterfaceMethod" $m}}
+{{-  end}}
+{{-  range $a := AttributesOf $}}
+{{-    template "InterfaceAttribute" $a}}
+{{-  end}}
+{{-  range $c := ConstantsOf  $}}
+{{-    template "Constant" $c}}
+{{-  end}}
+};
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Typedef emits the C++ header declaration that defines the interop type for
+-- the given ast.Interface
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Typedef"}}
+using {{$.Name}} = {{template "Type" $.Type}};
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Enum emits the C++ header declaration that defines the interop type for
+-- the given ast.Enum
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Enum"}}
+enum class {{$.Name}} {
+{{-  range $ := $.Values}}
+  {{EnumEntryName $.Value}},
+{{-  end}}
+};
+
+template<>
+class Converter<{{$.Name}}> {
+public:
+  static bool FromJS(Napi::Env, Napi::Value, {{$.Name}}&);
+  static Napi::Value ToJS(Napi::Env, {{$.Name}});
+};
+
+std::ostream& operator<<(std::ostream& o, {{$.Name}});
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- DictionaryMember emits the C++ declaration for a single dictionary ast.Member
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "DictionaryMember"}}
+{{-   if $.Attribute}}{{template "AttributeType" $}} {{$.Name}}
+{{-     if $.Init}} = {{Eval "Literal" "Value" $.Init "Type" $.Type}}{{end}};
+{{-   else          }}{{template "Type" $.Type}} {{$.Name}}({{template "Parameters" $.Parameters}});
+{{-   end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- InterfaceMethod emits the C++ declaration for a single interface ast.Member
+-- method
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "InterfaceMethod"}}
+{{-   range $o := $.Overloads}}
+  virtual {{template "Type" $o.Type}} {{$.Name}}(Napi::Env{{template "ParametersWithLeadingComma" $o.Parameters}}) = 0;
+{{-   end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- InterfaceAttribute emits the C++ declaration for a single interface
+-- ast.Member attribute
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "InterfaceAttribute"}}
+  virtual {{template "Type" $.Type}} get{{Title $.Name}}(Napi::Env) = 0;
+{{-   if not $.Readonly}}
+  virtual void set{{Title $.Name}}(Napi::Env, {{template "Type" $.Type}} value) = 0;
+{{-   end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Constant emits the C++ declaration for a single ast.Member constant
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Constant"}}
+  static constexpr {{template "Type" $.Type}} {{$.Name}} = {{Eval "Literal" "Value" $.Init "Type" $.Type}};
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Parameters emits the C++ comma separated list of parameter declarations for
+-- the given []ast.Parameter
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Parameters"}}
+{{-   range $i, $param := $ }}
+{{-     if $i }}, {{end}}
+{{-     template "Parameter" $param}}
+{{-   end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- ParametersWithLeadingComma emits the C++ comma separated list of parameter
+-- declarations for the given []ast.Parameter, starting with a leading comma
+-- for the first parameter
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "ParametersWithLeadingComma"}}
+{{-   range $i, $param := $ }}, {{/*  */}}
+{{-     template "Parameter" $param}}
+{{-   end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Parameter emits the C++ parameter type and name for the given ast.Parameter
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Parameter" -}}
+{{-   if $.Optional -}}
+std::optional<{{template "Type" $.Type}}> {{$.Name}}
+{{-   else}}
+{{-     template "Type" $.Type}} {{$.Name}}
+{{-   end}}
+{{- end}}
diff --git a/src/dawn_node/interop/WebGPUCommon.tmpl b/src/dawn_node/interop/WebGPUCommon.tmpl
new file mode 100644
index 0000000..8630773
--- /dev/null
+++ b/src/dawn_node/interop/WebGPUCommon.tmpl
@@ -0,0 +1,126 @@
+{{/*
+ Copyright 2021 The Dawn Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/}}
+
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with src/dawn_node/tools/cmd/idlgen/main.go.
+This file provides common template definitions and is included by WebGPU.h.tmpl
+and WebGPU.cpp.tmpl.
+
+See:
+* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST
+  types used by this template
+* src/dawn_node/tools/cmd/idlgen/main.go for additional structures and functions
+  used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Type generates the C++ type for the given ast.Type
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Type" -}}
+{{-        if IsUndefinedType $}}void
+{{-   else if IsTypeName      $}}
+{{-          if eq $.Name "boolean"             }}bool
+{{-     else if eq $.Name "long"                }}int32_t
+{{-     else if eq $.Name "unsigned long"       }}uint32_t
+{{-     else if eq $.Name "long long"           }}int64_t
+{{-     else if eq $.Name "unsigned long long"  }}uint64_t
+{{-     else if eq $.Name "object"              }}Object
+{{-     else if eq $.Name "DOMString"           }}std::string
+{{-     else if eq $.Name "USVString"           }}std::string
+{{-     else if eq $.Name "ArrayBuffer"         }}ArrayBuffer
+{{-     else if IsInterface (Lookup $.Name)     }}Interface<{{$.Name}}>
+{{-     else                                    }}{{$.Name}}
+{{-     end                                     }}
+{{-   else if IsParametrizedType $}}{{$.Name}}<{{template "TypeList" $.Elems}}>
+{{-   else if IsNullableType     $}}std::optional<{{template "Type" $.Type}}>
+{{-   else if IsUnionType        $}}std::variant<{{template "VariantTypeList" $.Types}}>
+{{-   else if IsSequenceType     $}}std::vector<{{template "Type" $.Elem}}>
+{{-   else if IsRecordType       $}}std::unordered_map<{{template "Type" $.Key}}, {{template "Type" $.Elem}}>
+{{-   else                        }} /* Unhandled Type {{printf "%T" $}} */
+{{-   end -}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- AttributeType generates the C++ type for the given ast.Member
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "AttributeType" -}}
+{{-   if      $.Required }}{{template "Type" $.Type}}
+{{-   else if $.Init     }}{{template "Type" $.Type}}
+{{-   else               }}std::optional<{{template "Type" $.Type}}>
+{{-   end}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Literal generates a C++ literal value using the following arguments:
+--   Value - the ast.Literal
+--   Type  - the ast.Type of the literal
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Literal" -}}
+{{-   if      IsDefaultDictionaryLiteral $.Value}}{{template "Type" $.Type}}{}
+{{-   else if IsTypeName                 $.Type }}
+{{-     $ty := Lookup $.Type.Name}}
+{{-     if      IsEnum         $ty     }}{{$.Type.Name}}::{{EnumEntryName $.Value.Value}}
+{{-     else if IsBasicLiteral $.Value }}{{$.Value.Value}}
+{{-     else                           }}/* Unhandled Type {{printf "ty: %v $.Type.Name: %T $.Value: %T" $ty $.Type.Name $.Value}} */
+{{-     end                            }}
+{{-   else if IsSequenceType $.Type  }}{{template "Type" $.Type}}{} {{- /* TODO: Assumes the initialiser is empty */}}
+{{-   else if IsBasicLiteral $.Value }}{{$.Value.Value}}
+{{-   else }} /* Unhandled Type {{printf "%T %T" $.Type $.Value}} */
+{{-   end}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- TypeList generates a C++ comma separated list of types from the given
+-- []ast.Type
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "TypeList" -}}
+{{-   range $i, $ty := $}}
+{{-     if $i }}, {{end}}
+{{-     template "Type" $ty}}
+{{-   end}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- VariantTypeList generates a C++ comma separated list of types from the given
+-- []ast.Type, skipping any 'undefined' types
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "VariantTypeList" -}}
+{{-   range $i, $ty := $}}
+{{-     if not (IsUndefinedType $ty)}}
+{{-       if $i }}, {{end}}
+{{-       template "Type" $ty}}
+{{-     end}}
+{{-   end}}
+{{- end }}
+