Skip to content
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 (
// 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{
// 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() {
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.
if u.tmpDir != "" {
if err := os.RemoveAll(u.tmpDir); err != nil {
return err
return nil
func (u *ui) Load(url string) error { return }
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, 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)
return nil, errors.New("unexpected number of return values")
func (u *ui) Eval(js string) Value {
v, err :=
return value{err: err, raw: v}
func (u *ui) SetBounds(b Bounds) error {
func (u *ui) Bounds() (Bounds, error) {
You can’t perform that action at this time.