Go language binding for Web IDL

A Collection of Interesting Ideas,

This version:
https://jcbhmr.me/WebIDL-Go/
Issue Tracking:
GitHub
Inline In Spec
Editor:
Jacob Hummer
Participate:
https://github.com/jcbhmr/WebIDL-Go

Abstract

Outline conventions & ideas for mapping Web IDL concepts & types into native Go patterns, conventions, and types.

Not affiliated with Go, Google, W3C, or WHATWG.

1. Introduction

This section is non-normative.

💡 This document was inspired by Java language binding for Web IDL and Web IDL Standard JavaScript Bindings. This is just a rough collection of ideas on how to map types from Web IDL Standard to Go. It’s like a miniature reference book. If you have a better way to do things, chances are you’re right! Open an Issue! I’m always looking for more egonomic & better ways to translate Web IDL concepts to Go. ❤️

1.1. Use case: Ergonomic Go ↔ Web API bindings

There currently is a lack of high-quality Go bindings to Web APIs for use when writing Go code that targets GOOS=js GOARCH=wasm.

dominikh/go-js-dom provides a lot of bindings to access DOM Standard APIs.

  • It doesn’t let you cast very easily. It’s inconsistent with some types being struct and others being interface. The DX and ergonomics could be improved by using interface everywhere.

  • It doesn’t provide modern [DOM] features like append. It’s a bit outdated. This could be improved with a Web IDL 👉 Go scaffolding generator and more formalized Go ↔ Web IDL rules.

  • It doesn’t handle optional primitives at all. nodeValue is a DOMString? in [WEBIDL] but is defined as NodeValue() string in go-js-dom. Using nil-able types would be more idiomatic Go.

  • It doesn’t work well with complex function signatures like addEventListener(type, callback, options) which has a callback interface and a union type. A standardized well-known way to idiomatically convert [WEBIDL] overloads, union types, and optional parameters to Go would be helpful.

realPy/hogosuru is a Go web framework that offers some Web API bindings too.

  • It doesn’t embrace union types. addEventListener(type, callback, options) is represented only as AddEventListener(string, func(Event)) with no options parameter. An ergonomic way to idiomatically represnt overloads and optional parameters in Go would be nice.

  • Packages are fragmented. There are so many packages! One for each [DOM] definition is too many. A more cohesive API might group things based on which specification ([DOM], [FETCH], [WEB-SHARE], etc.) that they come from.

  • Everything returns a (T, error). This is just a gripe of how granular I think the error handling is.

  • nodeValue is represented as NodeValue() string. The nodeValue of a node is actually DOMString? though. A standard way to represent nullable [WEBIDL] values in Go (particularily primitives) would help.

1.2. Use case: Path to port Web APIs to Go

URL Pattern Standard, DOM Standard, Fetch Standard, Web Share API, Clipboard API and events, and more are all cool Web APIs that could be ported to Go. Not because being Web IDL-based is better than a custom API, but because it gives developers a very concrete API surface to cover. And as a side effect it makes it very easy to bind to the existing JavaScript bindings of the same Web API.

🌎 JavaScript APIs are also very familiar to most web developers and it’s very cool 😎 when you can use the same concepts and libraries uniformly across domains and languages. It’s just cool.

2. Go binding

  1. You should use Go 1.11 modules when developing Go Web IDL bindings. That means go mod init and not GOPATH.

  2. Try to group things based on which specification they are a part of. This helps segement the vast collection of browser APIs into develop-able chunks.

  3. Put everything on one level. Don’t nest things in sub-packages; put it all on the root. Some developers will want to import . "github.com/octocat/go-fetch" to make Fetch() and all associated things top-level.

  4. The Window, WorkerGlobalScope, etc. interfaces are all ✨special since they are global interfaces. Anything that would be defined as a method on Window or other global scope interface should be defined directly at the top level of the Go package instead of as methods on a Window struct/interface. You should also omit the Window and other global interfaces from the types that you define. Think of Window as an invisible package-level interface instead of a concrete type.

  5. Many Web IDL definitions (particularily in HTML Standard) will make reference to "the global document" or "the current window". There is no "current window" in a typical Go program. You should try to follow the spirit if not the letter of these definitions.

