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