Fixed cgo callbacks

This commit is contained in:
ImVexed 2019-10-09 01:37:17 -05:00
parent 06751b7aab
commit 78f2c420ef
2 changed files with 113 additions and 84 deletions

114
muon.go
View File

@ -2,7 +2,7 @@ package muon
import ( import (
"encoding/json" "encoding/json"
"fmt" "errors"
"net" "net"
"net/http" "net/http"
"reflect" "reflect"
@ -13,18 +13,18 @@ import (
// Window represents a single Ultralight instance // Window represents a single Ultralight instance
type Window struct { type Window struct {
wnd ULWindow wnd ULWindow
ov ULOverlay ov ULOverlay
view ULView view ULView
app ULApp app ULApp
handler http.Handler handler http.Handler
cfg *Config cfg *Config
callbacks map[string]*ipf
} }
type ipf struct { type ipf struct {
Function reflect.Value Function reflect.Value
ParamTypes []reflect.Type ParamTypes []reflect.Type
ReturnTypes []reflect.Type
} }
// Config contains configurable controls for the Ultralight engine // Config contains configurable controls for the Ultralight engine
@ -44,8 +44,9 @@ type Config struct {
// New creates a Ultralight Window // New creates a Ultralight Window
func New(cfg *Config, handler http.Handler) *Window { func New(cfg *Config, handler http.Handler) *Window {
w := &Window{ w := &Window{
cfg: cfg, cfg: cfg,
handler: handler, handler: handler,
callbacks: make(map[string]*ipf),
} }
ufg := UlCreateConfig() ufg := UlCreateConfig()
@ -104,6 +105,8 @@ func (w *Window) Start() error {
return nil return nil
} }
var registerCount int
// Bind registers the given function to the given name in the Window's JS global object // Bind registers the given function to the given name in the Window's JS global object
func (w *Window) Bind(name string, function interface{}) { func (w *Window) Bind(name string, function interface{}) {
f := &ipf{ f := &ipf{
@ -118,13 +121,13 @@ func (w *Window) Bind(name string, function interface{}) {
f.ParamTypes[i] = t.In(i) f.ParamTypes[i] = t.In(i)
} }
f.ReturnTypes = make([]reflect.Type, t.NumOut()) if t.NumOut() > 1 {
panic("Too many return values!")
for i := 0; i < t.NumOut(); i++ {
f.ReturnTypes[i] = t.Out(i)
} }
addFunctionToView(w.view, name, w.makeIPCCallback(f)) w.callbacks[name] = f
w.addFunction(name)
} }
// Eval evaluates a given JavaScript string in the given Window view. `ret` is necessary for JSON serialization if an object is returned. // Eval evaluates a given JavaScript string in the given Window view. `ret` is necessary for JSON serialization if an object is returned.
@ -134,6 +137,7 @@ func (w *Window) Eval(js string, ret reflect.Type) (interface{}, error) {
ref := UlViewEvaluateScript(w.view, us) ref := UlViewEvaluateScript(w.view, us)
ctx := UlViewGetJSContext(w.view) ctx := UlViewGetJSContext(w.view)
val, err := fromJSValue(ctx, ref, ret) val, err := fromJSValue(ctx, ref, ret)
if err != nil { if err != nil {
@ -153,40 +157,41 @@ func (w *Window) Move(x int, y int) {
UlOverlayMoveTo(w.ov, int32(x), int32(y)) UlOverlayMoveTo(w.ov, int32(x), int32(y))
} }
func (w *Window) makeIPCCallback(f *ipf) func(JSContextRef, JSObjectRef, JSObjectRef, uint, []JSValueRef, []JSValueRef) JSValueRef { func (w *Window) ipcCallback(ctx JSContextRef, functin JSObjectRef, thisObject JSObjectRef, argumentCount uint, arguments []JSValueRef, exception []JSValueRef) JSValueRef {
return func( jsName := JSStringCreateWithUTF8CString("name")
ctx JSContextRef, defer JSStringRelease(jsName)
function JSObjectRef,
thisObject JSObjectRef,
argumentCount uint,
arguments []JSValueRef,
exception []JSValueRef) JSValueRef {
params := make([]reflect.Value, argumentCount) prop := JSObjectGetProperty(ctx, functin, jsName, nil)
jsProp := JSValueToStringCopy(ctx, prop, nil)
defer JSStringRelease(jsProp)
for i := uint(0); i < argumentCount; i++ { name := fromJSString(jsProp)
val, err := fromJSValue(ctx, arguments[i], f.ParamTypes[i])
if err != nil { f := w.callbacks[name]
panic(err)
}
params[i] = val params := make([]reflect.Value, argumentCount)
for i := uint(0); i < argumentCount; i++ {
val, err := fromJSValue(ctx, arguments[i], f.ParamTypes[i])
if err != nil {
panic(err)
} }
val := f.Function.Call(params) params[i] = val
if len(val) > 1 {
panic("Javascript does not support more than 1 return value!")
}
if len(val) == 0 {
return JSValueMakeNull(ctx)
}
return toJSValue(ctx, val[0])
} }
val := f.Function.Call(params)
if len(val) > 1 {
panic("Javascript does not support more than 1 return value!")
}
if len(val) == 0 {
return JSValueMakeNull(ctx)
}
return toJSValue(ctx, val[0])
} }
func fromJSValue(ctx JSContextRef, value JSValueRef, rtype reflect.Type) (reflect.Value, error) { func fromJSValue(ctx JSContextRef, value JSValueRef, rtype reflect.Type) (reflect.Value, error) {
@ -205,6 +210,11 @@ func fromJSValue(ctx JSContextRef, value JSValueRef, rtype reflect.Type) (reflec
prop := JSObjectGetProperty(ctx, obj, l, nil) prop := JSObjectGetProperty(ctx, obj, l, nil)
length := int(JSValueToNumber(ctx, prop, nil)) length := int(JSValueToNumber(ctx, prop, nil))
if rtype.Kind() != reflect.Slice {
return reflect.Zero(rtype), errors.New("JS return is of type Array while Go type target is not")
}
values := reflect.MakeSlice(rtype, length, length) values := reflect.MakeSlice(rtype, length, length)
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
@ -289,20 +299,24 @@ func toJSValue(ctx JSContextRef, value reflect.Value) JSValueRef {
} }
if err != nil { if err != nil {
fmt.Println(err.Error())
return JSValueMakeNull(ctx) return JSValueMakeNull(ctx)
} }
return jsv return jsv
} }
func addFunctionToView(view ULView, name string, callback JSObjectCallAsFunctionCallback) { func (w *Window) addFunction(name string) {
ctx := UlViewGetJSContext(view) ctx := UlViewGetJSContext(w.view)
gobj := JSContextGetGlobalObject(ctx) gobj := JSContextGetGlobalObject(ctx)
fn := JSStringCreateWithUTF8CString(name) fn := JSStringCreateWithUTF8CString(name)
defer JSStringRelease(fn) defer JSStringRelease(fn)
fob := JSObjectMakeFunctionWithCallback(ctx, fn, callback) fname := JSStringCreateWithUTF8CString("name")
defer JSStringRelease(fname)
fob := JSObjectMakeFunctionWithCallback(ctx, fn, w.ipcCallback)
JSObjectSetProperty(ctx, fob, fname, JSValueMakeString(ctx, fname), KJSPropertyAttributeNone, []JSValueRef{})
val := *(*JSValueRef)(unsafe.Pointer(&fob)) val := *(*JSValueRef)(unsafe.Pointer(&fob))
@ -322,7 +336,9 @@ func serveHandler(handler http.Handler) (string, error) {
} }
go func() { go func() {
panic(http.Serve(ln, handler)) if err := http.Serve(ln, handler); err != nil {
panic(err)
}
}() }()
return "http://" + ln.Addr().String(), nil return "http://" + ln.Addr().String(), nil

View File

@ -2,10 +2,28 @@ package muon
import ( import (
"net/http" "net/http"
"os"
"reflect" "reflect"
"testing" "testing"
) )
var w *Window
func TestMain(m *testing.M) {
cfg := &Config{
Height: 1,
Width: 1,
}
w = New(cfg, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
go func() {
w.Start()
}()
os.Exit(m.Run())
}
type testObject struct { type testObject struct {
S1 string S1 string
F1 float64 F1 float64
@ -13,14 +31,8 @@ type testObject struct {
} }
func TestComplexType(t *testing.T) { func TestComplexType(t *testing.T) {
cfg := &Config{
Height: 1,
Width: 1,
}
m := New(cfg, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) w.Bind("complexTest", func(to *testObject) *testObject {
m.Bind("test", func(to *testObject) *testObject {
return &testObject{ return &testObject{
S1: to.S1 + " World!", S1: to.S1 + " World!",
F1: to.F1 + 1, F1: to.F1 + 1,
@ -28,11 +40,7 @@ func TestComplexType(t *testing.T) {
} }
}) })
go func() { res, err := w.Eval(`complexTest({S1: "Hello,", F1: 9000, B1: false})`, reflect.TypeOf(&testObject{}))
m.Start()
}()
res, err := m.Eval(`test({S1: "Hello,", F1: 9000, B1: false})`, reflect.TypeOf(&testObject{}))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -53,15 +61,17 @@ func TestComplexType(t *testing.T) {
} }
} }
func TestArrayType(t *testing.T) { func t2(to *testObject) *testObject {
cfg := &Config{ return &testObject{
Height: 1, S1: to.S1 + " World!",
Width: 1, F1: to.F1 + 1,
B1: !to.B1,
} }
}
m := New(cfg, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) func TestArrayType(t *testing.T) {
m.Bind("test", func(strings []string) []float64 { w.Bind("arrayTest", func(strings []string) []float64 {
if strings[0] != "Hello" { if strings[0] != "Hello" {
t.Errorf("strings[0] was not Hello, got %s", strings[0]) t.Errorf("strings[0] was not Hello, got %s", strings[0])
} }
@ -71,11 +81,7 @@ func TestArrayType(t *testing.T) {
return []float64{1, 2, 3} return []float64{1, 2, 3}
}) })
go func() { res, err := w.Eval(`arrayTest(["Hello","World!"])`, reflect.TypeOf([]float64{}))
m.Start()
}()
res, err := m.Eval(`test(["Hello","World!"])`, reflect.TypeOf([]float64{}))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -97,14 +103,8 @@ func TestArrayType(t *testing.T) {
} }
func TestEmptyType(t *testing.T) { func TestEmptyType(t *testing.T) {
cfg := &Config{
Height: 1,
Width: 1,
}
m := New(cfg, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) w.Bind("emptyTypeTest", func(nullValue string, undefinedValue string) {
m.Bind("test", func(nullValue string, undefinedValue string) {
if nullValue != "" { if nullValue != "" {
t.Errorf("nullType was not empty!") t.Errorf("nullType was not empty!")
} }
@ -113,11 +113,24 @@ func TestEmptyType(t *testing.T) {
} }
}) })
go func() { _, err := w.Eval(`emptyTypeTest(null, undefined)`, nil)
m.Start()
}() if err != nil {
t.Error(err)
_, err := m.Eval(`test(null, undefined)`, nil) }
}
func TestMultipleFuncs(t *testing.T) {
w.Bind("multiple1Test", func(value1 string) {})
w.Bind("multiple2Test", func(value2 bool) {})
_, err := w.Eval(`multiple1Test("Hello, World1")`, nil)
if err != nil {
t.Error(err)
}
_, err = w.Eval(`multiple2Test(true)`, nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)