partial interface Window {
  undefined alert(DOMString message);
};
func Alert(message string) {
  // ...
}

2.1. Names

Remember: to be exported from a package a Go name must start with an uppercase letter.

Use your own best judgement to split the words and PascalCase-ify them to make them conform to the Go convention. Note that initialisms should be all-caps.

For const & static members of interfaces, use the interface name followed by the member name.

For Web IDL constructors use New followed by the interface name.

innerHTML e.InnerHTML()
getElementById document.GetElementByID()
htmlFor e.HTMLFor()
url r.URL()
XMLHttpRequest XMLHTTPRequest
HTMLHtmlElement HTMLHTMLElement
HTMLPreElement HTMLPreElement
Node.TEXT_NODE NodeTextNode
doctype document.Doctype()
id e.ID()
new Document NewDocument

2.2. Go type mapping

Can’t find a Web IDL type defined in this section? Add it yourself or open an issue. ❤️

The following list of types doesn’t require much explaining.

any any
boolean bool
byte int8
octet byte
short int16
unsigned short uint16
long int32
unsigned long uint32
long long int64
unsigned long long uint64
unrestricted float float32
unrestricted double float64
bigint math/big.Int
DOMString string
ByteString string

2.2.1. Restricted float and double

Reminder: the unrestricted double type means that the value can be NaN, +Infinity, or -Infinity. The counterpart is the plain old double type which cannot be NaN, +Infinity, or -Infinity.

Web IDL float values in Go are represented as Go float32 values. Web IDL double values in Go are represented as Go float64 values.

Implementers should panic() if their input Go float32 or float64 value is NaN, +Infinity, or -Infinity when the Web IDL signature uses a restricted float or double type.

partial interface Window {
  double add(double a, double b);
  unrestricted double addUnrestricted(unrestricted double a, unrestricted double b);
};
func Add(a, b float64) float64 {
  if math.IsNaN(a) || math.IsInf(a, 0) { panic("a is not a double") }
  if math.IsNaN(b) || math.IsInf(b, 0) { panic("b is not a double") }
  return a + b
}
func AddUnrestricted(a, b float64) float64 {
  return a + b
}

2.2.2. undefined

In function return position undefined should be omitted from the Go function signature.

interface Storage {
  setter undefined setItem(DOMString name, DOMString value);
  // ...
};
type Storage interface {
  SetItem(name string, value string)
}

**When used in sequence<undefined>** or other places where a type is required use struct{}.

interface A {
  sequence<undefined> doThing();
};
type A interface {
  DoThing() []struct{}
}

When part of a type union treat it like a nullable ? type.

interface CustomElementRegistry {
  (CustomElementConstructor or undefined) get(DOMString name);
  // ...
};
callback CustomElementConstructor = HTMLElement ();
type CustomElementRegistry interface {
  // CustomElementConstructor is a nil-able type.
  Get(name string) CustomElementConstructor
}
type CustomElementConstructor = func() HTMLElement

2.2.3. USVString

The USVString type corresponds to scalar value strings. Depending on the context, these can be treated as sequences of either 16-bit unsigned integer code units or scalar values.

Honestly I’m not entirely sure how to represent USVString in Go.

If you have a better explaination or some other insight into what DOMString vs USVString means in the context of a Go string I’m all ears. 😊

func DoThing(v string) {
  if !utf8.Valid(v) { panic("v is not a well-formed UTF-8 string") }
}

2.2.4. object

object is similar to any but it **cannot be null or a primitive**. In Go-land that means it’s like any just with a panic() check to make sure it’s not nil or a string|int|bool|etc... type.

func DoThing(v any) {
  if v == nil || reflect.ValueOf(v).Kind() < reflect.Array {
    panic("v is not object type")
  }
}

2.2.5. symbol

Not yet defined.

2.2.6. Interface types

To support downcasting (Node 👉 HTMLAnchorElement) easily Web IDL types are represented as Go interface instead of struct. Each Web IDL interface type should have a corresponding Go interface type. Unless it’s Window or another global interface, in which case it should be omitted and all its members should go in the package scope.

interface Node : EventTarget {
  readonly attribute DOMString nodeName;
};
interface Window {
  undefined alert(DOMString message);
};
type Node interface {
  EventTarget
  NodeName() string
}
func Alert(message string) {}

