Commit 7d9649bf authored by Richard Musiol's avatar Richard Musiol Committed by Brad Fitzpatrick

syscall/js: rename js.Callback to js.Func

The name "Callback" does not fit to all use cases of js.Callback.
This commit changes its name to Func. Accordingly NewCallback
gets renamed to FuncOf, which matches ValueOf and TypedArrayOf.

The package syscall/js is currently exempt from Go's compatibility
promise and js.Callback is already affected by a breaking change in
this release cycle. See #28711 for details.

Fixes #28711

Change-Id: I2c380970c3822bed6a3893909672c15d0cbe9da3
Reviewed-on: https://go-review.googlesource.com/c/153559
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent f95578cd
...@@ -87,8 +87,8 @@ ...@@ -87,8 +87,8 @@
this._exitPromise = new Promise((resolve) => { this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve; this._resolveExitPromise = resolve;
}); });
this._pendingCallback = null; this._pendingEvent = null;
this._callbackTimeouts = new Map(); this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1; this._nextCallbackTimeoutID = 1;
const mem = () => { const mem = () => {
...@@ -204,7 +204,7 @@ ...@@ -204,7 +204,7 @@
this.importObject = { this.importObject = {
go: { go: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may trigger a synchronous callback to Go. This makes Go code get executed in the middle of the imported // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function. // This changes the SP, thus we have to update the SP used by the imported function.
...@@ -238,22 +238,22 @@ ...@@ -238,22 +238,22 @@
mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
}, },
// func scheduleCallback(delay int64) int32 // func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleCallback": (sp) => { "runtime.scheduleTimeoutEvent": (sp) => {
const id = this._nextCallbackTimeoutID; const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++; this._nextCallbackTimeoutID++;
this._callbackTimeouts.set(id, setTimeout( this._scheduledTimeouts.set(id, setTimeout(
() => { this._resume(); }, () => { this._resume(); },
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
)); ));
mem().setInt32(sp + 16, id, true); mem().setInt32(sp + 16, id, true);
}, },
// func clearScheduledCallback(id int32) // func clearTimeoutEvent(id int32)
"runtime.clearScheduledCallback": (sp) => { "runtime.clearTimeoutEvent": (sp) => {
const id = mem().getInt32(sp + 8, true); const id = mem().getInt32(sp + 8, true);
clearTimeout(this._callbackTimeouts.get(id)); clearTimeout(this._scheduledTimeouts.get(id));
this._callbackTimeouts.delete(id); this._scheduledTimeouts.delete(id);
}, },
// func getRandomData(r []byte) // func getRandomData(r []byte)
...@@ -420,7 +420,7 @@ ...@@ -420,7 +420,7 @@
_resume() { _resume() {
if (this.exited) { if (this.exited) {
throw new Error("bad callback: Go program has already exited"); throw new Error("Go program has already exited");
} }
this._inst.exports.resume(); this._inst.exports.resume();
if (this.exited) { if (this.exited) {
...@@ -428,13 +428,13 @@ ...@@ -428,13 +428,13 @@
} }
} }
_makeCallbackHelper(id) { _makeFuncWrapper(id) {
const go = this; const go = this;
return function () { return function () {
const cb = { id: id, this: this, args: arguments }; const event = { id: id, this: this, args: arguments };
go._pendingCallback = cb; go._pendingEvent = event;
go._resume(); go._resume();
return cb.result; return event.result;
}; };
} }
} }
...@@ -450,10 +450,10 @@ ...@@ -450,10 +450,10 @@
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
go.exit = process.exit; go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
process.on("exit", (code) => { // Node.js exits if no callback is pending process.on("exit", (code) => { // Node.js exits if no event handler is pending
if (code === 0 && !go.exited) { if (code === 0 && !go.exited) {
// deadlock, make Go print error and stack traces // deadlock, make Go print error and stack traces
go._pendingCallback = { id: 0 }; go._pendingEvent = { id: 0 };
go._resume(); go._resume();
} }
}); });
......
...@@ -12,7 +12,7 @@ runtime/asm_wasm.s: [wasm] rt0_go: use of 8(SP) points beyond argument frame ...@@ -12,7 +12,7 @@ runtime/asm_wasm.s: [wasm] rt0_go: use of 8(SP) points beyond argument frame
// Calling WebAssembly import. No write from Go assembly. // Calling WebAssembly import. No write from Go assembly.
runtime/sys_wasm.s: [wasm] nanotime: RET without writing to 8-byte ret+0(FP) runtime/sys_wasm.s: [wasm] nanotime: RET without writing to 8-byte ret+0(FP)
runtime/sys_wasm.s: [wasm] scheduleCallback: RET without writing to 4-byte ret+8(FP) runtime/sys_wasm.s: [wasm] scheduleTimeoutEvent: RET without writing to 4-byte ret+8(FP)
syscall/js/js_js.s: [wasm] stringVal: RET without writing to 8-byte ret+16(FP) syscall/js/js_js.s: [wasm] stringVal: RET without writing to 8-byte ret+16(FP)
syscall/js/js_js.s: [wasm] valueGet: RET without writing to 8-byte ret+24(FP) syscall/js/js_js.s: [wasm] valueGet: RET without writing to 8-byte ret+24(FP)
syscall/js/js_js.s: [wasm] valueIndex: RET without writing to 8-byte ret+16(FP) syscall/js/js_js.s: [wasm] valueIndex: RET without writing to 8-byte ret+16(FP)
......
...@@ -93,7 +93,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) { ...@@ -93,7 +93,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
respCh = make(chan *Response, 1) respCh = make(chan *Response, 1)
errCh = make(chan error, 1) errCh = make(chan error, 1)
) )
success := js.NewCallback(func(this js.Value, args []js.Value) interface{} { success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
result := args[0] result := args[0]
header := Header{} header := Header{}
// https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries // https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
...@@ -141,7 +141,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) { ...@@ -141,7 +141,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
return nil return nil
}) })
defer success.Release() defer success.Release()
failure := js.NewCallback(func(this js.Value, args []js.Value) interface{} { failure := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
err := fmt.Errorf("net/http: fetch() failed: %s", args[0].String()) err := fmt.Errorf("net/http: fetch() failed: %s", args[0].String())
select { select {
case errCh <- err: case errCh <- err:
...@@ -190,7 +190,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) { ...@@ -190,7 +190,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) {
bCh = make(chan []byte, 1) bCh = make(chan []byte, 1)
errCh = make(chan error, 1) errCh = make(chan error, 1)
) )
success := js.NewCallback(func(this js.Value, args []js.Value) interface{} { success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
result := args[0] result := args[0]
if result.Get("done").Bool() { if result.Get("done").Bool() {
errCh <- io.EOF errCh <- io.EOF
...@@ -204,7 +204,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) { ...@@ -204,7 +204,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) {
return nil return nil
}) })
defer success.Release() defer success.Release()
failure := js.NewCallback(func(this js.Value, args []js.Value) interface{} { failure := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// Assumes it's a TypeError. See // Assumes it's a TypeError. See
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
// for more information on this type. See // for more information on this type. See
...@@ -258,7 +258,7 @@ func (r *arrayReader) Read(p []byte) (n int, err error) { ...@@ -258,7 +258,7 @@ func (r *arrayReader) Read(p []byte) (n int, err error) {
bCh = make(chan []byte, 1) bCh = make(chan []byte, 1)
errCh = make(chan error, 1) errCh = make(chan error, 1)
) )
success := js.NewCallback(func(this js.Value, args []js.Value) interface{} { success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// Wrap the input ArrayBuffer with a Uint8Array // Wrap the input ArrayBuffer with a Uint8Array
uint8arrayWrapper := js.Global().Get("Uint8Array").New(args[0]) uint8arrayWrapper := js.Global().Get("Uint8Array").New(args[0])
value := make([]byte, uint8arrayWrapper.Get("byteLength").Int()) value := make([]byte, uint8arrayWrapper.Get("byteLength").Int())
...@@ -269,7 +269,7 @@ func (r *arrayReader) Read(p []byte) (n int, err error) { ...@@ -269,7 +269,7 @@ func (r *arrayReader) Read(p []byte) (n int, err error) {
return nil return nil
}) })
defer success.Release() defer success.Release()
failure := js.NewCallback(func(this js.Value, args []js.Value) interface{} { failure := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// Assumes it's a TypeError. See // Assumes it's a TypeError. See
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
// for more information on this type. // for more information on this type.
......
...@@ -92,7 +92,7 @@ func notetsleepg(n *note, ns int64) bool { ...@@ -92,7 +92,7 @@ func notetsleepg(n *note, ns int64) bool {
delay = 1<<31 - 1 // cap to max int32 delay = 1<<31 - 1 // cap to max int32
} }
id := scheduleCallback(delay) id := scheduleTimeoutEvent(delay)
mp := acquirem() mp := acquirem()
notes[n] = gp notes[n] = gp
notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline} notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
...@@ -100,7 +100,7 @@ func notetsleepg(n *note, ns int64) bool { ...@@ -100,7 +100,7 @@ func notetsleepg(n *note, ns int64) bool {
gopark(nil, nil, waitReasonSleep, traceEvNone, 1) gopark(nil, nil, waitReasonSleep, traceEvNone, 1)
clearScheduledCallback(id) // note might have woken early, clear timeout clearTimeoutEvent(id) // note might have woken early, clear timeout
mp = acquirem() mp = acquirem()
delete(notes, n) delete(notes, n)
delete(notesWithTimeout, n) delete(notesWithTimeout, n)
...@@ -134,17 +134,17 @@ func checkTimeouts() { ...@@ -134,17 +134,17 @@ func checkTimeouts() {
} }
} }
var returnedCallback *g var returnedEventHandler *g
func init() { func init() {
// At the toplevel we need an extra goroutine that handles asynchronous callbacks. // At the toplevel we need an extra goroutine that handles asynchronous events.
initg := getg() initg := getg()
go func() { go func() {
returnedCallback = getg() returnedEventHandler = getg()
goready(initg, 1) goready(initg, 1)
gopark(nil, nil, waitReasonZero, traceEvNone, 1) gopark(nil, nil, waitReasonZero, traceEvNone, 1)
returnedCallback = nil returnedEventHandler = nil
pause(getcallersp() - 16) pause(getcallersp() - 16)
}() }()
...@@ -152,44 +152,43 @@ func init() { ...@@ -152,44 +152,43 @@ func init() {
} }
// beforeIdle gets called by the scheduler if no goroutine is awake. // beforeIdle gets called by the scheduler if no goroutine is awake.
// If a callback has returned, then we resume the callback handler which // We resume the event handler (if available) which will pause the execution.
// will pause the execution.
func beforeIdle() bool { func beforeIdle() bool {
if returnedCallback != nil { if returnedEventHandler != nil {
goready(returnedCallback, 1) goready(returnedEventHandler, 1)
return true return true
} }
return false return false
} }
// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until a callback is triggered. // pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
func pause(newsp uintptr) func pause(newsp uintptr)
// scheduleCallback tells the WebAssembly environment to trigger a callback after ms milliseconds. // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
// It returns a timer id that can be used with clearScheduledCallback. // It returns a timer id that can be used with clearTimeoutEvent.
func scheduleCallback(ms int64) int32 func scheduleTimeoutEvent(ms int64) int32
// clearScheduledCallback clears a callback scheduled by scheduleCallback. // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
func clearScheduledCallback(id int32) func clearTimeoutEvent(id int32)
func handleCallback() { func handleEvent() {
prevReturnedCallback := returnedCallback prevReturnedEventHandler := returnedEventHandler
returnedCallback = nil returnedEventHandler = nil
checkTimeouts() checkTimeouts()
callbackHandler() eventHandler()
returnedCallback = getg() returnedEventHandler = getg()
gopark(nil, nil, waitReasonZero, traceEvNone, 1) gopark(nil, nil, waitReasonZero, traceEvNone, 1)
returnedCallback = prevReturnedCallback returnedEventHandler = prevReturnedEventHandler
pause(getcallersp() - 16) pause(getcallersp() - 16)
} }
var callbackHandler func() var eventHandler func()
//go:linkname setCallbackHandler syscall/js.setCallbackHandler //go:linkname setEventHandler syscall/js.setEventHandler
func setCallbackHandler(fn func()) { func setEventHandler(fn func()) {
callbackHandler = fn eventHandler = fn
} }
...@@ -15,7 +15,7 @@ TEXT _rt0_wasm_js(SB),NOSPLIT,$0 ...@@ -15,7 +15,7 @@ TEXT _rt0_wasm_js(SB),NOSPLIT,$0
Drop Drop
// wasm_export_run gets called from JavaScript. It initializes the Go runtime and executes Go code until it needs // wasm_export_run gets called from JavaScript. It initializes the Go runtime and executes Go code until it needs
// to wait for a callback. It does NOT follow the Go ABI. It has two WebAssembly parameters: // to wait for an event. It does NOT follow the Go ABI. It has two WebAssembly parameters:
// R0: argc (i32) // R0: argc (i32)
// R1: argv (i32) // R1: argv (i32)
TEXT wasm_export_run(SB),NOSPLIT,$0 TEXT wasm_export_run(SB),NOSPLIT,$0
...@@ -44,9 +44,9 @@ TEXT wasm_export_run(SB),NOSPLIT,$0 ...@@ -44,9 +44,9 @@ TEXT wasm_export_run(SB),NOSPLIT,$0
Return Return
// wasm_export_resume gets called from JavaScript. It resumes the execution of Go code until it needs to wait for // wasm_export_resume gets called from JavaScript. It resumes the execution of Go code until it needs to wait for
// a callback. // an event.
TEXT wasm_export_resume(SB),NOSPLIT,$0 TEXT wasm_export_resume(SB),NOSPLIT,$0
I32Const $runtime·handleCallback(SB) I32Const $runtime·handleEvent(SB)
I32Const $16 I32Const $16
I32ShrU I32ShrU
Set PC_F Set PC_F
......
...@@ -187,11 +187,11 @@ TEXT ·walltime(SB), NOSPLIT, $0 ...@@ -187,11 +187,11 @@ TEXT ·walltime(SB), NOSPLIT, $0
CallImport CallImport
RET RET
TEXT ·scheduleCallback(SB), NOSPLIT, $0 TEXT ·scheduleTimeoutEvent(SB), NOSPLIT, $0
CallImport CallImport
RET RET
TEXT ·clearScheduledCallback(SB), NOSPLIT, $0 TEXT ·clearTimeoutEvent(SB), NOSPLIT, $0
CallImport CallImport
RET RET
......
...@@ -474,7 +474,7 @@ func fsCall(name string, args ...interface{}) (js.Value, error) { ...@@ -474,7 +474,7 @@ func fsCall(name string, args ...interface{}) (js.Value, error) {
} }
c := make(chan callResult, 1) c := make(chan callResult, 1)
jsFS.Call(name, append(args, js.NewCallback(func(this js.Value, args []js.Value) interface{} { jsFS.Call(name, append(args, js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var res callResult var res callResult
if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments
......
...@@ -9,75 +9,75 @@ package js ...@@ -9,75 +9,75 @@ package js
import "sync" import "sync"
var ( var (
callbacksMu sync.Mutex funcsMu sync.Mutex
callbacks = make(map[uint32]func(Value, []Value) interface{}) funcs = make(map[uint32]func(Value, []Value) interface{})
nextCallbackID uint32 = 1 nextFuncID uint32 = 1
) )
var _ Wrapper = Callback{} // Callback must implement Wrapper var _ Wrapper = Func{} // Func must implement Wrapper
// Callback is a Go function that got wrapped for use as a JavaScript callback. // Func is a wrapped Go function to be called by JavaScript.
type Callback struct { type Func struct {
Value // the JavaScript function that invokes the Go function Value // the JavaScript function that invokes the Go function
id uint32 id uint32
} }
// NewCallback returns a wrapped callback function. // FuncOf returns a wrapped function.
// //
// Invoking the callback in JavaScript will synchronously call the Go function fn with the value of JavaScript's // 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. // "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. // The return value of the invocation is the result of the Go function mapped back to JavaScript according to ValueOf.
// //
// A callback triggered during a call from Go to JavaScript gets executed on the same goroutine. // A wrapped function triggered during a call from Go to JavaScript gets executed on the same goroutine.
// A callback triggered by JavaScript's event loop gets executed on an extra goroutine. // A wrapped function triggered by JavaScript's event loop gets executed on an extra goroutine.
// Blocking operations in the callback will block the event loop. // Blocking operations in the wrapped function will block the event loop.
// As a consequence, if one callback blocks, other callbacks will not be processed. // As a consequence, if one wrapped function blocks, other wrapped funcs will not be processed.
// A blocking callback should therefore explicitly start a new goroutine. // A blocking function 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.Release must be called to free up resources when the function will not be used any more.
func NewCallback(fn func(this Value, args []Value) interface{}) Callback { func FuncOf(fn func(this Value, args []Value) interface{}) Func {
callbacksMu.Lock() funcsMu.Lock()
id := nextCallbackID id := nextFuncID
nextCallbackID++ nextFuncID++
callbacks[id] = fn funcs[id] = fn
callbacksMu.Unlock() funcsMu.Unlock()
return Callback{ return Func{
id: id, id: id,
Value: jsGo.Call("_makeCallbackHelper", id), Value: jsGo.Call("_makeFuncWrapper", id),
} }
} }
// Release frees up resources allocated for the callback. // Release frees up resources allocated for the function.
// The callback must not be invoked after calling Release. // The function must not be invoked after calling Release.
func (c Callback) Release() { func (c Func) Release() {
callbacksMu.Lock() funcsMu.Lock()
delete(callbacks, c.id) delete(funcs, c.id)
callbacksMu.Unlock() funcsMu.Unlock()
} }
// setCallbackHandler is defined in the runtime package. // setEventHandler is defined in the runtime package.
func setCallbackHandler(fn func()) func setEventHandler(fn func())
func init() { func init() {
setCallbackHandler(handleCallback) setEventHandler(handleEvent)
} }
func handleCallback() { func handleEvent() {
cb := jsGo.Get("_pendingCallback") cb := jsGo.Get("_pendingEvent")
if cb == Null() { if cb == Null() {
return return
} }
jsGo.Set("_pendingCallback", Null()) jsGo.Set("_pendingEvent", Null())
id := uint32(cb.Get("id").Int()) id := uint32(cb.Get("id").Int())
if id == 0 { // zero indicates deadlock if id == 0 { // zero indicates deadlock
select {} select {}
} }
callbacksMu.Lock() funcsMu.Lock()
f, ok := callbacks[id] f, ok := funcs[id]
callbacksMu.Unlock() funcsMu.Unlock()
if !ok { if !ok {
Global().Get("console").Call("error", "call to closed callback") Global().Get("console").Call("error", "call to released function")
return return
} }
......
...@@ -107,7 +107,7 @@ func Global() Value { ...@@ -107,7 +107,7 @@ func Global() Value {
// | ---------------------- | ---------------------- | // | ---------------------- | ---------------------- |
// | js.Value | [its value] | // | js.Value | [its value] |
// | js.TypedArray | typed array | // | js.TypedArray | typed array |
// | js.Callback | function | // | js.Func | function |
// | nil | null | // | nil | null |
// | bool | boolean | // | bool | boolean |
// | integers and floats | number | // | integers and floats | number |
......
...@@ -300,9 +300,9 @@ func TestZeroValue(t *testing.T) { ...@@ -300,9 +300,9 @@ func TestZeroValue(t *testing.T) {
} }
} }
func TestCallback(t *testing.T) { func TestFuncOf(t *testing.T) {
c := make(chan struct{}) c := make(chan struct{})
cb := js.NewCallback(func(this js.Value, args []js.Value) interface{} { cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if got := args[0].Int(); got != 42 { if got := args[0].Int(); got != 42 {
t.Errorf("got %#v, want %#v", got, 42) t.Errorf("got %#v, want %#v", got, 42)
} }
...@@ -314,10 +314,10 @@ func TestCallback(t *testing.T) { ...@@ -314,10 +314,10 @@ func TestCallback(t *testing.T) {
<-c <-c
} }
func TestInvokeCallback(t *testing.T) { func TestInvokeFunction(t *testing.T) {
called := false called := false
cb := js.NewCallback(func(this js.Value, args []js.Value) interface{} { cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
cb2 := js.NewCallback(func(this js.Value, args []js.Value) interface{} { cb2 := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
called = true called = true
return 42 return 42
}) })
...@@ -329,15 +329,15 @@ func TestInvokeCallback(t *testing.T) { ...@@ -329,15 +329,15 @@ func TestInvokeCallback(t *testing.T) {
t.Errorf("got %#v, want %#v", got, 42) t.Errorf("got %#v, want %#v", got, 42)
} }
if !called { if !called {
t.Error("callback not called") t.Error("function not called")
} }
} }
func ExampleNewCallback() { func ExampleFuncOf() {
var cb js.Callback var cb js.Func
cb = js.NewCallback(func(this js.Value, args []js.Value) interface{} { cb = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fmt.Println("button clicked") fmt.Println("button clicked")
cb.Release() // release the callback if the button will not be clicked again cb.Release() // release the function if the button will not be clicked again
return nil return nil
}) })
js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb) js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment