// 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 // +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() var buf [20]byte sid := string(itoa(buf[:], uint64(id))) wrap := functionConstructor.New(ValueOf(` const event = { id:` + sid + `, this: this, args: arguments }; Module._llgo_invoke(event); return event.result; `)) return Func{ id: id, Value: wrap, } } func itoa(buf []byte, val uint64) []byte { i := len(buf) - 1 for val >= 10 { buf[i] = byte(val%10 + '0') i-- val /= 10 } buf[i] = byte(val + '0') return buf[i:] } // 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() } //export llgo_export_invoke func llgo_export_invoke(cb Value) bool { id := uint32(cb.Get("id").Int()) 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 }