You can’t downcast a struct *Node into a struct *HTMLAudioElement in Go. You can only cast with interface-es. This means that if .QuerySelector() returns a *Element then there’s no way to try to instanceof HTMLAnchorElement or instanceof HTMLVideoElement and then cast it to those types to use .Click() or .Play().

element, err := document.QuerySelector("audio")
// This won't work
audio := element.(*HTMLAudioElement)
// So how do you convert element (which is *Element) into an *HTMLAudioElement?
element, err := document.QuerySelector("audio")
// This is how you do it
audio := element.(HTMLAudioElement)

Interface constructors are pulled out into a New___() function where you fill in the blank with the interface name. For example the Request interface has a constructor NewRequest(). For interfaces without a constructor you should omit this New___() function. Treat interface constructors as though they were their own Window function and apply all the same union, overload, and optional rules.

[Exposed=(Window,Worker)]
interface Headers {
  constructor(optional HeadersInit init);
  // ...
};
func NewHeaders() Headers {}
func NewHeadersInit(init any) Headers {}
2.2.6.1. interface mixin

Mixins are used by Web IDL to define a set of functions implemented by multiple interfaces. The best example of this is the Body mixin which defines arrayBuffer, text, and json on Request and Response objects.

In Go, that just corrolates to an embedded interface!

interface mixin Body {
  readonly attribute boolean bodyUsed;
  [NewObject] Promise<ArrayBuffer> arrayBuffer();
  // ...
};
Request includes Body;
Response includes Body;
type Body interface {
  BodyUsed() bool
  ArrayBuffer() func() ([]byte, error)
  // ...
}
type Request interface {
  Body
}
type Response interface {
  Body
}

2.2.7. Callback interface types

Callback interfaces are a way to codify the below into Web IDL:

// Both of these work!
globalThis.addEventListener("load", { handleEvent: () => console.log("handleEvent") })
globalThis.addEventListener("load", () => console.log("function"))

It’s rarely used.

Treat callback interfaces as though they were just a union type of the function signature and an unnamed interface with that single method defined. Practically, this means that callback interfaces are represented as any.

You should provide a same-name type TheCallbackInterface func(...)... type as well as an interface type with the suffix Object like type TheCallbackInterfaceObject interface {...}.

interface Thing {
  undefined addEventListener(listener EventListener);
};
callback interface EventListener {
  undefined handleEvent(Event event);
};
type EventListener func(event Event)
type EventListenerObject interface {
  HandleEvent(event Event)
}

func (e *thingImpl) AddEventListener(listener any) {
  if b, ok := a.(EventListenerObject); ok {
    // Can use like this:
    b.HandleEvent(event)
  } else if b, ok := a.(EventListener); ok {
    // Can use like this:
    b(event)
  } else {
    panic("listener is not an EventListener or EventListenerObject")
  }
}

2.2.8. Dictionary types

Web IDL interfaces map quite well to Go struct. The biggest obstacle here is optional struct fields. Since Go doesn’t have a Rust Option<T> or a Java null-able primitive (Integer, Double, String) objects it means we need to use something else. The most Go-like way of doing things is with nil-able types. For interfaces this is no sweat: interfaces are always nil-able. For primitives it means passing by *string instead of string.

Go doesn’t let you to take the & address of literal values. You can use a helper function like this for that:

func ptr[T any](v T) *T {
  return &v
}

func DoThing(v *string) {}

func main() {
  // Doesn't work 😢
  // DoThing(&"hello ptr magic!")

  // Works! 😄
  DoThing(ptr("hello ptr magic!"))
}
dictionary ResponseInit {
  unsigned short status = 200;
  ByteString statusText = "";
  HeadersInit headers;
};
type ResponseInit struct {
  Status *uint16
  StatusText *string
  Headers any
}

Dictionaries that extend other dictionaries should be completely flattened into a single struct. This is more ergonomic from a consumer’s perspective since all the fields are flattened one level deep instead of being nested.

dictionary AddEventListenerOptions : EventListenerOptions {
  boolean passive = false;
  boolean once = false;
};
dictionary EventListenerOptions {
  boolean capture = false;
};

