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