If WebAssembly cannot interact with the DOM why the “syscall/js” package can?

I’ve read on several sites that WebAssembly can’t be used to interact with the DOM but if I’m not misunderstanding the syscall/js package in a Go program that is then compiled to WebAssembly CAN interact with the DOM.

Or what does “interact” mean here?

Hi @Willy,

I’m not deep into WebAssembly, but it seems that this issue explains your observations.

The issue is about the need of a garbage collector to be able to access the DOM directly from WebAssembly code. Without that, WASM code needs to take a detour by calling JavaScript for handling DOM manipulations.

I guess that’s what syscall/js does for WebAssembly-compiled Go code.

1 Like

I’m also not a WASM expert. But - syscall/js is calling JavaScript, which is why the JavaScript you are calling can manipulate the DOM. You know how you have to add this to your index.html to make things work?

<script src="wasm_exec.js"></script>

Take a look at the contents of wasm_exec.js. For example, here is the Call implementation:

// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
	sp >>>= 0;
	try {
		const v = loadValue(sp + 8);
		const m = Reflect.get(v, loadString(sp + 16));
		const args = loadSliceOfValues(sp + 32);
		const result = Reflect.apply(m, v, args);
		sp = this._inst.exports.getsp() >>> 0; // see comment above
		storeValue(sp + 56, result);
		this.mem.setUint8(sp + 64, 1);
	} catch (err) {
		sp = this._inst.exports.getsp() >>> 0; // see comment above
		storeValue(sp + 56, err);
		this.mem.setUint8(sp + 64, 0);
	}
},

… and then note in the syscall/js package the following:

// Call does a JavaScript call to the method m of value v with the given arguments.
// It panics if v has no method m.
// The arguments get mapped to JavaScript values according to the ValueOf function.
func (v Value) Call(m string, args ...any) Value {
	argVals, argRefs := makeArgSlices(len(args))
	storeArgs(args, argVals, argRefs)
	res, ok := valueCall(v.ref, m, argRefs)
	runtime.KeepAlive(v)
	runtime.KeepAlive(argVals)
	if !ok {
		if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case
			panic(&ValueError{"Value.Call", vType})
		}
		if propType := v.Get(m).Type(); propType != TypeFunction {
			panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String())
		}
		panic(Error{makeValue(res)})
	}
	return makeValue(res)
}

I’m not exactly sure how these annotations work with the compiler:

// valueCall does a JavaScript call to the method name m of ref v with the given arguments.
//
// (noescape): This is safe because no references are maintained to the
//             Go string m after the syscall returns. Additionally, the args slice
//             is only used temporarily to collect the JavaScript objects for
//             the JavaScript method invocation.
//
//go:wasmimport gojs syscall/js.valueCall
//go:nosplit
//go:noescape
func valueCall(v ref, m string, args []ref) (ref, bool)

Anyway, the short answer is: in order to use Go WASM in the browser, you have to have that interop js file that is handling interop for you. It allows you to call JavaScript. JavaScript is able to manipulate the DOM.

1 Like

@christophberger @Dean_Davidson Wait a second, are you telling me that if I organize myself correctly I can do bypass javascript and program everything in Go?

Yes but it’s probably more of a pain than it’s worth for most cases. Here’s a guide for go wasm + react:

Most of the REAL use cases I’ve seen in the wild are where people want to share logic between the server and the client. A common one is validation. You need to validate on the server but often times you want to run the same validations on client + server so I’ve seen teams that ship wasm to the client.