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