package webview /* #cgo CFLAGS: -I${SRCDIR}/libs/webview/include #cgo CXXFLAGS: -I${SRCDIR}/libs/webview/include #cgo linux openbsd freebsd netbsd CXXFLAGS: -DWEBVIEW_GTK -std=c++11 #cgo linux openbsd freebsd netbsd pkg-config: gtk+-3.0 webkit2gtk-4.0 #cgo darwin CXXFLAGS: -DWEBVIEW_COCOA -std=c++11 #cgo darwin LDFLAGS: -framework WebKit #cgo windows CXXFLAGS: -DWEBVIEW_EDGE -std=c++14 -I${SRCDIR}/libs/mswebview2/include #cgo windows LDFLAGS: -static -ladvapi32 -lole32 -lshell32 -lshlwapi -luser32 -lversion #include "webview.h" #include #include void CgoWebViewDispatch(webview_t w, uintptr_t arg); void CgoWebViewBind(webview_t w, const char *name, uintptr_t index); void CgoWebViewUnbind(webview_t w, const char *name); */ import "C" import ( "encoding/json" "errors" "reflect" "runtime" "sync" "unsafe" ) func init() { // Ensure that main.main is called from the main thread runtime.LockOSThread() } // Hints are used to configure window sizing and resizing type Hint int const ( // Width and height are default size HintNone = C.WEBVIEW_HINT_NONE // Window size can not be changed by a user HintFixed = C.WEBVIEW_HINT_FIXED // Width and height are minimum bounds HintMin = C.WEBVIEW_HINT_MIN // Width and height are maximum bounds HintMax = C.WEBVIEW_HINT_MAX ) type WebView interface { // Run runs the main loop until it's terminated. After this function exits - // you must destroy the webview. Run() // Terminate stops the main loop. It is safe to call this function from // a background thread. Terminate() // Dispatch posts a function to be executed on the main thread. You normally // do not need to call this function, unless you want to tweak the native // window. Dispatch(f func()) // Destroy destroys a webview and closes the native window. Destroy() // Window returns a native window handle pointer. When using GTK backend the // pointer is GtkWindow pointer, when using Cocoa backend the pointer is // NSWindow pointer, when using Win32 backend the pointer is HWND pointer. Window() unsafe.Pointer // SetTitle updates the title of the native window. Must be called from the UI // thread. SetTitle(title string) // SetSize updates native window size. See Hint constants. SetSize(w int, h int, hint Hint) // Navigate navigates webview to the given URL. URL may be a properly encoded data. // URI. Examples: // w.Navigate("https://github.com/webview/webview") // w.Navigate("data:text/html,%3Ch1%3EHello%3C%2Fh1%3E") // w.Navigate("data:text/html;base64,PGgxPkhlbGxvPC9oMT4=") Navigate(url string) // SetHtml sets the webview HTML directly. // Example: w.SetHtml(w, "

Hello

"); SetHtml(html string) // Init injects JavaScript code at the initialization of the new page. Every // time the webview will open a the new page - this initialization code will // be executed. It is guaranteed that code is executed before window.onload. Init(js string) // Eval evaluates arbitrary JavaScript code. Evaluation happens asynchronously, // also the result of the expression is ignored. Use RPC bindings if you want // to receive notifications about the results of the evaluation. Eval(js string) // Bind binds a callback function so that it will appear under the given name // as a global JavaScript function. Internally it uses webview_init(). // Callback receives a request string and a user-provided argument pointer. // 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 Bind(name string, f interface{}) error // Removes a callback that was previously set by Bind. Unbind(name string) error } type webview struct { w C.webview_t } var ( m sync.Mutex index uintptr dispatch = map[uintptr]func(){} bindings = map[uintptr]func(id, req string) (interface{}, error){} ) func boolToInt(b bool) C.int { if b { return 1 } return 0 } // New calls NewWindow to create a new window and a new webview instance. If debug // is non-zero - developer tools will be enabled (if the platform supports them). func New(debug bool) WebView { return NewWindow(debug, nil) } // NewWindow creates a new webview instance. If debug is non-zero - developer // tools will be enabled (if the platform supports them). Window parameter can be // a pointer to the native window handle. If it's non-null - then child WebView is // embedded into the given parent window. Otherwise a new window is created. // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be passed // here. func NewWindow(debug bool, window unsafe.Pointer) WebView { w := &webview{} w.w = C.webview_create(boolToInt(debug), window) return w } func (w *webview) Destroy() { C.webview_destroy(w.w) } func (w *webview) Run() { C.webview_run(w.w) } func (w *webview) Terminate() { C.webview_terminate(w.w) } func (w *webview) Window() unsafe.Pointer { return C.webview_get_window(w.w) } func (w *webview) Navigate(url string) { s := C.CString(url) defer C.free(unsafe.Pointer(s)) C.webview_navigate(w.w, s) } func (w *webview) SetHtml(html string) { s := C.CString(html) defer C.free(unsafe.Pointer(s)) C.webview_set_html(w.w, s) } func (w *webview) SetTitle(title string) { s := C.CString(title) defer C.free(unsafe.Pointer(s)) C.webview_set_title(w.w, s) } func (w *webview) SetSize(width int, height int, hint Hint) { C.webview_set_size(w.w, C.int(width), C.int(height), C.int(hint)) } func (w *webview) Init(js string) { s := C.CString(js) defer C.free(unsafe.Pointer(s)) C.webview_init(w.w, s) } func (w *webview) Eval(js string) { s := C.CString(js) defer C.free(unsafe.Pointer(s)) C.webview_eval(w.w, s) } func (w *webview) Dispatch(f func()) { m.Lock() for ; dispatch[index] != nil; index++ { } dispatch[index] = f m.Unlock() C.CgoWebViewDispatch(w.w, C.uintptr_t(index)) } //export _webviewDispatchGoCallback func _webviewDispatchGoCallback(index unsafe.Pointer) { m.Lock() f := dispatch[uintptr(index)] delete(dispatch, uintptr(index)) m.Unlock() f() } //export _webviewBindingGoCallback func _webviewBindingGoCallback(w C.webview_t, id *C.char, req *C.char, index uintptr) { 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) } 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 { v := reflect.ValueOf(f) // f must be a function 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") } binding := func(id, req string) (interface{}, error) { raw := []json.RawMessage{} if err := json.Unmarshal([]byte(req), &raw); err != nil { return nil, 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") } args := []reflect.Value{} for i := range raw { var arg reflect.Value if isVariadic && i >= numIn-1 { arg = reflect.New(v.Type().In(numIn - 1).Elem()) } else { arg = reflect.New(v.Type().In(i)) } if err := json.Unmarshal(raw[i], arg.Interface()); err != nil { return nil, 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") } } m.Lock() for ; bindings[index] != nil; index++ { } bindings[index] = binding m.Unlock() cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) C.CgoWebViewBind(w.w, cname, C.uintptr_t(index)) return nil } func (w *webview) Unbind(name string) error { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) C.CgoWebViewUnbind(w.w, cname) return nil }