// 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 ( pendingCallbacks = Global().Get("Array").New() makeCallbackHelper = Global().Get("Go").Get("_makeCallbackHelper") makeEventCallbackHelper = Global().Get("Go").Get("_makeEventCallbackHelper") ) var ( callbacksMu sync.Mutex callbacks = make(map[uint32]func([]Value)) nextCallbackID uint32 = 1 ) // Callback is a Go function that got wrapped for use as a JavaScript callback. type Callback struct { Value // the JavaScript function that queues the callback for execution id uint32 } // NewCallback returns a wrapped callback function. // // Invoking the callback in JavaScript will queue the Go function fn for execution. // This execution happens asynchronously on a special goroutine that handles all callbacks and preserves // the order in which the callbacks got called. // As a consequence, if one callback blocks this goroutine, other callbacks will not be processed. // A blocking callback should therefore explicitly start a new goroutine. // // Callback.Release must be called to free up resources when the callback will not be used any more. func NewCallback(fn func(args []Value)) Callback { callbackLoopOnce.Do(func() { go callbackLoop() }) callbacksMu.Lock() id := nextCallbackID nextCallbackID++ callbacks[id] = fn callbacksMu.Unlock() return Callback{ Value: makeCallbackHelper.Invoke(id, pendingCallbacks, jsGo), id: id, } } type EventCallbackFlag int const ( // PreventDefault can be used with NewEventCallback to call event.preventDefault synchronously. PreventDefault EventCallbackFlag = 1 << iota // StopPropagation can be used with NewEventCallback to call event.stopPropagation synchronously. StopPropagation // StopImmediatePropagation can be used with NewEventCallback to call event.stopImmediatePropagation synchronously. StopImmediatePropagation ) // NewEventCallback returns a wrapped callback function, just like NewCallback, but the callback expects to have // exactly one argument, the event. Depending on flags, it will synchronously call event.preventDefault, // event.stopPropagation and/or event.stopImmediatePropagation before queuing the Go function fn for execution. func NewEventCallback(flags EventCallbackFlag, fn func(event Value)) Callback { c := NewCallback(func(args []Value) { fn(args[0]) }) return Callback{ Value: makeEventCallbackHelper.Invoke( flags&PreventDefault != 0, flags&StopPropagation != 0, flags&StopImmediatePropagation != 0, c, ), id: c.id, } } // Release frees up resources allocated for the callback. // The callback must not be invoked after calling Release. func (c Callback) Release() { callbacksMu.Lock() delete(callbacks, c.id) callbacksMu.Unlock() } var callbackLoopOnce sync.Once func callbackLoop() { for !jsGo.Get("_callbackShutdown").Bool() { sleepUntilCallback() for { cb := pendingCallbacks.Call("shift") if cb == Undefined() { break } id := uint32(cb.Get("id").Int()) callbacksMu.Lock() f, ok := callbacks[id] callbacksMu.Unlock() if !ok { Global().Get("console").Call("error", "call to closed callback") continue } argsObj := cb.Get("args") args := make([]Value, argsObj.Length()) for i := range args { args[i] = argsObj.Index(i) } f(args) } } } // sleepUntilCallback is defined in the runtime package func sleepUntilCallback()