blob: 53a4d79a95e322a96764187687e23e0bef9ed857 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build js && wasm
package js
import "sync"
var (
funcsMu sync.Mutex
funcs = make(map[uint32]func(Value, []Value) any)
nextFuncID uint32 = 1
)
// Func is a wrapped Go function to be called by JavaScript.
type Func struct {
Value // the JavaScript function that invokes the Go function
id uint32
}
// FuncOf returns a function to be used by JavaScript.
//
// The Go function fn is called with the value of JavaScript's "this" keyword and the
// arguments of the invocation. The return value of the invocation is
// the result of the Go function mapped back to JavaScript according to ValueOf.
//
// Invoking the wrapped Go function from JavaScript will
// pause the event loop and spawn a new goroutine.
// Other wrapped functions which are triggered during a call from Go to JavaScript
// get executed on the same goroutine.
//
// As a consequence, if one wrapped function blocks, JavaScript's event loop
// is blocked until that function returns. Hence, calling any async JavaScript
// API, which requires the event loop, like fetch (http.Client), will cause an
// immediate deadlock. Therefore a blocking function should explicitly start a
// new goroutine.
//
// Func.Release must be called to free up resources when the function will not be invoked any more.
func FuncOf(fn func(this Value, args []Value) any) Func {
funcsMu.Lock()
id := nextFuncID
nextFuncID++
funcs[id] = fn
funcsMu.Unlock()
return Func{
id: id,
Value: jsGo.Call("_makeFuncWrapper", id),
}
}
// Release frees up resources allocated for the function.
// The function must not be invoked after calling Release.
// It is allowed to call Release while the function is still running.
func (c Func) Release() {
funcsMu.Lock()
delete(funcs, c.id)
funcsMu.Unlock()
}
// setEventHandler is defined in the runtime package.
func setEventHandler(fn func() bool)
func init() {
setEventHandler(handleEvent)
}
// handleEvent retrieves the pending event (window._pendingEvent) and calls the js.Func on it.
// It returns true if an event was handled.
func handleEvent() bool {
// Retrieve the event from js
cb := jsGo.Get("_pendingEvent")
if cb.IsNull() {
return false
}
jsGo.Set("_pendingEvent", Null())
id := uint32(cb.Get("id").Int())
if id == 0 { // zero indicates deadlock
select {}
}
// Retrieve the associated js.Func
funcsMu.Lock()
f, ok := funcs[id]
funcsMu.Unlock()
if !ok {
Global().Get("console").Call("error", "call to released function")
return true
}
// Call the js.Func with arguments
this := cb.Get("this")
argsObj := cb.Get("args")
args := make([]Value, argsObj.Length())
for i := range args {
args[i] = argsObj.Index(i)
}
result := f(this, args)
// Return the result to js
cb.Set("result", result)
return true
}