]> Cypherpunks.ru repositories - gostls13.git/commitdiff
syscall/js: rename js.Callback to js.Func
authorRichard Musiol <mail@richard-musiol.de>
Tue, 11 Dec 2018 13:23:17 +0000 (14:23 +0100)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 13 Dec 2018 18:34:01 +0000 (18:34 +0000)
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: Brad Fitzpatrick <bradfitz@golang.org>
misc/wasm/wasm_exec.js
src/cmd/vet/all/whitelist/wasm.txt
src/net/http/roundtrip_js.go
src/runtime/lock_js.go
src/runtime/rt0_js_wasm.s
src/runtime/sys_wasm.s
src/syscall/fs_js.go
src/syscall/js/callback.go [deleted file]
src/syscall/js/func.go [new file with mode: 0644]
src/syscall/js/js.go
src/syscall/js/js_test.go

index 743eaf70b2ced60142d4145f85019517dbeec516..165d567750506b58450e4b90735b71925c949355 100644 (file)
@@ -87,8 +87,8 @@
                        this._exitPromise = new Promise((resolve) => {
                                this._resolveExitPromise = resolve;
                        });
-                       this._pendingCallback = null;
-                       this._callbackTimeouts = new Map();
+                       this._pendingEvent = null;
+                       this._scheduledTimeouts = new Map();
                        this._nextCallbackTimeoutID = 1;
 
                        const mem = () => {
                        this.importObject = {
                                go: {
                                        // 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).
                                        // This changes the SP, thus we have to update the SP used by the imported function.
 
                                                mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
                                        },
 
-                                       // func scheduleCallback(delay int64) int32
-                                       "runtime.scheduleCallback": (sp) => {
+                                       // func scheduleTimeoutEvent(delay int64) int32
+                                       "runtime.scheduleTimeoutEvent": (sp) => {
                                                const id = this._nextCallbackTimeoutID;
                                                this._nextCallbackTimeoutID++;
-                                               this._callbackTimeouts.set(id, setTimeout(
+                                               this._scheduledTimeouts.set(id, setTimeout(
                                                        () => { this._resume(); },
                                                        getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
                                                ));
                                                mem().setInt32(sp + 16, id, true);
                                        },
 
-                                       // func clearScheduledCallback(id int32)
-                                       "runtime.clearScheduledCallback": (sp) => {
+                                       // func clearTimeoutEvent(id int32)
+                                       "runtime.clearTimeoutEvent": (sp) => {
                                                const id = mem().getInt32(sp + 8, true);
-                                               clearTimeout(this._callbackTimeouts.get(id));
-                                               this._callbackTimeouts.delete(id);
+                                               clearTimeout(this._scheduledTimeouts.get(id));
+                                               this._scheduledTimeouts.delete(id);
                                        },
 
                                        // func getRandomData(r []byte)
 
                _resume() {
                        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();
                        if (this.exited) {
                        }
                }
 
-               _makeCallbackHelper(id) {
+               _makeFuncWrapper(id) {
                        const go = this;
                        return function () {
-                               const cb = { id: id, this: this, args: arguments };
-                               go._pendingCallback = cb;
+                               const event = { id: id, this: this, args: arguments };
+                               go._pendingEvent = event;
                                go._resume();
-                               return cb.result;
+                               return event.result;
                        };
                }
        }
                go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
                go.exit = process.exit;
                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) {
                                        // deadlock, make Go print error and stack traces
-                                       go._pendingCallback = { id: 0 };
+                                       go._pendingEvent = { id: 0 };
                                        go._resume();
                                }
                        });
index a3f8c291bf9e2326aa4aa692e7c9d22be3a6a5af..45496ed3f6e0b2b9d75be9369912b7ee06e08965 100644 (file)
@@ -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.
 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] 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)
index 79598164452e8c92fcff446591be254a0ac47194..1e38b908d387e7e6b5d26d6e9724f6c6feac2496 100644 (file)
@@ -93,7 +93,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
                respCh = make(chan *Response, 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]
                header := Header{}
                // https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
@@ -141,7 +141,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
                return nil
        })
        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())
                select {
                case errCh <- err:
@@ -190,7 +190,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) {
                        bCh   = make(chan []byte, 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]
                        if result.Get("done").Bool() {
                                errCh <- io.EOF
@@ -204,7 +204,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) {
                        return nil
                })
                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
                        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
                        // for more information on this type. See