Here’s an example of what this "flattening" aims to avoid. 🛑 This is the *incorrect* way to represent this Web IDL dictionary.

type AddEventListenerOptions struct {
  EventListenerOptions // 👈 Embedded struct!
  Passive *bool
  Once *bool
}
type EventListenerOptions struct {
  Capture *bool
}
// 🤷‍♀️ Not ideal.
AddEventListener("click", listener, AddEventListenerOptions{
  EventListenerOptions: EventListenerOptions{
    Capture: ptr(true),
  },
  Passive: ptr(true),
  Once: ptr(true),
})

Why is this bad? Because it introduces an unnecessary level of nesting when constructing a dictionary which has an inheritance chain like AddEventListenerOptions. Since most of the use of these dictionaries will be in constructing them, not reading from them (reading admittedly is very ergonomic with Go struct embedding) it’s better to flatten them for the benefit of the user.

// 👍 Better.
AddEventListener("click", listener, AddEventListenerOptions{
  Capture: ptr(true),
  Passive: ptr(true),
  Once: ptr(true),
})

I’m open to being wrong about nested structs being bad. Give me your experience! Maybe embedded structs are better.

2.2.9. Enumeration types

Todo

2.2.10. Callback function types

Use a type TheType func(...)... newtype.

callback TheCallback = long (long a, long b);
type TheCallback func(a int32, b int32) int32

func useTheCallback(cb TheCallback) {
  fmt.Printf("Result: %d", cb(1, 2))
}

func main() {
  useTheCallback(func(a int32, b int32) int32 {
    return a + b
  })
}

2.2.11. Nullable types

Nullable types become nil-able types in Go. All Web IDL interface types should be nil-able by default since all Go interface types are nil-able. Go primitives like string, int32, etc. can all be made nil-able through a pointer. Go structs can be made nil-able through a pointer as well.

partial interface Window {
  undefined doThing(DOMString? message);
};
func DoThing(message *string) {
  fmt.Println(message)
}

2.2.12. Sequences

For now Web IDL sequences are analagous to Array<T> types. Each sequence<T> type can be exactly mapped into a Go []T slice.

In the future iterables might be accepted.

partial interface Window {
  undefined doThing(sequence<DOMString> items);
};
func DoThing(items []string) {
  for i, v := range items {
    fmt.Printf("item %d: %s", i, v)
  }
}

2.2.13. Records

record<K, V> maps to map[K]V in Go.

Usually records will use some kind of string key like DOMString or USVString.
partial interface Window {
  undefined doThing(record<DOMString, long> things);
};
func DoThing(things map[string]int32) {
  for k, v := range things {
    fmt.Println(k, v)
  }
}

So far all use-cases for record<K, V> have been unordered so to Go random map[K]V ordering is OK.

2.2.14. Promise types

Promises are a bit tricky in Go. Unlike <-chan T, a Promise<T> can be await-ed multiple times and will always spit out the same cached value. What this means is that just <-chan T isn’t enough to represent a Promise<T> in Go. It falls apart specifically when you have a static Promise<T> that you want to await multiple times like ready.

Instead, the solution is to use a getter function. The current idiom that seems favored by Go is to *return something with a .Wait() method* and then call that to "await" the function’s result.

Promise<T> interface{ Wait() (T, error) }
Promise<undefined> interface{ Wait() error }
Infalliable Promise<T> interface{ Wait() T }
Infalliable Promise<undefined> interface{ Wait() }
partial interface Window {
  Promise<Response> fetch();
};
func Fetch() interface{Wait() (Response, error)} {
  // ...
}

An infalliable promise is a promise that never errors. These are extremely rare in Web APIs since most things that are async call system-level operations that can often fail for arbitrary reasons. If you see a Promise<T> in Web IDL it’s almost always a Promise<T> which should be a interface { Wait() (T, error) } in Go.

ready is an example of the rare case where a Promise<T> will never reject.
partial interface ServiceWorkerContainer {
  readonly attribute Promise<ServiceWorkerRegistration> ready;
};
type ServiceWorkerContainer interface {
  Ready() interface{ Wait() ServiceWorkerRegistration }
}

In all cases the returned object should support being called concurrently. sync.* friends handle this for you.

