Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
6 contributors

Users who have contributed to this file

@zserge @zncoder @yaodingyd @velut @RikiyaFujii @jkuri
175 lines (159 sloc) 4.45 KB
package lorca
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
)
// UI interface allows talking to the HTML5 UI from Go.
type UI interface {
Load(url string) error
Bounds() (Bounds, error)
SetBounds(Bounds) error
Bind(name string, f interface{}) error
Eval(js string) Value
Done() <-chan struct{}
Close() error
}
type ui struct {
chrome *chrome
done chan struct{}
tmpDir string
}
var defaultChromeArgs = []string{
"--disable-background-networking",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-breakpad",
"--disable-client-side-phishing-detection",
"--disable-default-apps",
"--disable-dev-shm-usage",
"--disable-infobars",
"--disable-extensions",
"--disable-features=site-per-process",
"--disable-hang-monitor",
"--disable-ipc-flooding-protection",
"--disable-popup-blocking",
"--disable-prompt-on-repost",
"--disable-renderer-backgrounding",
"--disable-sync",
"--disable-translate",
"--metrics-recording-only",
"--no-first-run",
"--safebrowsing-disable-auto-update",
"--enable-automation",
"--password-store=basic",
"--use-mock-keychain",
}
// New returns a new HTML5 UI for the given URL, user profile directory, window
// size and other options passed to the browser engine. If URL is an empty
// string - a blank page is displayed. If user profile directory is an empty
// string - a temporary directory is created and it will be removed on
// ui.Close(). You might want to use "--headless" custom CLI argument to test
// your UI code.
func New(url, dir string, width, height int, customArgs ...string) (UI, error) {
if url == "" {
url = "data:text/html,<html></html>"
}
tmpDir := ""
if dir == "" {
name, err := ioutil.TempDir("", "lorca")
if err != nil {
return nil, err
}
dir, tmpDir = name, name
}
args := append(defaultChromeArgs, fmt.Sprintf("--app=%s", url))
args = append(args, fmt.Sprintf("--user-data-dir=%s", dir))
args = append(args, fmt.Sprintf("--window-size=%d,%d", width, height))
args = append(args, customArgs...)
args = append(args, "--remote-debugging-port=0")
chrome, err := newChromeWithArgs(ChromeExecutable(), args...)
done := make(chan struct{})
if err != nil {
return nil, err
}
go func() {
chrome.cmd.Wait()
close(done)
}()
return &ui{chrome: chrome, done: done, tmpDir: tmpDir}, nil
}
func (u *ui) Done() <-chan struct{} {
return u.done
}
func (u *ui) Close() error {
// ignore err, as the chrome process might be already dead, when user close the window.
u.chrome.kill()
<-u.done
if u.tmpDir != "" {
if err := os.RemoveAll(u.tmpDir); err != nil {
return err
}
}
return nil
}
func (u *ui) Load(url string) error { return u.chrome.load(url) }
func (u *ui) 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")
}
return u.chrome.bind(name, func(raw []json.RawMessage) (interface{}, error) {
if len(raw) != v.Type().NumIn() {
return nil, errors.New("function arguments mismatch")
}
args := []reflect.Value{}
for i := range raw {
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")
}
})
}
func (u *ui) Eval(js string) Value {
v, err := u.chrome.eval(js)
return value{err: err, raw: v}
}
func (u *ui) SetBounds(b Bounds) error {
return u.chrome.setBounds(b)
}
func (u *ui) Bounds() (Bounds, error) {
return u.chrome.bounds()
}
You can’t perform that action at this time.