| // 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. | |
| // +build js,wasm | |
| package js | |
| import "sync" | |
| var ( | |
| funcsMu sync.Mutex | |
| funcs = make(map[uint32]func(Value, []Value) interface{}) | |
| nextFuncID uint32 = 1 | |
| ) | |
| var _ Wrapper = Func{} // Func must implement Wrapper | |
| // 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 wrapped function. | |
| // | |
| // Invoking the JavaScript function will synchronously call the Go function fn 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. | |
| // | |
| // A wrapped function triggered during a call from Go to JavaScript gets executed on the same goroutine. | |
| // A wrapped function triggered by JavaScript's event loop gets executed on an extra goroutine. | |
| // Blocking operations in the wrapped function will block the event loop. | |
| // As a consequence, if one wrapped function blocks, other wrapped funcs will not be processed. | |
| // A blocking function should therefore explicitly start a new goroutine. | |
| // | |
| // Func.Release must be called to free up resources when the function will not be used any more. | |
| func FuncOf(fn func(this Value, args []Value) interface{}) Func { | |
| funcsMu.Lock() | |
| id := nextFuncID | |
| nextFuncID++ | |
| funcs[id] = fn | |
| funcsMu.Unlock() | |
| return Func{ | |
| id: id, | |
| Value: jsGo.Call("_makeFuncWrapper", id), | |
| } | |
| } | |
| // Release frees up resources allocated for the function. | |
| // The function must not be invoked after calling Release. | |
| func (c Func) Release() { | |
| funcsMu.Lock() | |
| delete(funcs, c.id) | |
| funcsMu.Unlock() | |
| } | |
| // setEventHandler is defined in the runtime package. | |
| func setEventHandler(fn func()) | |
| func init() { | |
| setEventHandler(handleEvent) | |
| } | |
| func handleEvent() { | |
| cb := jsGo.Get("_pendingEvent") | |
| if cb == Null() { | |
| return | |
| } | |
| jsGo.Set("_pendingEvent", Null()) | |
| id := uint32(cb.Get("id").Int()) | |
| if id == 0 { // zero indicates deadlock | |
| select {} | |
| } | |
| funcsMu.Lock() | |
| f, ok := funcs[id] | |
| funcsMu.Unlock() | |
| if !ok { | |
| Global().Get("console").Call("error", "call to released function") | |
| return | |
| } | |
| 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) | |
| cb.Set("result", result) | |
| } |