diff --git a/muon.go b/muon.go index e087b6e..32b7f07 100644 --- a/muon.go +++ b/muon.go @@ -2,7 +2,7 @@ package muon import ( "encoding/json" - "fmt" + "errors" "net" "net/http" "reflect" @@ -13,18 +13,18 @@ import ( // Window represents a single Ultralight instance type Window struct { - wnd ULWindow - ov ULOverlay - view ULView - app ULApp - handler http.Handler - cfg *Config + wnd ULWindow + ov ULOverlay + view ULView + app ULApp + handler http.Handler + cfg *Config + callbacks map[string]*ipf } type ipf struct { - Function reflect.Value - ParamTypes []reflect.Type - ReturnTypes []reflect.Type + Function reflect.Value + ParamTypes []reflect.Type } // Config contains configurable controls for the Ultralight engine @@ -44,8 +44,9 @@ type Config struct { // New creates a Ultralight Window func New(cfg *Config, handler http.Handler) *Window { w := &Window{ - cfg: cfg, - handler: handler, + cfg: cfg, + handler: handler, + callbacks: make(map[string]*ipf), } ufg := UlCreateConfig() @@ -104,6 +105,8 @@ func (w *Window) Start() error { return nil } +var registerCount int + // Bind registers the given function to the given name in the Window's JS global object func (w *Window) Bind(name string, function interface{}) { f := &ipf{ @@ -118,13 +121,13 @@ func (w *Window) Bind(name string, function interface{}) { f.ParamTypes[i] = t.In(i) } - f.ReturnTypes = make([]reflect.Type, t.NumOut()) - - for i := 0; i < t.NumOut(); i++ { - f.ReturnTypes[i] = t.Out(i) + if t.NumOut() > 1 { + panic("Too many return values!") } - 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. @@ -134,6 +137,7 @@ func (w *Window) Eval(js string, ret reflect.Type) (interface{}, error) { ref := UlViewEvaluateScript(w.view, us) ctx := UlViewGetJSContext(w.view) + val, err := fromJSValue(ctx, ref, ret) if err != nil { @@ -153,40 +157,41 @@ func (w *Window) Move(x int, y int) { UlOverlayMoveTo(w.ov, int32(x), int32(y)) } -func (w *Window) makeIPCCallback(f *ipf) func(JSContextRef, JSObjectRef, JSObjectRef, uint, []JSValueRef, []JSValueRef) JSValueRef { - return func( - ctx JSContextRef, - function JSObjectRef, - thisObject JSObjectRef, - argumentCount uint, - arguments []JSValueRef, - exception []JSValueRef) JSValueRef { +func (w *Window) ipcCallback(ctx JSContextRef, functin JSObjectRef, thisObject JSObjectRef, argumentCount uint, arguments []JSValueRef, exception []JSValueRef) JSValueRef { + jsName := JSStringCreateWithUTF8CString("name") + defer JSStringRelease(jsName) - 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++ { - val, err := fromJSValue(ctx, arguments[i], f.ParamTypes[i]) + name := fromJSString(jsProp) - if err != nil { - panic(err) - } + f := w.callbacks[name] - 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) - - 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]) - + params[i] = val } + + 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) { @@ -205,6 +210,11 @@ func fromJSValue(ctx JSContextRef, value JSValueRef, rtype reflect.Type) (reflec prop := JSObjectGetProperty(ctx, obj, l, 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) for i := 0; i < length; i++ { @@ -289,20 +299,24 @@ func toJSValue(ctx JSContextRef, value reflect.Value) JSValueRef { } if err != nil { - fmt.Println(err.Error()) return JSValueMakeNull(ctx) } + return jsv } -func addFunctionToView(view ULView, name string, callback JSObjectCallAsFunctionCallback) { - ctx := UlViewGetJSContext(view) +func (w *Window) addFunction(name string) { + ctx := UlViewGetJSContext(w.view) gobj := JSContextGetGlobalObject(ctx) fn := JSStringCreateWithUTF8CString(name) 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)) @@ -322,7 +336,9 @@ func serveHandler(handler http.Handler) (string, error) { } go func() { - panic(http.Serve(ln, handler)) + if err := http.Serve(ln, handler); err != nil { + panic(err) + } }() return "http://" + ln.Addr().String(), nil diff --git a/muon_test.go b/muon_test.go index 5499e0e..53703ac 100644 --- a/muon_test.go +++ b/muon_test.go @@ -2,10 +2,28 @@ package muon import ( "net/http" + "os" "reflect" "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 { S1 string F1 float64 @@ -13,14 +31,8 @@ type testObject struct { } func TestComplexType(t *testing.T) { - cfg := &Config{ - Height: 1, - Width: 1, - } - m := New(cfg, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - - m.Bind("test", func(to *testObject) *testObject { + w.Bind("complexTest", func(to *testObject) *testObject { return &testObject{ S1: to.S1 + " World!", F1: to.F1 + 1, @@ -28,11 +40,7 @@ func TestComplexType(t *testing.T) { } }) - go func() { - m.Start() - }() - - res, err := m.Eval(`test({S1: "Hello,", F1: 9000, B1: false})`, reflect.TypeOf(&testObject{})) + res, err := w.Eval(`complexTest({S1: "Hello,", F1: 9000, B1: false})`, reflect.TypeOf(&testObject{})) if err != nil { t.Error(err) @@ -53,15 +61,17 @@ func TestComplexType(t *testing.T) { } } -func TestArrayType(t *testing.T) { - cfg := &Config{ - Height: 1, - Width: 1, +func t2(to *testObject) *testObject { + return &testObject{ + S1: to.S1 + " World!", + 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" { 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} }) - go func() { - m.Start() - }() - - res, err := m.Eval(`test(["Hello","World!"])`, reflect.TypeOf([]float64{})) + res, err := w.Eval(`arrayTest(["Hello","World!"])`, reflect.TypeOf([]float64{})) if err != nil { t.Error(err) @@ -97,14 +103,8 @@ func TestArrayType(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) {})) - - m.Bind("test", func(nullValue string, undefinedValue string) { + w.Bind("emptyTypeTest", func(nullValue string, undefinedValue string) { if nullValue != "" { t.Errorf("nullType was not empty!") } @@ -113,11 +113,24 @@ func TestEmptyType(t *testing.T) { } }) - go func() { - m.Start() - }() - - _, err := m.Eval(`test(null, undefined)`, nil) + _, err := w.Eval(`emptyTypeTest(null, undefined)`, nil) + + if err != nil { + t.Error(err) + } +} + +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 { t.Error(err)