| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- 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",
- "--disable-windows10-custom-titlebar",
- "--metrics-recording-only",
- "--no-first-run",
- "--no-default-browser-check",
- "--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()
- }
|