runtime, sycall/js: add support for callbacks from JavaScript

This commit adds support for JavaScript callbacks back into
WebAssembly. This is experimental API, just like the rest of the
syscall/js package. The time package now also uses this mechanism
to properly support timers without resorting to a busy loop.

JavaScript code can call into the same entry point multiple times.
The new RUN register is used to keep track of the program's
run state. Possible values are: starting, running, paused and exited.
If no goroutine is ready any more, the scheduler can put the
program into the "paused" state and the WebAssembly code will
stop running. When a callback occurs, the JavaScript code puts
the callback data into a queue and then calls into WebAssembly
to allow the Go code to continue running.

Updates #18892
Updates #25506

Change-Id: Ib8701cfa0536d10d69bd541c85b0e2a754eb54fb
Reviewed-on: https://go-review.googlesource.com/114197
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Richard Musiol 2018-05-20 00:56:36 +02:00 committed by Austin Clements
parent 5fdacfa89f
commit e083dc6307
18 changed files with 482 additions and 49 deletions

View file

@ -7,6 +7,7 @@
package js_test
import (
"fmt"
"syscall/js"
"testing"
)
@ -144,3 +145,52 @@ func TestNew(t *testing.T) {
t.Errorf("got %#v, want %#v", got, 42)
}
}
func TestCallback(t *testing.T) {
c := make(chan struct{})
cb := js.NewCallback(func(args []js.Value) {
if got := args[0].Int(); got != 42 {
t.Errorf("got %#v, want %#v", got, 42)
}
c <- struct{}{}
})
defer cb.Close()
js.Global.Call("setTimeout", cb, 0, 42)
<-c
}
func TestEventCallback(t *testing.T) {
for _, name := range []string{"preventDefault", "stopPropagation", "stopImmediatePropagation"} {
c := make(chan struct{})
var flags js.EventCallbackFlag
switch name {
case "preventDefault":
flags = js.PreventDefault
case "stopPropagation":
flags = js.StopPropagation
case "stopImmediatePropagation":
flags = js.StopImmediatePropagation
}
cb := js.NewEventCallback(flags, func(event js.Value) {
c <- struct{}{}
})
defer cb.Close()
event := js.Global.Call("eval", fmt.Sprintf("({ called: false, %s: function() { this.called = true; } })", name))
js.ValueOf(cb).Invoke(event)
if !event.Get("called").Bool() {
t.Errorf("%s not called", name)
}
<-c
}
}
func ExampleNewCallback() {
var cb js.Callback
cb = js.NewCallback(func(args []js.Value) {
fmt.Println("button clicked")
cb.Close() // close the callback if the button will not be clicked again
})
js.Global.Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
}