@@ -258,7 +258,7 @@ func (r *arrayReader) Read(p []byte) (n int, err error) {
                        bCh   = make(chan []byte, 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
                        uint8arrayWrapper := js.Global().Get("Uint8Array").New(args[0])
                        value := make([]byte, uint8arrayWrapper.Get("byteLength").Int())
@@ -269,7 +269,7 @@ func (r *arrayReader) Read(p []byte) (n int, err error) {
                        return nil
                })
                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
                        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
                        // for more information on this type.
index 98aed8796b5ee156cbebc860255915f8e0c153fe..b04ccdb107e4e3118e3769f59bb3ef733e467175 100644 (file)
@@ -92,7 +92,7 @@ func notetsleepg(n *note, ns int64) bool {
                        delay = 1<<31 - 1 // cap to max int32
                }
 
-               id := scheduleCallback(delay)
+               id := scheduleTimeoutEvent(delay)
                mp := acquirem()
                notes[n] = gp
                notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
@@ -100,7 +100,7 @@ func notetsleepg(n *note, ns int64) bool {
 
                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()
                delete(notes, n)
                delete(notesWithTimeout, n)
@@ -134,17 +134,17 @@ func checkTimeouts() {
        }
 }
 
-var returnedCallback *g
+var returnedEventHandler *g
 
 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()
        go func() {
-               returnedCallback = getg()
+               returnedEventHandler = getg()
                goready(initg, 1)
 
                gopark(nil, nil, waitReasonZero, traceEvNone, 1)
-               returnedCallback = nil
+               returnedEventHandler = nil
 
                pause(getcallersp() - 16)
        }()
@@ -152,44 +152,43 @@ func init() {
 }
 
 // beforeIdle gets called by the scheduler if no goroutine is awake.
-// If a callback has returned, then we resume the callback handler which
-// will pause the execution.
+// We resume the event handler (if available) which will pause the execution.
 func beforeIdle() bool {
-       if returnedCallback != nil {
-               goready(returnedCallback, 1)
+       if returnedEventHandler != nil {
+               goready(returnedEventHandler, 1)
                return true
        }
        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)
 
-// scheduleCallback tells the WebAssembly environment to trigger a callback after ms milliseconds.
-// It returns a timer id that can be used with clearScheduledCallback.
-func scheduleCallback(ms int64) int32
+// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
+// It returns a timer id that can be used with clearTimeoutEvent.
+func scheduleTimeoutEvent(ms int64) int32
 
-// clearScheduledCallback clears a callback scheduled by scheduleCallback.
-func clearScheduledCallback(id int32)
+// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
+func clearTimeoutEvent(id int32)
 
-func handleCallback() {
-       prevReturnedCallback := returnedCallback
-       returnedCallback = nil
+func handleEvent() {
+       prevReturnedEventHandler := returnedEventHandler
+       returnedEventHandler = nil
 
        checkTimeouts()
-       callbackHandler()
+       eventHandler()
 
-       returnedCallback = getg()
+       returnedEventHandler = getg()
        gopark(nil, nil, waitReasonZero, traceEvNone, 1)
 
-       returnedCallback = prevReturnedCallback
+       returnedEventHandler = prevReturnedEventHandler
 
        pause(getcallersp() - 16)
 }
 
-var callbackHandler func()
+var eventHandler func()
 
-//go:linkname setCallbackHandler syscall/js.setCallbackHandler
-func setCallbackHandler(fn func()) {
-       callbackHandler = fn
+//go:linkname setEventHandler syscall/js.setEventHandler
+func setEventHandler(fn func()) {
+       eventHandler = fn
 }
index 8b92fcbdb72c7b599d30a8a2f9413c43be6f3dbe..50adbe22256da7ba81b48ea160fa353bc51902f6 100644 (file)
@@ -15,7 +15,7 @@ TEXT _rt0_wasm_js(SB),NOSPLIT,$0
        Drop
 
 // 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)
 // R1: argv (i32)
 TEXT wasm_export_run(SB),NOSPLIT,$0
@@ -44,9 +44,9 @@ TEXT wasm_export_run(SB),NOSPLIT,$0
        Return
 
 // 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
-       I32Const $runtime·handleCallback(SB)
+       I32Const $runtime·handleEvent(SB)
        I32Const $16
        I32ShrU
        Set PC_F
index 3ca844a4c73c5e68665d7630ffd58160e3ba816e..6e28656340d79464b9d1fd8f4922b558f47a7906 100644 (file)
@@ -187,11 +187,11 @@ TEXT ·walltime(SB), NOSPLIT, $0
        CallImport
        RET
 
-TEXT ·scheduleCallback(SB), NOSPLIT, $0
+TEXT ·scheduleTimeoutEvent(SB), NOSPLIT, $0
        CallImport
        RET
 
-TEXT ·clearScheduledCallback(SB), NOSPLIT, $0
+TEXT ·clearTimeoutEvent(SB), NOSPLIT, $0
        CallImport
        RET
 
index 58d8216f2112898653f032d819a784e21a7b1fa2..fcc5f038b823c10d481a3e835976b2d8bbedc859 100644 (file)
@@ -474,7 +474,7 @@ func fsCall(name string, args ...interface{}) (js.Value, error) {
        }
 
        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
 
                if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments
diff --git a/src/syscall/js/callback.go b/src/syscall/js/callback.go
deleted file mode 100644 (file)
index 7f65409..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-// 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 (
-       callbacksMu    sync.Mutex
-       callbacks             = make(map[uint32]func(Value, []Value) interface{})
-       nextCallbackID uint32 = 1
-)
-
-var _ Wrapper = Callback{} // Callback must implement Wrapper
-
-// Callback is a Go function that got wrapped for use as a JavaScript callback.
-type Callback struct {
-       Value // the JavaScript function that invokes the Go function
-       id    uint32
-}
-
-// NewCallback returns a wrapped callback function.
-//
-// Invoking the callback in JavaScript will synchronously call the Go function fn with the value of JavaScript's
-// "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.
-//
-// A callback 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.
-// Blocking operations in the callback will block the event loop.
-// As a consequence, if one callback blocks, 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(this Value, args []Value) interface{}) Callback {
-       callbacksMu.Lock()
-       id := nextCallbackID
-       nextCallbackID++
-       callbacks[id] = fn
-       callbacksMu.Unlock()
-       return Callback{
-               id:    id,
-               Value: jsGo.Call("_makeCallbackHelper", 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()
-}
-
-// setCallbackHandler is defined in the runtime package.
-func setCallbackHandler(fn func())
-
-func init() {
-       setCallbackHandler(handleCallback)
-}
-
-func handleCallback() {
-       cb := jsGo.Get("_pendingCallback")
-       if cb == Null() {
-               return
-       }
-       jsGo.Set("_pendingCallback", Null())
-
-       id := uint32(cb.Get("id").Int())
-       if id == 0 { // zero indicates deadlock
-               select {}
-       }
-       callbacksMu.Lock()
-       f, ok := callbacks[id]
-       callbacksMu.Unlock()
-       if !ok {
-               Global().Get("console").Call("error", "call to closed callback")
-               return
-       }
-
-       this := cb.Get("this")
-       argsObj := cb.Get("args")
-       args := make([]Value, argsObj.Length())
-       for i := range args {
-               args[i] = argsObj.Index(i)
-       }
-       result := f(this, args)
-       cb.Set("result", result)
-}
diff --git a/src/syscall/js/func.go b/src/syscall/js/func.go
new file mode 100644 (file)
index 0000000..6b7f39b
--- /dev/null
@@ -0,0 +1,92 @@
+// 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 (
+       funcsMu    sync.Mutex
+       funcs             = make(map[uint32]func(Value, []Value) interface{})
+       nextFuncID uint32 = 1
+)
+
+var _ Wrapper = Func{} // Func must implement Wrapper
+
+// Func is a wrapped Go function to be called by JavaScript.
+type Func struct {
+       Value // the JavaScript function that invokes the Go function
+       id    uint32
+}
+
+// FuncOf returns a wrapped function.
+//
+// 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.
+// The return value of the invocation is the result of the Go function mapped back to JavaScript according to ValueOf.
+//
+// A wrapped function triggered during a call from Go to JavaScript gets executed on the same goroutine.
+// A wrapped function triggered by JavaScript's event loop gets executed on an extra goroutine.
+// Blocking operations in the wrapped function will block the event loop.
+// As a consequence, if one wrapped function blocks, other wrapped funcs will not be processed.
+// A blocking function should therefore explicitly start a new goroutine.
+//
+// Func.Release must be called to free up resources when the function will not be used any more.
+func FuncOf(fn func(this Value, args []Value) interface{}) Func {
+       funcsMu.Lock()
+       id := nextFuncID
+       nextFuncID++
+       funcs[id] = fn
+       funcsMu.Unlock()
+       return Func{
+               id:    id,
+               Value: jsGo.Call("_makeFuncWrapper", id),
+       }
+}
+
+// Release frees up resources allocated for the function.
+// The function must not be invoked after calling Release.
+func (c Func) Release() {
+       funcsMu.Lock()
+       delete(funcs, c.id)
+       funcsMu.Unlock()
+}
+
+// setEventHandler is defined in the runtime package.
+func setEventHandler(fn func())
+
+func init() {
+       setEventHandler(handleEvent)
+}
+
+func handleEvent() {
+       cb := jsGo.Get("_pendingEvent")
+       if cb == Null() {
+               return
+       }
+       jsGo.Set("_pendingEvent", Null())
+
+       id := uint32(cb.Get("id").Int())
+       if id == 0 { // zero indicates deadlock
+               select {}
+       }
+       funcsMu.Lock()
+       f, ok := funcs[id]
+       funcsMu.Unlock()
+       if !ok {
+               Global().Get("console").Call("error", "call to released function")
+               return
+       }
+
+       this := cb.Get("this")
+       argsObj := cb.Get("args")
+       args := make([]Value, argsObj.Length())
+       for i := range args {
+               args[i] = argsObj.Index(i)
+       }
+       result := f(this, args)
+       cb.Set("result", result)
+}
index 885723f87d690bd2977fa65deaee33540ce70e4e..0893db022d356255ea86f5fc1b11e02e77ed6b92 100644 (file)
@@ -107,7 +107,7 @@ func Global() Value {
 //  | ---------------------- | ---------------------- |
 //  | js.Value               | [its value]            |
 //  | js.TypedArray          | typed array            |
-//  | js.Callback            | function               |
+//  | js.Func                | function               |
 //  | nil                    | null                   |
 //  | bool                   | boolean                |
 //  | integers and floats    | number                 |
index b4d2e66faf76e25dd98d1d078f28879842bff412..c14d2cc24c90b8c5d08fe29db33218bc5e00975c 100644 (file)
@@ -300,9 +300,9 @@ func TestZeroValue(t *testing.T) {
        }
 }
 
-func TestCallback(t *testing.T) {
+func TestFuncOf(t *testing.T) {
        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 {
                        t.Errorf("got %#v, want %#v", got, 42)
                }
@@ -314,10 +314,10 @@ func TestCallback(t *testing.T) {
        <-c
 }
 
-func TestInvokeCallback(t *testing.T) {
+func TestInvokeFunction(t *testing.T) {
        called := false
-       cb := js.NewCallback(func(this js.Value, args []js.Value) interface{} {
-               cb2 := js.NewCallback(func(this js.Value, args []js.Value) interface{} {
+       cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+               cb2 := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
                        called = true
                        return 42
                })
@@ -329,15 +329,15 @@ func TestInvokeCallback(t *testing.T) {
                t.Errorf("got %#v, want %#v", got, 42)
        }
        if !called {
-               t.Error("callback not called")
+               t.Error("function not called")
        }
 }
 
-func ExampleNewCallback() {
-       var cb js.Callback
-       cb = js.NewCallback(func(this js.Value, args []js.Value) interface{} {
+func ExampleFuncOf() {
+       var cb js.Func
+       cb = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
                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
        })
        js.Global().Get("document").Call("getElementById", "myButton").Call("addEventListener", "click", cb)