aboutsummaryrefslogtreecommitdiff
path: root/src/syscall/js/callback.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/syscall/js/callback.go')
-rw-r--r--src/syscall/js/callback.go145
1 files changed, 145 insertions, 0 deletions
diff --git a/src/syscall/js/callback.go b/src/syscall/js/callback.go
new file mode 100644
index 0000000000..2c693240fa
--- /dev/null
+++ b/src/syscall/js/callback.go
@@ -0,0 +1,145 @@
+// 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()
+
+var makeCallbackHelper = Global.Call("eval", `
+ (function(id, pendingCallbacks, resolveCallbackPromise) {
+ return function() {
+ pendingCallbacks.push({ id: id, args: arguments });
+ resolveCallbackPromise();
+ };
+ })
+`)
+
+var makeEventCallbackHelper = Global.Call("eval", `
+ (function(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
+ return function(event) {
+ if (preventDefault) {
+ event.preventDefault();
+ }
+ if (stopPropagation) {
+ event.stopPropagation();
+ }
+ if (stopImmediatePropagation) {
+ event.stopImmediatePropagation();
+ }
+ fn(event);
+ };
+ })
+`)
+
+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.
+// A Callback can be passed to functions of this package that accept interface{},
+// for example Value.Set and Value.Call.
+type Callback struct {
+ id uint32
+ enqueueFn Value // the JavaScript function that queues the callback for execution
+}
+
+// NewCallback returns a wrapped callback function. It can be passed to functions of this package
+// that accept interface{}, for example Value.Set and Value.Call.
+//
+// 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.Close 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{
+ id: id,
+ enqueueFn: makeCallbackHelper.Invoke(id, pendingCallbacks, resolveCallbackPromise),
+ }
+}
+
+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{
+ id: c.id,
+ enqueueFn: makeEventCallbackHelper.Invoke(
+ flags&PreventDefault != 0,
+ flags&StopPropagation != 0,
+ flags&StopImmediatePropagation != 0,
+ c,
+ ),
+ }
+}
+
+func (c Callback) Close() {
+ callbacksMu.Lock()
+ delete(callbacks, c.id)
+ callbacksMu.Unlock()
+}
+
+var callbackLoopOnce sync.Once
+
+func callbackLoop() {
+ for {
+ 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()