The work that the operation is doing should be done in a go func(){}() Go-routine in an async fashion. The Promise<T> exposes the result to the caller.

2.2.15. Union

Web IDL union types are represented as any in Go. Then at runtime they are if b, ok := a.(T); ok {} checked to see if they fit the union terminal types. There’s no recursion for union types that contain union types; all of said types must be individually checked for at runtime using type conversion checks.

You should provide a stub type UnionType = any type alias (not a newtype) with a documentation comment outlining which values are accepted in the union.

Why not type UnionType any as a newtype? Because then it becomes an opaque type that is impossible to downcast to any of the terminal types.

typedef (DOMString or long) TheUnion;
partial interface Window {
  undefined doThing(TheUnion u);
};
type TheUnion any
func DoThing(u TheUnion) {
  if uString, ok := u.(string); ok {
    fmt.Println("got string")
  } else if uInt32, ok := u.(int32); ok {
    fmt.Println("got int32")
  } else {
    panic("not string|int32")
  }
}

func main() {
  DoThing("wasd") // will panic!
  DoThing(1) // will panic!

  var myVariable TheUnion = "qwerty"
  // ☝ This has a type of TheUnion, not string.

  // ✅
  _ = myVariable.(TheUnion)

  // ❌
  _ = myVariable.(string)
}

There’s not a good way to get the string or int32 value out of TheUnion in this example when using type U any. If you can find one let me know! I’m sure there’s a way.

On a semantics note: the union type should not behave as a distinct type; it should act like the _union_ of all the terminal types. type U = any is the best fit for that.

2.2.16. Buffer source types

Uint8Array []uint8
Uint8ClampedArray []uint8
Int8Array []int8
Uint16Array []uint16
Int16Array []int16
Uint32Array []uint32
Int32Array []int32
BigUint64Array []uint64
BigInt64Array []int64
Float32Array []float32
Float64Array []float64

An ArrayBuffer needs to support [AllowResizable] so it’s represented in Go as a bytes.Buffer. SharedArrayBuffer is a bit trickier. There’s no concurrent bytes.Buffer in the Go standard library (that I know of) so for now it’s just bytes.Buffer with the note that it should be used with caution.

partial interface Window {
  Float64Array doThing(ArrayBuffer b);
};
func DoThing(b *bytes.Buffer) []float64 {
  return unsafe.Slice((*float64)(unsafe.Pointer(&b.Bytes()[0])), len(b.Bytes())/unsafe.Sizeof(float64(0)))
}

A DataView is roughly equivalent to a []byte and can then be used with other encoding functions to use and mutate the slice’s contents.

2.2.17. Frozen arrays

Use a normal Go slice and just make sure you emphasize that it’s intended to be immutable.

2.2.18. Observable arrays

This is fairly rare. adoptedStyleSheets is one case where I know this is used. Don’t know how to model this in Go.

2.3. Extended attributes

[AllowResizable] Add doc comment to note that this bytes.Buffer instance can be resized.
[AllowShared] Make sure to emphasize that this bytes.Buffer should be behind a concurrency lock.
[Clamp] Not relevant with exactly matching number types.
[CrossOriginIsolated] Assume that we are always cross-origin isolated. 🤷‍♀️
[Default] I don’t know what this does.
[EnforceRange] Not relevant. Go has exactly matching number types.
[Exposed] Not relevant. every Web IDL definition is exposed to other Go code.
[Global] Any interface that has this should expose all its members in the package scope and not provide a concrete type itself.
[NewObject] Must always return a new instance such that f() != f().
[Replaceable] Not relevant. Go has a stricter type system.
[SameObject] Must always return the same instance such that f() == f().
[SecureContext] Assume that we are always in a secure context. 🤷‍♀️
[Unscopable] Not relevant. Go has no with block.

-tags=coi might be something that happens in the future to disable cross-origin isolated features by default.

2.3.1. [PutForwards]

This attribute was designed so that this would work:

[Exposed=Window]
interface Name {
  attribute DOMString full;
  attribute DOMString family;
  attribute DOMString given;
};

[Exposed=Window]
interface Person {
  [PutForwards=full] readonly attribute Name name;
  attribute unsigned short age;
};
var p = getPerson();           // Obtain an instance of Person.

