1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5//go:build js && wasm 6 7package js 8 9import "sync" 10 11var ( 12 funcsMu sync.Mutex 13 funcs = make(map[uint32]func(Value, []Value) any) 14 nextFuncID uint32 = 1 15) 16 17// Func is a wrapped Go function to be called by JavaScript. 18type Func struct { 19 Value // the JavaScript function that invokes the Go function 20 id uint32 21} 22 23// FuncOf returns a function to be used by JavaScript. 24// 25// The Go function fn is called with the value of JavaScript's "this" keyword and the 26// arguments of the invocation. The return value of the invocation is 27// the result of the Go function mapped back to JavaScript according to ValueOf. 28// 29// Invoking the wrapped Go function from JavaScript will 30// pause the event loop and spawn a new goroutine. 31// Other wrapped functions which are triggered during a call from Go to JavaScript 32// get executed on the same goroutine. 33// 34// As a consequence, if one wrapped function blocks, JavaScript's event loop 35// is blocked until that function returns. Hence, calling any async JavaScript 36// API, which requires the event loop, like fetch (http.Client), will cause an 37// immediate deadlock. Therefore a blocking function should explicitly start a 38// new goroutine. 39// 40// Func.Release must be called to free up resources when the function will not be invoked any more. 41func FuncOf(fn func(this Value, args []Value) any) Func { 42 funcsMu.Lock() 43 id := nextFuncID 44 nextFuncID++ 45 funcs[id] = fn 46 funcsMu.Unlock() 47 return Func{ 48 id: id, 49 Value: jsGo.Call("_makeFuncWrapper", id), 50 } 51} 52 53// Release frees up resources allocated for the function. 54// The function must not be invoked after calling Release. 55// It is allowed to call Release while the function is still running. 56func (c Func) Release() { 57 funcsMu.Lock() 58 delete(funcs, c.id) 59 funcsMu.Unlock() 60} 61 62// setEventHandler is defined in the runtime package. 63func setEventHandler(fn func() bool) 64 65func init() { 66 setEventHandler(handleEvent) 67} 68 69// handleEvent retrieves the pending event (window._pendingEvent) and calls the js.Func on it. 70// It returns true if an event was handled. 71func handleEvent() bool { 72 // Retrieve the event from js 73 cb := jsGo.Get("_pendingEvent") 74 if cb.IsNull() { 75 return false 76 } 77 jsGo.Set("_pendingEvent", Null()) 78 79 id := uint32(cb.Get("id").Int()) 80 if id == 0 { // zero indicates deadlock 81 select {} 82 } 83 84 // Retrieve the associated js.Func 85 funcsMu.Lock() 86 f, ok := funcs[id] 87 funcsMu.Unlock() 88 if !ok { 89 Global().Get("console").Call("error", "call to released function") 90 return true 91 } 92 93 // Call the js.Func with arguments 94 this := cb.Get("this") 95 argsObj := cb.Get("args") 96 args := make([]Value, argsObj.Length()) 97 for i := range args { 98 args[i] = argsObj.Index(i) 99 } 100 result := f(this, args) 101 102 // Return the result to js 103 cb.Set("result", result) 104 return true 105} 106