mirror of git://gcc.gnu.org/git/gcc.git
123 lines
3.5 KiB
Go
123 lines
3.5 KiB
Go
// 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()
|