p.name = 'John Citizen';       // This statement...
p.name.full = 'John Citizen';  // ...has the same behavior as this one.

Since this attribute can only be applied on readonly attributes, the port to Go is simple: delegate the setter of the parent attribute to the specified child attribute. There’s no union type or any type deduction.

[Exposed=Window]
interface Name {
  attribute DOMString full;
  attribute DOMString family;
  attribute DOMString given;
};

[Exposed=Window]
interface Person {
  [PutForwards=full] readonly attribute Name name;
  attribute unsigned short age;
};
type Name interface {
  Full() string
  SetFull(v string)
  Family() string
  SetFamily(v string)
  Given() string
  SetGiven(v string)
  isName()
}

type Person interface {
  Name() Name
  SetName(v string)
  Age() uint16
  SetAge(v uint16)
  isPerson()
}

// ✅ The getter stays the same as always. This is a readonly property.
func (p *personImpl) Name() Name {
  return p.name
}

// 👇 We delegate to the p.name.SetFull() just like [PutForwards=full] said!
func (p *personImpl) SetName(v string) {
  p.name.SetFull(v)
}

This attribute is used in ElementCSSInlineStyle/style.

2.4. Legacy extended attributes

[LegacyFactoryFunction] Define this function in the same way JavaScript would.
[LegacyLenientSetter] Not relevant.
[LegacyLenientThis] Not relevant.
[LegacyNamespace] Interfaces will have the namespace name prefixed to their name. Used extensively in the WebAssembly APIs.
[LegacyNoInterfaceObject] Make the type private. Used in WebGL specifications.
[LegacyNullToEmptyString] Not implemented.
[LegacyOverrideBuiltIns] Not implemented.
[LegacyTreatNonObjectAsNull] Not implemented.
[LegacyUnenumerableNamedProperties] Not relevant. Go has no concept of iterable object properties.
[LegacyUnforgeable] Not relevant. All Go properties are unforgeable (not overridable) by default.
[LegacyWindowAlias] Do a type T = U type alias. Usually used for vendor-specific prefixes.

2.5. Overloads

Normally Web IDL overloads would be routed under a single JavaScript function. In Go the overload convention is different; you define overloads as separate functions with slightly different names. The naming convention for these extra functions usually focuses either on a type like Match vs MatchString or a named parameter like Split vs SplitAfterN.

The convention that this standard has adopted is to use TODO

2.6. Interfaces

TODO

2.6.1. Constants

TODO

2.6.2. Attributes

2.6.3. Stringifiers

2.6.4. Iterable declarations

2.6.5. Asynchronous iterable declarations

2.6.6. Maplike declarations

2.6.7. Setlike declarations

2.7. Namespaces

2.8. Exceptions

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). URL: https://drafts.csswg.org/cssom/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[SERVICE-WORKERS]
Jake Archibald; Marijn Kruisselbrink. Service Workers. URL: https://w3c.github.io/ServiceWorker/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[CLIPBOARD-APIS]
Gary Kacmarcik; Anupam Snigdha. Clipboard API and events. URL: https://w3c.github.io/clipboard-apis/
[SVG2]
Amelia Bellamy-Royds; et al. Scalable Vector Graphics (SVG) 2. URL: https://svgwg.org/svg2-draft/
[URLPATTERN]
Ben Kelly; Jeremy Roman; 宍戸俊哉 (Shunya Shishido). URL Pattern Standard. Living Standard. URL: https://urlpattern.spec.whatwg.org/
[WEB-SHARE]
Matt Giuca; Eric Willigers; Marcos Caceres. Web Share API. URL: https://w3c.github.io/web-share/
[WEBIDL-JAVA]
Cameron McCormack. Java language binding for Web IDL. 14 May 2013. NOTE. URL: https://www.w3.org/TR/WebIDL-Java/
[XHR]
Anne van Kesteren. XMLHttpRequest Standard. Living Standard. URL: https://xhr.spec.whatwg.org/

Issues Index

Honestly I’m not entirely sure how to represent USVString in Go.

If you have a better explaination or some other insight into what DOMString vs USVString means in the context of a Go string I’m all ears. 😊