Skip to content

Commit

Permalink
Refactor Bind to enable consumer to call Return
Browse files Browse the repository at this point in the history
  • Loading branch information
sheepsteak committed Apr 22, 2023
1 parent c446e9a commit 8918907
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 71 deletions.
72 changes: 60 additions & 12 deletions examples/bind.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,49 @@
package main

import "github.com/webview/webview"
import (
"fmt"
"time"

"github.com/webview/webview"
)

const html = `<button id="increment">Tap me</button>
<div>You tapped <span id="count">0</span> time(s).</div>
<hr />
<label for="name">Enter your name:</label>
<input id="name" type="text" name="name" />
<button id="submitBtn" type="submit">Submit</button>
<div id="result"></div>
<script>
const [incrementElement, countElement] =
document.querySelectorAll("#increment, #count");
document.addEventListener("DOMContentLoaded", () => {
incrementElement.addEventListener("click", () => {
window.increment().then(result => {
countElement.textContent = result.count;
});
});
});
const [incrementElement, countElement, nameElement, submitElement, resultElement] =
document.querySelectorAll("#increment, #count, #name, #submitBtn, #result");
document.addEventListener("DOMContentLoaded", () => {
incrementElement.addEventListener("click", () => {
window.increment().then(result => {
countElement.textContent = result.count;
});
});
submitElement.addEventListener("click", () => {
submitBtn.disabled = true;
resultElement.textContent = "Loading...";
var name = nameElement.value;
window.sendName(name).then(result => {
resultElement.textContent = result.name;
submitBtn.disabled = false;
});
});
});
</script>`

type IncrementResult struct {
Count uint `json:"count"`
}

type SendNameResult struct {
Name string `json:"name"`
}

func main() {
var count uint = 0
w := webview.New(false)
Expand All @@ -28,9 +52,33 @@ func main() {
w.SetSize(480, 320, webview.HintNone)

// A binding that increments a value and immediately returns the new value.
w.Bind("increment", func() IncrementResult {
w.Bind("increment", func(id string) {
count++
return IncrementResult{Count: count}

fmt.Println("incrementing count to", count)

w.Return(
id,
webview.StatusSuccess,
IncrementResult{Count: count},
)
})

// A binding that returns a string after a delay.
w.Bind("sendName", func(id string, name string) {
go func() {
time.Sleep(2 * time.Second)

var nameString string

if len(name) > 0 {
nameString = name
} else {
nameString = "World"
}

w.Return(id, webview.StatusSuccess, SendNameResult{Name: nameString})
}()
})

w.SetHtml(html)
Expand Down
86 changes: 31 additions & 55 deletions webview.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ type WebView interface {
// Request string is a JSON array of all the arguments passed to the
// JavaScript function.
//
// f must be a function
// f must return either value and error or just error
// f must be a function that accepts an id string as the first argument, and
// then any other arguments that you want to pass from the JavaScript function.
Bind(name string, f interface{}) error

// Return sends a response to the JavaScript callback.
Expand All @@ -134,7 +134,7 @@ var (
m sync.Mutex
index uintptr
dispatch = map[uintptr]func(){}
bindings = map[uintptr]func(id, req string) (interface{}, error){}
bindings = map[uintptr]func(id, req string) error{}
)

func boolToInt(b bool) C.int {
Expand Down Expand Up @@ -236,21 +236,16 @@ func _webviewBindingGoCallback(w C.webview_t, id *C.char, req *C.char, index uin
m.Lock()
f := bindings[uintptr(index)]
m.Unlock()
jsString := func(v interface{}) string { b, _ := json.Marshal(v); return string(b) }
status, result := 0, ""
if res, err := f(C.GoString(id), C.GoString(req)); err != nil {
status = -1
result = jsString(err.Error())
} else if b, err := json.Marshal(res); err != nil {
status = -1
result = jsString(err.Error())
} else {
status = 0
result = string(b)

// handle any error trying to call the binding function
if err := f(C.GoString(id), C.GoString(req)); err != nil {
jsString := func(v interface{}) string { b, _ := json.Marshal(v); return string(b) }
result := jsString(err.Error())

s := C.CString(result)
defer C.free(unsafe.Pointer(s))
C.webview_return(w, id, C.int(StatusError), s)
}
s := C.CString(result)
defer C.free(unsafe.Pointer(s))
C.webview_return(w, id, C.int(status), s)
}

func (w *webview) Bind(name string, f interface{}) error {
Expand All @@ -259,62 +254,43 @@ func (w *webview) Bind(name string, f interface{}) error {
if v.Kind() != reflect.Func {
return errors.New("only functions can be bound")
}
// f must return either value and error or just error
if n := v.Type().NumOut(); n > 2 {
return errors.New("function may only return a value or a value+error")

if v.Type().NumIn() > 0 && v.Type().In(0).Kind() != reflect.String {
return errors.New("first argument must be a string for the request id")
}

binding := func(id, req string) (interface{}, error) {
binding := func(id, req string) error {
raw := []json.RawMessage{}
if err := json.Unmarshal([]byte(req), &raw); err != nil {
return nil, err
return err
}

isVariadic := v.Type().IsVariadic()
numIn := v.Type().NumIn()
if (isVariadic && len(raw) < numIn-1) || (!isVariadic && len(raw) != numIn) {
return nil, errors.New("function arguments mismatch")

if (isVariadic && len(raw) < numIn-1) || (!isVariadic && len(raw) != numIn-1) {
return errors.New("function arguments mismatch")
}
args := []reflect.Value{
reflect.ValueOf(id),
}
args := []reflect.Value{}
for i := range raw {
var arg reflect.Value
if isVariadic && i >= numIn-1 {
// skip `id` when looking at `f` parameters
argNum := i + 1
if isVariadic && argNum >= numIn-1 {
arg = reflect.New(v.Type().In(numIn - 1).Elem())
} else {
arg = reflect.New(v.Type().In(i))
arg = reflect.New(v.Type().In(argNum))
}
if err := json.Unmarshal(raw[i], arg.Interface()); err != nil {
return nil, err
return err
}
args = append(args, arg.Elem())
}
errorType := reflect.TypeOf((*error)(nil)).Elem()
res := v.Call(args)
switch len(res) {
case 0:
// No results from the function, just return nil
return nil, nil
case 1:
// One result may be a value, or an error
if res[0].Type().Implements(errorType) {
if res[0].Interface() != nil {
return nil, res[0].Interface().(error)
}
return nil, nil
}
return res[0].Interface(), nil
case 2:
// Two results: first one is value, second is error
if !res[1].Type().Implements(errorType) {
return nil, errors.New("second return value must be an error")
}
if res[1].Interface() == nil {
return res[0].Interface(), nil
}
return res[0].Interface(), res[1].Interface().(error)
default:
return nil, errors.New("unexpected number of return values")
}

v.Call(args)
return nil
}

m.Lock()
Expand Down
8 changes: 4 additions & 4 deletions webview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ func Example() {
w := New(true)
defer w.Destroy()
w.SetTitle("Hello")
w.Bind("noop", func() string {
w.Bind("noop", func(id string) {
log.Println("hello")
return "hello"
w.Return(id, StatusSuccess, "hello")
})
w.Bind("add", func(a, b int) int {
return a + b
w.Bind("add", func(id string, a, b int) {
w.Return(id, StatusSuccess, a+b)
})
w.Bind("quit", func() {
w.Terminate()
Expand Down

0 comments on commit 8918907

Please sign in to comment.